跳到主要内容

能力协商、扩展位与协议演进

协议一定会变。ACP 的核心难题是:怎么加功能而不把现有实现搞挂。本章讲它的三层答案——能力协商、_meta 扩展位、版本与 feature gate。

1. 为什么需要能力协商

不是所有编辑器都支持终端,不是所有 agent 都支持加载历史。如果协议「要么全支持要么不合规」,生态就长不起来。

ACP 的办法:initialize 时双方互报能力(capabilities),之后只用对方宣告支持的功能。这条贯穿全协议——几乎每个可选方法的文档都写着「only available if … capability」。

2. 三套能力

Agent 能力(agent.rs:3987 AgentCapabilities)

字段宣告什么
loadSession是否支持 session/load
promptCapabilitiesprompt 里能收哪些非基线内容(图片/音频/内嵌资源)
mcpCapabilities支持的 MCP 接入方式
sessionCapabilities会话生命周期能力(list/resume/close…)
auth鉴权相关能力

Client 能力(client.rs:1732 ClientCapabilities)

字段宣告什么
fs文件系统能力——决定 agent 能否请求 fs/read_text_file / fs/write_text_file
terminal一个布尔:是否支持全部 terminal/* 方法(client.rs:1739)

Prompt 能力(在 §3 的基线之上做加法)

PromptRequest 的注释把规则说死了:agent 必须支持 TextResourceLink;其它内容类型由 PromptCapabilities 选择性开启;而且 Client 必须按 agent 宣告的 prompt 能力来调整自己的界面(agent.rs:3268-3271)。这是双向契约,不是单方声明。

心智模型: 把能力协商想成「握手时交换的一张功能清单」,之后所有交互都先查这张清单——清单上没有的功能,谁都不准用。

3. _meta —— 无处不在的扩展逃生舱

几乎每个结构体末尾都有一个 _meta 字段(类型 Meta = serde_json::Map,ext.rs:15)。它的契约写在每处注释里:「ACP 保留 _meta 让 client 和 agent 附加额外元数据;实现绝不能对这些 key 的值做假设」(例如 agent.rs:73-77)。

这给了「在不改协议结构的前提下塞自定义数据」的官方口子。配套的还有 ExtRequest / ExtNotification(ext.rs),让实现能跑完全自定义的方法而仍是合法 ACP——ClientRequest::ExtMethodRequestmethod 字段直接透传(agent.rs:5140)。

4. 版本策略:能力优先,版本号克制

ProtocolVersion 是个 u16,注释一句话定了基调:「只在破坏性变更时才 bump;非破坏的变化应通过能力引入」(version.rs:6-8)。

所以你看到的版本很少:

版本状态
V0预发布,基本当作不支持(version.rs:26-30)
V1当前稳定版(README 也确认「current stable ACP protocol version is 1」)
V2unstable 草案,需显式开 unstable_protocol_v2 feature(version.rs:35-41)

README 还区分了两个「版本」概念:线缆协议版本(initialize 里协商的 protocolVersion,决定消息形状)vs crate / schema 工件版本(影响 SDK 代码生成,但不一定改线缆)。两者可以一个变一个不变——消费者应只用协商出的 protocolVersion 判断线缆兼容性。

5. unstable feature gate:草案与稳定 schema 隔离

Rust 侧用 Cargo feature 把「还在迭代的协议草案」和「稳定面」隔开(agent-client-protocol-schema/Cargo.toml:24-50)。一堆 unstable_*:unstable_nes(下一步编辑建议)、unstable_elicitationunstable_llm_providersunstable_session_forkunstable_mcp_over_acp

源码里这些功能全被 #[cfg(feature = "unstable_…")] 包住(例如 agent.rs:4957ListProvidersRequest),且文档统一加 UNSTABLE 警告(「not part of the spec yet, may be removed or changed at any point」)。

值得注意:unstable_protocol_v2 故意不在 unstable 总开关里(Cargo.toml:37-40),因为它引入一整个平行 v2 模块和(最终)不同的线缆版本,必须显式选入——避免有人「开了 unstable 就意外切到 v2」。

6. schema 怎么从 Rust 生成

这些类型都派生 JsonSchema(schemars),schema-generator/src/main.rs 把六个聚合 enum(AgentRequest/ClientRequest/…)组装成 AgentOutgoingMessage / ClientOutgoingMessage(main.rs:63-78),再吐出 schema/v1/schema.json 等工件,供 Kotlin/Java/Python/TS 等 SDK 生成各自的类型。每个请求/响应结构体上的 #[schemars(extend("x-side" = …, "x-method" = …))](如 agent.rs:55)把「这消息属于哪一侧、对应哪个方法」也写进 schema,给代码生成器当元数据用。

7. 代码地图(本章)

主题文件符号
Agent 能力…/src/v1/agent.rsAgentCapabilities
Client 能力…/src/v1/client.rsClientCapabilities
Prompt 基线 + 能力规则…/src/v1/agent.rsPromptRequest(prompt 字段注释)
扩展位类型…/src/v1/ext.rsMeta / ExtRequest / ExtNotification
自定义方法透传…/src/v1/agent.rsClientRequest::ExtMethodRequest
版本号与策略…/src/version.rsProtocolVersion
feature 列表agent-client-protocol-schema/Cargo.toml[features](unstable_*unstable_protocol_v2)
schema 生成入口schema-generator/src/main.rsAgentOutgoingMessage / ClientOutgoingMessage