最近我给 raft-acp-bridge 做了一次 AGY 的 ACP 实现切换(见 commit da834bc):把 Raft 接入 Antigravity 的后端从原先单独分发的 ACP server 1.1.1,换成了 Antigravity CLI 自带的 agy acp 子命令(本次验收基于 1.5.5,新安装建议用 1.5.1 及以上版本)。
升级之后,本机只要安装并登录好 Antigravity CLI 即可直接使用,原先单独摆放的 agy_acp_server.par 和配套 localharness_external(光磁盘入口就占了约 872 MiB)都可以省掉。日常在 Raft 里用起来也有三处直观变化:只要账号本身有 Claude 权限,Raft 的模型选择器里现在就能直接刷出 Claude 模型;Agent 执行中途可以追加指令(Steering),不用像旧版那样只能排在本地内存队列里等上一轮跑完;Bridge 重启后也能直接续上之前的会话。
这是 Antigravity 外部集成系列的第三篇(前两篇分别梳理了 agy 的三种外部调用方式,以及借用 Grok Build 把 Antigravity 和 Maka 接进 Raft)。这次改造的起因其实挺偶然:前阵子看到 GDE 群里的朋友在聊 Google 官方的 VS Code Antigravity 插件,反馈体验挺顺手;刚好那天我在 Raft 里让 Agent 帮忙润色卡片上的三段文案,足足等了 11 分钟才收到回复(后面发现是 system prompt 和 harness 的问题)。我就好奇,VS Code 插件底层到底是怎么接 Antigravity 的?换过去会不会更快?带着这个疑问,我们拆开插件发布包看了一圈,发现它跑的是另一条 agy --hub 路线,于是干脆写了个临时适配器,把两条路拉出来做了一轮实测对比。
VS Code 插件是怎么接 Antigravity 的
我们先拆开 Marketplace 上的官方插件包 Google.google-antigravity(1.11.0,安装说明见官方 VS Code 文档)看一眼它的进程结构。这个插件没有走 ACP,直接在本地拉起一个 agy --hub 进程,把当前打开的工作区目录通过 --add-dir 传进去,再在侧边栏的 Webview 里加载由 hub 提供的聊天页面。页面与 hub 之间通过本地 WebSocket(/connect-websocket)收发 RPC 请求并持续订阅 Agent 状态流;插件自身的 Extension Host 则通过 postMessage 和页面通信,负责打开文件、渲染 Diff、做后台健康检查,同时开启了隐藏侧栏保留页面状态的配置,收起面板再打开也不会丢现场。
相比之下,Antigravity 的 ACP 接口要轻量得多:客户端直接拉起 agy acp 子进程,在 stdio 上收发 JSON-RPC 消息,按会话接收正文片段、工具调用和权限审批请求。我们将两条接入方式放在一起比对,结构差异一目了然:
如上图所示,两套方案面对的宿主环境完全不同:
- 一方面是编辑器上下文的同步:在 VS Code 里选中要改的几行文字,插件的监听模块会把当前活动文件、选区范围,甚至缓冲区里还没保存的内容交给聊天页面;而在 Raft 这类独立群聊客户端里,如果只发一句“帮我改一下第二段”,Agent 就得先在目录里找稿子,再确认“第二段”具体指哪一处(至于模型实际用上了多少编辑器上下文、能少读几次文件,还得看具体任务)。
- 另一方面是桥接层的维护成本:Raft 的
GrokDriver本身说的就是 ACP 协议,raft-acp-bridge只需要处理握手、模型前缀和少量扩展方法的转换,绝大部分会话消息直接透传即可;如果硬切到agy --hub,Bridge 就得自己维护 WebSocket 状态订阅,把底层的步骤更新重新拼装成 Raft 识别的回合(Turn)与事件,而且hub内部接口没有公开兼容性承诺,后续每次 CLI 升级都得自己重新核对消息格式。
改同样的三段话,两条路差多少耗时
既然手里已经有了 hub 测试客户端,我们还是顺手做了一组定量对照,看看单论接入路径,--hub 会不会比当时在用的独立 ACP server 1.1.1 更快。实验固定了同一账号、同一个 gemini-4-argon-medium 模型,每次都新建进程与会话、隔离工作目录,并按 ACP → hub 与 hub → ACP 交替顺序执行:
- 第一种是纯文本润色:把约 300 字的三段原文直接放进 Prompt,禁用工具,只要求返回润色后的正文(每轮 0 次工具调用、1 次模型请求)。
- 第二种是带文件读写的固定流程:明确要求先用命令从文件读出原文,润色后再用一条命令写回并校验段落和字数(每轮各 2 次工具调用、3 次模型请求)。
每种任务各做 6 对、共 24 轮,从发出 Prompt 开始计时,直到收齐正文和回合结束信号才停表。结果显示:纯文本润色的中位数耗时,旧版独立 ACP 是 11.79 秒,hub 是 9.21 秒,6 对里 hub 快了 5 对;但一旦加上读写文件,ACP 的中位数是 29.65 秒,hub 反而是 32.23 秒(6 对里 ACP 快了 4 对)。最慢的一对文件测试里,两边分别用了 42.82 秒和 67.25 秒,其中 hub 的一次请求光等待首个 Token(timeToFirstToken)就花了约 47 秒。
需要说明的是,这组数字对比的是当时的独立 ACP server 1.1.1 与 CLI 1.5.3 的 --hub(后者用的是测试客户端直连 WebSocket,没拉起完整的 VS Code 界面),两边的默认上下文和工具策略也有差别;后来切到同一份 CLI 对照内置 agy acp 与 --hub 时,我们重点验了功能,耗时还没重测。但至少结论已经很清楚:两条接入路径之间那两三秒的差距,根本解释不了最初那 11 分钟的等待;真正拖慢小任务的,是 Agent 把局部修改发散成了几十轮工具调用和模型往返。 这部分只能通过微调下 prompt,简化一些重新导出的次数,换底层传输协议是没有区别的。
跑通 --hub 后,为什么还是选了内置 ACP
在第二篇文章里我们提到过,旧版独立 agy_acp_server.par 不支持回合中插话(Steering),忙时并发调用 session/prompt 会直接抛错,所以当时只能把追加消息暂存在本地队列里。为了试探 --hub 能不能补上这块体验,我们写了一版挂在 grok 入口后面的 hub 原型:读写文件、单次权限审批、切换模型、重启续聊都跑通了,任务执行中途断开 WebSocket 再重新订阅,原来的命令也能继续跑完。
但测到中途插话时,踩到了一个很典型的状态归属问题。在 hub 里,“追加新要求”和“停止当前命令”是两个独立操作:有一轮测试我们让 Agent 先执行一条约 24 秒的命令,中途追加新要求;它确实先按新要求在约 15 秒后给出了回复,但那条 24 秒的旧命令还在后台继续跑,等旧命令跑完,hub 把执行结果拼回轨迹,又触发了一次模型生成,吐出了第二段几乎相同的正文。因为这两段正文来自两次独立的规划步骤(而非重复推送的 WebSocket 帧),单靠帧去重拦不住——对 Raft 来说,适配器必须搞清楚这段正文和迟到的工具结果分别归属哪一步、什么时候才能关掉当前回合,用户点停止时还得另发取消请求。
就在评估要不要把这套状态机补全的时候,回头看了一眼 Antigravity CLI 的发布记录,刚好赶上官方连续两次关键更新:CLI 1.5.0 正式内置了 agy acp 子命令,原生支持向运行中的会话发送第二个 session/prompt 来做 Steering,同时提供了 session/resume 恢复会话;紧接着 1.5.1 又把账号可用的全部模型开放给了所有 ACP 客户端。无论走 --hub 还是 agy acp,现在都可以从同一份约 199 MiB 的 Antigravity CLI 启动,不用再单独装旧版服务端和配套程序了。
这样一来,在 raft-acp-bridge 的会话适配代码 src/shim.js 里,改动就干净多了:
- 忙时插话:Raft 在 Agent 忙碌时发来
_x.ai/interject追加消息,Bridge 就向同一个agy acp会话再发一次session/prompt,等相关请求都结束后,再把当前回合的完成响应交还给 Raft。 - 会话恢复:Bridge 把 Raft 发来的
session/load映射到了session/resume。之所以没直接走session/load,是因为在 1.5.3 的测试里,传一个根本不存在的随机 ID 给session/load也曾返回成功(只是没有历史更新),这会让 Raft 误以为旧会话还活着;而session/resume会明确返回读取失败,Bridge 就能将其转换成"session not found",让 Raft 重新建会话。
切到 CLI 1.5.5 再完整跑一轮验收:同一会话里先用 Gemini 约定暗号,切到 Claude 后,Claude 能准确答出上文的暗号;重启 Bridge 走 session/resume 恢复会话后依然能答出来;取消一个正在输出的回合,下一轮也能正常开始。在 Raft 频道里追发消息同样能让 Agent 当场改方向——不过有一轮是在正文已经开始输出时才追加的,从发出新消息到整轮彻底收尾还是等了约 34 秒。
整体来看,如果你在做一个像 VS Code 插件那样的编辑器界面,hub 的状态流订阅、后台任务管理和编辑器交互都很值得参考;而 Raft 自己已经有现成的聊天和工具界面,接入 Antigravity 所需的会话操作直接走内置 ACP 就够用了。日常使用继续选 GROK_SHIM_PROFILE=agy 即可(细节见仓库文档的安装与升级说明)。之前用独立 ACP 服务的朋友,切换后记得在 Raft 里重新扫描 Runtime 并重启对应的 Agent,部分旧版留下的会话重新开一轮就行。
