Execute a script
sandbox.eval runs one TypeScript script in an isolated runtime and returns everything it
produced. This guide covers the contract in depth: what you pass in, what a script can reach,
and what you get back.
The call
Section titled “The call”const result = await sandbox.eval({ script: 'const r = await add({ a: 1, b: 2 }); data.sum = r.sum;', data: {},});eval takes a single options object:
| Field | Type | Description |
|---|---|---|
script |
string |
TypeScript source, executed in a QuickJS isolate. Type-checked before it runs. |
data |
Record<string, unknown> |
A persistent object the script reads and writes, like a notebook kernel’s state. |
callLog |
(CallLogEntry | null)[] (optional) |
A prior run’s log, passed back to resume or replay. See Resume an interrupted script. |
resumption |
{ position, data, state? } (optional) |
The answer to a previous run’s interrupt. |
logger |
Logger (optional) |
Passed through to every tool’s execute. |
This guide covers a plain run. callLog and resumption are the mechanics of resuming an
interrupted run and are covered separately.
The script
Section titled “The script”The script is authored in TypeScript. Before it runs, the sandbox type-checks it against the
generated action signatures and the data global; if that fails, eval throws
ScriptCompileError and nothing executes (see
Type-check scripts). It is then transpiled to
JavaScript and run.
The script body may use top-level await — the sandbox wraps it in an async function — so
you can await action calls directly and coordinate several with Promise.all.
The runtime is sealed: no network, no filesystem, no import. The only things a script can
reach are the globals the sandbox installs — data, console, and one function per action.
The persistent data object
Section titled “The persistent data object”Whatever you pass as data is injected as a global the script can read, mutate, or replace
wholesale:
// Reads the incoming value and writes derived state back.await sandbox.eval({ script: 'data.doubled = data.value * 2;', data: { value: 21 },});// result.data => { value: 21, doubled: 42 }
// A script may replace `data` entirely.await sandbox.eval({ script: 'data = { replaced: true };', data: { old: 1 },});// result.data => { replaced: true }data is the run’s working memory, not its output. Its contents are never surfaced to a
caller directly — a script exposes what it wants seen by logging it. Pass the previous run’s
returned data back in as the next run’s data to carry state forward, notebook-style.
Actions as global functions
Section titled “Actions as global functions”Each entry in the Sandbox’s actions map is a tool and
becomes a global async function named by its key. The argument is the tool’s input; the
returned promise resolves to its output:
const sandbox = new Sandbox({ actions: { add, multiply } });
await sandbox.eval({ script: 'const r = await add({ a: 2, b: 3 }); data.sum = r.sum;', data: {},});The calls return real promises, so a script can run independent actions concurrently:
await sandbox.eval({ script: ` const results = await Promise.all([ fetchNote({ id: 'a' }), fetchNote({ id: 'b' }), fetchNote({ id: 'c' }), ]); data.titles = results.map((r) => r.title); `, data: {},});If an action’s execute rejects, the rejection surfaces inside the script as a normal
thrown error the script can try/catch. If it rejects because the script itself throws, that
error propagates out of eval.
The captured console
Section titled “The captured console”The sandbox installs a console whose log, info, warn, error, and debug calls are
captured host-side rather than written to stdout. Each becomes a SandboxConsoleEntry:
type SandboxConsoleEntry = { level: 'log' | 'info' | 'warn' | 'error' | 'debug'; message: string;};Arguments are formatted into a single string per call, in call order:
const { console: output } = await sandbox.eval({ script: "console.log('hello', { a: 1 }); console.error('bad', 2);", data: {},});// output => [// { level: 'log', message: 'hello {"a":1}' },// { level: 'error', message: 'bad 2' },// ]Because data is internal, console is the script’s output channel — the way a run reports
its results.
The result shape
Section titled “The result shape”eval resolves to a SandboxEvalResult:
type SandboxEvalResult = { data: Record<string, unknown>; // the persistent object after the run callLog: (CallLogEntry | null)[]; // the ordered record of the run console: SandboxConsoleEntry[]; // everything logged, in order};Practical notes:
- Read
datafor state,consolefor output. Feed the returneddataback into the nextevalto continue where you left off. - Keep the
callLogif you may need to resume or replay the run — it is the only thing that makes that possible. See Determinism and resumption. - Runs are isolated. Each
evalgets a fresh context; globals a script sets leak into no other run. Onlydata,callLog, andconsolecross the boundary.