Skip to content

3.2 Agentao 作为 ACP Server

把 Agentao 当成黑盒 ACP Server 驱动——这是任意语言宿主(Node / Go / Rust / Kotlin / Swift / C#…)的集成路径。

启动命令

bash
agentao --acp --stdio
  • --acp 打开 ACP 模式
  • --stdio 明确传输层(v1 只支持 stdio,但必须显式写上)
  • 进程会一直运行直到 stdin 关闭或接收到 SIGTERM

环境变量:和 CLI / SDK 完全一致(OPENAI_API_KEY 等)——ACP 协议层不负责凭据传递。

日志:Agentao 会把日志写入 <session-cwd>/agentao.log(ACP 握手里客户端传入的 cwd)。宿主可以轮询/监听这个文件做调试。

启动时恢复会话

bash
agentao --acp --resume                 # 恢复最近保存的会话
agentao --acp --resume <SESSION_ID>    # 恢复指定会话(UUID / 前缀 / 时间戳)

ACP 是客户端驱动的——server 不能自己开会话——所以 --resume 装载一个由第一个 session/new 消费的一次性指令。该请求会注入持久化历史、把它作为 session/update 通知重放(和 session/load 完全一致),并返回持久化的 sessionId 而非新生成的。此后该连接上的每个 session/new 都是全新空会话。

会话存储以客户端传入的 cwd 为键,因此查找在请求时针对 <cwd>/.agentao/sessions 进行。任何可恢复的未命中——空存储、未知 id、文件损坏、或 id 已在注册表中存活——都会降级为全新会话(以 WARNING 记录),而不是让客户端的第一个 session/new 失败。

恢复出的 sessionId 只在 session/new 响应里返回,因此重放的 session/update 通知会先于客户端得知该 id 到达。对 session/update.sessionId 做严格校验的客户端可能会丢弃这些早到的更新;但从下一次 prompt 起对话仍能正确续上。参见 docs/guides/acp.md

完整方法清单

宿主 → Agent(5 个请求方法)

方法作用关键参数
initialize握手 + 能力协商protocolVersion:int, clientCapabilities:obj, clientInfo?:obj
session/new创建新会话cwd:string, mcpServers?:array
session/prompt发送一轮用户提示sessionId:string, prompt:array<PromptChunk>
session/cancel取消进行中的 promptsessionId:string
session/load从历史恢复会话sessionId:string, history:array

Agent → 宿主(1 个请求、1 个通知、1 个扩展)

方法性质作用
session/update通知流式事件:文本 chunk、思考、工具调用状态
session/request_permission请求(需 Client 应答)请求批准危险工具(写文件、跑 shell 等)
_agentao.cn/ask_user请求(扩展)向用户追问文本

握手 initialize

请求

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": 1,
    "clientCapabilities": {},
    "clientInfo": {
      "name": "my-ide",
      "version": "1.0.0"
    }
  }
}

Agentao 响应

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": 1,
    "agentCapabilities": {
      "loadSession": true,
      "promptCapabilities": {
        "image": false,
        "audio": false,
        "embeddedContext": false
      },
      "mcpCapabilities": {
        "http": false,
        "sse": true
      }
    },
    "authMethods": [],
    "agentInfo": {
      "name": "agentao",
      "title": "Agentao",
      "version": "0.2.14"
    },
    "_meta": {
      "_agentao.cn/extensions": [
        {
          "method": "_agentao.cn/ask_user",
          "description": "Request free-form text input from the user."
        }
      ]
    }
  }
}

规则要点

  • protocolVersion 必须是整数"1" 字符串、True 都会被拒)
  • 版本协商:若你传的版本 Agentao 支持,原样回传;否则回传 Agentao 支持的最高版本。不报错——由 Client 决定是否继续
  • clientCapabilities{} 也必须传(必填,类型必须是 object)

