@brian-agent/codemode 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +373 -2
  2. package/dist/cloudflare.d.ts +53 -0
  3. package/dist/cloudflare.d.ts.map +1 -0
  4. package/dist/cloudflare.js +64 -0
  5. package/dist/cloudflare.js.map +1 -0
  6. package/dist/declarations.d.ts +43 -0
  7. package/dist/declarations.d.ts.map +1 -0
  8. package/dist/declarations.js +320 -0
  9. package/dist/declarations.js.map +1 -0
  10. package/dist/identifier.d.ts +6 -0
  11. package/dist/identifier.d.ts.map +1 -0
  12. package/dist/identifier.js +13 -0
  13. package/dist/identifier.js.map +1 -0
  14. package/dist/index.d.ts +11 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +10 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/portable.d.ts +15 -0
  19. package/dist/portable.d.ts.map +1 -0
  20. package/dist/portable.js +12 -0
  21. package/dist/portable.js.map +1 -0
  22. package/dist/runtime/execution.d.ts +66 -0
  23. package/dist/runtime/execution.d.ts.map +1 -0
  24. package/dist/runtime/execution.js +494 -0
  25. package/dist/runtime/execution.js.map +1 -0
  26. package/dist/runtime/host.d.ts +13 -0
  27. package/dist/runtime/host.d.ts.map +1 -0
  28. package/dist/runtime/host.js +86 -0
  29. package/dist/runtime/host.js.map +1 -0
  30. package/dist/runtime/inline.d.ts +63 -0
  31. package/dist/runtime/inline.d.ts.map +1 -0
  32. package/dist/runtime/inline.js +80 -0
  33. package/dist/runtime/inline.js.map +1 -0
  34. package/dist/runtime/prelude-source.d.ts +45 -0
  35. package/dist/runtime/prelude-source.d.ts.map +1 -0
  36. package/dist/runtime/prelude-source.js +520 -0
  37. package/dist/runtime/prelude-source.js.map +1 -0
  38. package/dist/runtime/protocol.d.ts +110 -0
  39. package/dist/runtime/protocol.d.ts.map +1 -0
  40. package/dist/runtime/protocol.js +15 -0
  41. package/dist/runtime/protocol.js.map +1 -0
  42. package/dist/runtime/remote.d.ts +102 -0
  43. package/dist/runtime/remote.d.ts.map +1 -0
  44. package/dist/runtime/remote.js +175 -0
  45. package/dist/runtime/remote.js.map +1 -0
  46. package/dist/runtime/vm.d.ts +24 -0
  47. package/dist/runtime/vm.d.ts.map +1 -0
  48. package/dist/runtime/vm.js +293 -0
  49. package/dist/runtime/vm.js.map +1 -0
  50. package/dist/runtime/worker.d.ts +2 -0
  51. package/dist/runtime/worker.d.ts.map +1 -0
  52. package/dist/runtime/worker.js +38 -0
  53. package/dist/runtime/worker.js.map +1 -0
  54. package/dist/source.d.ts +36 -0
  55. package/dist/source.d.ts.map +1 -0
  56. package/dist/source.js +98 -0
  57. package/dist/source.js.map +1 -0
  58. package/dist/types.d.ts +246 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +2 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/wasm.d.ts +12 -0
  63. package/dist/wasm.d.ts.map +1 -0
  64. package/dist/wasm.js +24 -0
  65. package/dist/wasm.js.map +1 -0
  66. package/package.json +75 -5
