Skip to content

zidane/codemode

A QuickJS sandbox that runs model-written JavaScript with only injected tools as its capabilities.

import { … } from 'zidane/codemode'src/codemode/index.ts

Use CodemodeSandbox to let a model write one script that calls many tools, so the intermediate results never enter its context. The script runs in a QuickJS VM in a worker thread, with no timers, network or modules, and needs the optional quickjs-wasi package. The render functions turn your tools into TypeScript declarations to show the model.

TypeScript
import { CodemodeSandbox } from 'zidane/codemode'

interface AddArgs { a: number, b: number }

const sandbox = new CodemodeSandbox({
  tools: [{ name: 'add', execute: (args) => { const { a, b } = args as AddArgs; return a + b } }],
})
const result = await sandbox.execute('return await tools.add({ a: 1, b: 2 })')
if (result.ok)
  console.log(result.value)
await sandbox.close()

Functions#

codemodeSandboxPathsFunction#

The paths set with setCodemodeSandboxPaths.

TypeScript
function codemodeSandboxPaths(): Readonly<CodemodeSandboxPaths>

src/codemode/sandbox-paths.ts:32

loadQuickJSWasmFunction#

Read and compile the QuickJS wasm once per path. path defaults to the file in the installed quickjs-wasi package (an optional peer dependency); pass it when the file lives elsewhere, for example embedded in a Bun compiled executable. A failed load is retried on the next call.

TypeScript
function loadQuickJSWasm(path?: string): Promise<object>
Parameters
NameTypeDescription
path?string

Path to quickjs.wasm. Defaults to the file in the installed quickjs-wasi package.

Promise<object>

src/codemode/wasm.ts:24

mcpStructuredContentSchemaFunction#

The structuredContent schema of an MCP CallToolResult output schema (detected by a content array of objects, boolean isError, and object _meta), true when it declares none, or undefined when the schema is not a CallToolResult.

TypeScript
function mcpStructuredContentSchema(
  schema: CodemodeJsonSchema | undefined,
): CodemodeJsonSchema | undefined
Parameters
NameTypeDescription
schemaCodemodeJsonSchema | undefined

Output schema of a tool.

CodemodeJsonSchema | undefined

src/codemode/declarations.ts:215

parseCodemodeSourceFunction#

Split an optional first-line // @options: {...} from the script. Throws CodemodeSourceError for empty input and invalid options.

TypeScript
function parseCodemodeSource(input: string): ParsedCodemodeSource
Parameters
NameTypeDescription
inputstring

Script text, optionally starting with an // @options: line.

Example
import { parseCodemodeSource } from 'zidane/codemode'

const { code, options } = parseCodemodeSource(
  '// @options: {"timeout_ms": 5000}\ntext("hi")',
)
console.log(options.timeoutMs, code)

src/codemode/source.ts:114

renderDeclarationsFunction#

Render TypeScript declarations for the script-visible API. Tools become members of declare const tools, globals become declare function statements, and ns.member globals members of declare const ns. Descriptions become doc comments; schemas become types.

TypeScript
function renderDeclarations(options: RenderDeclarationsOptions): string
Parameters
NameTypeDescription
optionsRenderDeclarationsOptions

Tools and globals to render.

string
Example
import { renderDeclarations } from 'zidane/codemode'

const declarations = renderDeclarations({
  tools: [{
    name: 'read_file',
    description: 'Reads a file.',
    inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
    execute: () => '',
  }],
})
console.log(declarations)

src/codemode/declarations.ts:123

renderToolOutputTypeFunction#

The type a tool call resolves to: CallToolResult<T> for MCP output schemas (needs MCP_TYPESCRIPT_PREAMBLE), the schema's type otherwise, and unknown without a schema.

TypeScript
function renderToolOutputType(
  schema: CodemodeJsonSchema | undefined,
  options?: { comments?: boolean },
): string
Parameters
NameTypeDefaultDescription
schemaCodemodeJsonSchema | undefined

Output schema of a tool.

options?{ comments?: boolean }{}

Rendering options.

string

src/codemode/declarations.ts:235

renderToolSampleFunction#

