工具协议 · 进程内与跨进程一套契约
grok 的工具默认在进程内跑,但同一套 ToolHandle/流式契约能无缝跨进程。这一章拆三层协议栈(types/runtime/protocol)、一个 object-safe 的 ToolHandle 如何同时套住本地工具与远程代理,以及那套 JSON-RPC「Computer Hub」线协议。
工具协议 · 进程内与跨进程一套契约
上一章的派发止于 local_registry.find。这一章往下挖一层:那个被 find 出来的 ToolHandle 到底是什么——以及为什么它能让「同一份工具契约,在同进程或跨进程都成立」。
三层协议栈
工具契约摊在三个通用 crate 上,层层递进:
| crate | 层 | 管什么 |
|---|---|---|
xai-tool-types | 叶子 | 纯共享类型(描述、任务工具的 wire 类型),所有上层都依赖它,保证形状不漂移 |
xai-tool-runtime | 进程内契约 | Tool / ToolDyn / ToolDispatch / ToolStream,流式执行 |
xai-tool-protocol | 线协议 | JSON-RPC 2.0「Computer Hub」,跨进程的方法与帧 |
一个 object-safe 的 ToolHandle 套住两种实现
关键抽象是 ToolHandle(xai-computer-hub-core/src/resolver.rs,object-safe、#[async_trait]):id()、description()、capabilities()、execute(ctx, args) -> ToolStream。它有两种实现,外面完全一视同仁:
- 进程内:
ErasedTool<T: Tool>——解 JSON 参数 → 驱动Tool::execute→ 把T::Output编回TypedToolOutput。装在LocalRegistry(xai-computer-hub-sdk里一个保序的IndexMap<ToolId, Arc<dyn ToolHandle>>)里。 - 跨进程:
RemoteToolProxy——它的execute把调用变成一个tool_call_request发过连接,按tool_call_id订阅进度,再把进度帧 + 终止帧合并回同一种ToolStream。
grok-build 只用进程内那套(FinalizedToolset.local_registry),但因为工具藏在 ToolHandle 后面,那套「进度流」契约无论工具在同进程还是隔进程,代码长得一模一样。这就是「一套契约、两种传输」的威力。
分裂的 trait:类型化的窄门 + 擦除的宽门
这里有个精妙的 Rust 设计,值得单独点出:
Tool用 RPITIT(async fnin trait,带显式Send)——不装箱、不 object-safe、只能泛型使用。窄,但零开销、类型全在。ToolDyn用#[async_trait]——装箱、object-safe。宽,能塞进Vec<Arc<dyn ...>>。
一个 blanket impl<T: Tool> ToolDyn for T 把前者自动升级成后者。类型擦除只在注册那一刻发生一次,dyn 兼容的分界线画得清清楚楚。这是「既要静态派发的性能、又要动态集合的灵活」时的标准答案。
线协议:三方模型
xai-tool-protocol 定义了一套 JSON-RPC 2.0 协议(方法是封闭的 Method 枚举),是三方的:harness ↔ hub(服务)↔ tool_server。TransportKind { Local, Remote } 就是那个「进程内 / 跨进程」开关。能力协商在 ToolCapabilities(streaming、supports_cancel、max_concurrency、ToolScope { Read, Write }……)。
一个亮点是 ToolDefinitionMode:
Full:把所有工具定义摊给模型。Concise { meta_search, meta_call }:协议为「工具太多、不能全塞给模型」预留的模式——只暴露一个搜索元工具和一个调用元工具,让模型先搜再调。不过 grok CLI 并没有构造这个枚举(全仓只用过Full),而是在本地常开一套等价机制:每轮都把 MCP 工具从给模型的工具列表里滤掉,只留内置的search_tool/use_tool两个元工具(见 MCP 注册表)。这正是主循环里那个use_tool的来历。
此后这条线协议还长出一块 bot-relay 面(bot_relay.rs):bot.command、bot.roster、bot.subscribe、bot.event 等方法,连 Kotlin/Swift 客户端类型都是从它生成的;xai-computer-hub-core/src/bot_tools.rs 另登记了一批由 hub 合成的「Grok Bot」工具(bot_create_agent、bot_send_prompt……)。同一条管道,开始承载「远程指挥 agent」这件事。
还有个多 agent 协作的伏笔:ToolScope { Read, Write }——写操作只路由到 leader agent,避免多个并行 agent 同时改文件打架。这条线在并行 worktree会再遇到。
和《从 LLM 到 Coding Agent》对照
那本的工具是进程内函数,不涉及跨进程。grok 的增量是——真实产品要接 MCP(可能是别的进程/机器上的工具服务器),于是把工具契约抽象到「同进程还是跨进程都成立」的高度:一个 object-safe 的 ToolHandle + 一套流式协议 + 一层 JSON-RPC。这是「玩具能内联,产品要抽象传输层」的又一个范例。
至此工具系统的框架清楚了。下一章起进具体工具:读、搜、三种编辑格式、shell、沙箱……