跳到主要内容

ACP (Agent Client Protocol) — 架构与原理

30 秒导读: ACP 是一套 JSON-RPC 协议,让「代码编辑器」(Zed、JetBrains、Neovim…)和「编码 AI agent」(Claude、Gemini CLI、Codex…)互通。编辑器只要实现一次 ACP,就能接所有 ACP agent;反之亦然——把过去 N 个编辑器 × M 个 agent 的私有集成,压成一套标准。本仓库是协议的类型定义(Rust 结构体 + JSON Schema 生成器),不是运行时。

1. 这是什么(零基础也能懂)

一句话定义。 ACP(Agent Client Protocol)是一套基于 JSON-RPC 2.0 的线缆协议,标准化「代码编辑器」与「编码 agent」之间的通信。

它要解决的痛点。 AI 编码 agent 和编辑器天然要紧耦合,但互通不是默认的:

  • 每出一个新 agent,每个编辑器都要为它写一份私有集成。
  • 每个 agent 要想被用上,得去实现一堆编辑器各自的私有 API。
  • 用户被锁死:选了某个 agent,就只能用它支持的那几个界面。

ACP 用「一套标准」打破这点——思路完全类比 LSP(Language Server Protocol,微软为「编辑器 ↔ 语言服务器」定的标准)。这一点项目自己也明说了,见 docs/get-started/introduction.mdx:17

一句话类比。

LSP 让「一个语言服务器」服务「所有编辑器」;ACP 让「一个编码 agent」服务「所有编辑器」。ACP 就是「agent 界的 LSP」。

典型场景(白话走一遍)。 你在 Zed 里写代码,想让一个 AI agent 帮你改一个大项目:

  1. 编辑器把 agent 作为子进程拉起来,双方通过 stdin/stdout 用 JSON-RPC 对话(docs/get-started/architecture.mdx:18)。
  2. 你输入一句「帮我把这个函数改成异步的」。
  3. agent 一边思考一边流式把「我在想什么/我要调哪个工具/改了哪个文件」推回编辑器,实时显示。
  4. agent 要改文件、跑命令这类敏感操作前,反向问编辑器:「准许吗?」——你点「允许」它才动手。
  5. 这一轮结束,agent 回一个「停止原因」(正常结束 / 触顶 / 被取消…)。

它长什么样(最小线缆示例)。 一条最关键的消息——用户发一句 prompt(agent.rs:3263 PromptRequest):

{
"jsonrpc": "2.0",
"id": 7,
"method": "session/prompt",
"params": {
"sessionId": "sess-abc",
"prompt": [{ "type": "text", "text": "把 foo 改成 async" }]
}
}

agent 处理途中往回推的流式更新(client.rs:50 SessionNotification,无 id 故是 notification):

{
"jsonrpc": "2.0",
"method": "sessionUpdate",
"params": {
"sessionId": "sess-abc",
"update": { "sessionUpdate": "agent_message_chunk",
"content": { "type": "text", "text": "好的,我来改" } }
}
}

注意 method 用的是协议线缆名 session/promptsessionUpdate,字段是 camelCase;Rust 侧的类型名是 PromptRequestSessionNotification——本仓库就是把这层映射用强类型固定下来。

本节不碰底层。下一层我们看「大盘怎么转」。

2. 顶层全景(它大概怎么转)

两个角色,一条管道

ACP 里只有两个角色,沟通是双向的——不是简单的「客户端请求 / 服务端响应」:

  • Client(客户端) = 代码编辑器 / IDE。它持有本地文件、终端、MCP 配置,掌握 UI 和用户授权。
  • Agent(代理) = 那个用 LLM 自主改代码的程序。

关键认知:两边都能发起请求。agent 不只是被动应答——它会反过来请编辑器读写文件、跑终端、问用户要权限。