A tool's sample: the description followed by the tool's declaration. Used for tool listings and ALL_TOOLS entries.

TypeScript
function renderToolSample(
  tool: Pick<CodemodeTool, 'name' | 'description' | 'inputSchema' | 'outputSchema'>,
  options?: { inputMaxChars?: number },
): string
Parameters
NameTypeDefaultDescription
toolPick<CodemodeTool, 'name' | 'description' | 'inputSchema' | 'outputSchema'>

Tool whose description and declaration to render.

options?{ inputMaxChars?: number }{}

inputMaxChars renders longer input types as unknown.

string

src/codemode/declarations.ts:200

renderToolSignatureFunction#

One tool as a member of the tools object: name(args: T): Promise<R>; with the name as the identifier scripts use. Input types longer than inputMaxChars render as unknown. A tool that takes a bare string (stringArgumentOf) renders as name(args: string | T). Tools whose output schema is an MCP CallToolResult render as Promise<CallToolResult<T>>, which needs MCP_TYPESCRIPT_PREAMBLE.

TypeScript
function renderToolSignature(
  tool: Pick<CodemodeTool, 'name' | 'inputSchema' | 'outputSchema'>,
  options?: { comments?: boolean; inputMaxChars?: number },
): string
Parameters
NameTypeDefaultDescription
toolPick<CodemodeTool, 'name' | 'inputSchema' | 'outputSchema'>

Tool whose name and schemas to render.

options?{ comments?: boolean; inputMaxChars?: number }{}

Rendering options.

string

src/codemode/declarations.ts:163

schemaToTypeFunction#

Convert a JSON Schema to a TypeScript type expression: objects on one line ({ a: string; b?: number; }) with properties sorted by name, or one property per line with // comments when a property has a description; Array<T> for arrays. Local references (#/$defs/..., #/definitions/...) resolve against schema; recursive and remote references render as unknown. A result longer than maxChars renders as unknown.

TypeScript
function schemaToType(
  schema: CodemodeJsonSchema,
  options?: { comments?: boolean; maxChars?: number },
): string
Parameters
NameTypeDefaultDescription
schemaCodemodeJsonSchema

JSON Schema to convert.

options?{ comments?: boolean; maxChars?: number }{}

Conversion options.

string

src/codemode/declarations.ts:290

setCodemodeSandboxPathsFunction#

Replace the process-wide sandbox paths. Explicit per-sandbox options (CodemodeSandboxOptions.workerUrl, loadQuickJSWasm(path)) still win.

TypeScript
function setCodemodeSandboxPaths(paths: CodemodeSandboxPaths): void
Parameters
NameTypeDescription
pathsCodemodeSandboxPaths

Paths to use; omitted fields fall back to the defaults.

src/codemode/sandbox-paths.ts:27

stringArgumentOfFunction#

The property a bare string argument fills: the schema's only required property, when it is a string. A script may then call tools.shell("ls") for tools.shell({ command: "ls" }), or tools.shell("ls", { timeout: 5000 }) to pass the other properties too. undefined otherwise.

TypeScript
function stringArgumentOf(schema: CodemodeJsonSchema | undefined): string | undefined
Parameters
NameTypeDescription
schemaCodemodeJsonSchema | undefined

Input schema of a tool.

string | undefined

src/codemode/declarations.ts:183

toCodemodeIdentifierFunction#

The identifier a script uses for a tool: characters that are not valid in a JavaScript identifier become _. mcp__docs__search stays as is, my-tool becomes my_tool.

TypeScript
function toCodemodeIdentifier(name: string): string
Parameters
NameTypeDescription
namestring

Tool name to convert.

string

src/codemode/identifier.ts:7

Classes#

CodemodeSandboxClass#

Runs JavaScript in a QuickJS VM (a separate wasm instance) inside a worker thread. The script sees tools.<name>(args) for every registered tool, ALL_TOOLS, the output helpers text, image, exit, and console.*, store/load, and the configured globals; nothing else (no timers, fetch, process, require, modules).

Each execute() gets its own worker and VM; the sandbox only holds the tool table and defaults. close() aborts in-flight executions.

