工具集 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 編排層。