16.3 ACP 协议深度解析
模型: claude-opus-4-6 (anthropic/claude-opus-4-6) 生成日期: 2026-02-18
前两节我们分析了 OpenCode 在 VSCode 和 Zed 中的集成方式——前者通过终端和 HTTP 实现轻量级通信,后者通过声明式配置和 ACP 协议实现深度集成。本节将深入剖析 ACP(Agent Client Protocol)的实现细节,这是 OpenCode 实现 IDE 集成的核心协议层。
16.3.1 ACP 协议简介
什么是 ACP
ACP(Agent Client Protocol)是一个标准化的协议,定义了 IDE(客户端) 与 AI Agent(服务端) 之间的通信规范。它使用 @agentclientprotocol/sdk 库实现,通信格式基于 JSON-RPC(类似 LSP),传输层使用 stdio(标准输入/输出)。
ACP 定义了以下核心操作:
initialize
Client → Agent
能力协商,交换版本和支持的功能
authenticate
Client → Agent
认证请求
newSession
Client → Agent
创建新对话 Session
loadSession
Client → Agent
加载已有 Session
prompt
Client → Agent
发送用户输入
cancel
Client → Agent
取消当前操作
sessionUpdate
Agent → Client
实时更新(消息、工具调用、进度等)
requestPermission
Agent → Client
请求用户批准敏感操作
ACP 与 MCP 的区别
衍生解释——ACP 与 MCP:两个互补的协议
读者在第 8 章已经详细了解了 MCP(Model Context Protocol)。ACP 和 MCP 是 AI Agent 生态中两个方向相反但互补的协议:
┌──────────┐ ACP ┌──────────────┐ MCP ┌──────────────┐ │ IDE │ ◄──────────────► │ AI Agent │ ◄──────────────► │ 外部工具 │ │ (Client) │ IDE 是消费者 │ (中间层) │ Agent 是消费者 │ (Provider) │ │ Zed, │ Agent 是提供者 │ OpenCode │ Tool 是提供者 │ 文件系统, │ │ VSCode │ │ │ │ 数据库, API │ └──────────┘ └──────────────┘ └──────────────┘
MCP(Model Context Protocol):Agent 作为客户端,连接到外部工具/服务(如文件系统、数据库、Web API)。Agent 消费工具提供的能力。我们在第 8 章看到,OpenCode 通过 MCP 连接到 Grep、Glob、Read 等工具服务器。
ACP(Agent Client Protocol):IDE 作为客户端,连接到 AI Agent。IDE 消费 Agent 提供的能力——发送 prompt、接收回复、审批权限请求、查看工具调用进度。
两者的关系可以类比为:
MCP 就像 Agent 的"手"——它通过 MCP 操作外部世界
ACP 就像 Agent 的"脸"——它通过 ACP 与用户(通过 IDE)交互
一个完整的 AI 编程助手工作流是:用户通过 IDE(ACP Client)发送请求 → Agent 接收请求 → Agent 通过 MCP 调用工具完成任务 → Agent 通过 ACP 将结果返回给 IDE。
协议生命周期
一个完整的 ACP Session 生命周期如下:
16.3.2 acp/agent.ts:Agent 接口实现
acp/agent.ts:Agent 接口实现acp/agent.ts 是整个 ACP 实现的核心文件,共 1677 行代码。它定义了 ACP.Agent 类,实现了 @agentclientprotocol/sdk 中的 ACPAgent 接口。
类结构与初始化
ACP 使用 TypeScript 的 namespace 组织代码,通过工厂模式暴露 init() 函数。核心成员变量:
connection
AgentSideConnection
ACP SDK 提供的连接对象,用于向 IDE 发送消息
config
ACPConfig
配置信息,包含 SDK 实例和默认模型
sdk
OpencodeClient
OpenCode 的 SDK 客户端,用于调用内部 API
sessionManager
ACPSessionManager
ACP Session 管理器
eventAbort
AbortController
用于取消事件订阅的控制器
permissionQueues
Map<string, Promise<void>>
按 Session 排队的权限请求
permissionOptions
PermissionOption[]
固定的三个权限选项
构造函数在创建后立即调用 startEventSubscription() 启动事件监听循环。
initialize():能力协商
initialize():能力协商衍生解释——能力协商(Capability Negotiation)
能力协商是网络协议设计中的经典模式。当两个系统开始通信时,它们首先交换各自支持的功能列表,然后在后续通信中只使用双方都支持的功能。这种模式出现在许多协议中:
HTTP 内容协商:通过
Accept和Content-Type头协商数据格式TLS 握手:客户端和服务器协商加密算法
LSP:语言服务器和编辑器协商支持的语言特性
ACP 的能力协商告诉 IDE:
loadSession: true:Agent 支持加载已有 Session(实现了对话历史恢复)
mcpCapabilities: { http: true, sse: true }:Agent 支持 HTTP 和 SSE(Server-Sent Events)类型的 MCP 服务器
promptCapabilities: { embeddedContext: true, image: true }:Agent 支持嵌入式上下文引用和图片输入
sessionCapabilities: { fork: {}, list: {}, resume: {} }:Agent 支持 Session 的分叉、列表和恢复操作
initialize() 返回的能力集决定了 IDE 可以调用哪些后续方法。如果 Agent 不声明 loadSession: true,IDE 就不会尝试调用 loadSession() 方法。
newSession() 与 loadSession():Session 管理
newSession() 与 loadSession():Session 管理newSession() 的流程:
解析默认模型:调用
defaultModel()确定使用哪个 LLM 模型创建 Session:通过
sessionManager.create()在 OpenCode 内部创建一个新 Session加载 Session 模式:调用
loadSessionMode()获取可用的模型列表、Agent 模式(如 "code"、"architect")和 MCP 服务器配置返回 Session 信息:包括
sessionId、可用模型列表、可用模式列表
loadSession() 更加复杂,因为它需要重播历史消息:
"重播"意味着 Agent 将历史消息逐条通过 sessionUpdate 发送给 IDE,IDE 据此重建对话界面。这使得用户可以在关闭 IDE 后重新打开时恢复之前的对话状态。
此外,ACP 还支持其他 Session 操作:
unstable_listSessions():列出所有 Session,支持分页(cursor-based pagination)unstable_forkSession():分叉 Session——从某个对话节点创建分支,类似 Git 的分支unstable_resumeSession():恢复 Session——重新加载状态并发送 usage 更新
注意 unstable_ 前缀——这些 API 尚在开发中,接口可能在后续版本中发生变化。
prompt():核心对话处理
prompt():核心对话处理prompt() 是 ACP Agent 最核心的方法,处理用户的每一次输入:
ACP 的 prompt 内容是多部分的(multi-part),每个部分可以是文本、图片、资源链接或嵌入式资源。OpenCode 需要将这些 ACP 格式的内容转换为自己内部的消息格式。
特别值得注意的是 audience 注解的处理:
["assistant"]
synthetic: true
合成消息,仅对 AI 可见(如系统指令)
["user"]
ignored: true
仅对用户可见,AI 不处理(如 UI 提示)
无或其他
无标记
正常消息,双方都可见
prompt() 还检测斜杠命令(/compact 等):
如果输入以 / 开头,它被解析为命令而不是普通 prompt。支持的命令包括自定义命令(通过 sdk.command.list() 获取)和内置命令(如 /compact 用于压缩 Session 上下文)。
最终 prompt() 返回使用量统计:
事件订阅系统
ACP Agent 的实时更新通过事件订阅机制实现。Agent 在构造时启动一个无限循环,监听 OpenCode 内部事件并转发给 IDE:
衍生解释——事件驱动架构与发布-订阅模式
事件驱动架构(Event-Driven Architecture, EDA)是一种软件设计范式,系统组件通过事件进行通信而非直接调用。ACP Agent 的事件订阅就是典型的发布-订阅(Pub/Sub)模式:
发布者:OpenCode 核心系统(通过
sdk.global.event()发布事件流)订阅者:ACP Agent(通过
for await...of消费事件流)事件:
permission.asked、message.part.updated等与直接函数调用相比,事件驱动的优势在于解耦——OpenCode 核心不需要知道谁在监听事件,ACP Agent 也不需要了解事件是如何产生的。这使得系统可以灵活地添加新的事件消费者(比如未来可能有其他 IDE 的扩展)。
事件处理器 handleEvent() 处理两大类事件:
权限请求事件(permission.asked)
permission.asked)权限处理的关键设计:
按 Session 串行化:
permissionQueues使用 Promise 链确保同一个 Session 的权限请求按顺序处理,避免并发冲突三种选择:Allow once(允许一次)、Always allow(始终允许)、Reject(拒绝)
编辑预览:对于文件编辑权限,Agent 会将 diff 应用到文件并通过
writeTextFile通知 IDE 显示文件变更。getNewContent()函数使用diff库的applyPatch()将 unified diff 应用到原始文件内容上
消息部分更新事件(message.part.updated)
message.part.updated)这个事件在 LLM 生成回复时持续触发,ACP Agent 将其转换为 IDE 可理解的更新:
这里有几个值得关注的细节:
工具调用的生命周期映射:OpenCode 内部工具的四个状态(pending → running → completed/error)被精确映射到 ACP 的工具调用更新协议。IDE 可以据此显示工具执行的实时进度。
Todo 工具的特殊处理:当 todowrite 工具完成时,ACP Agent 将 todo 列表转换为 ACP 的 "plan" 更新,这让 IDE 可以在专门的面板中显示任务计划:
工具分类映射
toToolKind() 函数将 OpenCode 的工具名称映射到 ACP 的标准工具类别:
ACP 定义了六种标准工具类别:
execute
执行系统命令
bash
fetch
网络请求
webfetch
edit
文件编辑
edit, patch, write
search
搜索操作
grep, glob, context7_*
read
文件读取
list, read
other
其他
所有未匹配的工具
IDE 可以根据工具类别显示不同的图标和交互方式——例如 edit 类工具可能显示 diff 视图,execute 类工具可能显示终端输出。
toLocations() 函数则从工具的输入参数中提取文件路径,让 IDE 知道哪些文件正在被操作:
processMessage():历史消息重播
processMessage():历史消息重播当用户恢复一个已有的 Session 时,ACP Agent 需要将历史消息"重播"给 IDE。processMessage() 函数处理了所有可能的消息部分类型:
文件类型的处理尤为精细——OpenCode 内部使用统一的 { type: "file", url, filename, mime } 格式存储文件,但 ACP 协议区分了多种内容类型。processMessage() 需要根据 URL 方案和 MIME 类型将文件转换为正确的 ACP 内容块:
file://
任意
resource_link(URI 引用)
data:
image/*
image(内联图片)
data:
text/* 或 application/json
resource(文本资源)
data:
其他
resource(二进制 blob)
loadSessionMode():Session 模式加载
loadSessionMode():Session 模式加载这是一个内部辅助方法,负责加载 Session 的完整上下文信息:
值得注意的第 4 步——IDE(如 Zed)可以在创建 Session 时传递自己的 MCP 服务器配置。OpenCode 会将这些外部 MCP 服务器注册到自己的 MCP 系统中,使 Agent 可以使用 IDE 提供的工具。这是 ACP 和 MCP 协同工作的一个精妙设计:IDE 不仅消费 Agent 的能力,还可以向 Agent 注入额外的工具。
defaultModel():模型选择策略
defaultModel():模型选择策略defaultModel() 函数实现了一套多级回退的模型选择策略:
这个六级回退策略确保了在各种配置场景下都能找到可用的模型。
衍生解释——适配器与外观模式
ACP.Agent类本质上是一个适配器(Adapter)——它将 OpenCode 的内部 API 适配为 ACP 协议的标准接口。同时它也是一个外观(Facade)——它将 OpenCode 内部多个子系统(Session、Provider、Permission、Command、MCP 等)的复杂交互隐藏在一个简洁的接口后面。
适配器模式:将一个类的接口转换为客户端期望的另一个接口。ACP 的
ToolKind需要 "execute"、"edit" 等分类,而 OpenCode 内部用的是具体工具名称。toToolKind()就是一个典型的适配器函数。外观模式:为子系统中的一组接口提供一个统一的高层接口。IDE 只需要与
ACP.Agent交互,不需要了解 OpenCode 内部的 Session 管理、Provider 选择、Permission 处理等复杂逻辑。
16.3.3 acp/session.ts:ACP Session 到 OpenCode Session 的映射
acp/session.ts:ACP Session 到 OpenCode Session 的映射acp/session.ts 实现了 ACPSessionManager 类,它是 ACP Session 与 OpenCode 内部 Session 之间的桥梁:
ACPSessionManager 维护了一个 Map<string, ACPSessionState>,以 session ID 为键存储 ACP 层的 Session 状态。它提供两种获取方式:
get():找不到时抛出RequestError.invalidParams异常(用于必须存在的场景)tryGet():找不到时返回undefined(用于可选检查的场景,如事件处理中检查事件是否属于当前管理的 Session)
注意 create() 方法使用 crypto.randomUUID() 生成 Session 标题——这确保每个 ACP Session 有唯一的可识别标题。
16.3.4 acp/types.ts:类型定义
acp/types.ts:类型定义acp/types.ts 定义了 ACP 层使用的核心类型:
ACPSessionState 是 ACP Session 的状态表示:
id
string
Session 唯一标识符(与 OpenCode 内部 Session ID 相同)
cwd
string
工作目录
mcpServers
McpServer[]
IDE 传递的 MCP 服务器列表
createdAt
Date
创建时间
model?
{ providerID, modelID }
当前使用的模型(可选,可能使用默认)
variant?
string
模型变体(如 "high"、"low" 质量等级)
modeId?
string
Agent 模式 ID(如 "code"、"architect")
ACPConfig 是 ACP Agent 的配置:
sdk
OpencodeClient
OpenCode SDK 客户端实例
defaultModel?
{ providerID, modelID }
命令行指定的默认模型
这两个类型很简洁,但它们定义了 ACP 层和 OpenCode 核心层之间的契约——ACPSessionState 包含了 ACP 需要但 OpenCode 内部 Session 不直接提供的信息(如 mcpServers、variant),而 ACPConfig 则封装了 Agent 初始化时的全局配置。
16.3.5 当前限制与未来规划
当前限制
认证未完全实现:
authenticate()方法直接抛出异常:
当前的认证流程依赖 initialize() 中返回的 authMethods,引导用户通过终端执行 opencode auth login。IDE 可以检测到认证需求(通过 RequestError.authRequired()),但实际的认证流程在 ACP 协议之外完成。
权限串行化是每 Session 的:虽然
permissionQueues实现了按 Session 的顺序处理,但跨 Session 的权限请求是并行的。如果多个 Session 同时请求权限,IDE 需要自行管理多个权限对话框的显示。部分 API 标记为 unstable:
listSessions、forkSession、resumeSession都使用unstable_前缀,表明接口可能在后续版本中变化。Zed URI 的特殊处理:
parseUri()函数包含对zed://URI 方案的特殊处理,这是一个与特定 IDE 耦合的实现细节:
理想情况下,ACP 协议应该标准化 URI 方案,而不是在 Agent 端处理 IDE 特定的 URI 格式。
未来方向
协议版本演进:当前
protocolVersion为 1,随着 ACP 规范的成熟,未来版本可能添加更丰富的能力——如内联代码建议(Inline Suggestions)、代码操作(Code Actions)、诊断信息(Diagnostics)等。更丰富的权限模型:当前的三选项模型(Allow once / Always / Reject)较为简单。未来可能支持更细粒度的权限控制,如"允许读取但不允许写入"、"允许在特定目录内操作"等。
多 Agent 协同:当前每个 ACP 连接对应一个 Agent 实例。未来可能支持一个 IDE 连接多个 Agent,实现多 Agent 协同工作(类似 oh-my-opencode 的多 Agent 架构,但在协议层面标准化)。
16.3.6 本章总结
本章分析了 OpenCode 的三种 IDE 集成方案:
轻量级
VSCode 扩展(终端 + HTTP)
138 行代码
基本的上下文传递
声明式
Zed 扩展(TOML + ACP)
0 行代码
完整的 Agent 交互
协议层
ACP 实现(agent.ts + session.ts + types.ts)
~1800 行代码
Session、权限、工具、流式响应
三者形成了一个递进的架构:
VSCode 扩展直接与 TUI 通信(通过 HTTP),绕过了 ACP 层;Zed 扩展通过 ACP 协议与 Agent 通信,获得了更丰富的交互能力。两种方案都最终与 OpenCode 核心系统交互,只是路径不同。
ACP 协议代表了 AI Agent 与 IDE 集成的未来方向——标准化的协议使得任何支持 ACP 的 IDE 都可以无缝接入任何支持 ACP 的 Agent,就像 LSP 让任何编辑器都能支持任何语言的智能补全一样。OpenCode 的 ACP 实现虽然目前仍在迭代中,但已经展示了这种标准化集成的完整蓝图。
Last updated
