/** * `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; } interface ExecutionOutcome { texts: string[]; progress: string[]; returnValue: unknown; toolCalls: Array<{ name: string; durationMs: number; isError: boolean; }>; 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 {};