Agent Client Protocol 架构

ACP 标准化了编码界面与编码 agent 之间的关系。它为它们提供了一种通用方式,用于建立能力、打开项目会话、交换提示、流式传输进度、请求权限、取消工作,以及关闭或恢复会话。

两个角色

角色职责
Client / host启动 agent 进程,提供人机界面,选择 workspace,提供可选文件、终端和 MCP 服务器,应用权限策略,并渲染实时更新
ACP agent接受项目 session 和提示,执行编码工作,报告消息和 tool 活动,在需要时请求权限,并返回一个带类型的 stop reason

ADK-Rust 可以承担任一角色。这是两个部署方向,不是两种 不同的协议。

Rendering architecture…

当 ADK-Rust 消费另一个编码代理时,左侧是 ADK-Rust,右侧是外部进程。当前端消费一个 ADK-Rust 代理时, 编辑器拥有左侧,而 AcpServer 拥有右侧。

一次 ACP 轮次

Rendering architecture…

连接是双向的。客户端在提示运行期间必须持续读取,因为代理可能会在最终提示响应之前发送通知或权限请求。

会话标识与状态

一个 ACP 会话用于标识围绕一个项目持续进行的对话。它包含一个绝对的 cwd、可选的附加目录、多个提示、流式更新以及生命周期。在 ADK-Rust 服务器中,一个 ACP 会话映射到一个 ADK-Rust 会话,因此模型历史和会话状态会始终附着在同一段对话上。

关闭活动连接与删除持久化历史不同:

  • session/close 释放活动会话及其进程;
  • session/resume 连接到持久化的 ADK 会话状态;
  • session/load 重新激活一个持久化会话,并在请求完成前按顺序将其存储的 对话作为 session/update 通知回放给客户端;
  • session/fork 将一个持久化会话分支为一个新的会话 id,其存储的 历史是源会话的副本,而源会话保持不变;
  • session/delete 删除持久化会话;
  • session/list 返回通过已配置 SessionService 可见的会话。

session/load 会将提供的 cwd 按照与 session/resume 相同的方式与会话存储的工作 目录进行校验,并在会话标识未知时返回会话未找到 错误。回放会按原始时间顺序将每个存储的用户、代理、思考和工具事件映射为其对应的 SessionUpdate 变体,因此重新连接的编辑器会按事件实际发生的顺序恢复可见历史。

交互式会话控制

代理可以通过提供一个 SessionControls 提供者向客户端暴露交互式控制。当它这样做时,服务器会在 session/newsession/loadsession/resumesession/fork 响应中将其公告:

  • 模式 —— 一组命名模式(例如“ask”与“code”),带有一个 当前选择。session/set_mode 会根据公告的集合校验请求的模式,记录它,并发出一个 CurrentModeUpdate;未知模式会被拒绝,当前模式保持不变。
  • 配置选项 —— 客户端可以读取和更改的选择项与开关。 session/set_config_option 会根据选项声明的 选择值校验该值,记录它,并发出一个 ConfigOptionUpdate;未知选项或 无效值会被拒绝。
  • 可用命令 —— 会话变为活动状态时作为一个 AvailableCommandsUpdate 显示的 ACP slash 命令。

模式和配置选择会持久保存在 ADK 会话状态(acp:modeacp:config:<id>)中,因此它们在加载、恢复和分叉时都能保留。记录下来的会话 标题会在激活时以及每次变更时作为 SessionInfoUpdate 显示。一个 Plan 更新映射存在,但在 ADK 计划原语暴露计划条目之前一直处于休眠状态。提供了无任何 SessionControls 的代理不会公告任何 模式或选项,从而使公告的能力与服务器实际实现的功能完全一致。

内容通过一个映射跨越边界

来自客户端的提示和流式返回给客户端的更新都会通过一个单一的内容模块,该模块在 ACP ContentBlock 值与 adk_core::Part 值之间双向映射。双向使用同一个映射意味着 服务器提示解析器、服务器流式输出器以及客户端都对每种内容类型的表示方式保持一致。

