ツール集 read/bash/edit/write/grep/find/ls
tools/ ディレクトリは pi の内蔵ツール集だ。各ツールは ToolDefinition (schema、説明、prompt snippet、guidelines を持つ) と AgentTool (実行関数) の二層構造で、wrapToolDefinition で変換される。index.ts はすべてのツールファクトリを re-export し、createCodingTools / createReadOnlyTools / createAllTools の 3 種のプリセット組み合わせを提供する。ファイル書き込み系ツール (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)、createReadOnlyToolsは 4 件 (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 をキーに使うので、シンボリックリンクと実パスが同じキューに対応する。異なるファイルは並行のままで、スループットを落とさない。
なぜ read にデフォルトで promptGuidelines: ["Use read to examine files instead of cat or sed."] が付くのか? システムプロンプト構築時にツールの guidelines が拼まれ、LLM が「bash で cat より read を優先」を見ると非効率なパスを減らせるからだ。
主要ファイル
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の 3 種プリセット組み合わせ。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が throw する時に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を throw し、黙って undefined を返さない。packages/coding-agent/src/core/tools/index.ts:112-114参照。
小ねた
tools/ は pi の内蔵ツールボックスで、二層構造 (ToolDefinition は LLM、AgentTool は runtime 向け) を持ち、書き込み系ツールは withFileMutationQueue で直列化して並列競合を避ける。3 種のプリセット組み合わせがコーディング、読み取り専用、フルセットの 3 シナリオを覆う。これらのツールを session に組み立てるのは createAgentSession 装配、ツール呼び出し前後のフックは AgentSession オーケストレーション層 参照。