TypeScript
class CodemodeSandbox
TypeScript
new CodemodeSandbox(options?: CodemodeSandboxOptions)

Creates a sandbox with its tools, globals and defaults. Throws when a global or shortcut name is invalid, reserved or duplicated.

Parameters
NameTypeDefaultDescription
options?CodemodeSandboxOptions{}

Tools, globals, timeout, memory limit and worker settings.

CodemodeSandbox properties
NameTypeDescription
globalsreadonlyCodemodeTool[]

Globals the script can call as top-level functions.

toolsreadonlyCodemodeTool[]

Tools currently registered.

close()

TypeScript
close(): Promise<void>

Aborts in-flight executions (they resolve with kind: "aborted") and rejects new ones.

Promise<void>

execute()

TypeScript
execute(code: string, options?: CodemodeExecuteOptions): Promise<CodemodeResult>

code is an async function body: return and top-level await work. Script failures come back as { ok: false }. The promise rejects only when the sandbox is closed. The script can use store(key, value) and load(key) on options.store.

Parameters
NameTypeDefaultDescription
codestring

Script source, run as an async function body.

options?CodemodeExecuteOptions{}

Per-execution signal, timeout, store values and determinism settings.

registerTool()

TypeScript
registerTool(tool: CodemodeTool): void

Throws if a tool with the same name is already registered.

Parameters
NameTypeDescription
toolCodemodeTool

Tool to add to the tools table.

unregisterTool()

TypeScript
unregisterTool(name: string): boolean

Removes a tool by name. Returns true when it was registered.

Parameters
NameTypeDescription
namestring

Name of the tool to remove.

boolean
Example
import { CodemodeSandbox } from 'zidane/codemode'

const sandbox = new CodemodeSandbox({
  tools: [{ name: 'add', execute: args => (args as { a: number, b: number }).a + (args as { a: number, b: number }).b }],
})
const result = await sandbox.execute('return await tools.add({ a: 1, b: 2 })')
if (result.ok)
  console.log(result.value)

src/codemode/runtime/host.ts:433

CodemodeSourceErrorClass#

Error thrown when codemode source is empty or its @options line is invalid.

TypeScript
class CodemodeSourceError extends Error
TypeScript
new CodemodeSourceError(message: string)

Creates the error.

Parameters
NameTypeDescription
messagestring

Description of the problem.

src/codemode/source.ts:49

CodemodeValueClass#

A tool result the script reads as value and prints as text. Printing it (return r, text(r), console.log(r), or an array holding such results) shows text, the result as a direct call returns it, while r.field reads value as usual. Return one from CodemodeTool.execute when value is structured.

TypeScript
class CodemodeValue
TypeScript
new CodemodeValue(value: unknown, text: string)
Parameters
NameTypeDescription
valueunknown

What the script receives. Must survive a JSON round trip.

textstring

What printing the value shows.

CodemodeValue properties
NameTypeDefaultDescription
[CODEMODE_VALUE]readonlytruetrue

Brands the instance, so CodemodeValue.is recognizes one from any copy of this module.

textreadonlystring

What printing the value shows.

valuereadonlyunknown

What the script receives. Must survive a JSON round trip.

CodemodeValue.is()

TypeScript
CodemodeValue.is(candidate: unknown): candidate is CodemodeValue

Whether candidate is a CodemodeValue, from any copy of this module.

Parameters
NameTypeDescription
candidateunknown

Value to check.

candidate is CodemodeValue
Example
const run: CodemodeTool = {
  name: 'run',
  execute: () => new CodemodeValue({ stdout: 'ok\n', exit_code: 0 }, 'ok\n(exit 0)'),
}

src/codemode/value.ts:18

Constants#

CODEMODE_OPTIONS_PREFIXConstant#

Codemode source format: JavaScript, optionally preceded by one options line.

Text
// @options: {"max_output_tokens": 2000, "timeout_ms": 30000}
const text = await tools.read_file({ path: "package.json" });
text(JSON.parse(text).name);
TypeScript
const CODEMODE_OPTIONS_PREFIX = '// @options:'