一个连接(通常 stdin/stdout),可并发多个 session
┌──────────────────────────────────────────────────────────────┐
│ │
│ Client (编辑器) Agent (LLM 程序) │
│ ┌───────────┐ ──── initialize ───────▶ ┌──────────────┐ │
│ │ UI / 文件 │ ──── session/new ──────▶ │ 会话上下文 │ │
│ │ 终端 / MCP │ ──── session/prompt ───▶ │ + LLM 循环 │ │
│ │ 用户授权 │ │ │ │
│ │ │ ◀── sessionUpdate(流) ── │ (流式吐内容) │ │
│ │ │ ◀── fs/read · terminal ─ │ (反向请求) │ │
│ │ │ ◀── request_permission ─ │ │ │
│ │ │ ──── (用户的选择) ──────▶ │ │ │
│ └───────────┘ ◀── PromptResponse ────── └──────────────┘ │
│ (带 stop_reason) │
└──────────────────────────────────────────────────────────────┘

怎么读这张图:横向箭头是 JSON-RPC 消息;向右是 Client→Agent 的方法(agent 侧实现),向左是 Agent→Client 的方法(client 侧实现)和流式通知。一个连接可承载多个并发 session(architecture.mdx:20)。

部件一句话职责

部件干什么在哪(克隆根相对)
JSON-RPC 信封包裹所有消息的 {jsonrpc, id?, method, params}agent-client-protocol-schema/src/rpc.rs
Agent 侧方法initialize / session/new / session/prompt 等,由 agent 实现…/src/v1/agent.rs
Client 侧方法session/update(流) / session/request_permission / fs/* / terminal/*,由编辑器实现…/src/v1/client.rs
内容块文本/图片/音频/资源,prompt 与输出的统一载荷,兼容 MCP…/src/v1/content.rs
工具调用agent 执行动作的状态机 + diff/terminal 展示…/src/v1/tool_call.rs
计划 / 错误 / 版本Plan、JSON-RPC 错误码、协议版本号…/src/v1/{plan,error}.rs…/src/version.rs
Schema 生成器把这些 Rust 类型吐成 JSON Schema 供各语言 SDK 用schema-generator/src/main.rs

主线走一遍(高层)

一次完整交互的骨架(三步,细节见 01-lifecycle):

  1. 握手 initialize:协商协议版本 + 互换能力 + 列出可用鉴权方式。
  2. 建会话 session/new:给定工作目录和 MCP 服务器,拿回一个 sessionId
  3. prompt 回合 session/prompt:用户发消息 → agent 流式吐内容/工具调用 → 必要时反向请求权限和文件操作 → 回合以一个 stop_reason 结束。

3. 这套文档怎么读(阅读地图)

建议顺序,由浅入深:

  1. 01-lifecycle.md —— 先把「一次会话从握手到一轮 prompt 结束」整条主线走通。这是理解 ACP 的主干。
  2. 02-roles-and-routing.md —— 为什么是「两套方法 + 双向请求」,SDK 怎么用 ClientRequest / AgentRequest 等 enum 把收到的 JSON-RPC 分发到对的处理函数。
  3. 03-content-and-tools.md —— prompt 和输出共用的 ContentBlock,以及 ToolCall 的状态机、diff、终端嵌入、Plan、权限请求。这是「UX-first」设计最集中的地方。
  4. 04-capabilities-and-extensibility.md —— 能力协商、_meta 扩展位、unstable_* feature gate、版本策略与 schema 生成——「协议如何在不破坏兼容的前提下演进」。
  5. 05-clever-and-boundaries.md —— serde 上几处真正巧妙的容错设计(三态 MaybeUndefinedDefaultOnErrorVecSkipError)、协议边界、横向对比、代码地图。

本仓库的范围(重要,避免误解): 这是 agent-client-protocol-schema crate——只有类型与 schema,即所有请求/响应/通知的强类型定义 + serde 管线 + JSON Schema 生成(src/lib.rs:12-16)。真正的「运行时」(传输、连接装配、Agent/Client trait)在另一个上层 crate agent-client-protocol 里。所以本文讲的是协议形状,不是某个具体实现的事件循环。