用 Python 直连 Gemini 的 ACP 接口
文章演示通过标准输入输出上的 JSON-RPC 与 Gemini CLI 通信,用约70行 Python 发送编辑器所需的四类请求。ACP 提供结构化的代理通信方式,减少解析终端界面对集成稳定性的影响。
AI 深度解读
把 AI 编程工具接进编辑器,最麻烦的往往不是发出一句提示词,而是判断它正在做什么、何时需要授权、这一轮是否结束。ACP 把这些交互变成结构化消息,让编辑器不用再从终端画面里猜状态。
作者用约 70 行 Python 拆解了 gemini --acp 的通信过程,并比较了 ACP 与直接嵌入终端的取舍。协议部分适合拿来理解 Agent 客户端怎么写,但脚本未经真实账号运行验证,不能当成已验证的接入教程。
ACP 解决的是交互协议,不只是聊天接口
Zed 在 2025 年 8 月 27 日发布 Agent Client Protocol,采用 Apache 许可证,Gemini CLI 是最初的参考实现。2026 年 1 月 28 日,Zed 与 JetBrains 合作开放 ACP Registry,希望让 Agent 注册一次,就能被支持协议的编辑器接入。
它要解决的问题很具体:终端界面会重绘,ANSI 转义序列负责显示效果,却不直接表达「正在编辑文件」或「等待批准」。依赖屏幕内容推断状态,很容易被一次界面改版打断。
ACP 使用 JSON-RPC 2.0。编辑器启动 Agent 子进程,向 stdin 写消息,从 stdout 读消息。每条消息占一行,以换行符分隔,消息内部不能包含实际换行;日志写入 stderr,stdout 只承载协议消息。
这里有两类交互:请求通过 id 匹配响应,通知不需要回复。通信还是双向的:编辑器调用 initialize、session/new、session/prompt,Agent 也会反过来调用编辑器,例如 session/request_permission。
收到输出片段,不代表这一轮已经结束。
发出 session/prompt 后,客户端会持续收到 session/update 通知。只有原始请求的响应抵达,并带回 stopReason,这一轮才算结束。这比「终端一段时间没输出就算完成」可靠得多。
四个请求能串起流程,难点在双向请求
原文使用 npm install -g @google/gemini-cli 安装工具,再通过 gemini --acp 启动。旧参数 --experimental-acp 仍被源码接受,但已标记弃用;作者提醒用本机的 gemini --help 核对,因为文档页面存在差异。
认证也有前提。原文引用 README 称,免费层及 Google One 用户的 Gemini CLI 已于 2026 年 6 月 18 日被 Antigravity CLI 替代。示例采用 GEMINI_API_KEY,免费层密钥是否仍可用需要另行确认,文章没有提供验证结果。
下面是原文的四步请求,依赖前面定义的 send、wait_for 和已启动的子进程:
send({"jsonrpc": "2.0", "id": 0, "method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"fs": {"readTextFile": False,
"writeTextFile": False},
"terminal": False}}})
wait_for(0)
send({"jsonrpc": "2.0", "id": 1, "method": "authenticate",
"params": {"methodId": "gemini-api-key",
"_meta": {"api-key": KEY}}})
wait_for(1)
send({"jsonrpc": "2.0", "id": 2, "method": "session/new",
"params": {"cwd": os.getcwd(), "mcpServers": []}})
sid = wait_for(2)["sessionId"]
send({"jsonrpc": "2.0", "id": 3, "method": "session/prompt",
"params": {"sessionId": sid, "prompt": [
{"type": "text",
"text": "Create hello.txt containing: hi"}]}})
print("stop reason:", wait_for(3)["stopReason"])
agent.stdin.close()
agent.wait()
最需要留意的是 clientCapabilities:把文件和终端能力设为 False,表示客户端不替 Agent 提供这些服务。按作者引用的实现,Gemini CLI 的文件服务会回退到本地磁盘。能力声明不是文件系统沙箱。
四步分别负责协商协议、选择认证、创建会话、提交提示词。protocolVersion 使用 1;API key 认证的 methodId 是 gemini-api-key,密钥放在 _meta["api-key"],认证处理还会保存所选方式到用户设置。cwd 必须是绝对路径,mcpServers 可以传入客户端提供的 MCP 服务,返回的 sessionId 用于后续请求。
提示词采用内容块列表,斜杠命令也可以作为普通文本发送,例如 /web agent client protocol。
真正容易写错的是等待响应。下面这段同时处理 Agent 发来的授权请求:
def wait_for(req_id):
for line in agent.stdout:
msg = json.loads(line)
print("<<", line.rstrip())
if msg.get("method") == "session/request_permission":
answer(msg)
elif msg.get("id") == req_id and "method" not in msg:
if "error" in msg:
sys.exit(msg["error"])
return msg["result"]
sys.exit("agent closed stdout")
关键是 "method" not in msg。双方各自生成请求 ID,Agent 的授权请求可能与客户端的请求撞号。只检查 id,就可能把一个需要回答的请求误判成最终响应。
运行期间,session/update 可携带 agent_message_chunk、agent_thought_chunk、user_message_chunk、tool_call、tool_call_update、usage_update 和 available_commands_update。其中工具状态会经过 pending、in_progress,最终进入 completed 或 failed;更新消息只带变化字段。
授权选项也不能写死。「本会话允许」取决于配置,永久授权选项只在开启相关能力时出现。客户端应返回实际选择的 optionId,例如 proceed_once。若发送 session/cancel,仍需把尚未回答的授权请求以 {"outcome": {"outcome": "cancelled"}} 结束。
正常结束原因是 end_turn,其他还包括 max_tokens、max_turn_requests、refusal 和 cancelled。前端展示时应保留这些区别,不能统一标成「成功」。
统一界面与原生终端,各自放弃了什么
ACP 让编辑器拿到类型明确的事件,因此可以绘制工具卡片、授权按钮,也可以在声明支持后承接 fs/read_text_file、fs/write_text_file 和 terminal/* 请求。
代价是界面只能呈现协议及适配器传递出来的内容。原文指出,Claude Code 和 Codex 并非开箱即用地支持 ACP,Zed 通过适配器接入。Zed 在 2025 年 9 月介绍 Claude Code 集成时,曾说明 SDK 尚不支持许多内置斜杠命令。作者也承认,这个具体缺口后来可能已补上,不能拿旧说明断言今天仍然如此。
另一条路线是保留 Agent 自己的终端界面。作者开发的 Hermes IDE 就采用这种设计:每个任务有分支和 worktree,外围提供会话状态、等待处理的收件箱,以及按轮次保存的变更记录。

图中的 Hermes 2.3.4 展示了两个独立会话及各自的 hermes/ worktree。用户在 Agent 原生界面里授权,Hermes 不代答提示,也不自行向终端输入。
这样更容易保留厂商新命令和模式,但拿不到完整的类型化事件流。Hermes 无法为 Gemini CLI 绘制对应的工具卡片,部分状态需要推断,并标注推断性质。其 ADR 003 规定,只有厂商提供稳定、公开的机器协议,结构化视图确有额外价值,且用户选择启用时,才建设这类视图;原文称目前只有 Claude 提供可选 Agent view。
我的判断
我认同作者对请求、通知和回合结束条件的拆解。它把 Agent 接入从「展示一段输出」推进到「维护一个双向交互过程」,这对自己写客户端很有帮助。
但示例的可信边界必须保留:作者明确没有用真实 Gemini 账号运行脚本,依据来自规范和源码。它能说明预期消息形状,不能证明认证、文件授权和退出流程在读者环境里都能跑通。约 70 行代码展示的是最小链路,也不是完整客户端。
选型部分则带有明确立场:作者正在开发 Hermes。终端保留原生界面、ACP 提供结构化事件,这个区别成立;哪条路线维护成本更低、用户效率更高,原文没有对照数据,属于设计判断。
一般团队不必照搬 Hermes 的选择。需要统一授权、工具进度和编辑器文件访问时,ACP 更贴近需求;需要完整保留多个 CLI 的操作习惯时,终端路线更直接。关键是先确定产品要掌控哪些交互,再决定接到哪一层。
能拿来做什么
- 用 Node 编写 Agent 客户端时,可以先复现四步请求,并按行缓冲解析 stdout;不要假设一次流回调恰好收到一条完整 JSON。
- 给前端会话界面分别保存文本输出、工具状态、待授权请求和最终
stopReason,让「仍在运行」「等待操作」和「已结束」有明确依据。 - 在临时目录验证文件任务,并检查本地访问权限;不要把
readTextFile: False、writeTextFile: False当作阻止 Agent 访问磁盘的配置。 - 评估编辑器集成时,分别核对原生 CLI、SDK 和 ACP 适配器支持哪些命令,再决定是否需要保留终端入口。