src/codemode/source.ts:11

CODEMODE_SOURCE_GRAMMARConstant#

Lark grammar for providers with grammar-constrained tool input. It only fixes the shape of the options line; the options JSON and the code are checked by parseCodemodeSource.

TypeScript
const CODEMODE_SOURCE_GRAMMAR: string

src/codemode/source.ts:22

DEFAULT_INPUT_SCHEMA_MAX_CHARSConstant#

Largest rendered input type, in characters, before it becomes unknown.

TypeScript
const DEFAULT_INPUT_SCHEMA_MAX_CHARS = 16_000

src/codemode/declarations.ts:9

MAX_OUTPUT_CHARSConstant#

Output one script may produce with text(), image(), and console.*: characters of text and base64 image data, and items. The host keeps all output until the script ends, so without a limit a script that prints in a loop grows the host's memory until it crashes. The item limit covers loops that print empty strings.

TypeScript
const MAX_OUTPUT_CHARS: number

src/codemode/runtime/prelude.ts:13

MAX_OUTPUT_ITEMSConstant#

Maximum number of output items one script may produce.

TypeScript
const MAX_OUTPUT_ITEMS = 100_000

src/codemode/runtime/prelude.ts:15

MAX_STORE_TOTAL_CHARSConstant#

Maximum total size, in characters of JSON, of all values a script writes with store() in one execution.

TypeScript
const MAX_STORE_TOTAL_CHARS: number

src/codemode/runtime/prelude.ts:6

MAX_STORE_VALUE_CHARSConstant#

Maximum size, in characters of JSON, of one value a script writes with store().

TypeScript
const MAX_STORE_VALUE_CHARS: number

src/codemode/runtime/prelude.ts:4

MCP_TYPESCRIPT_PREAMBLEConstant#

TypeScript types for MCP results, from the MCP CallToolResult schema, so CallToolResult<T> declarations can refer to them.

TypeScript
const MCP_TYPESCRIPT_PREAMBLE = ...

src/codemode/declarations.ts:17

Interfaces#

CodemodeCallInterface#

Record of one tool call made by a script.

TypeScript
interface CodemodeCall
CodemodeCall properties
NameTypeDescription
durationMsnumber

Time the call took, in milliseconds.

namestring

Name of the tool that was called.

seqnumber

Same as CodemodeToolContext.seq for this call.

statusCodemodeCallStatus

How the call ended.

src/codemode/types.ts:71

CodemodeDeterminismInterface#

Pins the VM's only nondeterministic inputs, the WASI clock and random source. Date.now() returns nowMs for the whole run and Math.random() (seeded from the clock by QuickJS) plus WASI random_get derive from seed. With these set, a script that sees the same tool results takes the same path, which is what durable replay needs.

TypeScript
interface CodemodeDeterminism
CodemodeDeterminism properties
NameTypeDescription
nowMsnumber

Fixed value returned by Date.now(), in milliseconds since the epoch.

seednumber

Seed for Math.random() and the WASI random source.

src/codemode/types.ts:136

CodemodeErrorInterface#

Failure of a codemode execution.

TypeScript
interface CodemodeError
CodemodeError properties
NameTypeDescription
excerpt?string

Script errors with a location: the offending source line and a ^ under the column, so the line can be fixed without counting lines.

kindCodemodeErrorKind

What kind of failure occurred.

messagestring

Error message.

name?string

Error name from the script, for script errors.

stack?string

Stack trace from the script, for script errors.

src/codemode/types.ts:94

CodemodeExecuteOptionsInterface#

Options for one CodemodeSandbox.execute call.

TypeScript
interface CodemodeExecuteOptions
CodemodeExecuteOptions properties
NameTypeDescription
determinism?CodemodeDeterminism

Pin the clock and random source for this execution. See CodemodeDeterminism.

onOutput?(item: CodemodeOutputItem) => void

Called with each output item as the script produces it, before the execution ends. The result still carries every item. A throwing callback is ignored.

signal?AbortSignal

Aborts the execution when signaled. The result is an aborted error.

store?Readonly<Record<string, unknown>>

