内容块、工具调用状态机与权限
ACP 的设计哲学里有一条是 UX-first(
architecture.mdx:13):协议要让编辑器能清晰渲染 agent 的意图——正在说什么、在调什么工具、改了哪个文件、要不要授权。本章就是这套「可渲染的语义」。
1. ContentBlock —— 一切可显示内容的统一载荷
它要解决的小问题。 prompt 里要带文本、图片、引用的文件;agent 的输出也是文本、图片、资源。如果各处各定义一套,就乱了。
思路。 定义一个 ContentBlock 枚举,prompt、流式输出、工具结果全都复用它(content.rs:25-31)。而且它刻意兼容 MCP 的 JSON 表示——这样 agent 能把 MCP 工具的输出原样转发,不用再转换一层(content.rs:30-31、introduction.mdx:37)。
五种内容块(content.rs:38):
| 变体 | 是什么 | 备注 |
|---|---|---|
Text | 纯文本或 Markdown | 所有 agent 必须支持;client 应按 Markdown 渲染(content.rs:39-42) |
ResourceLink | 指向 agent 可访问的资源的引用 | 所有 agent 必须支持 |
Image | 图片 | 在 prompt 中需 image 能力 |
Audio | 音频 | 在 prompt 中需 audio 能力 |
Resource | 直接内嵌完整资源内容 | 需 embeddedContext 能力;优先用它,省一次往返(content.rs:56-60) |
用 tag = "type" 做区分(content.rs:35),线缆上长这样:{ "type": "text", "text": "..." }。
2. SessionUpdate —— agent 往 UI 推的所有「实时进度」种类
回合进行中,agent 发的每条 sessionUpdate 通知里包一个 SessionUpdate(client.rs:99),用 sessionUpdate 字段区分类型。完整种类:
变体(线缆 sessionUpdate 值) | 含义 |
|---|---|
user_message_chunk | 用户消息的分片(回放/回显) |
agent_message_chunk | agent 回复的分片(主要的「正文流」) |
agent_thought_chunk | agent 内部思考的分片(可单独渲染) |
tool_call | 新工具调用开始 |
tool_call_update | 工具调用状态/结果更新 |
plan | agent 的执行计划(见 §5) |
available_commands_update | 可用命令变化 |
current_mode_update | 当前模式变化 |
config_option_update | 会话配置项更新 |
session_info_update | 会话元信息(标题、时间戳)更新 |
usage_update | 上下文窗口占用与花费更新 |
注意「思考」和「回复」是分开的变体——这样编辑器能把 agent 的内心独白折叠/灰显,和正式回复区分开。这就是 UX-first 的体现。
3. ToolCall —— 一个带状态机的「动作」
它要解决的小问题。 LLM 说「我要读文件 / 跑命令」,编辑器得把这件事作为一个有进度、有结果的实体画出来,而不是一行 log。
思路。 ToolCall(tool_call.rs:29)是个有 id、有标题、有 kind、有 status、有 content 的实体。它的状态会就地更新:agent 先发一个 tool_call,之后发若干 tool_call_update 改它的状态/内容(tool_call.rs:188 ToolCallUpdate,只带变化的字段)。
状态机(tool_call.rs:471 ToolCallStatus):
pending ───▶ in_progress ───▶ completed
(排队/等审批) (执行中) └──▶ failed
kind 给编辑器选图标用(tool_call.rs:431 ToolKind):read / edit / delete / move / search / execute / think / fetch / switch_mode / other(默认 other)。
locations 字段列出受影响的文件路径,用途注释写得很直白:「让 client 实现『跟随』功能」(tool_call.rs:50-51)——即编辑器能随 agent 自动跳到正在改的文件。
4. 工具结果的三种内容:普通 / diff / 终端
ToolCallContent(tool_call.rs:502)有三种,这是 ACP 「为编码 UX 定制」最明显的地方:
| 变体 | 是什么 | 关键字段 |
|---|---|---|
Content | 普通内容块(文本/图片/资源) | 包一个 ContentBlock |
Diff | 文件修改,以差异形式展示 | path + oldText(新文件为 None)+ newText(tool_call.rs:627) |
Terminal | 嵌入一个用 terminal/create 建的终端 | terminalId;注释提醒「必须在 terminal/release 之前嵌入」(tool_call.rs:507-512) |
Diff 单列一种、而不是塞进文本,是因为编辑器要画成红绿对照的 diff 视图——这正是 introduction.mdx:37 说的「为有用的 agent 编码 UX 元素(如显示 diff)定制类型」。
5. Plan —— agent 的「待办清单」
复杂任务里,agent 可以发一个 Plan(plan.rs:33),让用户看到它打算分几步走。每个 PlanEntry(plan.rs:446)有:
content:这步要干什么priority:high/medium/low(plan.rs:502)status:pending/in_progress/completed…(plan.rs:518)
关键语义:更新计划时,agent 必须发完整的全量列表,client 用它整体替换旧计划(plan.rs:34-37)——不是增量打补丁。这把「计划同步」做成了无状态的幂等替换,简单可靠。
6. 权限请求 —— 「信任但要确认」
它要解决的小问题。 agent 要改文件、删东西、跑命令,这些是危险动作。ACP 的信任模型是「你在用编辑器跟一个你信任的模型对话,但你仍要对工具调用有控制权」(architecture.mdx:14)。
机制。 agent 在动手前发 session/request_permission(client.rs:653 RequestPermissionRequest),带上:
toolCall:要执行的工具调用详情(一个ToolCallUpdate)options:给用户选的若干PermissionOption
每个选项有个 kind,告诉 UI 该用什么图标/措辞(client.rs:776 PermissionOptionKind):
| kind | 含义 |
|---|---|
allow_once | 这次允许 |
allow_always | 允许并记住 |
reject_once | 这次拒绝 |
reject_always | 拒绝并记住 |
用户选完,Client 回 RequestPermissionResponse,outcome 是 Selected{optionId};若这轮 prompt 被取消,则回 Cancelled(client.rs:837 RequestPermissionOutcome,与 01 章 §7 的取消握手对应)。
7. 代码地图(本章)
| 主题 | 文件 | 符号 |
|---|---|---|
| 内容块(prompt/输出共用) | …/src/v1/content.rs | ContentBlock / TextContent |
| 流式更新种类 | …/src/v1/client.rs | SessionUpdate / SessionNotification |
| 工具调用实体 | …/src/v1/tool_call.rs | ToolCall / ToolCallUpdate |
| 工具状态 / 类别 | …/src/v1/tool_call.rs | ToolCallStatus / ToolKind |
| 工具结果(diff/终端) | …/src/v1/tool_call.rs | ToolCallContent / Diff / Terminal |
| 计划 | …/src/v1/plan.rs | Plan / PlanEntry / PlanEntryStatus |
| 权限请求 | …/src/v1/client.rs | RequestPermissionRequest / PermissionOption / RequestPermissionOutcome |