创建会话 session/new

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/new",
  "params": {
    "cwd": "/path/to/user/project",
    "mcpServers": [
      {
        "name": "github",
        "type": "stdio",
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-github"],
        "env": [{"name":"GITHUB_TOKEN","value":"<secret>"}]
      }
    ]
  }
}

响应

json
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"sess-a1b2c3"}}

关键说明

  • cwd 决定了这个会话的工作目录——文件工具、AGENTAO.md.agentao/ 全部相对它。多会话并发时务必不同
  • mcpServers 只接受 "type":"stdio""type":"sse"(因为 mcpCapabilities.http=false
  • 这些 MCP 配置被内部映射为 extra_mcp_servers 参数

发送提示 session/prompt

json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "session/prompt",
  "params": {
    "sessionId": "sess-a1b2c3",
    "prompt": [
      {"type": "text", "text": "找到 3 个最大的 .py 文件"}
    ]
  }
}

v1 prompt 数组里只能放 {"type":"text", "text": ...}

响应(一次 prompt 的最终结果)会在所有流式更新结束后返回:

json
{
  "jsonrpc":"2.0",
  "id":3,
  "result":{
    "stopReason":"end_turn"
  }
}

stopReason 取值:end_turn(正常完成)、max_tokenscancelledrefusalerror 等。

流式更新 session/update(通知)

session/prompt 返回前,Agentao 会发出多条

json
{
  "jsonrpc":"2.0",
  "method":"session/update",
  "params":{
    "sessionId":"sess-a1b2c3",
    "update":{
      "sessionUpdate":"agent_message_chunk",
      "content":{"type":"text","text":"我来帮你"}
    }
  }
}

sessionUpdate 字段的关键值:

含义
agent_message_chunk流式文本 chunk
agent_thought_chunk思考过程(如果启用)
tool_call工具开始调用
tool_call_update工具执行进度/输出

宿主不应对通知响应(JSON-RPC 2.0 规范:通知无 id)。

工具确认 session/request_permission(请求)

当 Agentao 尝试调用一个 requires_confirmation=True 的工具时:

json
{
  "jsonrpc":"2.0",
  "id":42,
  "method":"session/request_permission",
  "params":{
    "sessionId":"sess-a1b2c3",
    "toolCall":{
      "toolCallId":"call-x",
      "status":"pending",
      "title":"Run: rm -rf build/",
      ...
    },
    "options":[
      {"optionId":"allow_once","name":"Allow once","kind":"allow_once"},
      {"optionId":"reject_once","name":"Reject","kind":"reject_once"}
    ]
  }
}

宿主必须响应(否则 Agent 会阻塞直到超时):

json
{
  "jsonrpc":"2.0",
  "id":42,
  "result":{
    "outcome":{"outcome":"selected","optionId":"allow_once"}
  }
}

建议的宿主 UI 流程:

  1. 收到请求 → 弹出模态框展示 titletoolCall 细节
  2. 用户选择 → 立即 result 响应
  3. 若用户超时不响应,宿主主动响应 {"outcome":"cancelled"} 并可选 session/cancel

取消 session/cancel

json
{"jsonrpc":"2.0","id":99,"method":"session/cancel","params":{"sessionId":"sess-a1b2c3"}}

效果:当前 session/prompt 的轮次会以 stopReason:"cancelled" 结束。幂等——重复调用不会出错。

幂等性 —— 当前轮次已结束后再发一次 session/cancel 不会报错(不会抛、不会再发额外通知)。

恢复会话 session/load

用于持久化会话场景:你把 sessionId + 历史消息存到数据库,进程重启后 restore。Agentao 在 initialize 响应里声明 loadSession:true 表示支持。

json
{
  "jsonrpc":"2.0",
  "id":5,
  "method":"session/load",
  "params":{
    "sessionId":"sess-restored",
    "cwd":"/path/to/project",
    "history":[
      {"role":"user","content":[{"type":"text","text":"先前的问题"}]},
      {"role":"assistant","content":[{"type":"text","text":"先前的答复"}]}
    ]
  }
}

