Skip to content

Ensemble d'outils read/bash/edit/write/grep/find/ls

源码版本v0.73.1

Le répertoire tools/ est l'ensemble d'outils intégrés de pi. Chaque outil est une structure à deux couches — ToolDefinition (avec schema, description, prompt snippet, guidelines) et AgentTool (la fonction d'exécution) — reliées par wrapToolDefinition. index.ts re-export toutes les factories d'outils et fournit trois combinaisons préréglées : createCodingTools / createReadOnlyTools / createAllTools. Les outils d'écriture de fichiers (edit, write) sont sérialisés via withFileMutationQueue pour éviter les races liées à des modifications concurrentes du même fichier.

Responsabilités

  1. read : lit fichiers texte et images ; les images peuvent être auto-resize (autoResizeImages) ; le texte est tronqué selon DEFAULT_MAX_LINES / DEFAULT_MAX_BYTES. Voir packages/coding-agent/src/core/tools/read.ts:205-236.
  2. bash : exécute des commandes shell, suit les sous-processus detached, gère abort et timeout. Voir packages/coding-agent/src/core/tools/bash.ts:264-280.
  3. edit / write : édition et création de fichiers, tous deux sérialisés via withFileMutationQueue. Voir packages/coding-agent/src/core/tools/edit.ts:288-316 et packages/coding-agent/src/core/tools/write.ts:181-210.
  4. grep / find / ls : recherche et listing de fichiers, basés sur fd et rg, respectent .gitignore. Voir packages/coding-agent/src/core/tools/grep.ts:122-135, packages/coding-agent/src/core/tools/find.ts:112-125 et packages/coding-agent/src/core/tools/ls.ts:99-112.
  5. Factories de combinaison : createToolDefinition / createTool dispatchent par ToolName ; createCodingTools donne le quatuor par défaut (read/bash/edit/write), createReadOnlyTools un quatuor read-only (read/grep/find/ls). Voir packages/coding-agent/src/core/tools/index.ts:96-136 et packages/coding-agent/src/core/tools/index.ts:138-184.
  6. Sérialisation des écritures : withFileMutationQueue maintient une chaîne de Promises par realpath de fichier ; les écritures sur un même fichier s'y mettent en file selon l'ordre d'appel, les fichiers distincts restent parallèles. Voir packages/coding-agent/src/core/tools/file-mutation-queue.ts:19-39.

Motivation de design

Pourquoi deux couches d'outils ? ToolDefinition est destiné au LLM : schema, description, prompt snippet, guidelines — tout ça est injecté dans le system prompt. AgentTool est le corps d'exécution, qui n'a de sens que pour le runtime. L'intérêt de la séparation est qu'une même définition peut piloter plusieurs exécutions — par exemple, la définition de read peut, en mode RPC, être branchée à une implémentation alternative de operations (les tests unitaires de read peuvent injecter un filesystem mock).

Pourquoi sérialiser les écritures ? Le LLM peut, dans un même tour, lancer plusieurs edit/write en parallèle sur le même fichier. Sans sérialisation, le dernier écrit écrase le premier, et le contenu part en vrille. withFileMutationQueue utilise realpathSync.native comme clé, de sorte que les liens symboliques et les chemins réels tombent dans la même file. Les fichiers distincts restent parallèles, sans perte de throughput.

Pourquoi read a-t-il par défaut promptGuidelines: ["Use read to examine files instead of cat or sed."] ? Parce qu'à la construction du system prompt, les guidelines de l'outil y sont concaténées, et un LLM qui voit « préférez read à bash qui lance cat » évite quelques chemins inefficaces.

Fichiers clés

L'implémentation de withFileMutationQueue est une chaîne de promises : chaque nouvel appel s'accroche au .then de la précédente :

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

L'execute de read reçoit un signal: AbortSignal et reject immédiatement quand l'utilisateur fait Ctrl+C :

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

Flux de données

Sélection et exécution d'outil :

Limites et échecs

Résumé

tools/ est la boîte à outils intégrée de pi : structure à deux couches (ToolDefinition pour le LLM, AgentTool pour le runtime) ; les outils d'écriture sont sérialisés via withFileMutationQueue pour éviter les races concurrentes. Trois combinaisons préréglées couvrent les cas codage, lecture seule, et full set. Pour l'assemblage de ces outils dans la session, voir Assemblage createAgentSession ; pour les hooks autour des appels d'outils, voir AgentSession : couche d'orchestration.