首页
/ Gemini CLI ACP 模式详解:通过 Agent Client Protocol 将 Agent 接入 IDE 的架构与实践

Gemini CLI ACP 模式详解:通过 Agent Client Protocol 将 Agent 接入 IDE 的架构与实践

2026-09-06 23:46:13作者:尤辰城Agatha

ACP(Agent Client Protocol,Agent Client Protocol)是 Gemini CLI 面向程序化集成的专用运行模式:它通过 stdio 上的 JSON-RPC 2.0 协议在 IDE 等客户端与 Gemini CLI Agent 之间建立双向通道,让编辑器可以以编程方式创建会话、发送提示词、切换审批模式并代理文件系统访问。读完本文,你将理解 gemini --acp 的完整工作原理——从进程启动到协议握手、MCP 双向集成、文件系统代理的安全边界,以及如何用调试日志与遥测排查协议问题,并能在自己的 IDE 插件或工具中实现一个 ACP 客户端来驱动 Gemini CLI。

什么是 ACP 模式,如何启动

ACP 模式是 Gemini CLI 的一种特殊运行模式,专为 IDE 和其他开发者工具的集成而设计。与交互式终端模式不同,ACP 模式下 Gemini CLI 不再渲染 TUI 界面,而是作为服务器常驻,等待客户端通过标准输入/输出发送 JSON-RPC 请求。

启动方式很简单:

gemini --acp

从源码看,--acp 是一个布尔型命令行选项,在 config.ts 中定义;此外还有一个已弃用的旧选项 --experimental-acp,其帮助文本明确写着 "Starts the agent in ACP mode (deprecated, use --acp instead)"。解析逻辑位于同一文件:

const isAcpMode = !!argv.acp || !!argv.experimentalAcp;

isAcpMode 为真时,配置对象会设置 acpMode: true,并在检测到 IDE 环境时把客户端名称标记为 acp-<ide.name>,否则标记为 acp——这个 clientName 会被写入遥测事件,用于区分 ACP 通道流量与普通终端流量。值得注意的是,config.test.ts 中还有专门的用例验证 --acp 模式会排除 ask_user 工具(因为 ACP 客户端有自己的提问通道,Agent 不应再使用终端式追问)。

为什么需要 ACP:解决客户端碎片化问题

ACP 是一个开放协议,标准化了 AI 编码 Agent 与代码编辑器/IDE 之间的通信方式。它解决的核心痛点是集成碎片化:在没有 ACP 之前,每个 Agent 想接入一个新的编辑器,都要为每个客户端写一套定制集成代码,维护成本随编辑器数量线性膨胀。

引入 ACP 之后,开发者只需实现一次 Agent 侧的协议,它就能兼容任何遵守 ACP 规范的编辑器——集成的复杂度从 M×N 降为 M+N。Gemini CLI 正是这样一个 ACP 兼容 Agent,可通过 ACP Agent Registry 在各类 IDE 中分发;关于如何在 JetBrains、Zed 等具体 IDE 中配置和使用,参见仓库中的 IDE 集成文档

架构:stdio 上的 JSON-RPC 客户端-服务器模型

ACP 模式在客户端(你的 IDE/工具)与 Gemini CLI(Agent 服务器)之间建立客户端-服务器关系:

  • 通信载体:全部通信走标准输入/输出(stdio),采用 JSON-RPC 2.0 协议;
  • 客户端职责:发送请求(如提示词),处理来自 Gemini CLI 的响应与通知;
  • Gemini CLI 职责:监听入站 JSON-RPC 请求,处理后返回响应,并流式推送中间更新。

传输层:ndjson 流与 AgentSideConnection

原始 I/O 由 acpStdioTransport.ts 承担。runAcpClient 函数把进程 stdin/stdout 包装为 Web Streams,并用行分隔 JSON(ndjson)构建 AgentSideConnection

const stream = acp.ndJsonStream(stdout, stdin);
const connection = new acp.AgentSideConnection(
  (connection) => new GeminiAgent(config, settings, argv, connection),
  stream,
);

// SIGTERM/SIGINT handlers (in sdk.ts) don't fire when stdin closes.
// We must explicitly await the connection close to flush telemetry.
await connection.closed.finally(runExitCleanup);

两个细节值得注意:其一,协议消息以行分隔 JSON 而非裸 JSON 拼接传输,这保证了消息边界清晰、可增量解析;其二,源码注释指出 stdin 关闭时常规的信号处理不会触发,因此必须显式 await connection.closed 再执行清理,以确保证遥测数据(telemetry)在进程退出前刷盘。

协议分发层:GeminiAgent