@@ -0,0 +1,86 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import { loadQuickJSWasm } from "../wasm.js";
3
+ import { CodemodeSandboxBase, errorMessage } from "./execution.js";
4
+ function defaultWorkerUrl() {
5
+ // `.ts` when running from source (tests, tsx), `.js` from the published dist.
6
+ return new URL(import.meta.url.endsWith(".ts") ? "./worker.ts" : "./worker.js", import.meta.url);
7
+ }
8
+ /**
9
+ * Each execution gets its own worker thread and QuickJS VM. A fresh worker per run keeps
10
+ * termination simple: a runaway script, including one that only spins the microtask queue, is
11
+ * killed with `terminate()` and cannot poison a later run. Before terminating, the host sets a
12
+ * shared interrupt flag that the VM polls, because Bun's `terminate()` cannot stop a thread that
13
+ * is spinning in wasm. Stopping requests termination without waiting for exit: some built-ins do
14
+ * not poll the flag, so on Bun a worker can outlive the script's result.
15
+ */
16
+ function workerLauncher(options) {
17
+ return async (data, events) => {
18
+ let module;
19
+ try {
20
+ module = await (options.wasm ?? loadQuickJSWasm());
21
+ }
22
+ catch (error) {
23
+ throw new Error(`Failed to load QuickJS: ${errorMessage(error)}`);
24
+ }
25
+ const interrupt = new SharedArrayBuffer(4);
26
+ const workerData = { ...data, wasm: module, interrupt };
27
+ const releaseWorker = options.acquireWorker?.();
28
+ let worker;
29
+ try {
30
+ worker = new Worker(options.workerUrl ?? defaultWorkerUrl(), { workerData });
31
+ }
32
+ catch (error) {
33
+ releaseWorker?.();
34
+ throw new Error(`Failed to start worker: ${errorMessage(error)}`);
35
+ }
36
+ if (releaseWorker)
37
+ worker.once("exit", releaseWorker);
38
+ worker.on("message", (message) => {
39
+ // Node 24/26 --watch leaks dependency reports onto the worker channel (nodejs/node#65044).
40
+ if (typeof message === "object" &&
41
+ message !== null &&
42
+ ("watch:import" in message || "watch:require" in message))
43
+ return;
44
+ events.message(message);
45
+ });
46
+ worker.on("error", (error) => {
47
+ events.failure({
48
+ kind: "sandbox",
49
+ name: error instanceof Error ? error.name : undefined,
50
+ message: errorMessage(error),
51
+ });
52
+ });
53
+ worker.on("exit", (code) => {
54
+ events.failure({ kind: "sandbox", message: `Worker exited with code ${code} before the script settled` });
55
+ });
56
+ const stop = async () => {
57
+ Atomics.store(new Int32Array(interrupt), 0, 1);
58
+ // A non-interruptible built-in can delay Bun's terminate() until it returns (#10748).
59
+ // Do not hold up the result while that worker exits.
60
+ void worker.terminate().catch(() => undefined);
61
+ // terminate() refs the worker internally; unref it after requesting termination.
62
+ worker.unref();
63
+ };
64
+ try {
65
+ options.onWorkerStarted?.(new Promise((resolve) => worker.once("exit", () => resolve())));
66
+ }
67
+ catch (error) {
68
+ void stop();
69
+ throw error;
70
+ }
71
+ return { post: (message) => worker.postMessage(message), stop };
72
+ };
73
+ }
74
+ /**
75
+ * Runs JavaScript in a QuickJS VM (a separate wasm instance) inside a worker thread. The worker
76
+ * keeps script execution off the host thread: QuickJS runs synchronously, so a spinning script on
77
+ * the host thread would block its event loop. Requires `node:worker_threads` (Node or Bun); see
78
+ * `InlineCodemodeSandbox` and `RemoteCodemodeSandbox` in `@brian-agent/codemode/portable`
79
+ * for other runtimes.
80
+ */
81
+ export class CodemodeSandbox extends CodemodeSandboxBase {
82
+ constructor(options = {}) {
83
+ super(options, workerLauncher(options));
84
+ }
85
+ }
86
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/runtime/host.ts"],"names":[],"mappings":"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","sourcesContent":["import { Worker } from \"node:worker_threads\";\nimport type { CodemodeSandboxOptions } from \"../types.ts\";\nimport { type CodemodeWasmModule, loadQuickJSWasm } from \"../wasm.ts\";\nimport { CodemodeSandboxBase, errorMessage, type VmLauncher } from \"./execution.ts\";\nimport type { WorkerData } from \"./protocol.ts\";\n\nfunction defaultWorkerUrl(): URL {\n\t// `.ts` when running from source (tests, tsx), `.js` from the published dist.\n\treturn new URL(import.meta.url.endsWith(\".ts\") ? \"./worker.ts\" : \"./worker.js\", import.meta.url);\n}\n\n/**\n * Each execution gets its own worker thread and QuickJS VM. A fresh worker per run keeps\n * termination simple: a runaway script, including one that only spins the microtask queue, is\n * killed with `terminate()` and cannot poison a later run. Before terminating, the host sets a\n * shared interrupt flag that the VM polls, because Bun's `terminate()` cannot stop a thread that\n * is spinning in wasm. Stopping requests termination without waiting for exit: some built-ins do\n * not poll the flag, so on Bun a worker can outlive the script's result.\n */\nfunction workerLauncher(options: CodemodeSandboxOptions): VmLauncher {\n\treturn async (data, events) => {\n\t\tlet module: CodemodeWasmModule;\n\t\ttry {\n\t\t\tmodule = await (options.wasm ?? loadQuickJSWasm());\n\t\t} catch (error) {\n\t\t\tthrow new Error(`Failed to load QuickJS: ${errorMessage(error)}`);\n\t\t}\n\t\tconst interrupt = new SharedArrayBuffer(4);\n\t\tconst workerData: WorkerData = { ...data, wasm: module, interrupt };\n\t\tconst releaseWorker = options.acquireWorker?.();\n\t\tlet worker: Worker;\n\t\ttry {\n\t\t\tworker = new Worker(options.workerUrl ?? defaultWorkerUrl(), { workerData });\n\t\t} catch (error) {\n\t\t\treleaseWorker?.();\n\t\t\tthrow new Error(`Failed to start worker: ${errorMessage(error)}`);\n\t\t}\n\t\tif (releaseWorker) worker.once(\"exit\", releaseWorker);\n\t\tworker.on(\"message\", (message: unknown) => {\n\t\t\t// Node 24/26 --watch leaks dependency reports onto the worker channel (nodejs/node#65044).\n\t\t\tif (\n\t\t\t\ttypeof message === \"object\" &&\n\t\t\t\tmessage !== null &&\n\t\t\t\t(\"watch:import\" in message || \"watch:require\" in message)\n\t\t\t)\n\t\t\t\treturn;\n\t\t\tevents.message(message);\n\t\t});\n\t\tworker.on(\"error\", (error: unknown) => {\n\t\t\tevents.failure({\n\t\t\t\tkind: \"sandbox\",\n\t\t\t\tname: error instanceof Error ? error.name : undefined,\n\t\t\t\tmessage: errorMessage(error),\n\t\t\t});\n\t\t});\n\t\tworker.on(\"exit\", (code) => {\n\t\t\tevents.failure({ kind: \"sandbox\", message: `Worker exited with code ${code} before the script settled` });\n\t\t});\n\t\tconst stop = async (): Promise<void> => {\n\t\t\tAtomics.store(new Int32Array(interrupt), 0, 1);\n\t\t\t// A non-interruptible built-in can delay Bun's terminate() until it returns (#10748).\n\t\t\t// Do not hold up the result while that worker exits.\n\t\t\tvoid worker.terminate().catch(() => undefined);\n\t\t\t// terminate() refs the worker internally; unref it after requesting termination.\n\t\t\tworker.unref();\n\t\t};\n\t\ttry {\n\t\t\toptions.onWorkerStarted?.(new Promise<void>((resolve) => worker.once(\"exit\", () => resolve())));\n\t\t} catch (error) {\n\t\t\tvoid stop();\n\t\t\tthrow error;\n\t\t}\n\t\treturn { post: (message) => worker.postMessage(message), stop };\n\t};\n}\n\n/**\n * Runs JavaScript in a QuickJS VM (a separate wasm instance) inside a worker thread. The worker\n * keeps script execution off the host thread: QuickJS runs synchronously, so a spinning script on\n * the host thread would block its event loop. Requires `node:worker_threads` (Node or Bun); see\n * `InlineCodemodeSandbox` and `RemoteCodemodeSandbox` in `@brian-agent/codemode/portable`\n * for other runtimes.\n */\nexport class CodemodeSandbox extends CodemodeSandboxBase {\n\tconstructor(options: CodemodeSandboxOptions = {}) {\n\t\tsuper(options, workerLauncher(options));\n\t}\n}\n"]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Runs the VM on the calling thread, for runtimes without worker threads (Cloudflare Workers and
3
+ * Durable Objects, browsers) and for the server side of `RemoteCodemodeSandbox`.
4
+ *
5
+ * Problem: QuickJS runs synchronously. While a script computes, the host's event loop does not
6
+ * run, so neither the deadline timer nor an abort signal can fire. On Workers even `Date.now()` is
7
+ * frozen during synchronous execution, so the VM cannot check a clock either. Example: with a
8
+ * worker thread, `while (true) {}` is stopped by the host's 200 ms timer; on the calling thread
9
+ * that timer never gets a chance to run.
10
+ *
11
+ * Solution: an interrupt budget. QuickJS polls its interrupt handler periodically while a script
12
+ * runs; this launcher counts the polls across the whole execution and interrupts the script once
13
+ * the count passes the budget. The execution then fails with `kind: "timeout"`. The deadline and
14
+ * abort signal still work whenever the script waits on a tool, since the event loop runs then.
15
+ *
16
+ * The rate of polls depends on the code and the machine. Measured with a tight loop: about 50,000
17
+ * per second of CPU on an Apple M-series laptop and about 10,000 per second on Cloudflare Durable
18
+ * Objects (October 2026). The default of 100,000 allows roughly 2 to 10 seconds of uninterrupted
19
+ * computation; keep it well below the platform's own CPU limit, because hitting that limit resets
20
+ * a Durable Object instead of failing one script.
21
+ *
22
+ * Once the VM reports `done` or `crash`, the result is decided, so the VM is interrupted at the
23
+ * next poll instead of running on until the host stops it. Example: a script that keeps printing in
24
+ * a `for (;;)` loop after its output limit was reached would otherwise run until the budget ran out
25
+ * and be reported as a timeout.
26
+ *
27
+ * Stack: the VM runs on the caller's stack, which is smaller than a worker thread's (about 1 MB
28
+ * on Node's main thread), so QuickJS's stack guard is set to {@link INLINE_MAX_STACK_SIZE}.
29
+ *
30
+ * Reentrancy: VM callbacks run inside QuickJS. Messages from the VM are delivered to the host in
31
+ * a microtask, after the VM has returned, so host code (tool calls, `finish()`, disposal) never
32
+ * runs inside a VM callback and never re-enters the VM.
33
+ */
34
+ import type { CodemodeSandboxBaseOptions } from "../types.ts";
35
+ import type { CodemodeWasmModule } from "../wasm.ts";
36
+ import { CodemodeSandboxBase, type VmLauncher } from "./execution.ts";
37
+ export declare const DEFAULT_INTERRUPT_BUDGET = 100000;
38
+ /** Allows recursion about 1,000 calls deep and trips well before a 1 MB host stack overflows. */
39
+ export declare const INLINE_MAX_STACK_SIZE: number;
40
+ export interface InlineCodemodeSandboxOptions extends CodemodeSandboxBaseOptions {
41
+ /**
42
+ * Compiled `quickjs-wasi/quickjs.wasm`. On Cloudflare, import it with Wrangler's `CompiledWasm`
43
+ * rule (`@brian-agent/codemode/cloudflare` does this); on Node, `loadQuickJSWasm()`.
44
+ */
45
+ wasm: CodemodeWasmModule | Promise<CodemodeWasmModule>;
46
+ /**
47
+ * Interrupt polls an execution may use before it fails with `kind: "timeout"`. The only limit on
48
+ * a script that computes without awaiting a tool. Default: {@link DEFAULT_INTERRUPT_BUDGET}.
49
+ */
50
+ interruptBudget?: number;
51
+ }
52
+ export declare function inlineLauncher(wasm: CodemodeWasmModule | Promise<CodemodeWasmModule>, interruptBudget?: number): VmLauncher;
53
+ /**
54
+ * Runs each execution's QuickJS VM on the calling thread. Works in any runtime with WebAssembly
55
+ * and microtasks; needs no worker threads, files, or Node built-ins. A script that computes
56
+ * blocks the caller's thread until it awaits a tool or uses up its interrupt budget, so prefer
57
+ * `CodemodeSandbox` where worker threads exist, and `RemoteCodemodeSandbox` to keep scripts off a
58
+ * thread that must stay responsive (such as a Durable Object that serves a session).
59
+ */
60
+ export declare class InlineCodemodeSandbox extends CodemodeSandboxBase {
61
+ constructor(options: InlineCodemodeSandboxOptions);
62
+ }
63
+ //# sourceMappingURL=inline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/runtime/inline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAgB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGpF,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAEhD,iGAAiG;AACjG,eAAO,MAAM,qBAAqB,QAAa,CAAC;AAEhD,MAAM,WAAW,4BAA6B,SAAQ,0BAA0B;IAC/E;;;OAGG;IACH,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvD;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,cAAc,CAC7B,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,EACtD,eAAe,GAAE,MAAiC,GAChD,UAAU,CAqDZ;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,mBAAmB;IAC7D,YAAY,OAAO,EAAE,4BAA4B,EAEhD;CACD"}
@@ -0,0 +1,80 @@
1
+ import { CodemodeSandboxBase, errorMessage } from "./execution.js";
2
+ import { startVm } from "./vm.js";
3
+ export const DEFAULT_INTERRUPT_BUDGET = 100_000;
4
+ /** Allows recursion about 1,000 calls deep and trips well before a 1 MB host stack overflows. */
5
+ export const INLINE_MAX_STACK_SIZE = 256 * 1024;
6
+ export function inlineLauncher(wasm, interruptBudget = DEFAULT_INTERRUPT_BUDGET) {
7
+ return async (data, events) => {
8
+ let module;
9
+ try {
10
+ module = await wasm;
11
+ }
12
+ catch (error) {
13
+ throw new Error(`Failed to load QuickJS: ${errorMessage(error)}`);
14
+ }
15
+ let stopped = false;
16
+ let settled = false;
17
+ let polls = 0;
18
+ let exhausted = false;
19
+ const session = await startVm({
20
+ data,
21
+ wasm: module,
22
+ maxStackSize: INLINE_MAX_STACK_SIZE,
23
+ interruptBudget,
24
+ shouldInterrupt: () => {
25
+ if (stopped || settled || exhausted)
26
+ return true;
27
+ if (++polls <= interruptBudget)
28
+ return false;
29
+ exhausted = true;
30
+ return true;
31
+ },
32
+ post: (message) => {
33
+ const terminal = message.type === "done" || message.type === "crash";
34
+ // Only the first terminal message counts: a crash caused by interrupting a settled VM is noise.
35
+ if (terminal && settled)
36
+ return;
37
+ if (terminal)
38
+ settled = true;
39
+ queueMicrotask(() => {
40
+ if (stopped)
41
+ return;
42
+ // The interrupt surfaces as a crash or a failed done; report what caused it instead.
43
+ if (exhausted && (message.type === "done" || message.type === "crash")) {
44
+ events.failure({
45
+ kind: "timeout",
46
+ message: `Execution exceeded its interrupt budget of ${interruptBudget}`,
47
+ });
48
+ return;
49
+ }
50
+ events.message(message);
51
+ });
52
+ },
53
+ });
54
+ return {
55
+ post: (message) => {
56
+ if (!stopped)
57
+ session.deliver(message);
58
+ },
59
+ stop: async () => {
60
+ if (stopped)
61
+ return;
62
+ stopped = true;
63
+ session.dispose();
64
+ },
65
+ };
66
+ };
67
+ }
68
+ /**
69
+ * Runs each execution's QuickJS VM on the calling thread. Works in any runtime with WebAssembly
70
+ * and microtasks; needs no worker threads, files, or Node built-ins. A script that computes
71
+ * blocks the caller's thread until it awaits a tool or uses up its interrupt budget, so prefer
72
+ * `CodemodeSandbox` where worker threads exist, and `RemoteCodemodeSandbox` to keep scripts off a
73
+ * thread that must stay responsive (such as a Durable Object that serves a session).
74
+ */
75
+ export class InlineCodemodeSandbox extends CodemodeSandboxBase {
76
+ constructor(options) {
77
+ super(options, inlineLauncher(options.wasm, options.interruptBudget));
78
+ }
79
+ }
80
+ //# sourceMappingURL=inline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline.js","sourceRoot":"","sources":["../../src/runtime/inline.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAmB,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAC;AAEhD,iGAAiG;AACjG,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,GAAG,IAAI,CAAC;AAehD,MAAM,UAAU,cAAc,CAC7B,IAAsD,EACtD,eAAe,GAAW,wBAAwB;IAElD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;QAC7B,IAAI,MAA0B,CAAC;QAC/B,IAAI,CAAC;YACJ,MAAM,GAAG,MAAM,IAAI,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,2BAA2B,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;YAC7B,IAAI;YACJ,IAAI,EAAE,MAAM;YACZ,YAAY,EAAE,qBAAqB;YACnC,eAAe;YACf,eAAe,EAAE,GAAG,EAAE;gBACrB,IAAI,OAAO,IAAI,OAAO,IAAI,SAAS;oBAAE,OAAO,IAAI,CAAC;gBACjD,IAAI,EAAE,KAAK,IAAI,eAAe;oBAAE,OAAO,KAAK,CAAC;gBAC7C,SAAS,GAAG,IAAI,CAAC;gBACjB,OAAO,IAAI,CAAC;YACb,CAAC;YACD,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE;gBACjB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC;gBACrE,gGAAgG;gBAChG,IAAI,QAAQ,IAAI,OAAO;oBAAE,OAAO;gBAChC,IAAI,QAAQ;oBAAE,OAAO,GAAG,IAAI,CAAC;gBAC7B,cAAc,CAAC,GAAG,EAAE;oBACnB,IAAI,OAAO;wBAAE,OAAO;oBACpB,qFAAqF;oBACrF,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC;wBACxE,MAAM,CAAC,OAAO,CAAC;4BACd,IAAI,EAAE,SAAS;4BACf,OAAO,EAAE,8CAA8C,eAAe,EAAE;yBACxE,CAAC,CAAC;wBACH,OAAO;oBACR,CAAC;oBACD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACzB,CAAC,CAAC,CAAC;YACJ,CAAC;SACD,CAAC,CAAC;QACH,OAAO;YACN,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE;gBACjB,IAAI,CAAC,OAAO;oBAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACxC,CAAC;YACD,IAAI,EAAE,KAAK,IAAI,EAAE;gBAChB,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;SACD,CAAC;IACH,CAAC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,qBAAsB,SAAQ,mBAAmB;IAC7D,YAAY,OAAqC;QAChD,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;IACvE,CAAC;CACD","sourcesContent":["/**\n * Runs the VM on the calling thread, for runtimes without worker threads (Cloudflare Workers and\n * Durable Objects, browsers) and for the server side of `RemoteCodemodeSandbox`.\n *\n * Problem: QuickJS runs synchronously. While a script computes, the host's event loop does not\n * run, so neither the deadline timer nor an abort signal can fire. On Workers even `Date.now()` is\n * frozen during synchronous execution, so the VM cannot check a clock either. Example: with a\n * worker thread, `while (true) {}` is stopped by the host's 200 ms timer; on the calling thread\n * that timer never gets a chance to run.\n *\n * Solution: an interrupt budget. QuickJS polls its interrupt handler periodically while a script\n * runs; this launcher counts the polls across the whole execution and interrupts the script once\n * the count passes the budget. The execution then fails with `kind: \"timeout\"`. The deadline and\n * abort signal still work whenever the script waits on a tool, since the event loop runs then.\n *\n * The rate of polls depends on the code and the machine. Measured with a tight loop: about 50,000\n * per second of CPU on an Apple M-series laptop and about 10,000 per second on Cloudflare Durable\n * Objects (October 2026). The default of 100,000 allows roughly 2 to 10 seconds of uninterrupted\n * computation; keep it well below the platform's own CPU limit, because hitting that limit resets\n * a Durable Object instead of failing one script.\n *\n * Once the VM reports `done` or `crash`, the result is decided, so the VM is interrupted at the\n * next poll instead of running on until the host stops it. Example: a script that keeps printing in\n * a `for (;;)` loop after its output limit was reached would otherwise run until the budget ran out\n * and be reported as a timeout.\n *\n * Stack: the VM runs on the caller's stack, which is smaller than a worker thread's (about 1 MB\n * on Node's main thread), so QuickJS's stack guard is set to {@link INLINE_MAX_STACK_SIZE}.\n *\n * Reentrancy: VM callbacks run inside QuickJS. Messages from the VM are delivered to the host in\n * a microtask, after the VM has returned, so host code (tool calls, `finish()`, disposal) never\n * runs inside a VM callback and never re-enters the VM.\n */\nimport type { CodemodeSandboxBaseOptions } from \"../types.ts\";\nimport type { CodemodeWasmModule } from \"../wasm.ts\";\nimport { CodemodeSandboxBase, errorMessage, type VmLauncher } from \"./execution.ts\";\nimport { startVm } from \"./vm.ts\";\n\nexport const DEFAULT_INTERRUPT_BUDGET = 100_000;\n\n/** Allows recursion about 1,000 calls deep and trips well before a 1 MB host stack overflows. */\nexport const INLINE_MAX_STACK_SIZE = 256 * 1024;\n\nexport interface InlineCodemodeSandboxOptions extends CodemodeSandboxBaseOptions {\n\t/**\n\t * Compiled `quickjs-wasi/quickjs.wasm`. On Cloudflare, import it with Wrangler's `CompiledWasm`\n\t * rule (`@brian-agent/codemode/cloudflare` does this); on Node, `loadQuickJSWasm()`.\n\t */\n\twasm: CodemodeWasmModule | Promise<CodemodeWasmModule>;\n\t/**\n\t * Interrupt polls an execution may use before it fails with `kind: \"timeout\"`. The only limit on\n\t * a script that computes without awaiting a tool. Default: {@link DEFAULT_INTERRUPT_BUDGET}.\n\t */\n\tinterruptBudget?: number;\n}\n\nexport function inlineLauncher(\n\twasm: CodemodeWasmModule | Promise<CodemodeWasmModule>,\n\tinterruptBudget: number = DEFAULT_INTERRUPT_BUDGET,\n): VmLauncher {\n\treturn async (data, events) => {\n\t\tlet module: CodemodeWasmModule;\n\t\ttry {\n\t\t\tmodule = await wasm;\n\t\t} catch (error) {\n\t\t\tthrow new Error(`Failed to load QuickJS: ${errorMessage(error)}`);\n\t\t}\n\t\tlet stopped = false;\n\t\tlet settled = false;\n\t\tlet polls = 0;\n\t\tlet exhausted = false;\n\t\tconst session = await startVm({\n\t\t\tdata,\n\t\t\twasm: module,\n\t\t\tmaxStackSize: INLINE_MAX_STACK_SIZE,\n\t\t\tinterruptBudget,\n\t\t\tshouldInterrupt: () => {\n\t\t\t\tif (stopped || settled || exhausted) return true;\n\t\t\t\tif (++polls <= interruptBudget) return false;\n\t\t\t\texhausted = true;\n\t\t\t\treturn true;\n\t\t\t},\n\t\t\tpost: (message) => {\n\t\t\t\tconst terminal = message.type === \"done\" || message.type === \"crash\";\n\t\t\t\t// Only the first terminal message counts: a crash caused by interrupting a settled VM is noise.\n\t\t\t\tif (terminal && settled) return;\n\t\t\t\tif (terminal) settled = true;\n\t\t\t\tqueueMicrotask(() => {\n\t\t\t\t\tif (stopped) return;\n\t\t\t\t\t// The interrupt surfaces as a crash or a failed done; report what caused it instead.\n\t\t\t\t\tif (exhausted && (message.type === \"done\" || message.type === \"crash\")) {\n\t\t\t\t\t\tevents.failure({\n\t\t\t\t\t\t\tkind: \"timeout\",\n\t\t\t\t\t\t\tmessage: `Execution exceeded its interrupt budget of ${interruptBudget}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tevents.message(message);\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t\treturn {\n\t\t\tpost: (message) => {\n\t\t\t\tif (!stopped) session.deliver(message);\n\t\t\t},\n\t\t\tstop: async () => {\n\t\t\t\tif (stopped) return;\n\t\t\t\tstopped = true;\n\t\t\t\tsession.dispose();\n\t\t\t},\n\t\t};\n\t};\n}\n\n/**\n * Runs each execution's QuickJS VM on the calling thread. Works in any runtime with WebAssembly\n * and microtasks; needs no worker threads, files, or Node built-ins. A script that computes\n * blocks the caller's thread until it awaits a tool or uses up its interrupt budget, so prefer\n * `CodemodeSandbox` where worker threads exist, and `RemoteCodemodeSandbox` to keep scripts off a\n * thread that must stay responsive (such as a Durable Object that serves a session).\n */\nexport class InlineCodemodeSandbox extends CodemodeSandboxBase {\n\tconstructor(options: InlineCodemodeSandboxOptions) {\n\t\tsuper(options, inlineLauncher(options.wasm, options.interruptBudget));\n\t}\n}\n"]}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * JavaScript evaluated inside the QuickJS VM before the script runs.
3
+ *
4
+ * The VM is its own wasm instance, so nothing here guards a realm boundary.
5
+ * The prelude keeps the host bridge in a closure so the script cannot call it
6
+ * directly, and builds `tools`, `ALL_TOOLS`, the output helpers (`text`, `image`,
7
+ * `exit`, `console`), and globals on top of it. Tool arguments and results cross
8
+ * as JSON strings and are parsed on this side.
9
+ *
10
+ * Output helpers: `text(value)` appends a text item (non-strings are
11
+ * JSON-stringified), `image(urlOrItem)` appends an image from a base64 `data:` URL, an
12
+ * `{ image_url }` object, or an image block `{ type: "image", data, mimeType }`, and `exit()` ends the script
13
+ * successfully. `console.*` appends text items marked as console output.
14
+ *
15
+ * `store(key, value)` and `load(key)` are synchronous: they work on a snapshot of
16
+ * JSON text passed in as `storeJson`, and the keys the script wrote are reported
17
+ * with a successful "done".
18
+ *
19
+ * Evaluates to a function `(bridge, toolsJson, globalsJson, storeJson) => { settle, run, stalled }`.
20
+ * `settle(id, ok, payload, propertiesJson)` settles call `id`; a rejection's `Error` gets the own properties of the
21
+ * `propertiesJson` object, except `name`, `message`, and `stack`.
22
+ * `toolsJson` lists `{ name, jsName, description }`: `tools[jsName]` and `tools[name]` call the
23
+ * tool, and `ALL_TOOLS` lists `{ name: jsName, description }`.
24
+ * `stalled()` reports a script that has not finished while no host call is pending: with no timers
25
+ * or I/O in the VM, nothing can ever resume it.
26
+ * `globalsJson` lists `{ name, spread }`; `a.b` names are grouped into a frozen `a` object.
27
+ * `bridge(kind, a, b, c)` with kind "call" or "global" (id, name, argsJson),
28
+ * "output" ("text" or "console", text) or ("image", data, mimeType), or "done" (ok, valueJsonOrErrorJson, writesJson).
29
+ *
30
+ * Before anything else the prelude freezes the built-ins and makes the built-in globals read-only.
31
+ * The prelude shares them with the script, so a script that patched one (for example
32
+ * `Array.prototype.toJSON`) could otherwise corrupt what the prelude reports to the host.
33
+ */
34
+ export declare const MAX_STORE_VALUE_CHARS: number;
35
+ export declare const MAX_STORE_TOTAL_CHARS: number;
36
+ /**
37
+ * Output one script may produce with `text()`, `image()`, and `console.*`: characters of text and
38
+ * base64 image data, and items. The host keeps all output until the script ends, so without a
39
+ * limit a script that prints in a loop grows the host's memory until it crashes. The item limit
40
+ * covers loops that print empty strings.
41
+ */
42
+ export declare const MAX_OUTPUT_CHARS: number;
43
+ export declare const MAX_OUTPUT_ITEMS = 100000;
44
+ export declare const PRELUDE_SOURCE: string;
45
+ //# sourceMappingURL=prelude-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prelude-source.d.ts","sourceRoot":"","sources":["../../src/runtime/prelude-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,qBAAqB,QAAa,CAAC;AAChD,eAAO,MAAM,qBAAqB,QAAc,CAAC;AACjD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAmB,CAAC;AACjD,eAAO,MAAM,gBAAgB,SAAU,CAAC;AAKxC,eAAO,MAAM,cAAc,EAAE,MA0d1B,CAAC"}