/** * `run_code` — the single meta-tool that collapses N tool calls into * one model round-trip. The model emits a JavaScript program; we * execute it inside a `vm` sandbox where every other tool is exposed * as `tools.(args) => Promise<...>`, and only what the program * passes to `text(...)` (plus the resolved return value) flows back * to the model. * * Why `node:vm` instead of `isolated-vm`: * - The model is the koi's own brain, not adversarial input. We * don't need a process-level boundary, just a clean global * surface that hides `process`, `require`, `fetch`, etc. * - `isolated-vm` is a native module — every EC2 deploy would need * a prebuild for the target platform. `node:vm` is built-in. * - Execution still runs in the koi process, so per-tool side * effects (sessions, file writes, sandbox bash) work unchanged. * * Lifetime: each `run_code` call spins up a fresh context. There's no * persistent state between calls — the model can stitch one program * across many tools, but if it wants to remember something across * turns it has to call `text()`. */ import type { TSchema } from "@sinclair/typebox"; import type { AgentTool as KoiTool } from "@mariozechner/pi-agent-core"; /** * Schema for the model-facing tool. We keep it boring on purpose so * the model doesn't waste tokens on irrelevant params. */ declare const RUN_CODE_PARAMETERS: TSchema; export interface RunCodeToolDeps { /** All other tools that should be reachable from inside the sandbox. */ inner: KoiTool[]; /** Optional hook fired when a wrapped tool call starts; lets us * surface the same UI bubbles the conversational loop would. */ onInnerStart?: (toolName: string, args: unknown) => void; onInnerEnd?: (toolName: string, isError: boolean) => void; /** * Live progress snapshot for the terminal's activity card: fired on every * inner-call start/end and progress() push so a long program streams its * inner life instead of sitting as one opaque step for minutes. */ onLiveUpdate?: (snapshot: Record) => void; } interface ExecutionOutcome { texts: string[]; progress: string[]; returnValue: unknown; toolCalls: Array<{ name: string; durationMs: number; isError: boolean; /** Sanitized, size-capped view of the call's arguments (path/command/oldText/newText/content…) so UIs can render real cards for inner calls. */ args?: Record; /** The numbered diff an inner edit/write produced (result.details.diff), capped. */ diff?: string; /** Short plain-text preview of the inner call's result content. */ resultPreview?: string; }>; totalDurationMs: number; } /** * Build the single meta-tool that replaces the koi's tool surface * when code-mode is enabled. The inner tool set is captured at * construction time — the same set the conversational loop would * see — so behavior is consistent. */ export declare function createRunCodeTool(deps: RunCodeToolDeps): KoiTool; export {};