入站 JSON-RPC 消息的主入口是 acpRpcDispatcher.ts 中的 GeminiAgent 类。它实现协议方法,并把会话相关工作委托给会话管理器与具体会话对象。文档中提到的"ACP 实现核心"在早期版本集中于单一文件,当前仓库已完成模块化重构——如 ACP 模块 README 所述,职责被拆分为:

模块 职责
acpStdioTransport.ts 原始 I/O,建立 ndjson 流与 AgentSideConnection
acpRpcDispatcher.ts GeminiAgent,协议方法的 RPC 入口
acpSessionManager.ts 多会话状态管理(newSession / loadSession / 配置合并)
acpSession.ts 单个会话:提示词执行、@path 文件解析、工具执行、流式更新
acpUtils.ts 共享辅助函数、类型映射与 Zod schema
acpErrors.ts 集中式错误处理与 ACP 错误码映射
acpCommandHandler.ts 拦截并执行经 ACP 提示词发送的斜杠命令(如 /memory/init
acpFileSystemService.ts 受工作区边界与权限约束的文件系统访问

初始化握手:能力协商与认证方法

initialize 是整个 ACP 会话的起点。查看 acpRpcDispatcher.ts 中的 initialize 实现,可以清楚看到响应中向客户端声明的内容:

  1. 协议版本protocolVersion: acp.PROTOCOL_VERSION(来自 @agentclientprotocol/sdk);
  2. 可用认证方法authMethods):
    • login_with_google —— 使用 Google 账户登录;
    • use_gemini —— 使用 Gemini Developer API 的 API Key;
    • use_vertex_ai —— 使用 Vertex AI GenAI API;
    • gateway —— 自定义 AI API Gateway(元数据中标注 protocol: 'google')。
  3. Agent 信息agentInfo 返回 name: 'gemini-cli'、标题与版本号;
  4. Agent 能力agentCapabilities):
    • loadSession: true —— 支持恢复既有会话;
    • promptCapabilities —— 提示词支持 imageaudioembeddedContext 三类内嵌内容;
    • mcpCapabilities —— 支持 httpsse 两种 MCP 传输。

同时 initialize 会读取客户端上报的 clientCapabilities 并存入会话管理器,供后续判断客户端是否支持文件系统代理等特性。

authenticate 方法(acpRpcDispatcher.ts)则完成实际的凭据切换:用 Zod 的 z.nativeEnum(AuthType) 校验客户端传入的 methodId;若请求的 _meta 中携带 api-keygateway(含 baseUrl 与自定义 headers)字段,会先做 schema 安全解析(解析失败抛出 -32602Malformed gateway payload 错误),再调用 config.refreshAuth 刷新认证,并把所选认证类型写回用户级设置。切换认证方式时会先清除缓存的凭据文件,避免旧凭据残留。

核心方法一览

ACP 协议向客户端暴露的方法覆盖了会话全生命周期:

方法 说明 源码位置
initialize 建立初始连接,协商协议版本/能力,客户端在此注册其 MCP server acpRpcDispatcher.ts
authenticate 按客户端指定的方法完成用户认证 acpRpcDispatcher.ts
newSession 启动新的聊天会话 acpSessionManager.ts
loadSession 加载/恢复先前会话(依赖 loadSession 能力) acpSessionManager.ts
prompt 向 Agent 发送提示词 acpRpcDispatcher.ts
cancel 取消正在进行的提示词 acpRpcDispatcher.ts

所有涉及会话的方法都会先通过 sessionManager.getSession(sessionId) 查找会话,找不到即抛出 -32602(Invalid params)语义的 Session not found: <sessionId> 错误,错误映射逻辑集中在 acpErrors.tsprompt 执行进入 acpSession.ts 后,会话层负责提示词执行、@path 文件引用解析、工具执行,并把流式更新回推给客户端;斜杠命令(如 /init/memory)则被 acpCommandHandler.ts 拦截并就地执行,不会发给模型。

会话控制

两个与常规请求不同的会话级控制方法:

  • setSessionMode:切换工具调用的审批级别,例如切到 auto-approve 模式后工具调用无需逐次确认,适合自动化场景;
  • unstable_setSessionModel:更改当前会话使用的模型。方法名中的 unstable 前缀表明该接口属于协议中尚未稳定的扩展点,未来签名可能调整,集成时应对该能力做特性探测而非硬依赖。

两者同样通过 acpRpcDispatcher.ts 委托给对应会话对象的 setMode / setModel 实现。

与 MCP 的双向集成