Values the script reads with load(key). Must be JSON-serializable. The script's own store() calls come back as result.storeWrites; persisting them is up to the caller.

timeoutMs?number

Overrides the sandbox default for this execution.

trackLines?boolean

Report the script line of each call as CodemodeToolContext.line. Costs one stack capture per call. Default: off.

src/codemode/types.ts:192

CodemodeSandboxOptionsInterface#

Options for creating a CodemodeSandbox.

TypeScript
interface CodemodeSandboxOptions
CodemodeSandboxOptions properties
NameTypeDescription
globals?CodemodeTool[]

Functions exposed as top-level identifiers instead of on tools, for host helpers such as attaching an image to the result. They behave like tools (JSON round trip, promise result) but are not recorded in result.calls. Names must be identifiers and may not shadow the built-in globals (tools, ALL_TOOLS, console, text, image, exit, globalThis, store, load).

memoryLimitBytes?number

Maximum memory the QuickJS VM may allocate. Allocations beyond it fail inside the script as InternalError: out of memory. Default: no limit beyond wasm32's 4 GiB address space.

shortcuts?Record<string, string>

Global functions that call a tool, by function name: { sh: 'shell' } makes sh(...args) the same call as tools.shell(...args), recorded like it. Names follow the rules for globals; a shortcut to a tool the sandbox does not have is not defined.

timeoutMs?number

Overall deadline per execution, including time spent in tools. Infinity disables the deadline; the execution then only ends when the script settles or is aborted. Default: 300000.

toolHints?Record<string, string>

Advice shown when a script reads a tools member that does not exist but that the host knows, by member name (e.g. spawn, which runs outside scripts). Replaces the generic "does not exist" message for those names.

tools?CodemodeTool[]

Tools the script calls as tools.<id>(args). Recorded in result.calls.

wasm?object | Promise<object>

Compiled quickjs-wasi/quickjs.wasm, usually from loadQuickJSWasm. Default: the file in the installed quickjs-wasi package. Pass it when that file is not on disk, for example in a Bun compiled executable.

workerUrl?string | URL

Worker entry (src/codemode/runtime/worker.ts, dist/codemode-worker.js). Default: the sibling of this module. Pass it when zidane is re-bundled. Bun compiled executables need the relative string specifier of an embedded build entrypoint; other hosts usually use a URL.

src/codemode/types.ts:144

CodemodeSandboxPathsInterface#

Paths the sandbox uses instead of its defaults.

TypeScript
interface CodemodeSandboxPaths
CodemodeSandboxPaths properties
NameTypeDescription
wasmPath?string

quickjs.wasm. Default: the file in the installed quickjs-wasi package.

workerUrl?URL

The sandbox worker module. Default: codemode-worker.js next to the bundle (worker.ts in source).

src/codemode/sandbox-paths.ts:13

CodemodeSourceOptionsInterface#

Options read from the // @options: line of a codemode script.

TypeScript
interface CodemodeSourceOptions
CodemodeSourceOptions properties
NameTypeDescription
maxOutputTokens?number

Token budget for the script's output.

timeoutMs?number

Hard deadline for the whole script in milliseconds, including tool calls.

src/codemode/source.ts:33

CodemodeStoreWritesInterface#

Keys the script changed with store(). Only successful executions report writes.

TypeScript
interface CodemodeStoreWrites
CodemodeStoreWrites properties
NameTypeDescription
deletestring[]

Keys stored as undefined.

setRecord<string, unknown>

Keys written with a value, mapped to the stored value.

src/codemode/types.ts:111

CodemodeToolInterface#

A host function a codemode script can call, either as tools.<id>(args) or, for globals, as a top-level function.

TypeScript
interface CodemodeTool
CodemodeTool properties
NameTypeDescription
description?string

Shown as a doc comment in renderDeclarations, and listed in ALL_TOOLS for tools.

execute(args: unknown, context: CodemodeToolContext) => unknown

args is whatever the script passed, after a JSON round trip; a bare string becomes the object stringArgumentOf(inputSchema) names. The return value must be JSON-serializable, or a CodemodeValue to print as text; a thrown error surfaces in the script as an Error with the same message.

