Test each behavior at one layer, share the UI context and tool result helpers in the suite harness, loosen assertions on prompt text, and drop the /mcp manager UI test.
pi-mcp exports toLlmContent(), which converts a CallToolResult to pi-ai-shaped text and image content. The coding agent's MCP adapter uses it.
A typechecked example in packages/agent/examples/mcp-codemode shows a plain Agent with MCP tools and a codemode tool whose nested calls go through runToolCall() and the agent's hooks. The pi-mcp, pi-codemode, and pi-agent-core READMEs describe the same wiring.
The tool is named codemode again and scripts configure it with a first-line // @options: {...} instead of the // @exec: pragma. yield_time_ms is gone; max_output_tokens and timeout_ms remain. Comments, docs, and tests no longer describe the tool in terms of another agent.
- structuredContent after tool_result/afterToolCall: providing it replaces it; replacing content without it drops it. No more key-presence checks.
- Tools run outside a session get a ToolContext (loop services plus optional extension fields) instead of a context typed as a full ExtensionToolContext.
- Document that nested calls get synthesized <parent>/<n> tool call ids that never appear in the transcript.
- The exec description no longer mentions yield_time_ms, which is accepted and ignored.
- The sandbox abort test waits for the tool call instead of a fixed delay.
The codemode tool is now exec and follows the exec tool of OpenAI Codex, so scripts written for Codex run unchanged:
- // @exec: pragma with yield_time_ms, max_output_tokens (default 10000 tokens) and the pi addition timeout_ms; Codex's grammar and error messages
- text(), image() (data URLs, { image_url }, MCP ImageContent), exit(), ALL_TOOLS, tools under normalized identifiers; console and return values append text
- Results start with Script completed/failed and the wall time; failed scripts return an error result that keeps partial output and appends Script error
- Output over the token budget keeps its start and end like Codex; the full text is spilled to a temp file
- MCP tools resolve to their CallToolResult (isError resolves) and are declared as CallToolResult<T>; bash resolves to { output, exit_code, wall_time_seconds }, also for non-zero exits
- The description uses Codex's layout: helper list, deferred-tool guidance, shared MCP types, one section per tool grouped by namespace (MCP servers, with their instructions)
- A script awaiting a promise nothing can settle (no pending host call,
no timers in the VM) now fails instead of hanging until aborted, which
matters because pi runs scripts without a default deadline
- Declarations resolve #/$defs and #/definitions references, which MCP
servers built on pydantic or zod emit for nested arguments
Scripts get models.getModelsOfType/getAvailableOfType/getModelOfType/classify, mirroring ModelRuntime. The host resolves models by provider and id so scripts cannot redirect credentials, strips headers from listed models, and runs at most four classifications per script at once. pi-codemode gains namespaced globals, spread arguments, and explicit signatures.
Codemode input is now the script itself, optionally starting with a // @options {"timeout": N} line, and the tool declares a Lark grammar so capable models write raw JavaScript. Scripts can keep JSON values with store()/load(); successful writes are appended as codemode-store custom entries and read back from the current branch.
The MCP client, transports, and OAuth code load only when mcp.json lists a server, and connecting starts after the first render. The codemode sandbox loads on the first script; the tool description only needs the new pi-codemode/declarations entry. TUI startup matches main again.
Replaces the node:vm context with quickjs-wasi, so the sandbox is a separate wasm instance instead of a same-process V8 context. The worker stays to keep the host thread responsive; a shared interrupt flag stops spinning scripts on Bun, where worker.terminate() cannot interrupt wasm.
The Node bundle and the Bun binary build the worker as a separate entrypoint, and the Bun binary embeds quickjs.wasm.
Adds a built-in, opt-in codemode tool that runs model-written JavaScript in the codemode sandbox. Scripts call the other active tools as tools.<name>(args), including concurrently with Promise.all; only the return value, console output, and images attached with image() reach the model.
Nested calls run through the agent loop via a new AgentToolContext (5th execute argument, ctx.executeTool for extension tools), so argument validation and the beforeToolCall/afterToolCall hooks apply. Hooks and tool_call/tool_result events carry the parent call id.
Tools can declare outputSchema and return structuredContent; codemode passes that to scripts and falls back to text content otherwise. The codemode package gains globals, declaration rendering from JSON Schema, and optional deadlines.