anna-executa-go 是一个用于开发 Executa / ANNA 插件的 Go SDK。
它提供两层能力:
core:与具体 CLI 框架无关的协议运行时、工具注册和 JSON-RPC over stdio 处理adapters/cobra:把 Cobra 命令树适配为一个 Executa Tool
如果你想把现有 Go CLI 包装成符合 Executa 协议的插件,或者想直接写多个 Tool 并打包成一个二进制,这个仓库就是对应的基础库。
- 支持
describe、health、invoke - 支持一个插件注册多个 Tool
- 支持普通 Handler Tool
- 支持把 Cobra 命令树注册成 Tool
- 支持
context.credentials - 支持按 Tool 配置 file transport
- 支持结构化 help 和 usage error
- 默认单次处理 stdin 请求,也支持显式连续监听模式
安装 SDK:
go get github.com/wuyyyyyou/anna-executa-go@latest如果你希望锁定稳定版本,可以显式指定 tag,例如:
go get github.com/wuyyyyyou/anna-executa-go@v1.0.0代码中通常按包导入:
import (
executacobra "github.com/wuyyyyyou/anna-executa-go/adapters/cobra"
"github.com/wuyyyyyou/anna-executa-go/core"
)本地开发时可直接在仓库内使用:
go test ./...
go run ./examples/echo下面是一个最小示例,注册一个 Cobra Tool 并通过 stdio 响应 Executa 请求。
package main
import (
"fmt"
"os"
executacobra "github.com/wuyyyyyou/anna-executa-go/adapters/cobra"
"github.com/wuyyyyyou/anna-executa-go/core"
gocobra "github.com/spf13/cobra"
)
func main() {
root := &gocobra.Command{
Use: "demo",
Short: "Demo CLI",
}
root.AddCommand(&gocobra.Command{
Use: "echo <message>",
Args: gocobra.ExactArgs(1),
Run: func(cmd *gocobra.Command, args []string) {
cmd.Println(args[0])
},
})
plugin := core.NewPlugin(core.PluginConfig{
Meta: core.ManifestMeta{
Name: "demo-plugin",
DisplayName: "Demo Plugin",
Version: "1.0.0",
Description: "A minimal Executa plugin",
},
Runtime: &core.RuntimeInfo{Type: "binary"},
})
tool, err := executacobra.NewTool(executacobra.Config{
Name: "demo_cli",
Description: "Run the demo Cobra command tree",
Root: root,
})
if err != nil {
panic(err)
}
if err := plugin.RegisterTool(tool); err != nil {
panic(err)
}
if err := plugin.Serve(os.Stdin, os.Stdout, os.Stderr); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}插件二进制通过 stdin 接收一条 JSON-RPC 请求,通过 stdout 返回一条 JSON-RPC 响应。
默认行为:
plugin.Serve(...)只处理第一条非空请求,然后退出- 适合 shell 管道、子进程调用、一次性执行场景
如果你在本地调试或需要持续消费多条请求,可以显式开启连续模式:
if err := plugin.Serve(
os.Stdin,
os.Stdout,
os.Stderr,
core.WithServeMode(core.ServeModeContinuous),
); err != nil {
panic(err)
}如果你想在调试时把返回 JSON 格式化输出,可以再加上:
if err := plugin.Serve(
os.Stdin,
os.Stdout,
os.Stderr,
core.WithPrettyResponseJSON(true),
); err != nil {
panic(err)
}注意:
- 默认协议输出仍推荐单行 JSON,便于按行消费
WithPrettyResponseJSON(true)也可以和core.WithServeMode(core.ServeModeContinuous)一起使用- 但此时连续响应会变成多个格式化 JSON value 的流,消费者需要使用流式 JSON decoder,而不是简单按换行切分
适合直接接收 JSON 参数并返回结构化结果:
tool := core.NewTool(core.ToolConfig{
Name: "echo_text",
Description: "Echo plain text",
Parameters: []core.Parameter{
{
Name: "text",
Type: "string",
Description: "Text to echo",
Required: true,
},
},
Handler: func(ctx context.Context, req core.InvokeRequest) (core.InvokeResult, error) {
var args struct {
Text string `json:"text"`
}
if err := req.DecodeArguments(&args); err != nil {
return core.InvokeResult{}, core.InvalidParamsError(err.Error())
}
token, _ := req.Context.Credential("TOKEN")
return core.InvokeResult{
Data: map[string]string{
"output": args.Text,
"token": token,
},
}, nil
},
})普通 Handler Tool 的 handler 会直接收到 core.InvokeRequest。如果 Agent 在 invoke.params.context.credentials 中注入了凭据,可以通过 req.Context.Credential("TOKEN") 或 req.Context.Credentials["TOKEN"] 读取。
适合复用已有 Cobra CLI:
tool, err := executacobra.NewTool(executacobra.Config{
Name: "demo_cli",
Description: "Run demo command tree",
Root: rootCmd,
})Cobra Tool 的固定参数模型为:
args: string[]cwd?: string
Cobra Tool 的业务逻辑通常写在 RunE / Run 里,此时拿不到 core.InvokeRequest,需要从 cmd.Context() 读取 adapter 注入的运行时上下文:
cmd := &gocobra.Command{
Use: "context",
RunE: func(cmd *gocobra.Command, args []string) error {
runtime, _ := executacobra.RuntimeFromContext(cmd.Context())
cwd := executacobra.WorkingDirectory(cmd.Context())
token := runtime.Credentials["TOKEN"]
cmd.Printf("cwd=%s token=%s\n", cwd, token)
return nil
},
}这里的 runtime.Credentials 来自 invoke.params.context.credentials,executacobra.WorkingDirectory(cmd.Context()) 来自 Cobra Tool 参数里的 arguments.cwd。
FileTransport 用来解决“大响应不适合直接塞进 stdout”这个问题。
当响应内容很大时,直接把完整 JSON-RPC 响应写到 stdout 会带来几个问题:
- stdout payload 过大,不利于 IPC 传输和调试
- 某些上层调用链对单次 stdio 消息大小更敏感
- 大块 JSON 不如返回一个文件指针稳定
SDK 的做法是:
- 先把完整 JSON-RPC 响应写入临时文件
- 然后通过 stdout 返回一个轻量指针消息
- 指针格式是
{"jsonrpc":"2.0","id":...,"__file_transport":"/tmp/xxx.json"}
这是按 Tool 配置的,不是按整个 Plugin 配置的。也就是说,不同 Tool 可以使用不同的 file transport 策略。
如果你没有显式配置 FileTransportConfig,默认等价于:
core.FileTransportConfig{
Mode: core.FileTransportThreshold,
ThresholdBytes: 512 * 1024,
Prefix: "executa-resp-",
}含义是:
- 默认模式是
threshold - 当完整 JSON-RPC 响应大于
512KB时,自动改走 file transport - 临时文件默认创建在系统临时目录
- 文件名前缀默认是
executa-resp-
FileTransportConfig.Mode 支持三种模式:
core.FileTransportThreshold超过阈值时才走 file transport。这是默认值,适合大多数场景。core.FileTransportAlways始终把完整响应写入临时文件,stdout 只返回__file_transport指针。适合调试大输出、或者你明确希望统一走文件传输时使用。core.FileTransportNever永远直接返回完整 JSON-RPC 响应,不走 file transport。适合你确定响应很小、或者希望强制 inline 返回时使用。
core.FileTransportConfig 支持这些字段:
Modefile transport 的启用策略。ThresholdBytesthreshold模式下的触发阈值。小于等于0时会回退到默认512KB。TempDir临时文件目录。为空时使用系统默认临时目录。Prefix临时文件名前缀。为空时使用默认前缀executa-resp-。
普通 Handler Tool:
tool := core.NewTool(core.ToolConfig{
Name: "report",
Description: "Generate a large report",
FileTransport: core.FileTransportConfig{
Mode: core.FileTransportThreshold,
ThresholdBytes: 1024 * 1024,
},
Handler: func(ctx context.Context, req core.InvokeRequest) (core.InvokeResult, error) {
return core.InvokeResult{
Data: map[string]any{"ok": true},
}, nil
},
})Cobra Tool:
tool, err := executacobra.NewTool(executacobra.Config{
Name: "demo_cli",
Description: "Run demo command tree",
Root: rootCmd,
FileTransport: core.FileTransportConfig{
Mode: core.FileTransportAlways,
},
}){"jsonrpc":"2.0","method":"describe","id":1}{"jsonrpc":"2.0","method":"invoke","params":{"tool":"demo_cli","arguments":{"args":["echo","hello"]}},"id":2}{"jsonrpc":"2.0","method":"invoke","params":{"tool":"demo_cli","arguments":{"args":["context"],"cwd":"/tmp/demo"},"context":{"credentials":{"TOKEN":"secret"}}},"id":3}invoke成功响应的业务内容放在result.data- 错误响应放在
error - 主要错误信息放在
error.message
Tool 成功执行时,SDK 会返回:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"success": true,
"tool": "demo_cli",
"data": {
"output": "hello"
}
}
}core/ 协议和运行时核心
adapters/cobra/ Cobra 适配层
examples/ 最小可运行示例
可直接运行 examples/echo 查看完整接入方式。这个示例同时演示了:
- Cobra Tool
- Handler Tool
cwd透传context.credentials透传- file transport
- help / usage error
如果你要修改 SDK 本身,建议优先运行:
go test ./...如果你要验证插件接入方式,建议运行:
go run ./examples/echo