feat(capability): 转码能力包(vendor 纯 Go AMR-NB 解码器 + 黄金向量逐样本对照 + 8 哨兵 + 注册面)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
AskaEth committed 2026-10-05 13:02:28 +08:00
1 parent 89715d1813
commit 4a2d276881
100 files changed
+11319

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
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
}