Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

anna-executa-go

anna-executa-go 是一个用于开发 Executa / ANNA 插件的 Go SDK。

它提供两层能力:

  • core:与具体 CLI 框架无关的协议运行时、工具注册和 JSON-RPC over stdio 处理
  • adapters/cobra:把 Cobra 命令树适配为一个 Executa Tool

如果你想把现有 Go CLI 包装成符合 Executa 协议的插件,或者想直接写多个 Tool 并打包成一个二进制,这个仓库就是对应的基础库。

特性

  • 支持 describehealthinvoke
  • 支持一个插件注册多个 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,而不是简单按换行切分

Tool 模型

1. 普通 Handler Tool

适合直接接收 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"] 读取。

2. Cobra Tool

适合复用已有 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.credentialsexecutacobra.WorkingDirectory(cmd.Context()) 来自 Cobra Tool 参数里的 arguments.cwd

File Transport

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 支持这些字段:

  • Mode file transport 的启用策略。
  • ThresholdBytes threshold 模式下的触发阈值。小于等于 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,
	},
})

协议示例

describe

{"jsonrpc":"2.0","method":"describe","id":1}

invoke

{"jsonrpc":"2.0","method":"invoke","params":{"tool":"demo_cli","arguments":{"args":["echo","hello"]}},"id":2}

invoke 携带 credentials

{"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

About

Go SDK for building Executa / ANNA plugins with JSON-RPC over stdio, tool registry, and Cobra adapter support.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages