文档

适配器契约预览

以下 Agent Adapter SDK 示例在公开 Go SDK 包稳定前仅作说明之用。它们描述 Edge Server 内的 Agent Runtime adapter 契约,不代表当前已有稳定的 import path。

Runtime adapter 把 Claude Code、Codex、OpenCode 或自定义 agent runtime 这类执行引擎接入 AgentHub。

还不是稳定公开 SDK

把本页视为 adapter contract preview。它适合理解 Edge Server 集成边界、事件归一化、取消和审批行为;它还不是第三方包使用指南、marketplace 提交指南或兼容性承诺。

概览

每个 runtime adapter 应提供三类能力:

  • 能力元数据:帮助 AgentHub 选择合适的 profile/runtime 组合。
  • 生命周期控制:让 Edge 可以 start、cancel、timeout 和 inspect 一次 run。
  • 结构化事件:让 Desktop/Web/Hub 渲染 progress、tool call、diff、artifact、approval 和 completion state。

Adapter 不是用户可见的 Agent Profile。Profile 是产品配置,adapter 是执行机器。

稳定性边界

区域当前稳定性说明
Runtime 目的方向稳定Adapter 把真实 coding runtime 归一化成 AgentHub run 和 event
Event 分类契约已定Progress、tool call、tool result、diff、artifact、approval、completion、failure 是预期类别
Import path未稳定example.com/agenthub/adapter 只是说明性占位
公开包未稳定暂时不能把第三方接入写成通用可用
提交流程未定义第三方 review、签名、兼容测试和文档要求仍需要产品化
安全策略必须具备Adapter 必须尊重 workspace allowlist、cancellation、redaction 和 approval policy

本页升级为稳定 SDK 参考前,需要公开 package path、版本策略、conformance tests、迁移说明和第三方 review 流程。

复制示例前先确认

下面的代码写得像 SDK 指南,是为了让契约更容易读;它还不是可以直接复制上线的生产集成材料。

问题当前答案
现在能 import 这个 path 吗?不能。example.com/agenthub/adapter 是占位符。
现在能发布第三方 adapter 吗?不能写成稳定生态提交。review 和 packaging 流程尚未定义。
Event 名称稳定了吗?API 与事件 中的 run-scoped 词汇是公开方向;schema 仍需要版本化 conformance test。
Provider key 放哪里?只能放本地/服务端 secret storage。不要把 API key 放进前端代码、文档截图或 issue 中复制的 adapter 示例。
稳定 SDK 还缺什么?公开 module path、语义化版本、conformance test、迁移策略、安全审查和提交流程清单。

如果你在 AgentHub 仓库内部实现,请使用 Edge Server 拥有的接口和测试。如果你在设计外部 adapter,在公开 package 存在前,把本页当成产品契约上下文,而不是稳定 SDK。

AgentAdapter 接口

Go
// AgentAdapter defines the contract every runtime adapter must implement.
type AgentAdapter interface {
    // Name returns the unique identifier for this agent type.
    Name() string

    // Capabilities lists what this agent can do.
    Capabilities() []Capability

    // Execute runs a task and streams progress events.
    Execute(ctx context.Context, task Task, events chan<- AgentEvent) (*AgentResult, error)

    // Validate checks whether the configuration is valid.
    Validate(config AgentConfig) error
}

实现适配器

步骤 1:定义 Agent

创建一个结构体保存 Agent 配置和运行状态:

Go
package myadapter

import "example.com/agenthub/adapter" // illustrative placeholder

type MyAgent struct {
    config adapter.AgentConfig
    client *http.Client
}

func New(config adapter.AgentConfig) *MyAgent {
    return &MyAgent{
        config: config,
        client: &http.Client{Timeout: 30 * time.Minute},
    }
}

func (a *MyAgent) Name() string {
    return "my-agent"
}

步骤 2:声明能力

能力声明帮助编排器选择正确的 Agent:

Go
func (a *MyAgent) Capabilities() []adapter.Capability {
    return []adapter.Capability{
        {
            Name:        "code_review",
            Description: "Reviews code for bugs, style, and security issues.",
            Priority:    8,
        },
        {
            Name:        "refactoring",
            Description: "Suggests and applies code refactorings.",
            Priority:    6,
        },
    }
}

步骤 3:实现 Execute

Execute 是适配器核心方法。它接收任务、流式上报进度,并返回最终结果:

Go
func (a *MyAgent) Execute(
    ctx context.Context,
    task adapter.Task,
    events chan<- adapter.AgentEvent,
) (*adapter.AgentResult, error) {
    defer close(events)

    events <- adapter.AgentEvent{
        Type:    adapter.EventProgress,
        Message: "Analyzing task...",
    }

    prompt := buildPrompt(task)

    events <- adapter.AgentEvent{
        Type:    adapter.EventProgress,
        Message: "Generating response...",
    }

    response, err := a.callLLM(ctx, prompt)
    if err != nil {
        return nil, fmt.Errorf("llm call failed: %w", err)
    }

    return &adapter.AgentResult{
        Content:     response.Text,
        ToolCalls:   response.ToolCalls,
        TokensUsed:  response.Usage.TotalTokens,
        CompletedAt: time.Now(),
    }, nil
}

步骤 4:校验配置

Go
func (a *MyAgent) Validate(config adapter.AgentConfig) error {
    if config.APIKey == "" {
        return fmt.Errorf("api_key is required")
    }
    if config.Model == "" {
        return fmt.Errorf("model is required")
    }
    return nil
}

步骤 5:注册适配器

在适配器的 init() 函数中注册:

Go
package myadapter

import "example.com/agenthub/adapter" // illustrative placeholder

func init() {
    adapter.Register("my-agent", func(config adapter.AgentConfig) (adapter.AgentAdapter, error) {
        if err := (&MyAgent{}).Validate(config); err != nil {
            return nil, err
        }
        return New(config), nil
    })
}

然后在主程序中引入适配器:

Go
package main

import (
    _ "github.com/your-org/agenthub-myadapter"
    "github.com/tokendancelab/agenthub/cmd/server"
)

func main() {
    server.Run()
}

事件类型

事件类型说明
EventProgress中间状态更新,会显示在聊天线程中。
EventToolCallAgent 发起了工具调用,例如读取文件或运行命令。
EventError发生非致命错误,Agent 仍可继续。
EventCompleteAgent 完成当前任务。

最佳实践

  • 持续上报进度:用户需要看到任务仍在推进。
  • 尊重取消:始终响应 ctx.Done(),平台会设置硬超时。
  • 工具调用幂等:让工具可以安全重试。
  • 结构化输出:能返回 JSON 时优先返回结构化结果,便于在聊天卡片中渲染。
  • 最小配置:只要求必要字段,例如 API Key 和模型名,其余使用合理默认值。

示例:最小 HTTP 适配器

下面是一个封装 HTTP LLM API 的完整适配器:

Go
package httpadapter

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"

    "example.com/agenthub/adapter" // illustrative placeholder
)

type HTTPAgent struct {
    config   adapter.AgentConfig
    endpoint string
}

func New(config adapter.AgentConfig) *HTTPAgent {
    endpoint := config.Extra["endpoint"]
    if endpoint == "" {
        endpoint = "https://api.example.com/v1/chat"
    }
    return &HTTPAgent{
        config:   config,
        endpoint: endpoint,
    }
}

func (a *HTTPAgent) Name() string { return "http-agent" }

func (a *HTTPAgent) Capabilities() []adapter.Capability {
    return []adapter.Capability{{
        Name: "general", Description: "General-purpose assistant.",
    }}
}

func (a *HTTPAgent) Execute(
    ctx context.Context,
    task adapter.Task,
    events chan<- adapter.AgentEvent,
) (*adapter.AgentResult, error) {
    defer close(events)

    events <- adapter.AgentEvent{
        Type: adapter.EventProgress, Message: "Calling LLM...",
    }

    body := map[string]interface{}{
        "model":    a.config.Model,
        "messages": []map[string]string{{"role": "user", "content": task.Prompt}},
    }

    buf := new(bytes.Buffer)
    json.NewEncoder(buf).Encode(body)

    req, _ := http.NewRequestWithContext(ctx, "POST", a.endpoint, buf)
    req.Header.Set("Authorization", "Bearer "+a.config.APIKey)
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("request failed: %w", err)
    }
    defer resp.Body.Close()

    var result struct {
        Choices []struct {
            Message struct {
                Content string `json:"content"`
            } `json:"message"`
        } `json:"choices"`
    }

    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, fmt.Errorf("decode failed: %w", err)
    }

    return &adapter.AgentResult{
        Content:     result.Choices[0].Message.Content,
        CompletedAt: time.Now(),
    }, nil
}

func (a *HTTPAgent) Validate(config adapter.AgentConfig) error {
    if config.APIKey == "" {
        return fmt.Errorf("api_key is required")
    }
    return nil
}

下一步

  • 阅读 系统架构,理解适配器在整体链路中的位置。
  • 阅读 API 与事件,保持事件形状一致。
  • 快速上手 验证本地 runtime 链路。
  • 在公开 adapter SDK 包和贡献流程稳定后,再通过 Pull Request 将适配器提案提交到 TokenDanceLab/AgentHub