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 編排層