ACP 可以与 MCP(Model Context Protocol)配合使用,形成一个双向集成回路:IDE 不仅接收 Agent 的输出,还能把自己的功能以"工具"形式暴露给 Gemini 模型调用。工作流程如下:

  1. 客户端实现一个 MCP server 并声明自己的工具;
  2. 在 ACP 的 newSession / loadSession 请求中,客户端通过 mcpServers 字段提供 MCP server 的连接详情;
  3. Gemini CLI 连接到该 MCP server,发现可用工具并注册给模型;
  4. 模型决定使用某个工具时,Gemini CLI 向该 MCP server 发出工具调用请求。

从源码看,合并逻辑在 acpSessionManager.tsnewSessionConfig 以本地设置中的 mcpServers 为基座,把客户端随会话请求传来的每个 server 逐个构造为 MCPServerConfig 后覆盖合并,再交给会话配置。结合 initialize 声明的 mcpCapabilities: { http: true, sse: true },可以确认当前支持的传输形态是 HTTP 与 SSE。MCP 客户端的底层逻辑位于 core 包的 mcp-client 实现(文档中路径为 packages/core/src/tools/mcp-client.ts)。这套机制意味着 Agent 可以借助 IDE 自身的能力(如"打开某文件"、"读取活动编辑器内容")来执行任务,而不局限于本地文件与 shell。

文件系统代理:以客户端为边界的访问控制

ACP 内置了一个代理式文件系统服务:当 Agent 需要读写文件时,请求经由 ACP 通道转发给客户端执行,而不是直接操作本机磁盘。这是一个安全特性——Agent 只能访问客户端(以及其背后的用户)明确授权的文件范围,例如 IDE 可以直接复用编辑器的缓冲区内容,实现"所见即所得"的读写一致性。

实现见 acpFileSystemService.ts。该类实现了 core 包的 FileSystemService 接口,核心路由逻辑在 shouldUseFallback

private shouldUseFallback(filePath: string): boolean {
  // 工作区之外的文件,以及 ~/.gemini 目录内的文件,始终走本地文件系统
  return (
    !isWithinRoot(filePath, this.root) ||
    isWithinRoot(filePath, this.geminiDir)
  );
}

两条规则值得注意:

  • 能力探测readTextFile / writeTextFile 前先检查客户端上报的 capabilities.readTextFile / capabilities.writeTextFile,客户端不支持代理时直接回退本地实现(this.fallback);
  • 强制本地路径:工作区根(root)之外的文件走本地文件系统;同时 ~/.gemini 目录被显式排除在代理之外——注释解释了原因:全局 CLI 目录必须始终用原生文件系统访问,即使用户直接在家目录运行 CLI(此时 IDE 项目根会与全局目录重叠)。

此外,normalizeFileSystemError 会把客户端返回的各种"文件不存在"表述(Resource not foundENOENTdoes not exist 等)统一归一化为标准 ENOENT 错误,保证 Agent 侧错误处理的一致性。相关行为在 acpFileSystemService.test.ts 中有对应测试覆盖。

调试与遥测

调试日志

启用通用调试日志只需追加 --debug

gemini --acp --debug

遥测文件输出

更细粒度的事件数据可通过以下环境变量捕获到本地文件:

GEMINI_TELEMETRY_ENABLED=true \
GEMINI_TELEMETRY_TARGET=local \
GEMINI_TELEMETRY_OUTFILE=/path/to/your/log.json \
gemini --acp

这会产生一个 JSON 日志文件,记录 Agent 内部发生的所有事件,包括 ACP 请求与响应的详细信息。仓库中 acp-telemetry.test.ts 集成测试提供了一个可直接参考的端到端示例,演示了如何设置上述环境变量并验证遥测输出;acp-env-auth.test.ts 则覆盖了 ACP 进程通过环境变量完成认证的场景。结合 acpStdioTransport.ts 中"必须显式等待连接关闭以刷出遥测"的清理逻辑,可以推断:排查"遥测文件末尾缺失事件"类问题时,应优先确认客户端是正常关闭了 stdin 而非强制杀进程。

小结

ACP 模式让 Gemini CLI 从一个终端交互工具变成了一个可被任意编辑器驱动的协议化 Agent 服务:传输层用 stdio + ndjson 保持零端口、零守护进程部署的简洁性;协议层通过 initialize 能力协商、会话级 prompt/cancel 控制、setSessionMode 审批切换构成完整的控制面;MCP 集成与文件系统代理则分别扩展了 Agent 的"手"与约束了它的"脚"。模块化的 packages/cli/src/acp 目录与并排的测试文件(acpRpcDispatcher.test.tsacpSessionManager.test.tsacpResume.test.ts 等)也为进一步阅读实现或为客户端编写集成提供了清晰的落点。

登录后查看全文
热门项目推荐
相关项目推荐