该映射会忠实保留负载。文本块映射为 Part::Text,并保留字符串原样。嵌入资源块映射为 Part::EmbeddedResource, 保留源 URI、可选的 MIME 类型以及内容。文本资源在两个方向上都按字面传输,绝不会进行 base64 编码;二进制资源在传输线上会进行 base64 编码,并在边界的 ADK 一侧解码为原始字节。图像和音频块映射为 Part::InlineData, 保留 MIME 类型和解码后的字节;服务器会公告并接受 这些提示媒体,而客户端会将非文本的 ADK 内容(嵌入资源、图像、音频)作为匹配的 ACP 块传输,而不是丢弃它。

流式更新携带的不只是文本

当提示运行时,服务器会将带类型的 ADK 事件转换为 ACP session/update 通知。模型文本和思考会变成消息块和 思考块,嵌入资源内容会变成嵌入资源消息块。除此之外,还有两类更新可以让客户端更丰富地了解这次轮次:

  • 用量更新。 当一个 ADK 事件携带用量元数据时,服务器会发送一个 UsageUpdate,反映报告的 token 计数;如果运行时报告了美元成本,也会一并发送。没有用量元数据的事件不会产生任何更新,服务器也绝不会凭空生成计数。
  • 富工具调用更新。 一个工具调用最初表现为一个 ToolCall,其工具 kind 由工具声明的行为推断而来。其后续的 ToolCallUpdate 会携带工具结果内容以及工具报告受影响的文件位置,因此编辑器可以渲染 diff 和受影响文件列表。该更新与最初的 ToolCall 保持相同的标识符,从而在整个轮次中保持关联。

客户端方向具有相匹配的保真度。当一个 ADK-Rust 应用 消费一个 External_Agent 时,其流式表面(OutputChunk)不仅暴露 代理文本和思考,还暴露 External_Agent 的 ToolCallUpdate(作为 一个按 id 关联的工具更新,携带状态、类型、标题、内容文本以及 受影响的文件位置)以及其 UsageUpdate(已使用的 token 和大小,以及在 有报告时的费用和货币)。代理消息文本会像之前一样原样呈现, 因此现有的文本消费者不受影响。

权限请求桥接工具确认

一个 ADK-Rust 代理可以在等待人工批准某个工具调用时暂停一个回合 (ToolConfirmationRequest)。在服务器端,这个暂停会变成一个原生的 ACP session/request_permission 请求,用于描述该工具及其参数。客户端的结果会恢复该回合: 批准映射为 allow,而拒绝或取消都映射为 deny,因此被取消的请求 绝不会执行该工具。每个结果都通过其函数调用标识符与确切调用关联, 并通过其工具确认决策反馈给运行器。嵌套的 权限请求由派生的提示任务发出,因此外层的 session/prompt 响应仍会正常完成。

能力是一种契约

初始化并不是装饰性的握手。双方只声明其支持的 操作和内容。ADK-Rust 利用这些能力来避免向只接受 stdio 的代理发送可选的 HTTP 或 SSE MCP 配置,并且只有在 应用提供相应实现时才会声明文件系统或终端主机操作。

服务器会准确声明其提示处理器接受的内容类型。它会声明 embedded_contextimageaudio 提示能力, 因为嵌入式资源内容会映射到 adk_core::Part::EmbeddedResource,而 图像和音频内容会映射到 adk_core::Part::InlineData。它会声明 load_session,因为它注册了一个 session/load 处理器,以及 fork 会话能力,因为它注册了一个 session/fork 处理器。会话模式 和配置选项只有在代理提供 SessionControls 提供者时才会被声明,因此没有该提供者的代理两者都不会声明。远程 传输、模型选择器以及实验性的协议扩展仍然不会被声明。一个携带服务器尚未声明的内容类型的提示会被 以描述性错误拒绝,而不是被部分处理。调用方应当围绕协商得到的能力对象进行设计,而不是假设每个 ACP 实现都具有相同的表面。

下一步

Agent Client Protocol 架构 - ADK-Rust 文档 | ADK-Rust