@fractaal/pi-codemode 0.86.9 → 1.4.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 (55) hide show
  1. package/README.md +196 -12
  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 +6 -21
  7. package/dist/declarations.d.ts.map +1 -1
  8. package/dist/declarations.js +113 -127
  9. package/dist/declarations.js.map +1 -1
  10. package/dist/index.d.ts +5 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/portable.d.ts +15 -0
  15. package/dist/portable.d.ts.map +1 -0
  16. package/dist/portable.js +12 -0
  17. package/dist/portable.js.map +1 -0
  18. package/dist/runtime/execution.d.ts +66 -0
  19. package/dist/runtime/execution.d.ts.map +1 -0
  20. package/dist/runtime/execution.js +494 -0
  21. package/dist/runtime/execution.js.map +1 -0
  22. package/dist/runtime/host.d.ts +8 -30
  23. package/dist/runtime/host.d.ts.map +1 -1
  24. package/dist/runtime/host.js +50 -332
  25. package/dist/runtime/host.js.map +1 -1
  26. package/dist/runtime/inline.d.ts +63 -0
  27. package/dist/runtime/inline.d.ts.map +1 -0
  28. package/dist/runtime/inline.js +80 -0
  29. package/dist/runtime/inline.js.map +1 -0
  30. package/dist/runtime/prelude-source.d.ts +5 -3
  31. package/dist/runtime/prelude-source.d.ts.map +1 -1
  32. package/dist/runtime/prelude-source.js +19 -9
  33. package/dist/runtime/prelude-source.js.map +1 -1
  34. package/dist/runtime/protocol.d.ts +47 -6
  35. package/dist/runtime/protocol.d.ts.map +1 -1
  36. package/dist/runtime/protocol.js +6 -1
  37. package/dist/runtime/protocol.js.map +1 -1
  38. package/dist/runtime/remote.d.ts +102 -0
  39. package/dist/runtime/remote.d.ts.map +1 -0
  40. package/dist/runtime/remote.js +175 -0
  41. package/dist/runtime/remote.js.map +1 -0
  42. package/dist/runtime/vm.d.ts +24 -0
  43. package/dist/runtime/vm.d.ts.map +1 -0
  44. package/dist/runtime/vm.js +293 -0
  45. package/dist/runtime/vm.js.map +1 -0
  46. package/dist/runtime/worker.js +11 -110
  47. package/dist/runtime/worker.js.map +1 -1
  48. package/dist/source.d.ts +2 -1
  49. package/dist/source.d.ts.map +1 -1
  50. package/dist/source.js +8 -2
  51. package/dist/source.js.map +1 -1
  52. package/dist/types.d.ts +115 -6
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js.map +1 -1
  55. package/package.json +16 -4
@@ -1,368 +1,86 @@
1
1
  import { Worker } from "node:worker_threads";
2
- import { toCodemodeIdentifier } from "../identifier.js";
3
2
  import { loadQuickJSWasm } from "../wasm.js";
4
- import { isWorkerToHostMessage, } from "./protocol.js";
5
- const DEFAULT_TIMEOUT_MS = 300_000;
6
- const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
7
- const RESERVED_GLOBALS = new Set([
8
- "tools",
9
- "ALL_TOOLS",
10
- "console",
11
- "text",
12
- "image",
13
- "exit",
14
- "globalThis",
15
- "store",
16
- "load",
17
- ]);
18
- function errorMessage(error) {
19
- return error instanceof Error ? error.message : String(error);
20
- }
21
- function serializeStore(store) {
22
- const serialized = {};
23
- for (const [key, value] of Object.entries(store ?? {})) {
24
- const json = JSON.stringify(value);
25
- if (json !== undefined)
26
- serialized[key] = json;
27
- }
28
- return serialized;
29
- }
30
- /**
31
- * The prelude that serializes values for the host runs in the same VM as the script, so a script
32
- * that patches built-ins (for example `Array.prototype.toJSON`) can make it send malformed data.
33
- * Payloads that do not decode throw this error, and the execution fails as a sandbox error.
34
- */
35
- class BridgeError extends Error {
36
- }
37
- function parseBridgeJson(json, what) {
38
- try {
39
- return JSON.parse(json);
40
- }
41
- catch {
42
- throw new BridgeError(`${what} is not valid JSON`);
43
- }
44
- }
45
- function parseStoreWrites(json) {
46
- const entries = parseBridgeJson(json, "store writes");
47
- if (!Array.isArray(entries))
48
- throw new BridgeError("store writes are not an array");
49
- const writes = { set: {}, delete: [] };
50
- for (const entry of entries) {
51
- if (!Array.isArray(entry) ||
52
- typeof entry[0] !== "string" ||
53
- (entry.length === 2 ? typeof entry[1] !== "string" : entry.length !== 1)) {
54
- throw new BridgeError("store writes contain a malformed entry");
55
- }
56
- const [key, value] = entry;
57
- if (value === undefined)
58
- writes.delete.push(key);
59
- else
60
- writes.set[key] = parseBridgeJson(value, `store value for ${JSON.stringify(key)}`);
61
- }
62
- return writes;
63
- }
64
- function parseScriptError(json) {
65
- const parsed = parseBridgeJson(json, "script error");
66
- if (typeof parsed !== "object" || parsed === null)
67
- throw new BridgeError("script error is not an object");
68
- const { name, message, stack } = parsed;
69
- if (typeof message !== "string" ||
70
- (name !== undefined && typeof name !== "string") ||
71
- (stack !== undefined && typeof stack !== "string")) {
72
- throw new BridgeError("script error is malformed");
73
- }
74
- return { kind: "script", name, message, stack };
75
- }
3
+ import { CodemodeSandboxBase, errorMessage } from "./execution.js";
76
4
  function defaultWorkerUrl() {
77
5
  // `.ts` when running from source (tests, tsx), `.js` from the published dist.
78
6
  return new URL(import.meta.url.endsWith(".ts") ? "./worker.ts" : "./worker.js", import.meta.url);
79
7
  }
