Skip to content

工具集 read/bash/edit/write/grep/find/ls

源码版本v0.73.1

tools/ 目录是 pi 的内置工具集合。每个工具都是 ToolDefinition(带 schema、描述、prompt snippet、guidelines)和 AgentTool(执行函数)双层结构,通过 wrapToolDefinition 转换。index.ts re-export 所有工具工厂,并提供 createCodingTools / createReadOnlyTools / createAllTools 三种预设组合。文件写入类工具(edit、write)经过 withFileMutationQueue 串行化,避免并发改同一文件导致竞态。

职责

  1. read:读文本/图片文件,图片支持自动 resize(autoResizeImages),文本按 DEFAULT_MAX_LINES / DEFAULT_MAX_BYTES 截断。见 packages/coding-agent/src/core/tools/read.ts:205-236
  2. bash:执行 shell 命令,跟踪 detached 子进程,提供 abort 与超时。见 packages/coding-agent/src/core/tools/bash.ts:264-280
  3. edit / write:文件编辑与新建,两者都走 withFileMutationQueue 串行化。见 packages/coding-agent/src/core/tools/edit.ts:288-316packages/coding-agent/src/core/tools/write.ts:181-210
  4. grep / find / ls:文件搜索与列举,基于 fdrg,尊重 .gitignore。见 packages/coding-agent/src/core/tools/grep.ts:122-135packages/coding-agent/src/core/tools/find.ts:112-125packages/coding-agent/src/core/tools/ls.ts:99-112
  5. 组合工厂:createToolDefinition / createToolToolName 分派,createCodingTools 默认 4 件套(read/bash/edit/write),createReadOnlyTools 4 件套(read/grep/find/ls)。见 packages/coding-agent/src/core/tools/index.ts:96-136packages/coding-agent/src/core/tools/index.ts:138-184
  6. 串行化写入: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 改同一文件,如果不串行,后写的会覆盖先写的,导致内容错乱。withFileMutationQueuerealpathSync.native 作 key,这样符号链接和真实路径都对应到同一个队列。不同文件仍然并行,不损失吞吐。

为什么 read 默认带 promptGuidelines: ["Use read to examine files instead of cat or sed."]?因为系统提示构建时会把 tool 的 guidelines 拼进去,LLM 看到「优先用 read 而不是 bash 跑 cat」会少走一些低效路径。

关键文件

withFileMutationQueue 的实现是一个 promise 链,新调用挂到旧 promise 的 .then 后面:

typescript
// 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 工具的 executesignal: AbortSignal,在用户 Ctrl+C 时立刻 reject:

typescript
// 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 });

数据流

工具选择与执行:

边界与失败

小结

tools/ 是 pi 的内置工具箱,双层结构(ToolDefinition 给 LLM、AgentTool 给 runtime),写入类工具通过 withFileMutationQueue 串行化避免并发竞态。三种预设组合覆盖编码、只读、全套三种场景。装配这些工具到 session 看 createAgentSession 装配,工具调用前后钩子看 AgentSession 编排层