130 lines
5.0 KiB
Go
130 lines
5.0 KiB
Go
package transcode
|
||
|
||
import (
|
||
"context"
|
||
"encoding/base64"
|
||
"fmt"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||
)
|
||
|
||
// 本文件是 design/25 §1.3.2 的**第二层注册**:把转码暴露成 agent 可选用的内置
|
||
// 工具(KindTool),走**既有**的 sdk.Registrar → internal/capability/registry。
|
||
// 与 vision.RegisterTools / io.Register 逐字同族,因此面板、策略、审计三面零新增
|
||
// 代码即覆盖;**不新增 Kind**(validKind 闭集不动),策略默认零值 = owner-only。
|
||
//
|
||
// ⚠️ 本工具是 design/25 §8-T1 的「RegisterTool 壳」:T4 定义的形态是
|
||
// `{name, to}`(服务端存储名进、新存储名出,薄壳:读存储 → 调 core → 写存储),
|
||
// 它依赖 design/24 §10 的音频副本存储与 `GET /api/v1/media/{name}`——那两件**尚未
|
||
// 落地**。因此本壳用「字节进、字节出」(base64 承载)先把**注册链路**打通并可测,
|
||
// T4 落地后再换入参形态;这条差异已登记在交接报告里。
|
||
//
|
||
// 红线提醒:转码产物是**字节**,不进上下文。因此本壳工具另设一个远小于
|
||
// MaxOutputBytes 的结果上限(MaxToolResultBytes),让「工具结果」不可能把上下文
|
||
// 灌爆——要转大文件请走进程内 DI 路径,不要走模型工具面。
|
||
const (
|
||
// ToolName 是转码工具在全局能力注册表里的名字。
|
||
ToolName = "transcode"
|
||
// MaxToolResultBytes 是本壳工具允许回给模型的产物体积上限(解码后字节数)。
|
||
// 它**不是** design/25 §2.5 的 MaxOutputBytes(那是能力上限,64 MiB);这里更小
|
||
// 是因为工具结果会进模型上下文。
|
||
MaxToolResultBytes = 256 << 10
|
||
)
|
||
|
||
// 入参字段名。抽成常量以免同一个键在三处各写一份字符串。
|
||
const (
|
||
toolArgData = "data_base64"
|
||
toolArgSource = "source"
|
||
toolArgTarget = "target"
|
||
)
|
||
|
||
// Tool 把 Transcoder 包装成 sdk.Tool,使 agent 能直接调用转码(design/25 §9-2
|
||
// 默认「暴露,owner-only」)。它可安全并发使用。
|
||
type Tool struct {
|
||
tr Transcoder
|
||
}
|
||
|
||
// Compile-time assertion that *Tool satisfies the SDK Tool interface.
|
||
var _ sdk.Tool = (*Tool)(nil)
|
||
|
||
// NewTool 用给定转码器构造工具。
|
||
func NewTool(tr Transcoder) *Tool { return &Tool{tr: tr} }
|
||
|
||
// Name 返回工具标识。
|
||
func (t *Tool) Name() string { return ToolName }
|
||
|
||
// Description 说明该工具做什么。它让模型知道「转码是本地能力、同步、不看身份」。
|
||
func (t *Tool) Description() string {
|
||
return "Convert audio bytes between formats locally (e.g. amr -> wav). " +
|
||
"Input and output are base64-encoded bytes; the result carries format, " +
|
||
"sample rate, channels, duration and frame count."
|
||
}
|
||
|
||
// Parameters 返回输入 JSON Schema:data_base64(必填)+ target(必填)+ source(可选)。
|
||
func (t *Tool) Parameters() (map[string]any, error) {
|
||
return map[string]any{
|
||
"type": "object",
|
||
"properties": map[string]any{
|
||
toolArgData: map[string]any{
|
||
"type": "string",
|
||
"description": "The source audio bytes, base64-encoded (standard alphabet).",
|
||
},
|
||
toolArgTarget: map[string]any{
|
||
"type": "string",
|
||
"description": "Target format name, lowercase and without a dot, e.g. \"wav\".",
|
||
},
|
||
toolArgSource: map[string]any{
|
||
"type": "string",
|
||
"description": "Optional source format name; omitted means sniff by magic bytes.",
|
||
},
|
||
},
|
||
"required": []any{toolArgData, toolArgTarget},
|
||
}, nil
|
||
}
|
||
|
||
// Execute 解码入参、调用转码器,并把结果包成模型可读的对象。
|
||
// 失败一律返回 errors.go 的哨兵(经 %w 保留),绝不假装成功。
|
||
func (t *Tool) Execute(ctx context.Context, args any) (any, error) {
|
||
if t.tr == nil {
|
||
return nil, fmt.Errorf("transcode: tool %q: %w: nil transcoder", ToolName, ErrUnsupportedFormat)
|
||
}
|
||
req, err := toolRequest(args)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
res, err := t.tr.Transcode(ctx, req)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("transcode: tool %q: %w", ToolName, err)
|
||
}
|
||
if len(res.Data) > MaxToolResultBytes {
|
||
return nil, fmt.Errorf("transcode: tool %q: %w: result %d bytes > %d (tool face is bounded; use the in-process API for larger audio)",
|
||
ToolName, ErrOutputTooLarge, len(res.Data), MaxToolResultBytes)
|
||
}
|
||
return map[string]any{
|
||
"format": FormatName(res.Format),
|
||
"source": FormatName(res.Source),
|
||
"data_base64": base64.StdEncoding.EncodeToString(res.Data),
|
||
"sample_rate": res.SampleRate,
|
||
"channels": res.Channels,
|
||
"duration_ms": res.DurationMs,
|
||
"frames": res.Frames,
|
||
}, nil
|
||
}
|
||
|
||
// RegisterTool 经 SDK 辅助函数把转码工具注册到 r(design/25 §1.3.2 第二层)。
|
||
// 与 vision.RegisterTools 同族:tr 为 nil 时不注册(返回 nil,不制造一个必然
|
||
// 失败的能力条目)。
|
||
func RegisterTool(r sdk.Registrar, tr Transcoder) error {
|
||
if r == nil {
|
||
return sdk.ErrNilRegistrar
|
||
}
|
||
if tr == nil {
|
||
return nil
|
||
}
|
||
tool := NewTool(tr)
|
||
if err := sdk.RegisterTool(r, tool); err != nil {
|
||
return fmt.Errorf("transcode: register tool %q: %w", tool.Name(), err)
|
||
}
|
||
return nil
|
||
}
|