跳到主要内容

巧妙之处、边界与横向对比

本章是给「想把精华带走」的人:ACP 类型层最值得抄的几招、它刻意不做什么、以及它在协议货架上的位置。

1. 巧妙之处:为「前向兼容」专门造的 serde 管线

协议会演进,旧实现会收到带新字段、或带它读不懂的值的消息。处理不好,一个坏字段就能让整条消息反序列化失败、整个 agent 崩掉。ACP 在 serde 层做了三件事专门防这个——这是全仓库工程含量最高的部分。

招 1:MaybeUndefined<T> —— 区分「没这个 key」和「key 是 null」

Option<T> 只有两态,但 JSON 有三态:键缺失、键为 null、键有值。对「部分更新」语义这很要命:null 通常意为「清空」,缺失意为「不动」。ACP 造了三态枚举(serde_util.rs:368):

// 真实定义,serde_util.rs:368
pub enum MaybeUndefined<T> {
Undefined, // 键不存在 —— "不要动这个字段"
Null, // 键存在且为 JSON null —— "清空它"
Value(T), // 键有值 —— "设成这个"
}

用在 SessionInfoUpdate.title 这种地方(client.rs:241-245),注释直接写「Set to null to clear」——配合 skip_serializing_if = "MaybeUndefined::is_undefined",只有真正要改的字段才上线缆。这个设计借鉴自 async-graphql(serde_util.rs:14)。

招 2:DefaultOnError —— 坏字段不毒死整条消息

几乎每个可选字段都标了 #[serde_as(deserialize_as = "DefaultOnError")](例如 agent.rs:62-64clientCapabilities)。语义:这个字段反序列化失败时,退回默认值,而不是让整条消息失败

为什么重要:新版 agent 给 clientCapabilities 加了个旧 client 不认识的子结构、或值类型变了,旧 client 不会因此崩——它把不认识的能力当「没有」处理,继续跑。这是「老实现遇到新消息也别死」的关键。

招 3:VecSkipError —— 丢坏元素,不丢整列表

列表字段(如 prompt: Vec<ContentBlock>mcpServers)标的是 DefaultOnError<VecSkipError<_, SkipListener>>(agent.rs:3279-3280)。语义:列表里某一项反序列化失败,就跳过那一项,保留其余——而不是整列表清空。

配套的 SkipListener(serde_util.rs:29-58)是个观测钩子:开了 tracing feature 时,每丢一个坏元素就 tracing::warn! 一条;不开时它直接是 (),serde_with 提供的 no-op,零运行时开销。既要容错、又要可观测、还不想给默认路径加负担——三者兼顾。

一句话总结这三招: 它们共同实现了协议的「鲁棒性原则」——严格地生成、宽容地接收(be strict in what you send, liberal in what you accept)。新增能力 / 新增内容类型 / 字段演进,旧实现都能不崩地降级处理。

2. 其它值得一看的小设计

  • 强类型 id 而非裸字符串。 SessionIdToolCallIdPermissionOptionIdTerminalId 都是 newtype 包 Arc<str>(如 v1/mod.rs:55)。Arc 让 id 到处克隆几乎免费,newtype 防止把 session id 误传给要 tool call id 的地方。
  • #[non_exhaustive] 几乎遍布所有类型。 这让上游以后给 enum 加变体、给 struct 加字段时,不算 Rust API 的破坏性变更——和「能力优先、少 bump 版本」的演进哲学一致。
  • 错误码沿用 JSON-RPC + 少量自定义。 标准码(-32700 ParseError…)加上 ACP 自己的 AuthRequired(-32000)、ResourceNotFound(-32002)等(error.rs:162-219);未知码落到 ErrorCode::Other(i32) 而非报错——又是「宽容接收」。

3. 边界与局限(诚实)

  • 这个仓库不含运行时。 没有事件循环、没有传输实现、没有 Agent/Client trait——那些在上层 agent-client-protocol crate(lib.rs:12-16 明说)。本文讲「协议形状」,不是「怎么跑一个 agent」。
  • 远程 agent 仍是 work in progress。 协议设计上支持本地(stdio)和远程(HTTP/WebSocket),但 introduction 明说远程支持「正在推进中」(introduction.mdx:31-35)。当下成熟路径是本地子进程 + stdio。
  • 信任模型是「本地、可信模型」。 设计前提是「你在编辑器里跟一个你信任的模型对话」(architecture.mdx:14)。权限请求给的是 UX 层的「人在回路」确认,不是对抗恶意 agent 的沙箱;真正的隔离要靠编辑器自己。
  • 大量功能仍是 unstable。 NES、elicitation、provider 配置、session fork、MCP-over-ACP、v2 等都在 feature gate 后,随时可能变(各处的 UNSTABLE 警告)。生产只应依赖稳定面。

4. 横向对比

和 LSP

ACP 是自觉地照着 LSP 学的(introduction.mdx:17):同样基于 JSON-RPC、同样「一次实现接整个生态」、同样用 $/ 前缀标记「可忽略的实现相关通知」。区别是 LSP 标准化「编辑器 ↔ 语言智能」,ACP 标准化「编辑器 ↔ 自主编码 agent」——后者多了双向请求(agent 反向要文件/终端/授权)和流式回合(prompt turn)。

和 MCP(Model Context Protocol)

两者互补,不竞争。MCP 标准化「LLM/agent ↔ 工具与数据源」;ACP 标准化「编辑器 ↔ agent」。ACP 刻意复用 MCP 的内容类型(ContentBlock 兼容 MCP,content.rs:30),好让 agent 把 MCP 工具输出原样转发。架构上常见组合:编辑器把它配置的 MCP 服务器透传给 agent,让 agent 直连(architecture.mdx:26-34);编辑器自己要导出工具时,可起一个小代理把 MCP 隧道回自己。

┌── ACP ──┐ ┌── MCP ──┐
编辑器 ◀──────────▶ Agent (LLM) ◀──────────▶ 工具 / 数据源
(本协议) "编排者" (MCP 协议)

一句话:ACP 管「人和编辑器怎么驱动 agent」,MCP 管「agent 怎么够到工具」。 同一个 agent 常常一头说 ACP、一头说 MCP。

5. 总代码地图(全仓库导航)

主题文件符号
crate 范围说明…/src/lib.rs顶部 //! 文档
JSON-RPC 信封…/src/rpc.rsRequestId / Request / Response / Notification / JsonRpcMessage
三态字段…/src/serde_util.rsMaybeUndefined
容错反序列化钩子…/src/serde_util.rsSkipListener(配 VecSkipErrorDefaultOnError)
握手 / 会话 / prompt…/src/v1/agent.rsInitializeRequest / NewSessionRequest / PromptRequest / StopReason
反向方法(fs/terminal/permission)…/src/v1/client.rsReadTextFileRequest / CreateTerminalRequest / RequestPermissionRequest
流式更新…/src/v1/client.rsSessionUpdate
内容块…/src/v1/content.rsContentBlock
工具调用…/src/v1/tool_call.rsToolCall / ToolCallContent / Diff
计划…/src/v1/plan.rsPlan / PlanEntry
错误码…/src/v1/error.rsError / ErrorCode
路由 enum(六个)…/src/v1/{agent,client}.rsClientRequest / AgentResponse / ClientNotification / AgentRequest / ClientResponse / AgentNotification
版本…/src/version.rsProtocolVersion
schema 生成schema-generator/src/main.rsAgentOutgoingMessage / ClientOutgoingMessage
feature gateagent-client-protocol-schema/Cargo.toml[features]