工具集 read/bash/edit/write/grep/find/ls
tools/ 目录是 pi 的内置工具集合。每个工具都是 ToolDefinition(带 schema、描述、prompt snippet、guidelines)和 AgentTool(执行函数)双层结构,通过 wrapToolDefinition 转换。index.ts re-export 所有工具工厂,并提供 createCodingTools / createReadOnlyTools / createAllTools 三种预设组合。文件写入类工具(edit、write)经过 withFileMutationQueue 串行化,避免并发改同一文件导致竞态。
职责
- read:读文本/图片文件,图片支持自动 resize(
autoResizeImages),文本按DEFAULT_MAX_LINES/DEFAULT_MAX_BYTES截断。见packages/coding-agent/src/core/tools/read.ts:205-236。 - bash:执行 shell 命令,跟踪 detached 子进程,提供 abort 与超时。见
packages/coding-agent/src/core/tools/bash.ts:264-280。 - edit / write:文件编辑与新建,两者都走
withFileMutationQueue串行化。见packages/coding-agent/src/core/tools/edit.ts:288-316、packages/coding-agent/src/core/tools/write.ts:181-210。 - grep / find / ls:文件搜索与列举,基于
fd和rg,尊重.gitignore。见packages/coding-agent/src/core/tools/grep.ts:122-135、packages/coding-agent/src/core/tools/find.ts:112-125、packages/coding-agent/src/core/tools/ls.ts:99-112。 - 组合工厂:
createToolDefinition/createTool按ToolName分派,createCodingTools默认 4 件套(read/bash/edit/write),createReadOnlyTools4 件套(read/grep/find/ls)。见packages/coding-agent/src/core/tools/index.ts:96-136、packages/coding-agent/src/core/tools/index.ts:138-184。 - 串行化写入:
withFileMutationQueue按文件 realpath 维护一个 Promise 链,同一文件的多次写按调用顺序排队,不同文件并行。见packages/coding-agent/src/core/tools/file-mutation-queue.ts:19-39。
设计动机
为什么工具分两层?ToolDefinition 是给 LLM 看的:schema、description、prompt snippet、guidelines,这些会被注入到系统提示。AgentTool 是执行体,只对 runtime 有意义。拆开的好处是同一个定义可以驱动多个执行体——比如 read 工具的定义在 RPC 模式下可以用不同的 operations 实现替换(read 单元测试可以注入 mock 文件系统)。
为什么写入要串行化?LLM 在一轮里可能并行调多次 edit/write 改同一文件,如果不串行,后写的会覆盖先写的,导致内容错乱。withFileMutationQueue 用 realpathSync.native 作 key,这样符号链接和真实路径都对应到同一个队列。不同文件仍然并行,不损失吞吐。
为什么 read 默认带 promptGuidelines: ["Use read to examine files instead of cat or sed."]?因为系统提示构建时会把 tool 的 guidelines 拼进去,LLM 看到「优先用 read 而不是 bash 跑 cat」会少走一些低效路径。
关键文件
packages/coding-agent/src/core/tools/— 工具目录,bash.ts / edit.ts / find.ts / grep.ts / ls.ts / read.ts / write.ts / truncate.ts / file-mutation-queue.ts。packages/coding-agent/src/core/tools/index.ts:1-69— 所有create*Tool/create*ToolDefinition的 re-export。packages/coding-agent/src/core/tools/index.ts:81-115—ToolName类型与createToolDefinition分派。packages/coding-agent/src/core/tools/index.ts:138-196—createCodingTools/createReadOnlyTools/createAllTools三种预设组合。packages/coding-agent/src/core/tools/file-mutation-queue.ts:4-13—fileMutationQueuesMap 和getMutationQueueKey,realpath 解析。packages/coding-agent/src/core/tools/file-mutation-queue.ts:19-39—withFileMutationQueue,Promise 链排队。packages/coding-agent/src/core/tools/read.ts:205-236—createReadToolDefinition,图片检测、resize、abort signal 处理。packages/coding-agent/src/core/tools/read.ts:360-362—createReadTool,wrapToolDefinition包装。
withFileMutationQueue 的实现是一个 promise 链,新调用挂到旧 promise 的 .then 后面:
// packages/coding-agent/src/core/tools/file-mutation-queue.ts:19-39
export async function withFileMutationQueue<T>(filePath: string, fn: () => Promise<T>): Promise<T> {
const key = getMutationQueueKey(filePath);
const currentQueue = fileMutationQueues.get(key) ?? Promise.resolve();
let releaseNext!: () => void;
const nextQueue = new Promise<void>((resolveQueue) => {
releaseNext = resolveQueue;
});
const chainedQueue = currentQueue.then(() => nextQueue);
fileMutationQueues.set(key, chainedQueue);
await currentQueue;
try {
return await fn();
} finally {
releaseNext();
if (fileMutationQueues.get(key) === chainedQueue) {
fileMutationQueues.delete(key);
}
}
}read 工具的 execute 接 signal: AbortSignal,在用户 Ctrl+C 时立刻 reject:
// packages/coding-agent/src/core/tools/read.ts:225-237
const absolutePath = resolveReadPath(path, cwd);
return new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(
(resolve, reject) => {
if (signal?.aborted) {
reject(new Error("Operation aborted"));
return;
}
let aborted = false;
const onAbort = () => {
aborted = true;
reject(new Error("Operation aborted"));
};
signal?.addEventListener("abort", onAbort, { once: true });数据流
工具选择与执行:
边界与失败
- abort 信号:read 在
signal.aborted时立即 reject,并在异步任务里检查aborted标志,避免 race condition,见packages/coding-agent/src/core/tools/read.ts:228-237。 - realpath 失败:
getMutationQueueKey在realpathSync.native抛错时回退到resolve(filePath),保证对已删除文件也能排队,见packages/coding-agent/src/core/tools/file-mutation-queue.ts:6-13。 - 图片 resize 失败:
resized返回 null 时改成文本占位「Image omitted: could not be resized below the inline image size limit」,不直接报错,见packages/coding-agent/src/core/tools/read.ts:255-258。 - 非视觉模型:read 检测
ctx?.model是否支持视觉,不支持时附加提示文本getNonVisionImageNote,让 LLM 知道图片实际不可见。 - 未知工具名:
createToolDefinition的 switch default 抛Unknown tool name,不静默返回 undefined,见packages/coding-agent/src/core/tools/index.ts:112-114。
小结
tools/ 是 pi 的内置工具箱,双层结构(ToolDefinition 给 LLM、AgentTool 给 runtime),写入类工具通过 withFileMutationQueue 串行化避免并发竞态。三种预设组合覆盖编码、只读、全套三种场景。装配这些工具到 session 看 createAgentSession 装配,工具调用前后钩子看 AgentSession 编排层。