80
8
  /**
81
- * One script run in its own worker and QuickJS VM. A fresh worker per run keeps
82
- * termination simple: a runaway script, including one that only spins the
83
- * microtask queue, is killed with `terminate()` and cannot poison a later run.
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.
84
15
  */
85
- class Execution {
86
- promise;
87
- resolveResult;
88
- worker;
89
- interrupt = new SharedArrayBuffer(4);
90
- tools;
91
- globals;
92
- signal;
93
- timer;
94
- output = [];
95
- calls = [];
96
- pending = new Map();
97
- finished = false;
98
- constructor(options) {
99
- this.promise = new Promise((resolve) => {
100
- this.resolveResult = resolve;
101
- });
102
- this.tools = options.tools;
103
- this.globals = options.globals;
104
- this.signal = options.signal;
105
- if (Number.isFinite(options.timeoutMs)) {
106
- this.timer = setTimeout(() => {
107
- this.finish({ kind: "timeout", message: `Execution timed out after ${options.timeoutMs} ms` });
108
- }, options.timeoutMs);
16
+ function workerLauncher(options) {
17
+ return async (data, events) => {
18
+ let module;
19
+ try {
20
+ module = await (options.wasm ?? loadQuickJSWasm());
109
21
  }
110
- if (options.signal) {
111
- if (options.signal.aborted) {
112
- this.onAbort();
113
- }
114
- else {
115
- options.signal.addEventListener("abort", this.onAbort, { once: true });
116
- }
22
+ catch (error) {
23
+ throw new Error(`Failed to load QuickJS: ${errorMessage(error)}`);
117
24
  }
118
- options.wasm.then((wasm) => this.start(options, wasm), (error) => {
119
- this.finish({ kind: "sandbox", message: `Failed to load QuickJS: ${errorMessage(error)}` });
120
- });
121
- }
122
- abort(message) {
123
- this.finish({ kind: "aborted", message });
124
- return this.promise;
125
- }
126
- start(options, wasm) {
127
- if (this.finished)
128
- return;
129
- const workerData = {
130
- code: options.code,
131
- tools: [...options.tools.values()].map((tool) => ({
132
- name: tool.name,
133
- jsName: toCodemodeIdentifier(tool.name),
134
- description: tool.description ?? "",
135
- })),
136
- globals: [...options.globals.values()].map((global) => ({
137
- name: global.name,
138
- spread: global.spread === true,
139
- })),
140
- wasm,
141
- memoryLimitBytes: options.memoryLimitBytes,
142
- store: options.store,
143
- interrupt: this.interrupt,
144
- };
25
+ const interrupt = new SharedArrayBuffer(4);
26
+ const workerData = { ...data, wasm: module, interrupt };
27
+ const releaseWorker = options.acquireWorker?.();
145
28
  let worker;
146
29
  try {
147
- worker = new Worker(options.workerUrl, { workerData });
30
+ worker = new Worker(options.workerUrl ?? defaultWorkerUrl(), { workerData });
148
31
  }
149
32
  catch (error) {
150
- this.finish({ kind: "sandbox", message: `Failed to start worker: ${errorMessage(error)}` });
151
- return;
33
+ releaseWorker?.();
34
+ throw new Error(`Failed to start worker: ${errorMessage(error)}`);
152
35
  }
153
- this.worker = worker;
36
+ if (releaseWorker)
37
+ worker.once("exit", releaseWorker);
154
38
  worker.on("message", (message) => {
155
- // An exception here would be uncaught in the host process and leave the execution unsettled.
156
- try {
157
- this.handleMessage(message);
158
- }
159
- catch (error) {
160
- this.finish({
161
- kind: "sandbox",
162
- message: error instanceof BridgeError
163
- ? `Sandbox bridge broken: ${error.message}. The script may have modified built-ins such as a prototype's toJSON.`
164
- : `Sandbox host failed: ${errorMessage(error)}`,
165
- });
166
- }
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);
167
45
  });
168
46
  worker.on("error", (error) => {
169
- this.finish({
47
+ events.failure({
170
48
  kind: "sandbox",
171
49
  name: error instanceof Error ? error.name : undefined,
172
50
  message: errorMessage(error),
173
51
  });
174
52
  });
175
53
  worker.on("exit", (code) => {
176
- this.finish({ kind: "sandbox", message: `Worker exited with code ${code} before the script settled` });
54
+ events.failure({ kind: "sandbox", message: `Worker exited with code ${code} before the script settled` });
177
55
  });
178
- }
179
- onAbort = () => {
180
- const reason = this.signal?.reason;
181
- this.finish({ kind: "aborted", message: reason instanceof Error ? reason.message : "Execution aborted" });
182
- };
183
- post(message) {
184
- this.worker?.postMessage(message);
185
- }
186
- handleMessage(message) {
187
- if (this.finished)
188
- return;
189
- if (!isWorkerToHostMessage(message))
190
- throw new BridgeError("unknown message from the worker");
191
- switch (message.type) {
192
- case "output":
193
- this.output.push(message.item);
194
- break;
195
- case "call":
196
- if (this.pending.has(message.id))
197
- throw new BridgeError(`duplicate call id ${message.id}`);
198
- void this.handleCall(message);
199
- break;
200
- case "done":
201
- this.handleDone(message);
202
- break;
203
- case "crash":
204
- this.finish({ kind: "sandbox", message: message.message });
205
- break;
206
- }
207
- }
208
- handleDone(message) {
209
- // Decode everything before finish(), which must not throw once it starts.
210
- if (!message.ok) {
211
- this.finish(parseScriptError(message.error));
212
- return;
213
- }
214
- const value = message.value === undefined ? undefined : parseBridgeJson(message.value, "return value");
215
- this.finish(undefined, value, parseStoreWrites(message.writes));
216
- }
217
- async handleCall(message) {
218
- const { id, name } = message;
219
- const isTool = message.target === "tool";
220
- const record = isTool ? { name, status: "cancelled", durationMs: 0 } : undefined;
221
- if (record)
222
- this.calls.push(record);
223
- const pending = { record, startedAt: performance.now(), controller: new AbortController() };
224
- this.pending.set(id, pending);
225
- let status;
226
- let reply;
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
+ };
227
64
  try {
228
- const tool = (isTool ? this.tools : this.globals).get(name);
229
- if (!tool)
230
- throw new Error(`Unknown ${isTool ? "tool" : "global"} "${name}"`);
231
- const args = message.args === undefined ? undefined : JSON.parse(message.args);
232
- const value = await tool.execute(args, { signal: pending.controller.signal });
233
- reply = { type: "result", id, ok: true, payload: value === undefined ? undefined : JSON.stringify(value) };
234
- status = "ok";
65
+ options.onWorkerStarted?.(new Promise((resolve) => worker.once("exit", () => resolve())));
235
66
  }
236
67
  catch (error) {
237
- reply = { type: "result", id, ok: false, payload: errorMessage(error) };
238
- status = "error";
239
- }
240
- // Already cancelled by finish(): the record keeps "cancelled" and the
241
- // worker is gone or going.
242
- if (!this.pending.delete(id))
243
- return;
244
- if (record) {
245
- record.status = status;
246
- record.durationMs = performance.now() - pending.startedAt;
247
- }
248
- this.post(reply);
249
- }
250
- finish(error, value, writes) {
251
- if (this.finished)
252
- return;
253
- this.finished = true;
254
- clearTimeout(this.timer);
255
- this.signal?.removeEventListener("abort", this.onAbort);
256
- const now = performance.now();
257
- for (const pending of this.pending.values()) {
258
- if (pending.record)
259
- pending.record.durationMs = now - pending.startedAt;
260
- pending.controller.abort();
261
- }
262
- this.pending.clear();
263
- const result = error
264
- ? { ok: false, error, output: this.output, calls: this.calls }
265
- : {
266
- ok: true,
267
- value,
268
- output: this.output,
269
- calls: this.calls,
270
- storeWrites: writes ?? { set: {}, delete: [] },
271
- };
272
- if (!this.worker) {
273
- this.resolveResult(result);
274
- return;
68
+ void stop();
69
+ throw error;
275
70
  }
276
- Atomics.store(new Int32Array(this.interrupt), 0, 1);
277
- this.worker
278
- .terminate()
279
- .catch(() => undefined)
280
- .then(() => this.resolveResult(result));
281
- }
71
+ return { post: (message) => worker.postMessage(message), stop };
72
+ };
282
73
  }
283
74
  /**
284
- * Runs JavaScript in a QuickJS VM (a separate wasm instance) inside a worker
285
- * thread. The script sees `tools.<name>(args)` for every registered tool, `ALL_TOOLS`,
286
- * the output helpers `text`, `image`, `exit`, and `console.*`, `store`/`load`, and the
287
- * configured globals; nothing else (no timers, `fetch`, `process`, `require`, modules).
288
- *
289
- * Each `execute()` gets its own worker and VM; the sandbox only holds the tool
290
- * table and defaults. `close()` aborts in-flight executions.
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 `@earendil-works/pi-codemode/portable`
79
+ * for other runtimes.
291
80
  */
292
- export class CodemodeSandbox {
293
- toolsByName = new Map();
294
- globalsByName = new Map();
295
- timeoutMs;
296
- memoryLimitBytes;
297
- wasm;
298
- workerUrl;
299
- running = new Set();
300
- closed = false;
81
+ export class CodemodeSandbox extends CodemodeSandboxBase {
301
82
  constructor(options = {}) {
302
- this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
303
- this.memoryLimitBytes = options.memoryLimitBytes;
304
- this.wasm = options.wasm;
305
- this.workerUrl = options.workerUrl ?? defaultWorkerUrl();
306
- for (const tool of options.tools ?? [])
307
- this.registerTool(tool);
308
- const namespaces = new Set();
309
- for (const global of options.globals ?? []) {
310
- const parts = global.name.split(".");
311
- if (parts.length > 2 || !parts.every((part) => IDENTIFIER.test(part)) || RESERVED_GLOBALS.has(parts[0])) {
312
- throw new Error(`Invalid global name "${global.name}"`);
313
- }
314
- if (this.globalsByName.has(global.name))
315
- throw new Error(`Global "${global.name}" is already registered`);
316
- if (parts.length === 2)
317
- namespaces.add(parts[0]);
318
- this.globalsByName.set(global.name, global);
319
- }
320
- for (const name of namespaces) {
321
- if (this.globalsByName.has(name))
322
- throw new Error(`Global "${name}" conflicts with the namespace "${name}"`);
323
- }
324
- }
325
- /** Throws if a tool with the same name is already registered. */
326
- registerTool(tool) {
327
- if (this.toolsByName.has(tool.name))
328
- throw new Error(`Tool "${tool.name}" is already registered`);
329
- this.toolsByName.set(tool.name, tool);
330
- }
331
- unregisterTool(name) {
332
- return this.toolsByName.delete(name);
333
- }
334
- get tools() {
335
- return [...this.toolsByName.values()];
336
- }
337
- get globals() {
338
- return [...this.globalsByName.values()];
339
- }
340
- /**
341
- * `code` is an async function body: `return` and top-level `await` work.
342
- * Never rejects for script failures; those come back as `{ ok: false }`.
343
- * The script can use `store(key, value)` and `load(key)` on `options.store`.
344
- */
345
- execute(code, options = {}) {
346
- if (this.closed)
347
- return Promise.reject(new Error("Sandbox is closed"));
348
- const execution = new Execution({
349
- code,
350
- tools: new Map(this.toolsByName),
351
- globals: this.globalsByName,
352
- timeoutMs: options.timeoutMs ?? this.timeoutMs,
353
- signal: options.signal,
354
- memoryLimitBytes: this.memoryLimitBytes,
355
- store: serializeStore(options.store),
356
- wasm: this.wasm === undefined ? loadQuickJSWasm() : Promise.resolve(this.wasm),
357
- workerUrl: this.workerUrl,
358
- });
359
- this.running.add(execution);
360
- return execution.promise.finally(() => this.running.delete(execution));
361
- }
362
- /** Aborts in-flight executions (they resolve with `kind: "aborted"`) and rejects new ones. */
363
- async close() {
364
- this.closed = true;
365
- await Promise.all([...this.running].map((execution) => execution.abort("Sandbox closed")));
83
+ super(options, workerLauncher(options));
366
84
  }
367
85
  }
368
86
  //# sourceMappingURL=host.js.map
@@ -1 +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 { toCodemodeIdentifier } from \"../identifier.ts\";\nimport type {\n\tCodemodeCall,\n\tCodemodeCallStatus,\n\tCodemodeError,\n\tCodemodeExecuteOptions,\n\tCodemodeOutputItem,\n\tCodemodeResult,\n\tCodemodeSandboxOptions,\n\tCodemodeStoreWrites,\n\tCodemodeTool,\n} from \"../types.ts\";\nimport { type CodemodeWasmModule, loadQuickJSWasm } from \"../wasm.ts\";\nimport {\n\ttype HostToWorkerMessage,\n\tisWorkerToHostMessage,\n\ttype WorkerData,\n\ttype WorkerToHostMessage,\n} from \"./protocol.ts\";\n\nconst DEFAULT_TIMEOUT_MS = 300_000;\nconst IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst RESERVED_GLOBALS: ReadonlySet<string> = new Set([\n\t\"tools\",\n\t\"ALL_TOOLS\",\n\t\"console\",\n\t\"text\",\n\t\"image\",\n\t\"exit\",\n\t\"globalThis\",\n\t\"store\",\n\t\"load\",\n]);\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction serializeStore(store: Readonly<Record<string, unknown>> | undefined): Record<string, string> {\n\tconst serialized: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(store ?? {})) {\n\t\tconst json = JSON.stringify(value);\n\t\tif (json !== undefined) serialized[key] = json;\n\t}\n\treturn serialized;\n}\n\n/**\n * The prelude that serializes values for the host runs in the same VM as the script, so a script\n * that patches built-ins (for example `Array.prototype.toJSON`) can make it send malformed data.\n * Payloads that do not decode throw this error, and the execution fails as a sandbox error.\n */\nclass BridgeError extends Error {}\n\nfunction parseBridgeJson(json: string, what: string): unknown {\n\ttry {\n\t\treturn JSON.parse(json);\n\t} catch {\n\t\tthrow new BridgeError(`${what} is not valid JSON`);\n\t}\n}\n\nfunction parseStoreWrites(json: string): CodemodeStoreWrites {\n\tconst entries = parseBridgeJson(json, \"store writes\");\n\tif (!Array.isArray(entries)) throw new BridgeError(\"store writes are not an array\");\n\tconst writes: CodemodeStoreWrites = { set: {}, delete: [] };\n\tfor (const entry of entries) {\n\t\tif (\n\t\t\t!Array.isArray(entry) ||\n\t\t\ttypeof entry[0] !== \"string\" ||\n\t\t\t(entry.length === 2 ? typeof entry[1] !== \"string\" : entry.length !== 1)\n\t\t) {\n\t\t\tthrow new BridgeError(\"store writes contain a malformed entry\");\n\t\t}\n\t\tconst [key, value] = entry as [string, string?];\n\t\tif (value === undefined) writes.delete.push(key);\n\t\telse writes.set[key] = parseBridgeJson(value, `store value for ${JSON.stringify(key)}`);\n\t}\n\treturn writes;\n}\n\nfunction parseScriptError(json: string): CodemodeError {\n\tconst parsed = parseBridgeJson(json, \"script error\");\n\tif (typeof parsed !== \"object\" || parsed === null) throw new BridgeError(\"script error is not an object\");\n\tconst { name, message, stack } = parsed as Record<string, unknown>;\n\tif (\n\t\ttypeof message !== \"string\" ||\n\t\t(name !== undefined && typeof name !== \"string\") ||\n\t\t(stack !== undefined && typeof stack !== \"string\")\n\t) {\n\t\tthrow new BridgeError(\"script error is malformed\");\n\t}\n\treturn { kind: \"script\", name, message, stack };\n}\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\ninterface PendingCall {\n\trecord: CodemodeCall | undefined;\n\tstartedAt: number;\n\tcontroller: AbortController;\n}\n\ninterface ExecutionOptions {\n\tcode: string;\n\ttools: ReadonlyMap<string, CodemodeTool>;\n\tglobals: ReadonlyMap<string, CodemodeTool>;\n\ttimeoutMs: number;\n\tsignal: AbortSignal | undefined;\n\tmemoryLimitBytes: number | undefined;\n\tstore: Record<string, string>;\n\twasm: Promise<CodemodeWasmModule>;\n\tworkerUrl: string | URL;\n}\n\n/**\n * One script run in its own worker and QuickJS VM. A fresh worker per run keeps\n * termination simple: a runaway script, including one that only spins the\n * microtask queue, is killed with `terminate()` and cannot poison a later run.\n */\nclass Execution {\n\treadonly promise: Promise<CodemodeResult>;\n\tprivate resolveResult!: (result: CodemodeResult) => void;\n\tprivate worker: Worker | undefined;\n\tprivate readonly interrupt = new SharedArrayBuffer(4);\n\tprivate readonly tools: ReadonlyMap<string, CodemodeTool>;\n\tprivate readonly globals: ReadonlyMap<string, CodemodeTool>;\n\tprivate readonly signal: AbortSignal | undefined;\n\tprivate readonly timer: NodeJS.Timeout | undefined;\n\tprivate readonly output: CodemodeOutputItem[] = [];\n\tprivate readonly calls: CodemodeCall[] = [];\n\tprivate readonly pending = new Map<number, PendingCall>();\n\tprivate finished = false;\n\n\tconstructor(options: ExecutionOptions) {\n\t\tthis.promise = new Promise<CodemodeResult>((resolve) => {\n\t\t\tthis.resolveResult = resolve;\n\t\t});\n\t\tthis.tools = options.tools;\n\t\tthis.globals = options.globals;\n\t\tthis.signal = options.signal;\n\n\t\tif (Number.isFinite(options.timeoutMs)) {\n\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\tthis.finish({ kind: \"timeout\", message: `Execution timed out after ${options.timeoutMs} ms` });\n\t\t\t}, options.timeoutMs);\n\t\t}\n\n\t\tif (options.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tthis.onAbort();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", this.onAbort, { once: true });\n\t\t\t}\n\t\t}\n\n\t\toptions.wasm.then(\n\t\t\t(wasm) => this.start(options, wasm),\n\t\t\t(error: unknown) => {\n\t\t\t\tthis.finish({ kind: \"sandbox\", message: `Failed to load QuickJS: ${errorMessage(error)}` });\n\t\t\t},\n\t\t);\n\t}\n\n\tabort(message: string): Promise<CodemodeResult> {\n\t\tthis.finish({ kind: \"aborted\", message });\n\t\treturn this.promise;\n\t}\n\n\tprivate start(options: ExecutionOptions, wasm: CodemodeWasmModule): void {\n\t\tif (this.finished) return;\n\t\tconst workerData: WorkerData = {\n\t\t\tcode: options.code,\n\t\t\ttools: [...options.tools.values()].map((tool) => ({\n\t\t\t\tname: tool.name,\n\t\t\t\tjsName: toCodemodeIdentifier(tool.name),\n\t\t\t\tdescription: tool.description ?? \"\",\n\t\t\t})),\n\t\t\tglobals: [...options.globals.values()].map((global) => ({\n\t\t\t\tname: global.name,\n\t\t\t\tspread: global.spread === true,\n\t\t\t})),\n\t\t\twasm,\n\t\t\tmemoryLimitBytes: options.memoryLimitBytes,\n\t\t\tstore: options.store,\n\t\t\tinterrupt: this.interrupt,\n\t\t};\n\t\tlet worker: Worker;\n\t\ttry {\n\t\t\tworker = new Worker(options.workerUrl, { workerData });\n\t\t} catch (error) {\n\t\t\tthis.finish({ kind: \"sandbox\", message: `Failed to start worker: ${errorMessage(error)}` });\n\t\t\treturn;\n\t\t}\n\t\tthis.worker = worker;\n\t\tworker.on(\"message\", (message: unknown) => {\n\t\t\t// An exception here would be uncaught in the host process and leave the execution unsettled.\n\t\t\ttry {\n\t\t\t\tthis.handleMessage(message);\n\t\t\t} catch (error) {\n\t\t\t\tthis.finish({\n\t\t\t\t\tkind: \"sandbox\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terror instanceof BridgeError\n\t\t\t\t\t\t\t? `Sandbox bridge broken: ${error.message}. The script may have modified built-ins such as a prototype's toJSON.`\n\t\t\t\t\t\t\t: `Sandbox host failed: ${errorMessage(error)}`,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\tworker.on(\"error\", (error: unknown) => {\n\t\t\tthis.finish({\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\tthis.finish({ kind: \"sandbox\", message: `Worker exited with code ${code} before the script settled` });\n\t\t});\n\t}\n\n\tprivate readonly onAbort = (): void => {\n\t\tconst reason: unknown = this.signal?.reason;\n\t\tthis.finish({ kind: \"aborted\", message: reason instanceof Error ? reason.message : \"Execution aborted\" });\n\t};\n\n\tprivate post(message: HostToWorkerMessage): void {\n\t\tthis.worker?.postMessage(message);\n\t}\n\n\tprivate handleMessage(message: unknown): void {\n\t\tif (this.finished) return;\n\t\tif (!isWorkerToHostMessage(message)) throw new BridgeError(\"unknown message from the worker\");\n\t\tswitch (message.type) {\n\t\t\tcase \"output\":\n\t\t\t\tthis.output.push(message.item);\n\t\t\t\tbreak;\n\t\t\tcase \"call\":\n\t\t\t\tif (this.pending.has(message.id)) throw new BridgeError(`duplicate call id ${message.id}`);\n\t\t\t\tvoid this.handleCall(message);\n\t\t\t\tbreak;\n\t\t\tcase \"done\":\n\t\t\t\tthis.handleDone(message);\n\t\t\t\tbreak;\n\t\t\tcase \"crash\":\n\t\t\t\tthis.finish({ kind: \"sandbox\", message: message.message });\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate handleDone(message: Extract<WorkerToHostMessage, { type: \"done\" }>): void {\n\t\t// Decode everything before finish(), which must not throw once it starts.\n\t\tif (!message.ok) {\n\t\t\tthis.finish(parseScriptError(message.error));\n\t\t\treturn;\n\t\t}\n\t\tconst value = message.value === undefined ? undefined : parseBridgeJson(message.value, \"return value\");\n\t\tthis.finish(undefined, value, parseStoreWrites(message.writes));\n\t}\n\n\tprivate async handleCall(message: Extract<WorkerToHostMessage, { type: \"call\" }>): Promise<void> {\n\t\tconst { id, name } = message;\n\t\tconst isTool = message.target === \"tool\";\n\t\tconst record: CodemodeCall | undefined = isTool ? { name, status: \"cancelled\", durationMs: 0 } : undefined;\n\t\tif (record) this.calls.push(record);\n\t\tconst pending: PendingCall = { record, startedAt: performance.now(), controller: new AbortController() };\n\t\tthis.pending.set(id, pending);\n\n\t\tlet status: CodemodeCallStatus;\n\t\tlet reply: HostToWorkerMessage;\n\t\ttry {\n\t\t\tconst tool = (isTool ? this.tools : this.globals).get(name);\n\t\t\tif (!tool) throw new Error(`Unknown ${isTool ? \"tool\" : \"global\"} \"${name}\"`);\n\t\t\tconst args: unknown = message.args === undefined ? undefined : JSON.parse(message.args);\n\t\t\tconst value = await tool.execute(args, { signal: pending.controller.signal });\n\t\t\treply = { type: \"result\", id, ok: true, payload: value === undefined ? undefined : JSON.stringify(value) };\n\t\t\tstatus = \"ok\";\n\t\t} catch (error) {\n\t\t\treply = { type: \"result\", id, ok: false, payload: errorMessage(error) };\n\t\t\tstatus = \"error\";\n\t\t}\n\n\t\t// Already cancelled by finish(): the record keeps \"cancelled\" and the\n\t\t// worker is gone or going.\n\t\tif (!this.pending.delete(id)) return;\n\t\tif (record) {\n\t\t\trecord.status = status;\n\t\t\trecord.durationMs = performance.now() - pending.startedAt;\n\t\t}\n\t\tthis.post(reply);\n\t}\n\n\tprivate finish(error: CodemodeError | undefined, value?: unknown, writes?: CodemodeStoreWrites): void {\n\t\tif (this.finished) return;\n\t\tthis.finished = true;\n\t\tclearTimeout(this.timer);\n\t\tthis.signal?.removeEventListener(\"abort\", this.onAbort);\n\n\t\tconst now = performance.now();\n\t\tfor (const pending of this.pending.values()) {\n\t\t\tif (pending.record) pending.record.durationMs = now - pending.startedAt;\n\t\t\tpending.controller.abort();\n\t\t}\n\t\tthis.pending.clear();\n\n\t\tconst result: CodemodeResult = error\n\t\t\t? { ok: false, error, output: this.output, calls: this.calls }\n\t\t\t: {\n\t\t\t\t\tok: true,\n\t\t\t\t\tvalue,\n\t\t\t\t\toutput: this.output,\n\t\t\t\t\tcalls: this.calls,\n\t\t\t\t\tstoreWrites: writes ?? { set: {}, delete: [] },\n\t\t\t\t};\n\t\tif (!this.worker) {\n\t\t\tthis.resolveResult(result);\n\t\t\treturn;\n\t\t}\n\t\tAtomics.store(new Int32Array(this.interrupt), 0, 1);\n\t\tthis.worker\n\t\t\t.terminate()\n\t\t\t.catch(() => undefined)\n\t\t\t.then(() => this.resolveResult(result));\n\t}\n}\n\n/**\n * Runs JavaScript in a QuickJS VM (a separate wasm instance) inside a worker\n * thread. The script sees `tools.<name>(args)` for every registered tool, `ALL_TOOLS`,\n * the output helpers `text`, `image`, `exit`, and `console.*`, `store`/`load`, and the\n * configured globals; nothing else (no timers, `fetch`, `process`, `require`, modules).\n *\n * Each `execute()` gets its own worker and VM; the sandbox only holds the tool\n * table and defaults. `close()` aborts in-flight executions.\n */\nexport class CodemodeSandbox {\n\tprivate readonly toolsByName = new Map<string, CodemodeTool>();\n\tprivate readonly globalsByName = new Map<string, CodemodeTool>();\n\tprivate readonly timeoutMs: number;\n\tprivate readonly memoryLimitBytes: number | undefined;\n\tprivate readonly wasm: CodemodeWasmModule | Promise<CodemodeWasmModule> | undefined;\n\tprivate readonly workerUrl: string | URL;\n\tprivate readonly running = new Set<Execution>();\n\tprivate closed = false;\n\n\tconstructor(options: CodemodeSandboxOptions = {}) {\n\t\tthis.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.memoryLimitBytes = options.memoryLimitBytes;\n\t\tthis.wasm = options.wasm;\n\t\tthis.workerUrl = options.workerUrl ?? defaultWorkerUrl();\n\t\tfor (const tool of options.tools ?? []) this.registerTool(tool);\n\t\tconst namespaces = new Set<string>();\n\t\tfor (const global of options.globals ?? []) {\n\t\t\tconst parts = global.name.split(\".\");\n\t\t\tif (parts.length > 2 || !parts.every((part) => IDENTIFIER.test(part)) || RESERVED_GLOBALS.has(parts[0])) {\n\t\t\t\tthrow new Error(`Invalid global name \"${global.name}\"`);\n\t\t\t}\n\t\t\tif (this.globalsByName.has(global.name)) throw new Error(`Global \"${global.name}\" is already registered`);\n\t\t\tif (parts.length === 2) namespaces.add(parts[0]);\n\t\t\tthis.globalsByName.set(global.name, global);\n\t\t}\n\t\tfor (const name of namespaces) {\n\t\t\tif (this.globalsByName.has(name)) throw new Error(`Global \"${name}\" conflicts with the namespace \"${name}\"`);\n\t\t}\n\t}\n\n\t/** Throws if a tool with the same name is already registered. */\n\tregisterTool(tool: CodemodeTool): void {\n\t\tif (this.toolsByName.has(tool.name)) throw new Error(`Tool \"${tool.name}\" is already registered`);\n\t\tthis.toolsByName.set(tool.name, tool);\n\t}\n\n\tunregisterTool(name: string): boolean {\n\t\treturn this.toolsByName.delete(name);\n\t}\n\n\tget tools(): CodemodeTool[] {\n\t\treturn [...this.toolsByName.values()];\n\t}\n\n\tget globals(): CodemodeTool[] {\n\t\treturn [...this.globalsByName.values()];\n\t}\n\n\t/**\n\t * `code` is an async function body: `return` and top-level `await` work.\n\t * Never rejects for script failures; those come back as `{ ok: false }`.\n\t * The script can use `store(key, value)` and `load(key)` on `options.store`.\n\t */\n\texecute(code: string, options: CodemodeExecuteOptions = {}): Promise<CodemodeResult> {\n\t\tif (this.closed) return Promise.reject(new Error(\"Sandbox is closed\"));\n\t\tconst execution = new Execution({\n\t\t\tcode,\n\t\t\ttools: new Map(this.toolsByName),\n\t\t\tglobals: this.globalsByName,\n\t\t\ttimeoutMs: options.timeoutMs ?? this.timeoutMs,\n\t\t\tsignal: options.signal,\n\t\t\tmemoryLimitBytes: this.memoryLimitBytes,\n\t\t\tstore: serializeStore(options.store),\n\t\t\twasm: this.wasm === undefined ? loadQuickJSWasm() : Promise.resolve(this.wasm),\n\t\t\tworkerUrl: this.workerUrl,\n\t\t});\n\t\tthis.running.add(execution);\n\t\treturn execution.promise.finally(() => this.running.delete(execution));\n\t}\n\n\t/** Aborts in-flight executions (they resolve with `kind: \"aborted\"`) and rejects new ones. */\n\tasync close(): Promise<void> {\n\t\tthis.closed = true;\n\t\tawait Promise.all([...this.running].map((execution) => execution.abort(\"Sandbox closed\")));\n\t}\n}\n"]}
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 `@earendil-works/pi-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 (`@earendil-works/pi-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