inputSchema?CodemodeJsonSchema

Schema of the single argument. Rendered as the parameter type; unknown when omitted.

namestring

The script calls tools as tools.<id>(args), where <id> is the name with characters that are not valid in identifiers replaced by _ (see toCodemodeIdentifier), and also as tools["<name>"](args). Globals are called as <name>(args) and must be identifiers, or <namespace>.<member>, which groups them into a frozen namespace object.

outputSchema?CodemodeJsonSchema

Schema of the resolved value. Rendered as the promise type; unknown when omitted.

signature?string

Globals only: TypeScript parameter list and return type for renderDeclarations, for example (type: string, id?: string): Promise<Model[]>. Replaces the rendering from the schemas.

spread?boolean

Globals only: execute receives all call arguments as an array instead of the first one.

src/codemode/types.ts:29

CodemodeToolContextInterface#

Context passed to a CodemodeTool on each call.

TypeScript
interface CodemodeToolContext
CodemodeToolContext properties
NameTypeDescription
line?number

Script line that made this call (the innermost frame of the script, so a call inside a helper reports the helper's line). Only with CodemodeExecuteOptions.trackLines.

seqnumber

1-based position of this call in the order the script issued it, shared by tools and globals. The VM is single-threaded and has no timers, so for a given script and given results the order is the same on every run: durable adapters can key journal entries on it.

signalAbortSignal

Aborted when the script finishes (including unawaited calls), the execution times out, the caller aborts, or the sandbox is closed.

src/codemode/types.ts:4

ParsedCodemodeSourceInterface#

Result of parseCodemodeSource.

TypeScript
interface ParsedCodemodeSource
ParsedCodemodeSource properties
NameTypeDescription
codestring

The script with the options line replaced by an empty line, so line numbers are unchanged.

optionsCodemodeSourceOptions

Options from the options line. Empty when the script has none.

src/codemode/source.ts:41

RenderDeclarationsOptionsInterface#

Options for renderDeclarations.

TypeScript
interface RenderDeclarationsOptions
RenderDeclarationsOptions properties
NameTypeDescription
globals?readonly CodemodeTool[]

Globals rendered as top-level declare function statements or namespace objects.

tools?readonly CodemodeTool[]

Tools rendered as members of declare const tools.

src/codemode/declarations.ts:95

Types#

CodemodeCallStatusType#

Outcome of a tool call: finished (ok), threw (error) or was aborted before it settled (cancelled).

TypeScript
type CodemodeCallStatus = 'ok' | 'error' | 'cancelled'

src/codemode/types.ts:68

CodemodeErrorKindType#

Category of a CodemodeError.

TypeScript
type CodemodeErrorKind = 'script' | 'timeout' | 'aborted' | 'sandbox'

src/codemode/types.ts:83

CodemodeJsonSchemaType#

A JSON Schema document. Only used to render declarations; values are not validated against it.

TypeScript
type CodemodeJsonSchema = { [key: string]: unknown } | boolean

src/codemode/types.ts:26

CodemodeOutputItemType#

One item of the script's output, in the order the script produced it: text() and console.* produce text items, with console: true for console.*, and image() image items. data is base64.

TypeScript
type CodemodeOutputItem =
  | { console?: true; text: string; type: 'text' }
  | { data: string; mimeType: string; type: 'image' }

src/codemode/types.ts:63

CodemodeResultType#

output is kept for failed executions too, up to the failure. exit() completes with value: undefined.

TypeScript
type CodemodeResult =
  | { calls: CodemodeCall[]; ok: true; output: CodemodeOutputItem[]; storeWrites: CodemodeStoreWrites; value: unknown }
  | { calls: CodemodeCall[]; error: CodemodeError; ok: false; output: CodemodeOutputItem[] }

src/codemode/types.ts:119

CodemodeWasmModuleType#

A compiled WebAssembly.Module of quickjs-wasi/quickjs.wasm. Typed opaquely so callers don't need the DOM lib for the WebAssembly globals.

TypeScript
type CodemodeWasmModule = object

src/codemode/wasm.ts:9