/** * Tool registry — the bag of tools the pi session passes to the model. * * Read/Write/Edit/Bash mirror the Claude SDK tools; Task is the background * sub-agent delegator (Phase B of the parity plan). Grep, Glob, LS, * NotebookEdit etc. are still pending (Phase D) to fully match Claude SDK's * surface. */ import type { PiTool } from './types.js'; import type { PiToolDef } from '../providers/types.js'; import { readTool } from './read.js'; import { writeTool } from './write.js'; import { editTool } from './edit.js'; import { bashTool, bashOutputTool, killShellTool } from './bash.js'; import { grepTool } from './grep.js'; import { globTool } from './glob.js'; import { todoWriteTool } from './todo-write.js'; import { webFetchTool } from './web-fetch.js'; import { taskTool, taskToolDef } from './task.js'; export const PI_TOOLS: PiTool[] = [ readTool, writeTool, editTool, bashTool, bashOutputTool, killShellTool, grepTool, globTool, todoWriteTool, webFetchTool, taskTool, ]; const TOOL_BY_NAME = new Map(); for (const t of PI_TOOLS) { TOOL_BY_NAME.set(t.name, t); // Some models lowercase or otherwise normalise tool names. Register // common aliases so we don't 404 a legitimate call over a casing nit. TOOL_BY_NAME.set(t.name.toLowerCase(), t); } // The pi system prompt calls background delegation "the Agent tool" (claude // heritage) — alias it so a model following the prompt verbatim still lands // on the Task implementation. TOOL_BY_NAME.set('Agent', taskTool); TOOL_BY_NAME.set('agent', taskTool); export function findTool(name: string): PiTool | undefined { return TOOL_BY_NAME.get(name) || TOOL_BY_NAME.get(name.toLowerCase()); } export function toolDefsForProvider(opts?: { withTask?: boolean }): PiToolDef[] { const defs: PiToolDef[] = []; for (const t of PI_TOOLS) { if (t.name === 'Task') { // Task is opt-in: only live PARENT conversations have a task host. // Sub-agent children can't spawn grandchildren (Claude SDK parity) and // one-shots (pulse/cron/customer/agent-API) have no host either — a // session that hallucinates a Task call still fails gracefully // (ctx.tasks is unset). if (!opts?.withTask) continue; // Rebuilt fresh so agent-roster/prompt edits apply per session start. defs.push(taskToolDef()); continue; } defs.push({ name: t.name, description: t.description, inputSchema: t.inputSchema }); } return defs; }