Running code
const result = await sandbox.run({
code, // the body of an async function
functions,
scope: {
root: 'sdk', // name of the tree inside the code (default)
values: { files, user }, // cloned in, exposed as `files`, `user`
spread: false, // true: top-level functions also as bare identifiers
},
approval: { mode: 'deny' }, // see Approval
caps: { timeoutMs: 30_000 }, // per-run override of the sandbox caps
signal: controller.signal, // cancel → status "aborted"
onLog: (line) => ui.append(line), // console output, live
onCall: (record) => audit.push(record) // every function call
});result.status is one of five, and every result carries logs, calls
and stats:
switch (result.status) {
case 'ok': result.value; // what the code returned (undefined → null)
case 'error': result.error; // { name, message, line? } — thrown by the code or a SyntaxError
case 'limit': result.limit; result.error.message; // "timeout" | "busy" | "ops" | "output" | "input" | "crash"
case 'aborted': // the signal fired
case 'pending': result.pending; result.resume; // collect mode, see Approval
}
result.logs; // [{ level, text, at }]
result.calls; // [{ seq, fn, input, ok, ms, decision?, error? }]
result.stats; // { ms, opsUsed, logsDropped }Error names you can match: SandboxUnavailable (the frame or the worker
could not start), ApprovalRejected, UnknownFunction, CloneError,
SandboxLimit, ReplayMismatch, BadDecision, InvalidRun.
toToolOutput(result) maps a result to what a chat tool usually returns,
worded for a model:
toToolOutput(result);
// { ok: true, result, logs? }
// { ok: false, error: "TypeError: x is not a function (line 3)" }
// { ok: false, error: "Denied (sandbox_limit): more than 200 function calls in one run" }
// { ok: false, error: "Paused: 3 operations in this code need the user's approval and were NOT run. …" }Runs on one sandbox are queued; create more sandboxes for parallel runs.
With workerPerRun: true (the default) nothing survives from one run to
the next.