@langchain/quickjs 0.2.0

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,787 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
27
+
28
+ //#endregion
29
+ let langchain = require("langchain");
30
+ let zod_v4 = require("zod/v4");
31
+ let deepagents = require("deepagents");
32
+ let dedent = require("dedent");
33
+ dedent = __toESM(dedent);
34
+ let quickjs_emscripten = require("quickjs-emscripten");
35
+ let quickjs_emscripten_core = require("quickjs-emscripten-core");
36
+ let json_schema_to_typescript = require("json-schema-to-typescript");
37
+ let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
38
+ let acorn = require("acorn");
39
+ let _sveltejs_acorn_typescript = require("@sveltejs/acorn-typescript");
40
+ let estree_walker = require("estree-walker");
41
+ let magic_string = require("magic-string");
42
+ magic_string = __toESM(magic_string);
43
+ let _langchain_langgraph = require("@langchain/langgraph");
44
+
45
+ //#region src/utils.ts
46
+ /**
47
+ * Convert a snake_case or kebab-case string to camelCase.
48
+ */
49
+ function toCamelCase(name) {
50
+ return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
51
+ }
52
+ /**
53
+ * Format the result of a REPL evaluation for the agent.
54
+ */
55
+ function formatReplResult(result) {
56
+ const parts = [];
57
+ if (result.logs.length > 0) parts.push(result.logs.join("\n"));
58
+ if (result.ok) {
59
+ if (result.value !== void 0) {
60
+ const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
61
+ parts.push(`→ ${formatted}`);
62
+ }
63
+ } else if (result.error) {
64
+ const errName = result.error.name || "Error";
65
+ const errMsg = result.error.message || "Unknown error";
66
+ parts.push(`${errName}: ${errMsg}`);
67
+ if (result.error.stack) parts.push(result.error.stack);
68
+ }
69
+ return parts.join("\n") || "(no output)";
70
+ }
71
+ function safeToJsonSchema(schema) {
72
+ try {
73
+ return (0, _langchain_core_utils_json_schema.toJsonSchema)(schema);
74
+ } catch {
75
+ return;
76
+ }
77
+ }
78
+ async function schemaToInterface(jsonSchema, interfaceName) {
79
+ return (await (0, json_schema_to_typescript.compile)({
80
+ ...jsonSchema,
81
+ additionalProperties: false
82
+ }, interfaceName, {
83
+ bannerComment: "",
84
+ additionalProperties: false
85
+ })).replace(/^export /, "").trimEnd();
86
+ }
87
+ function capitalize(s) {
88
+ return s.charAt(0).toUpperCase() + s.slice(1);
89
+ }
90
+ async function toolToTypeSignature(name, description, jsonSchema) {
91
+ const inputType = `${capitalize(name)}Input`;
92
+ if (!jsonSchema || !jsonSchema.properties) return dedent.default`
93
+ /**
94
+ * ${description}
95
+ */
96
+ async tools.${name}(input: Record<string, unknown>): Promise<string>
97
+ `;
98
+ return dedent.default`
99
+ ${await schemaToInterface(jsonSchema, inputType)}
100
+
101
+ /**
102
+ * ${description}
103
+ */
104
+ async tools.${name}(input: ${inputType}): Promise<string>
105
+ `;
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/transform.ts
110
+ /**
111
+ * AST-based code transform pipeline for the REPL.
112
+ *
113
+ * Transforms TypeScript/JavaScript code into plain JS that can be
114
+ * evaluated inside QuickJS with proper state persistence:
115
+ *
116
+ * 1. Parse with acorn + acorn-typescript (handles TS syntax)
117
+ * 2. Strip TypeScript-only nodes (type annotations, interfaces, etc.)
118
+ * 3. Hoist top-level declarations to globalThis for cross-eval persistence
119
+ * 4. Auto-return the last expression
120
+ * 5. Wrap in async IIFE so top-level await works
121
+ */
122
+ const TSParser = acorn.Parser.extend((0, _sveltejs_acorn_typescript.tsPlugin)());
123
+ /**
124
+ * Transform code for REPL evaluation.
125
+ *
126
+ * - Strips TypeScript syntax
127
+ * - Hoists top-level variable declarations to globalThis
128
+ * - Auto-returns the last expression
129
+ * - Wraps in async IIFE for top-level await support
130
+ */
131
+ function transformForEval(code) {
132
+ let ast;
133
+ try {
134
+ ast = TSParser.parse(code, {
135
+ ecmaVersion: "latest",
136
+ sourceType: "module",
137
+ locations: true
138
+ });
139
+ } catch {
140
+ return `(async () => {\n${code}\n})()`;
141
+ }
142
+ const s = new magic_string.default(code);
143
+ const topLevelNodes = ast.body;
144
+ for (let i = 0; i < topLevelNodes.length; i++) {
145
+ const node = topLevelNodes[i];
146
+ if (isTSOnlyNode(node)) {
147
+ s.remove(node.start, node.end);
148
+ continue;
149
+ }
150
+ if (node.type === "ImportDeclaration" || node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration") {
151
+ s.remove(node.start, node.end);
152
+ continue;
153
+ }
154
+ if (node.type === "VariableDeclaration") {
155
+ hoistDeclaration(s, node);
156
+ continue;
157
+ }
158
+ if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") {
159
+ stripTypeAnnotations(s, node);
160
+ const name = node.id?.name;
161
+ if (name) s.appendRight(node.end, `\nglobalThis.${name} = ${name};`);
162
+ continue;
163
+ }
164
+ }
165
+ for (const node of topLevelNodes) {
166
+ if (isTSOnlyNode(node)) continue;
167
+ if (node.type === "ImportDeclaration" || node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration") continue;
168
+ if (node.type !== "VariableDeclaration") (0, estree_walker.walk)(node, { enter(n) {
169
+ stripTypeAnnotationFromNode(s, n);
170
+ } });
171
+ }
172
+ const lastNode = findLastNonEmptyNode(topLevelNodes, s);
173
+ if (lastNode && isExpression(lastNode)) {
174
+ const { expression } = lastNode;
175
+ s.prependLeft(lastNode.start, "return (");
176
+ s.appendRight(expression.end, ")");
177
+ }
178
+ s.prepend("(async () => {\n");
179
+ s.append("\n})()");
180
+ return s.toString();
181
+ }
182
+ function isTSOnlyNode(node) {
183
+ const t = node.type;
184
+ return t === "TSTypeAliasDeclaration" || t === "TSInterfaceDeclaration" || t === "TSEnumDeclaration" || t === "TSModuleDeclaration" || t === "TSDeclareFunction" || t.startsWith("TS");
185
+ }
186
+ /**
187
+ * Rewrite a top-level VariableDeclaration to globalThis assignments.
188
+ *
189
+ * `const x = 1, y = 2` → `globalThis.x = 1; globalThis.y = 2`
190
+ *
191
+ */
192
+ function hoistDeclaration(s, decl) {
193
+ const parts = [];
194
+ for (const d of decl.declarations) {
195
+ const id = d.id;
196
+ if (id.type === "Identifier") {
197
+ const initCode = d.init ? extractCleanInit(s, d) : "undefined";
198
+ parts.push(`globalThis.${id.name} = ${initCode}`);
199
+ } else if (id.type === "ObjectPattern" || id.type === "ArrayPattern") {
200
+ const bindings = extractBindingNames(d.id);
201
+ const initCode = d.init ? extractCleanInit(s, d) : "undefined";
202
+ const patternCode = extractCleanSource(s, d.id);
203
+ parts.push(`var ${patternCode} = ${initCode}`);
204
+ for (const name of bindings) parts.push(`globalThis.${name} = ${name}`);
205
+ }
206
+ }
207
+ s.overwrite(decl.start, decl.end, parts.join("; ") + ";");
208
+ }
209
+ /**
210
+ * Extract the initializer code, stripping TypeScript annotations from
211
+ * within the expression (e.g. `as Type`, generics, parameter types in
212
+ * arrow functions).
213
+ */
214
+ function extractCleanInit(s, d) {
215
+ if (!d.init) return "undefined";
216
+ return extractCleanSource(s, d.init);
217
+ }
218
+ function extractBindingNames(pattern) {
219
+ const names = [];
220
+ if (pattern.type === "Identifier") {
221
+ if (pattern.name) names.push(pattern.name);
222
+ } else if (pattern.type === "ObjectPattern") for (const prop of pattern.properties || []) if (prop.type === "RestElement") names.push(...extractBindingNames(prop.argument));
223
+ else names.push(...extractBindingNames(prop.value));
224
+ else if (pattern.type === "ArrayPattern") {
225
+ for (const el of pattern.elements || []) if (el) names.push(...extractBindingNames(el));
226
+ } else if (pattern.type === "RestElement") names.push(...extractBindingNames(pattern.argument));
227
+ else if (pattern.type === "AssignmentPattern") names.push(...extractBindingNames(pattern.left));
228
+ return names;
229
+ }
230
+ function stripTypeAnnotations(s, node) {
231
+ (0, estree_walker.walk)(node, { enter(n) {
232
+ stripTypeAnnotationFromNode(s, n);
233
+ } });
234
+ }
235
+ function stripTypeAnnotationFromNode(s, n, offset = 0) {
236
+ if (n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - offset, n.typeAnnotation.end - offset);
237
+ if (n.returnType && n.returnType.start != null) s.remove(n.returnType.start - offset, n.returnType.end - offset);
238
+ if (n.typeParameters && n.typeParameters.start != null) s.remove(n.typeParameters.start - offset, n.typeParameters.end - offset);
239
+ if (n.typeArguments && n.typeArguments.start != null) s.remove(n.typeArguments.start - offset, n.typeArguments.end - offset);
240
+ if (n.type === "TSAsExpression" && n.expression) s.remove(n.expression.end - offset, n.end - offset);
241
+ if (n.type === "TSNonNullExpression" && n.expression) s.remove(n.expression.end - offset, n.end - offset);
242
+ if (n.type === "TSSatisfiesExpression" && n.expression) s.remove(n.expression.end - offset, n.end - offset);
243
+ }
244
+ /**
245
+ * Extract a clean JS source string from an AST node, stripping all
246
+ * TypeScript annotations. Works on a copy so the main MagicString is
247
+ * not mutated.
248
+ */
249
+ function extractCleanSource(s, node) {
250
+ const offset = node.start;
251
+ const source = new magic_string.default(s.slice(node.start, node.end));
252
+ (0, estree_walker.walk)(node, { enter(n) {
253
+ stripTypeAnnotationFromNode(source, n, offset);
254
+ } });
255
+ return source.toString();
256
+ }
257
+ function findLastNonEmptyNode(nodes, s) {
258
+ for (let i = nodes.length - 1; i >= 0; i--) {
259
+ const node = nodes[i];
260
+ const slice = s.slice(node.start, node.end).trim();
261
+ if (slice === "" || slice === ";") continue;
262
+ return node;
263
+ }
264
+ return null;
265
+ }
266
+ function isExpression(node) {
267
+ return node.type === "ExpressionStatement";
268
+ }
269
+
270
+ //#endregion
271
+ //#region src/session.ts
272
+ /**
273
+ * Core REPL engine built on quickjs-emscripten (asyncify variant).
274
+ *
275
+ * Host async functions (backend I/O, PTC tools) are exposed as
276
+ * promise-returning functions inside the QuickJS guest. Guest code
277
+ * uses `await` to consume them, enabling real concurrency via
278
+ * `Promise.all`, `Promise.race`, etc.
279
+ *
280
+ * We still use the asyncify WASM variant because `evalCodeAsync` is
281
+ * required to drive promise resolution from the host side.
282
+ *
283
+ * ## Architecture
284
+ *
285
+ * `ReplSession` is a serializable handle that can live in LangGraph state.
286
+ * It holds an `id` that keys into a static session map. The heavy QuickJS
287
+ * runtime is lazily started on the first `.eval()` call, making the session
288
+ * safe across graph interrupts and checkpointing.
289
+ *
290
+ * File writes inside the REPL are buffered (`pendingWrites`) and only
291
+ * flushed to the backend after a script finishes executing. Call
292
+ * `session.flushWrites(backend)` after eval to persist them.
293
+ */
294
+ const DEFAULT_MEMORY_LIMIT = 50 * 1024 * 1024;
295
+ const DEFAULT_MAX_STACK_SIZE = 320 * 1024;
296
+ const DEFAULT_EXECUTION_TIMEOUT = 3e4;
297
+ const DEFAULT_SESSION_ID = "__default__";
298
+ let asyncModulePromise;
299
+ async function getAsyncModule() {
300
+ if (!asyncModulePromise) asyncModulePromise = (async () => {
301
+ const variant = await import("@jitl/quickjs-ng-wasmfile-release-asyncify");
302
+ return (0, quickjs_emscripten_core.newQuickJSAsyncWASMModuleFromVariant)(variant.default ?? variant);
303
+ })();
304
+ return asyncModulePromise;
305
+ }
306
+ /**
307
+ * Sandboxed JavaScript REPL session backed by QuickJS WASM.
308
+ *
309
+ * Serializable — holds an `id` that keys into a static session map.
310
+ * The QuickJS runtime is lazily started on the first `.eval()` call
311
+ * and reconnected if a session with the same id already exists.
312
+ * This makes it safe to store in LangGraph state across interrupts.
313
+ *
314
+ * File writes are buffered during execution and flushed via
315
+ * `flushWrites(backend)` after eval completes.
316
+ */
317
+ var ReplSession = class ReplSession {
318
+ static sessions = /* @__PURE__ */ new Map();
319
+ id;
320
+ pendingWrites = [];
321
+ runtime = null;
322
+ context = null;
323
+ logs = [];
324
+ _options;
325
+ _backend = null;
326
+ constructor(id, options = {}) {
327
+ this.id = id;
328
+ this._options = options;
329
+ }
330
+ get backend() {
331
+ return this._backend;
332
+ }
333
+ set backend(b) {
334
+ this._backend = b;
335
+ }
336
+ async ensureStarted() {
337
+ if (this.runtime) return;
338
+ const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, backend, tools } = this._options;
339
+ const runtime = (await getAsyncModule()).newRuntime();
340
+ runtime.setMemoryLimit(memoryLimitBytes);
341
+ runtime.setMaxStackSize(maxStackSizeBytes);
342
+ const context = runtime.newContext();
343
+ this.runtime = runtime;
344
+ this.context = context;
345
+ this.setupConsole();
346
+ if (backend) this._backend = backend;
347
+ this.injectVfs();
348
+ if (tools && tools.length > 0) this.injectTools(tools);
349
+ }
350
+ /**
351
+ * Get or create a session for the given id.
352
+ *
353
+ * Sessions are deduped by id — calling `getOrCreate` twice with the
354
+ * same id returns the same instance. The QuickJS runtime is lazily
355
+ * started on the first `.eval()` call.
356
+ */
357
+ static getOrCreate(id, options = {}) {
358
+ const existing = ReplSession.sessions.get(id);
359
+ if (existing) {
360
+ if (options.backend) existing._backend = options.backend;
361
+ return existing;
362
+ }
363
+ const session = new ReplSession(id, options);
364
+ ReplSession.sessions.set(id, session);
365
+ return session;
366
+ }
367
+ /**
368
+ * Retrieve an existing session by id, or null if none exists.
369
+ */
370
+ static get(id) {
371
+ return ReplSession.sessions.get(id) ?? null;
372
+ }
373
+ /**
374
+ * Evaluate code in this session.
375
+ *
376
+ * Lazily starts the QuickJS runtime on the first call. Code is
377
+ * transformed via an AST pipeline that strips TypeScript syntax,
378
+ * hoists top-level declarations to globalThis for cross-eval
379
+ * persistence, auto-returns the last expression, and wraps in an
380
+ * async IIFE.
381
+ */
382
+ async eval(code, timeoutMs) {
383
+ await this.ensureStarted();
384
+ const runtime = this.runtime;
385
+ const context = this.context;
386
+ this.logs.length = 0;
387
+ if (timeoutMs >= 0) runtime.setInterruptHandler((0, quickjs_emscripten.shouldInterruptAfterDeadline)(Date.now() + timeoutMs));
388
+ else runtime.setInterruptHandler(() => false);
389
+ const transformed = transformForEval(code);
390
+ const result = await context.evalCodeAsync(transformed);
391
+ if (result.error) {
392
+ const error = context.dump(result.error);
393
+ result.error.dispose();
394
+ return {
395
+ ok: false,
396
+ error,
397
+ logs: [...this.logs]
398
+ };
399
+ }
400
+ const promiseState = context.getPromiseState(result.value);
401
+ if (promiseState.type === "fulfilled") {
402
+ if (promiseState.notAPromise) {
403
+ const value = context.dump(result.value);
404
+ result.value.dispose();
405
+ return {
406
+ ok: true,
407
+ value,
408
+ logs: [...this.logs]
409
+ };
410
+ }
411
+ const value = context.dump(promiseState.value);
412
+ promiseState.value.dispose();
413
+ result.value.dispose();
414
+ return {
415
+ ok: true,
416
+ value,
417
+ logs: [...this.logs]
418
+ };
419
+ }
420
+ if (promiseState.type === "rejected") {
421
+ const error = context.dump(promiseState.error);
422
+ promiseState.error.dispose();
423
+ result.value.dispose();
424
+ return {
425
+ ok: false,
426
+ error,
427
+ logs: [...this.logs]
428
+ };
429
+ }
430
+ const noTimeout = timeoutMs < 0;
431
+ const deadline = noTimeout ? Infinity : Date.now() + timeoutMs;
432
+ while (noTimeout || Date.now() < deadline) {
433
+ context.runtime.executePendingJobs();
434
+ const state = context.getPromiseState(result.value);
435
+ if (state.type === "fulfilled") {
436
+ const value = context.dump(state.value);
437
+ state.value.dispose();
438
+ result.value.dispose();
439
+ return {
440
+ ok: true,
441
+ value,
442
+ logs: [...this.logs]
443
+ };
444
+ }
445
+ if (state.type === "rejected") {
446
+ const error = context.dump(state.error);
447
+ state.error.dispose();
448
+ result.value.dispose();
449
+ return {
450
+ ok: false,
451
+ error,
452
+ logs: [...this.logs]
453
+ };
454
+ }
455
+ await new Promise((r) => setTimeout(r, 1));
456
+ }
457
+ result.value.dispose();
458
+ return {
459
+ ok: false,
460
+ error: { message: "Promise timed out — execution interrupted" },
461
+ logs: [...this.logs]
462
+ };
463
+ }
464
+ async flushWrites(backend) {
465
+ const writes = this.pendingWrites.splice(0);
466
+ for (const { path, content } of writes) await backend.write(path, content);
467
+ }
468
+ dispose() {
469
+ try {
470
+ this.context?.dispose();
471
+ } catch {}
472
+ try {
473
+ this.runtime?.dispose();
474
+ } catch {}
475
+ this.runtime = null;
476
+ this.context = null;
477
+ ReplSession.sessions.delete(this.id);
478
+ }
479
+ toJSON() {
480
+ return { id: this.id };
481
+ }
482
+ static fromJSON(data) {
483
+ return ReplSession.sessions.get(data.id) ?? new ReplSession(data.id);
484
+ }
485
+ /**
486
+ * Clear the static session cache. Useful for testing.
487
+ * @internal
488
+ */
489
+ static clearCache() {
490
+ for (const session of ReplSession.sessions.values()) session.dispose();
491
+ ReplSession.sessions.clear();
492
+ }
493
+ setupConsole() {
494
+ const context = this.context;
495
+ const logs = this.logs;
496
+ const consoleHandle = context.newObject();
497
+ for (const method of [
498
+ "log",
499
+ "warn",
500
+ "error",
501
+ "info",
502
+ "debug"
503
+ ]) {
504
+ const fnHandle = context.newFunction(method, (...args) => {
505
+ const formatted = args.map((a) => context.dump(a)).map((a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)).join(" ");
506
+ logs.push(method === "log" || method === "info" || method === "debug" ? formatted : `[${method}] ${formatted}`);
507
+ });
508
+ context.setProp(consoleHandle, method, fnHandle);
509
+ fnHandle.dispose();
510
+ }
511
+ context.setProp(context.global, "console", consoleHandle);
512
+ consoleHandle.dispose();
513
+ }
514
+ injectVfs() {
515
+ const context = this.context;
516
+ const getBackend = () => this._backend;
517
+ const { pendingWrites } = this;
518
+ const readFileHandle = context.newFunction("readFile", (pathHandle) => {
519
+ const backend = getBackend();
520
+ if (!backend) {
521
+ const promise = context.newPromise();
522
+ const err = context.newError("Backend not available");
523
+ promise.reject(err);
524
+ err.dispose();
525
+ promise.settled.then(context.runtime.executePendingJobs);
526
+ return promise.handle;
527
+ }
528
+ const path = context.getString(pathHandle);
529
+ const promise = context.newPromise();
530
+ (async () => {
531
+ try {
532
+ const fileData = await backend.readRaw(path);
533
+ const val = context.newString(fileData.content.join("\n"));
534
+ promise.resolve(val);
535
+ val.dispose();
536
+ } catch {
537
+ const err = context.newError(`ENOENT: no such file or directory '${path}'.`);
538
+ promise.reject(err);
539
+ err.dispose();
540
+ }
541
+ promise.settled.then(context.runtime.executePendingJobs);
542
+ })();
543
+ return promise.handle;
544
+ });
545
+ context.setProp(context.global, "readFile", readFileHandle);
546
+ readFileHandle.dispose();
547
+ const writeFileHandle = context.newFunction("writeFile", (pathHandle, contentHandle) => {
548
+ const path = context.getString(pathHandle);
549
+ const content = context.getString(contentHandle);
550
+ const promise = context.newPromise();
551
+ pendingWrites.push({
552
+ path,
553
+ content
554
+ });
555
+ promise.resolve(context.undefined);
556
+ promise.settled.then(context.runtime.executePendingJobs);
557
+ return promise.handle;
558
+ });
559
+ context.setProp(context.global, "writeFile", writeFileHandle);
560
+ writeFileHandle.dispose();
561
+ }
562
+ injectTools(tools) {
563
+ const context = this.context;
564
+ const toolsNs = context.newObject();
565
+ for (const t of tools) {
566
+ const camelName = toCamelCase(t.name);
567
+ const fnHandle = context.newFunction(camelName, (inputHandle) => {
568
+ const input = context.dump(inputHandle);
569
+ const promise = context.newPromise();
570
+ (async () => {
571
+ try {
572
+ const rawInput = typeof input === "object" && input !== null ? input : {};
573
+ const result = await t.invoke(rawInput);
574
+ const val = context.newString(typeof result === "string" ? result : JSON.stringify(result));
575
+ promise.resolve(val);
576
+ val.dispose();
577
+ } catch (e) {
578
+ const msg = e != null && typeof e.message === "string" ? e.message : String(e);
579
+ const err = context.newError(`Tool '${t.name}' failed: ${msg}`);
580
+ promise.reject(err);
581
+ err.dispose();
582
+ }
583
+ promise.settled.then(context.runtime.executePendingJobs);
584
+ })();
585
+ return promise.handle;
586
+ });
587
+ context.setProp(toolsNs, camelName, fnHandle);
588
+ fnHandle.dispose();
589
+ }
590
+ context.setProp(context.global, "tools", toolsNs);
591
+ toolsNs.dispose();
592
+ }
593
+ };
594
+
595
+ //#endregion
596
+ //#region src/middleware.ts
597
+ /**
598
+ * QuickJS REPL middleware for deepagents.
599
+ *
600
+ * Provides a `js_eval` tool that runs JavaScript in a WASM-sandboxed QuickJS
601
+ * interpreter. Supports:
602
+ * - Persistent state across evaluations (true REPL)
603
+ * - VFS integration via readFile/writeFile
604
+ * - Programmatic tool calling (PTC)
605
+ */
606
+ /**
607
+ * Backend-provided tools excluded from PTC by default.
608
+ * These are redundant inside the REPL since VFS helpers (readFile/writeFile)
609
+ * already cover file I/O against the agent's in-memory working set.
610
+ */
611
+ const DEFAULT_PTC_EXCLUDED_TOOLS = [
612
+ "ls",
613
+ "read_file",
614
+ "write_file",
615
+ "edit_file",
616
+ "glob",
617
+ "grep",
618
+ "execute"
619
+ ];
620
+ const REPL_SYSTEM_PROMPT = dedent.default`
621
+ ## TypeScript/JavaScript REPL (\`js_eval\`)
622
+
623
+ You have access to a sandboxed TypeScript/JavaScript REPL running in an isolated interpreter.
624
+ TypeScript syntax (type annotations, interfaces, generics, \`as\` casts) is supported and stripped at evaluation time.
625
+ Variables, functions, and closures persist across calls within the same session.
626
+
627
+ ### Hard rules
628
+
629
+ - **No network, no filesystem** — only the helpers below. Do not attempt \`fetch\`, \`require\`, or \`import\`.
630
+ - **Cite your sources** — when reporting values from files, include the path and key/index so the user can verify.
631
+ - **Use console.log()** for output — it is captured and returned. \`console.warn()\` and \`console.error()\` are also available.
632
+ - **Reuse state from previous cells** — variables, functions, and results from earlier \`js_eval\` calls persist across calls. Reference them by name in follow-up cells instead of re-embedding data as inline JSON literals.
633
+
634
+ ### First-time usage
635
+
636
+ \`\`\`typescript
637
+ // Read a file from the agent's virtual filesystem
638
+ const raw: string = await readFile("/data.json");
639
+ const data = JSON.parse(raw) as { n: number };
640
+ console.log(data);
641
+
642
+ // Write results back
643
+ await writeFile("/output.txt", JSON.stringify({ result: data.n }));
644
+ \`\`\`
645
+
646
+ ### API Reference — built-in globals
647
+
648
+ \`\`\`typescript
649
+ /**
650
+ * Read a file from the agent's virtual filesystem. Throws if the file does not exist.
651
+ */
652
+ async readFile(path: string): Promise<string>
653
+
654
+ /**
655
+ * Write a file to the agent's virtual filesystem.
656
+ */
657
+ async writeFile(path: string, content: string): Promise<void>
658
+ \`\`\`
659
+
660
+ ### Limitations
661
+
662
+ - ES2023+ syntax with TypeScript support. No Node.js APIs, no \`require\`, no \`import\`.
663
+ - Output is truncated beyond a fixed character limit — be selective about what you log.
664
+ - Execution timeout per call (default 30 s).
665
+ `;
666
+ /**
667
+ * Generate the PTC API Reference section for the system prompt.
668
+ */
669
+ async function generatePtcPrompt(tools) {
670
+ if (tools.length === 0) return "";
671
+ return dedent.default`
672
+
673
+ ### API Reference — \`tools\` namespace
674
+
675
+ The following agent tools are callable as async functions inside the REPL.
676
+ Each takes a single object argument and returns a Promise that resolves to a string.
677
+ Use \`await\` to call them. Promise APIs like \`Promise.all\` are also available.
678
+
679
+ **Example usage:**
680
+ \`\`\`javascript
681
+ // Call a tool
682
+ const result = await tools.searchWeb({ query: "QuickJS tutorial" });
683
+ console.log(result);
684
+
685
+ // Concurrent calls
686
+ const [a, b] = await Promise.all([
687
+ tools.fetchData({ url: "https://api.example.com/a" }),
688
+ tools.fetchData({ url: "https://api.example.com/b" }),
689
+ ]);
690
+ \`\`\`
691
+
692
+ **Available functions:**
693
+ \`\`\`typescript
694
+ ${(await Promise.all(tools.map((t) => {
695
+ const jsonSchema = t.schema ? safeToJsonSchema(t.schema) : void 0;
696
+ return toolToTypeSignature(toCamelCase(t.name), t.description, jsonSchema);
697
+ }))).join("\n\n")}
698
+ \`\`\`
699
+ `;
700
+ }
701
+ /**
702
+ * Resolve backend from factory or instance.
703
+ */
704
+ function getBackend(backend, stateAndStore) {
705
+ if (typeof backend === "function") return backend(stateAndStore);
706
+ return backend;
707
+ }
708
+ /**
709
+ * Create the QuickJS REPL middleware.
710
+ */
711
+ function createQuickJSMiddleware(options = {}) {
712
+ const { backend = (stateAndStore) => new deepagents.StateBackend(stateAndStore), ptc = false, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null } = options;
713
+ const usePtc = ptc !== false;
714
+ const baseSystemPrompt = customSystemPrompt || REPL_SYSTEM_PROMPT;
715
+ let cachedPtcPrompt = null;
716
+ let ptcTools = [];
717
+ function filterToolsForPtc(allTools) {
718
+ if (ptc === false) return [];
719
+ const candidates = allTools.filter((t) => t.name !== "js_eval");
720
+ if (ptc === true) {
721
+ const excluded = new Set(DEFAULT_PTC_EXCLUDED_TOOLS);
722
+ return candidates.filter((t) => !excluded.has(t.name));
723
+ }
724
+ if (Array.isArray(ptc)) {
725
+ const included = new Set(ptc);
726
+ return candidates.filter((t) => included.has(t.name));
727
+ }
728
+ if ("include" in ptc) {
729
+ const included = new Set(ptc.include);
730
+ return candidates.filter((t) => included.has(t.name));
731
+ }
732
+ if ("exclude" in ptc) {
733
+ const excluded = new Set([...DEFAULT_PTC_EXCLUDED_TOOLS, ...ptc.exclude]);
734
+ return candidates.filter((t) => !excluded.has(t.name));
735
+ }
736
+ return [];
737
+ }
738
+ return (0, langchain.createMiddleware)({
739
+ name: "QuickJSMiddleware",
740
+ tools: [(0, langchain.tool)(async (input, config) => {
741
+ const threadId = config.configurable?.thread_id || DEFAULT_SESSION_ID;
742
+ const resolvedBackend = getBackend(backend, {
743
+ state: (0, _langchain_langgraph.getCurrentTaskInput)(config) || {},
744
+ store: config.configurable?.__pregel_store
745
+ });
746
+ const session = ReplSession.getOrCreate(threadId, {
747
+ memoryLimitBytes,
748
+ maxStackSizeBytes,
749
+ backend: resolvedBackend,
750
+ tools: ptcTools
751
+ });
752
+ const result = await session.eval(input.code, executionTimeoutMs);
753
+ await session.flushWrites(resolvedBackend);
754
+ return formatReplResult(result);
755
+ }, {
756
+ name: "js_eval",
757
+ description: dedent.default`
758
+ Evaluate TypeScript/JavaScript code in a sandboxed REPL. State persists across calls.
759
+ Use readFile(path) and writeFile(path, content) for file access.
760
+ Use console.log() for output. Returns the result of the last expression.
761
+ `,
762
+ schema: zod_v4.z.object({ code: zod_v4.z.string().describe("TypeScript/JavaScript code to evaluate in the sandboxed REPL") })
763
+ })],
764
+ wrapModelCall: async (request, handler) => {
765
+ const agentTools = request.tools || [];
766
+ ptcTools = usePtc ? filterToolsForPtc(agentTools) : [];
767
+ if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
768
+ const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(cachedPtcPrompt || "");
769
+ return handler({
770
+ ...request,
771
+ systemMessage
772
+ });
773
+ }
774
+ });
775
+ }
776
+
777
+ //#endregion
778
+ exports.DEFAULT_EXECUTION_TIMEOUT = DEFAULT_EXECUTION_TIMEOUT;
779
+ exports.DEFAULT_MAX_STACK_SIZE = DEFAULT_MAX_STACK_SIZE;
780
+ exports.DEFAULT_MEMORY_LIMIT = DEFAULT_MEMORY_LIMIT;
781
+ exports.DEFAULT_PTC_EXCLUDED_TOOLS = DEFAULT_PTC_EXCLUDED_TOOLS;
782
+ exports.ReplSession = ReplSession;
783
+ exports.createQuickJSMiddleware = createQuickJSMiddleware;
784
+ exports.formatReplResult = formatReplResult;
785
+ exports.toCamelCase = toCamelCase;
786
+ exports.transformForEval = transformForEval;
787
+ //# sourceMappingURL=index.cjs.map