工具实现精读
pi 的七个内置工具住在 packages/coding-agent/src/core/tools/。mini-pi 的四个工具是它们的"教学简化版",本篇讲真实版本多出来的那些心思——每一处都对应一个踩过的坑。
公共基础设施
truncate.ts——输出的保险丝。两个常量定调:DEFAULT_MAX_LINES = 2000、DEFAULT_MAX_BYTES = 50 * 1024,谁先到谁截。三个函数三种策略:
truncateHead(read 用):留头部,告诉模型用offset翻页;truncateTail(bash 用):留尾部——命令的关键输出(错误、结果)通常在最后;truncateLine:单行过长时截(比如 grep 匹配行,GREP_MAX_LINE_LENGTH = 500)。
共同约束:绝不返回半行(truncateHead 在字节截断后回退到最后一个换行符),返回 TruncationResult 元数据(截断方式、总行数/字节)供 UI 展示。
file-mutation-queue.ts——写操作的交通灯(61 行,值得全文读)。并行工具执行是默认,但两个并发 edit 写同一文件会互相覆盖。解法:按文件(realpath 解析后的路径)维护 promise 链,同一文件的 write/edit 串行,不同文件照常并行。这是"默认并行 + 局部串行"思想在工具层的落地(loop 层的对应物是 executionMode,见设计思想:agent loop)。
path-utils.ts / tool-definition-wrapper.ts:所有路径相对 cwd 解析;wrapToolDefinition 把带 UI 渲染能力的 ToolDefinition 包成 agent-core 的 AgentTool——同一个工具,一份执行逻辑,一层可选的渲染皮肤。
output-accumulator.ts——bash 的缓冲策略:输出增量累积,超限时走 truncateTail,完整输出同时写入临时文件(pi-bash-*.log),给模型的截断说明里带上临时文件路径——上下文省了,信息一点没丢,模型想看全文自己 read 那个文件。
read:文本之外还有图片
- schema:
path/offset?(1 起)/limit?;描述里直接写明截断上限和"用 offset 翻页直到读完"——描述即使用说明。 - 支持图片(jpg/png/gif/webp/bmp):检测 MIME、自动缩放到 2000×2000 内,以附件形式进
content;模型不支持视觉时,附一句降级说明。 - 尾部空行裁剪、tab 展开、行号范围展示等 UI 细节与 LLM 内容分离。
write:最薄,但有两个细节
- 自动
mkdir -p父目录——少一类无谓失败; - 描述里明确"Creates the file if it doesn't exist, overwrites if it does",再配合系统提示词的 "Use write only for new files or complete rewrites"——覆盖语义对模型完全透明,避免误用。
edit:当前版本是 edits[] 数组
这是与博客时代相比最大的演进(edit.ts):
- schema 是
path+edits: [{oldText, newText}, …]:一次调用完成多处互不重曡的替换;描述里叮嘱"改同一区块就合并成一处,别为了串连远端改动捎带大段未改文本"。旧的单oldText/newText形式仍被兼容(LegacyEditToolInput,内部归一化为edits[])。 edit-diff.ts处理真实世界的文本:BOM(stripBom)、CRLF/LF(normalizeToLF/restoreLineEndings——匹配在规范化后进行,写回时恢复原行尾),并产出两种 diff:给 UI 渲染的diff字符串和标准unified patch,连同firstChangedLine(编辑器跳转用)一起放进details——给 LLM 的 content 与给 UI 的 details 分离的样板。- 多匹配/零匹配报错文案与 mini-pi 同款:"must be unique in the original file"。
- 写入走
withFileMutationQueue。
bash:同步执行,认真对待输出
- 通过
spawn起 shell(utils/shell.ts的getShellConfig处理各平台 shell 探测,支持 stdin 方式传命令); BashOperations是可插拔接口——默认本地执行(createLocalBashOperations),可以换成 SSH 远程执行、容器内执行(官方 gondolin/sandbox 扩展示例就是这么做的),工具的定义与执行环境解耦;BashSpawnHook允许扩展改写 spawn 上下文(比如统一加环境变量、命令前缀);- 输出走
OutputAccumulator(上文),超时由模型给的timeout秒数控制。
grep / find / ls:默认禁用的只读三件套
grep底层是 ripgrep(ensureTool("rg")找不到会自动下载);find底层是 fd(可注入自定义glob实现);ls是纯内部实现。- 默认不给模型(有
bash就够,见设计思想:极简核心);它们的主战场是pi --tools read,grep,find,ls的只读模式——一组没有写权限的温和工具。
一句话总结
pi 的工具实现里没有"聪明",只有"周到":截断不丢信息(temp 文件)、并行不乱写(mutation queue)、文本兼容 BOM/CRLF、错误文案写给模型看、描述即文档。这些正是 mini-pi 简化掉、而你的产品级 agent 迟早要补回来的部分。
下一篇:会话与运行时 →