⚠️ 常见陷阱

ACP 协议层最容易踩的坑

  1. 帧格式:一定是 NDJSON,JSON 里不能有裸换行——写 JSON 库时注意 compact 模式
  2. stdout 污染:Agentao 保证所有日志走 agentao.log + stderr,不污染 stdout。你的 Client 读 stdout 一定拿到纯 JSON
  3. stdin 阻塞:如果 Client 在发送请求后不读 stdout,Server 的 stdout buffer 会撑爆。用异步 I/O 或专门读取线程
  4. 版本字段类型protocolVersion 必须 int,不是日期字符串
  5. session/update 不回应:JSON-RPC 2.0 禁止为通知发响应

端到端最小宿主(Python 实现,仅作演示)

⚡ 端到端可跑(约 5 分钟)

产出 —— 从 50 行 Python 客户端驱动 agentao --acp --stdio;能看到 session/update 流式通知 + 最终 stopReason技术栈 —— pip install 'agentao[cli]>=0.4.0' + 3 个环境变量;下方 Python 即所需的全部宿主代码。 运行 —— 粘贴到 acp_demo.py,然后 python acp_demo.py宿主不是 Python? —— 读懂线协议流后,照 3.3 宿主作为 ACP Client 翻译到目标语言。

即便你的宿主不是 Python,这个例子也帮助理解协议流:

python
"""最小 ACP 客户端(Python),驱动 agentao --acp --stdio。"""
import json, subprocess, threading, queue, uuid

class AcpClient:
    def __init__(self):
        self.proc = subprocess.Popen(
            ["agentao", "--acp", "--stdio"],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE,
            bufsize=0, text=True,
        )
        self._pending: dict = {}
        self._notifications: queue.Queue = queue.Queue()
        threading.Thread(target=self._reader, daemon=True).start()

    def _reader(self):
        for line in self.proc.stdout:
            msg = json.loads(line)
            if "id" in msg and "method" not in msg:        # 是响应
                fut = self._pending.pop(msg["id"], None)
                if fut: fut.put(msg)
            elif "method" in msg and "id" not in msg:       # 是通知
                self._notifications.put(msg)
            elif "method" in msg and "id" in msg:           # 是请求(Agent→Client)
                self._notifications.put(msg)

    def call(self, method, params):
        id_ = str(uuid.uuid4())
        fut: queue.Queue = queue.Queue(maxsize=1)
        self._pending[id_] = fut
        msg = {"jsonrpc":"2.0","id":id_,"method":method,"params":params}
        self.proc.stdin.write(json.dumps(msg) + "\n"); self.proc.stdin.flush()
        return fut.get()

    def respond(self, id_, result):
        msg = {"jsonrpc":"2.0","id":id_,"result":result}
        self.proc.stdin.write(json.dumps(msg) + "\n"); self.proc.stdin.flush()

# 使用
cli = AcpClient()
print(cli.call("initialize", {"protocolVersion":1,"clientCapabilities":{}}))
r = cli.call("session/new", {"cwd":"/tmp"})
sid = r["result"]["sessionId"]
# 异步监听通知队列,同时发 prompt
cli.call("session/prompt", {
    "sessionId": sid,
    "prompt":[{"type":"text","text":"列 3 个最大文件"}],
})

生产级 Client 代码范式(错误处理、UI 桥接、超时)见 3.3 宿主作为 ACP 客户端架构

关键源码位置

话题文件
启动入口agentao/cli/entrypoints.py:254-389
协议常量agentao/acp/protocol.py:18, 47-58
握手agentao/acp/initialize.py
能力宣告agentao/acp/initialize.py:53-76
会话创建agentao/acp/session_new.py
Prompt 处理agentao/acp/session_prompt.py

第 4 部分 · 事件层与 UI 集成