@langchain/quickjs 0.3.0 → 0.5.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 CHANGED
@@ -23,21 +23,104 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let langchain = require("langchain");
25
25
  let zod_v4 = require("zod/v4");
26
+ let deepagents = require("deepagents");
26
27
  let dedent = require("dedent");
27
28
  dedent = __toESM(dedent, 1);
28
- let _langchain_langgraph = require("@langchain/langgraph");
29
- let deepagents = require("deepagents");
30
29
  let quickjs_emscripten = require("quickjs-emscripten");
31
30
  let quickjs_emscripten_core = require("quickjs-emscripten-core");
32
- let node_path_posix = require("node:path/posix");
33
- node_path_posix = __toESM(node_path_posix, 1);
31
+ let json_schema_to_typescript = require("json-schema-to-typescript");
32
+ let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
34
33
  let acorn = require("acorn");
35
34
  let _sveltejs_acorn_typescript = require("@sveltejs/acorn-typescript");
36
35
  let estree_walker = require("estree-walker");
37
36
  let magic_string = require("magic-string");
38
37
  magic_string = __toESM(magic_string, 1);
39
- let json_schema_to_typescript = require("json-schema-to-typescript");
40
- let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
38
+ let p_queue = require("p-queue");
39
+ p_queue = __toESM(p_queue, 1);
40
+ //#region src/errors.ts
41
+ /**
42
+ * Thrown when a single eval exhausts its configured PTC call budget.
43
+ */
44
+ var PTCCallBudgetExceededError = class extends Error {
45
+ limit;
46
+ attempted;
47
+ functionName;
48
+ constructor(options) {
49
+ super(`PTC call budget exceeded (limit=${options.limit}, attempted=${options.attempted}, function=${options.functionName})`);
50
+ this.name = "PTCCallBudgetExceededError";
51
+ this.limit = options.limit;
52
+ this.attempted = options.attempted;
53
+ this.functionName = options.functionName;
54
+ }
55
+ };
56
+ //#endregion
57
+ //#region src/utils.ts
58
+ /**
59
+ * Convert a snake_case or kebab-case string to camelCase.
60
+ */
61
+ function toCamelCase(name) {
62
+ return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
63
+ }
64
+ /**
65
+ * Format the result of a REPL evaluation for the agent.
66
+ */
67
+ function formatReplResult(result) {
68
+ const parts = [];
69
+ if (result.logs.length > 0) {
70
+ let logsText = result.logs.join("\n");
71
+ if (result.logsDroppedChars > 0) logsText += `\n[truncated ${result.logsDroppedChars} chars]`;
72
+ parts.push(logsText);
73
+ }
74
+ if (result.ok) {
75
+ if (result.value !== void 0) {
76
+ const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
77
+ parts.push(`→ ${formatted}`);
78
+ }
79
+ } else if (result.error) {
80
+ const errName = result.error.name || "Error";
81
+ const errMsg = result.error.message || "Unknown error";
82
+ parts.push(`${errName}: ${errMsg}`);
83
+ if (result.error.stack) parts.push(result.error.stack);
84
+ }
85
+ return parts.join("\n") || "(no output)";
86
+ }
87
+ function safeToJsonSchema(schema) {
88
+ try {
89
+ return (0, _langchain_core_utils_json_schema.toJsonSchema)(schema);
90
+ } catch {
91
+ return;
92
+ }
93
+ }
94
+ async function schemaToInterface(jsonSchema, interfaceName) {
95
+ return (await (0, json_schema_to_typescript.compile)({
96
+ ...jsonSchema,
97
+ additionalProperties: false
98
+ }, interfaceName, {
99
+ bannerComment: "",
100
+ additionalProperties: false
101
+ })).replace(/^export /, "").trimEnd();
102
+ }
103
+ function capitalize(s) {
104
+ return s.charAt(0).toUpperCase() + s.slice(1);
105
+ }
106
+ async function toolToTypeSignature(name, description, jsonSchema) {
107
+ const inputType = `${capitalize(name)}Input`;
108
+ if (!jsonSchema || !jsonSchema.properties) return dedent.default`
109
+ /**
110
+ * ${description}
111
+ */
112
+ async tools.${name}(input: Record<string, unknown>): Promise<string>
113
+ `;
114
+ return dedent.default`
115
+ ${await schemaToInterface(jsonSchema, inputType)}
116
+
117
+ /**
118
+ * ${description}
119
+ */
120
+ async tools.${name}(input: ${inputType}): Promise<string>
121
+ `;
122
+ }
123
+ //#endregion
41
124
  //#region src/transform.ts
42
125
  /**
43
126
  * AST-based code transform pipeline for the REPL.
@@ -113,7 +196,11 @@ function transformForEval(code) {
113
196
  }
114
197
  function isTSOnlyNode(node) {
115
198
  const t = node.type;
116
- return t === "TSTypeAliasDeclaration" || t === "TSInterfaceDeclaration" || t === "TSEnumDeclaration" || t === "TSModuleDeclaration" || t === "TSDeclareFunction" || t.startsWith("TS");
199
+ if (t === "TSTypeAliasDeclaration" || t === "TSInterfaceDeclaration" || t === "TSEnumDeclaration" || t === "TSModuleDeclaration" || t === "TSDeclareFunction" || t.startsWith("TS")) return true;
200
+ if (t === "VariableDeclaration" && node.declare === true) return true;
201
+ if (t === "ImportDeclaration" && node.importKind === "type") return true;
202
+ if (t === "ExportNamedDeclaration" && node.exportKind === "type") return true;
203
+ return false;
117
204
  }
118
205
  /**
119
206
  * Rewrite a top-level VariableDeclaration to globalThis assignments.
@@ -165,7 +252,11 @@ function stripTypeAnnotations(s, node) {
165
252
  } });
166
253
  }
167
254
  function stripTypeAnnotationFromNode(s, n, offset = 0) {
168
- if (n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - offset, n.typeAnnotation.end - offset);
255
+ if (n.optional === true && n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - 1 - offset, n.typeAnnotation.end - offset);
256
+ else if (n.optional === true && !n.typeAnnotation) {
257
+ const nameEnd = n.type === "Identifier" && typeof n.name === "string" ? n.start + n.name.length : null;
258
+ if (nameEnd != null) s.remove(nameEnd - offset, nameEnd + 1 - offset);
259
+ } else if (n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - offset, n.typeAnnotation.end - offset);
169
260
  if (n.returnType && n.returnType.start != null) s.remove(n.returnType.start - offset, n.returnType.end - offset);
170
261
  if (n.typeParameters && n.typeParameters.start != null) s.remove(n.typeParameters.start - offset, n.typeParameters.end - offset);
171
262
  if (n.typeArguments && n.typeArguments.start != null) s.remove(n.typeArguments.start - offset, n.typeArguments.end - offset);
@@ -231,228 +322,38 @@ function stripTypeSyntax(code) {
231
322
  return magicString.toString();
232
323
  }
233
324
  //#endregion
234
- //#region src/skills.ts
235
- /**
236
- * File extensions the loader will enumerate from a skill directory.
237
- */
238
- const SKILL_MODULE_EXTENSIONS = [
239
- ".js",
240
- ".mjs",
241
- ".cjs",
242
- ".ts",
243
- ".mts",
244
- ".cts",
245
- ".jsx",
246
- ".tsx"
247
- ];
248
- /**
249
- * Hard cap on total bytes pulled for one skill's bundle (1 MiB).
250
- */
251
- const MAX_SKILL_BUNDLE_BYTES = 1 * 1024 * 1024;
252
- /**
253
- * Validates a skill name against the spec's kebab-case rule.
254
- */
255
- const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
256
- /**
257
- * Matches `"@/skills/<name>"` or `'@/skills/<name>'` references in source.
258
- * Template literals and computed specifiers are not caught.
259
- */
260
- const SKILL_SPECIFIER_RE = /["']@\/skills\/([a-z0-9]+(?:-[a-z0-9]+)*)["']/g;
261
- /**
262
- * List every code-extension file under `skillDir` (recursive).
263
- */
264
- async function enumerateCodeFiles(backend, skillDir, skillName) {
265
- const seen = /* @__PURE__ */ new Set();
266
- for (const ext of SKILL_MODULE_EXTENSIONS) {
267
- const result = await backend.glob(`**/*${ext}`, skillDir);
268
- if (result.error !== void 0) throw new Error(`Skill '${skillName}': failed to list '${skillDir}': ${result.error}`);
269
- const matches = result.files ?? [];
270
- for (const match of matches) seen.add(match.path);
271
- }
272
- return [...seen].sort();
273
- }
274
- /**
275
- * Decode download responses into [path, source] pairs.
276
- */
277
- function decodeFiles(responses, skillName) {
278
- const decoder = new TextDecoder("utf-8", { fatal: true });
279
- const pairs = [];
280
- for (const response of responses) {
281
- if (response.error !== null || response.content === null) throw new Error(`Skill '${skillName}': failed to download '${response.path}': ${response.error ?? "no content"}`);
282
- let source;
283
- try {
284
- source = decoder.decode(response.content);
285
- } catch {
286
- throw new Error(`Skill '${skillName}': file '${response.path}' is not valid UTF-8`);
287
- }
288
- pairs.push([response.path, source]);
289
- }
290
- return pairs;
291
- }
292
- /**
293
- * Throws an Error when the total decoded size of all files exceeds
294
- * `MAX_SKILL_BUNDLE_BYTES`. Counts characters rather than bytes, which
295
- * over-counts multi-byte UTF-8. Intentionally errs toward rejection.
296
- */
297
- function validateBundleSize(pairs, skillName) {
298
- let total = 0;
299
- for (const [, source] of pairs) total += source.length;
300
- if (total > 1048576) throw new Error(`Skill '${skillName}': bundle exceeds ${MAX_SKILL_BUNDLE_BYTES} bytes (total ${total})`);
301
- }
325
+ //#region src/eval-queue.ts
302
326
  /**
303
- * Express `absolutePath` as a POSIX-relative path under `skillDir`.
304
- * Throws an Error if the path escapes the skill directory which indicates
305
- * a backend bug, not a user error.
306
- */
307
- function relativeUnder(skillDir, absolutePath, skillName) {
308
- const rel = node_path_posix.relative(skillDir, absolutePath);
309
- if (rel === "" || rel.startsWith("..")) throw new Error(`Skill '${skillName}': file ${absolutePath} is not under '${skillDir}'`);
310
- return rel;
311
- }
312
- /**
313
- * Build the relative-path → source map, applying `stripTypeSyntax` to each file.
314
- */
315
- function buildFilesMap(skillDir, entryRel, pairs, skillName) {
316
- const files = /* @__PURE__ */ new Map();
317
- let entryPresent = false;
318
- for (const [absPath, source] of pairs) {
319
- const rel = relativeUnder(skillDir, absPath, skillName);
320
- files.set(rel, stripTypeSyntax(source));
321
- if (rel === entryRel) entryPresent = true;
322
- }
323
- if (!entryPresent) throw new Error(`Skill '${skillName}': module path '${entryRel}' did not match any file in the skill directory`);
324
- return files;
325
- }
326
- /**
327
- * Build a `LoadedSkill` from a skill's metadata and a backend handle.
328
- *
329
- * Enumerates code files under the skill directory, downloads them,
330
- * strips TypeScript syntax, and validates the entrypoint is present.
331
- */
332
- async function loadSkill(metadata, backend) {
333
- const name = metadata.name;
334
- if (!SKILL_NAME_RE.test(name)) throw new Error(`Skill name '${name}' is not a valid kebab-case identifier`);
335
- const entryRel = metadata.module;
336
- if (entryRel === void 0 || entryRel === "") throw new Error(`Skill '${name}' has no 'module' frontmatter key - only skills with a declared entrypoint are installable`);
337
- const adapted = (0, deepagents.adaptBackendProtocol)(backend);
338
- if (adapted.downloadFiles === void 0) throw new Error(`Skill '${name}': backend does not implement downloadFiles`);
339
- const skillDir = node_path_posix.dirname(metadata.path);
340
- const codeFiles = await enumerateCodeFiles(adapted, skillDir, name);
341
- if (codeFiles.length === 0) throw new Error(`Skill '${name}': no JS/TS files under '${skillDir}'`);
342
- const filePairs = decodeFiles(await adapted.downloadFiles(codeFiles), name);
343
- validateBundleSize(filePairs, name);
344
- const files = buildFilesMap(skillDir, entryRel, filePairs, name);
345
- return {
346
- name,
347
- specifier: `@/skills/${name}`,
348
- entryRel,
349
- files
350
- };
351
- }
352
- /**
353
- * Extract skill names referenced by `"@/skills/<name>"` literals in source.
327
+ * Serializes async operations on a shared WASM module.
354
328
  *
355
- * Used as a pre-eval scan so the middleware can surface `SkillNotAvailable`
356
- * before evaluation starts. Dynamic imports with computed specifiers are
357
- * not detected.
329
+ * The quickjs-emscripten asyncify variant allows only one concurrent
330
+ * async call per module instance. This queue enforces that constraint
331
+ * by chaining operations into a promise queue — each caller waits for
332
+ * the previous one to finish before executing.
358
333
  */
359
- function scanSkillReferences(source) {
360
- const names = /* @__PURE__ */ new Set();
361
- const matches = source.matchAll(SKILL_SPECIFIER_RE);
362
- for (const match of matches) names.add(match[1]);
363
- return names;
364
- }
365
- //#endregion
366
- //#region src/errors.ts
367
- /**
368
- * Thrown when a single eval exhausts its configured PTC call budget.
369
- */
370
- var PTCCallBudgetExceededError = class extends Error {
371
- limit;
372
- attempted;
373
- functionName;
374
- constructor(options) {
375
- super(`PTC call budget exceeded (limit=${options.limit}, attempted=${options.attempted}, function=${options.functionName})`);
376
- this.name = "PTCCallBudgetExceededError";
377
- this.limit = options.limit;
378
- this.attempted = options.attempted;
379
- this.functionName = options.functionName;
334
+ var AsyncEvalQueue = class {
335
+ tail = Promise.resolve();
336
+ /**
337
+ * Enqueue an async operation. The operation will not start until all
338
+ * previously enqueued operations have completed.
339
+ */
340
+ async enqueue(fn) {
341
+ let release;
342
+ const gate = new Promise((r) => {
343
+ release = r;
344
+ });
345
+ const prev = this.tail;
346
+ this.tail = gate;
347
+ return prev.then(async () => {
348
+ try {
349
+ return await fn();
350
+ } finally {
351
+ release();
352
+ }
353
+ });
380
354
  }
381
355
  };
382
356
  //#endregion
383
- //#region src/utils.ts
384
- /**
385
- * Convert a snake_case or kebab-case string to camelCase.
386
- */
387
- function toCamelCase(name) {
388
- return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
389
- }
390
- /**
391
- * Format the result of a REPL evaluation for the agent.
392
- */
393
- function formatReplResult(result) {
394
- const parts = [];
395
- if (result.logs.length > 0) {
396
- let logsText = result.logs.join("\n");
397
- if (result.logsDroppedChars > 0) logsText += `\n[truncated ${result.logsDroppedChars} chars]`;
398
- parts.push(logsText);
399
- }
400
- if (result.ok) {
401
- if (result.value !== void 0) {
402
- const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
403
- parts.push(`→ ${formatted}`);
404
- }
405
- } else if (result.error) {
406
- const errName = result.error.name || "Error";
407
- const errMsg = result.error.message || "Unknown error";
408
- parts.push(`${errName}: ${errMsg}`);
409
- if (result.error.stack) parts.push(result.error.stack);
410
- }
411
- return parts.join("\n") || "(no output)";
412
- }
413
- function safeToJsonSchema(schema) {
414
- try {
415
- return (0, _langchain_core_utils_json_schema.toJsonSchema)(schema);
416
- } catch {
417
- return;
418
- }
419
- }
420
- async function schemaToInterface(jsonSchema, interfaceName) {
421
- return (await (0, json_schema_to_typescript.compile)({
422
- ...jsonSchema,
423
- additionalProperties: false
424
- }, interfaceName, {
425
- bannerComment: "",
426
- additionalProperties: false
427
- })).replace(/^export /, "").trimEnd();
428
- }
429
- function capitalize(s) {
430
- return s.charAt(0).toUpperCase() + s.slice(1);
431
- }
432
- async function toolToTypeSignature(name, description, jsonSchema) {
433
- const inputType = `${capitalize(name)}Input`;
434
- if (!jsonSchema || !jsonSchema.properties) return dedent.default`
435
- /**
436
- * ${description}
437
- */
438
- async tools.${name}(input: Record<string, unknown>): Promise<string>
439
- `;
440
- return dedent.default`
441
- ${await schemaToInterface(jsonSchema, inputType)}
442
-
443
- /**
444
- * ${description}
445
- */
446
- async tools.${name}(input: ${inputType}): Promise<string>
447
- `;
448
- }
449
- /**
450
- * Render a pre-eval error when referenced skills are not available on the agent.
451
- */
452
- function formatSkillNotAvailable(missing) {
453
- return `Skills unavailable: ${[...missing].sort().join(", ")}`;
454
- }
455
- //#endregion
456
357
  //#region src/session.ts
457
358
  /**
458
359
  * Core REPL engine built on quickjs-emscripten (asyncify variant).
@@ -477,64 +378,78 @@ const DEFAULT_MAX_STACK_SIZE = 320 * 1024;
477
378
  const DEFAULT_EXECUTION_TIMEOUT = 5e3;
478
379
  const DEFAULT_MAX_PTC_CALLS = 256;
479
380
  const DEFAULT_MAX_RESULTS_CHARS = 4e3;
381
+ const LINE_NUMBER_RE = /^\s*\d+(?:\.\d+)?\t/;
480
382
  const variantImport = import("@jitl/quickjs-ng-wasmfile-release-asyncify");
481
- async function newAsyncModule() {
482
- const variant = await variantImport;
483
- return (0, quickjs_emscripten_core.newQuickJSAsyncWASMModuleFromVariant)(variant.default ?? variant);
484
- }
485
- function makeErrorSource(message) {
486
- return `throw { name: "Error", message: ${JSON.stringify(message)} };`;
487
- }
488
383
  /**
489
- * Parse a canonicalized skill specifier into `{ name, rel }`.
490
- * Returns `undefined` for anything that isn't a valid `@/skills/<name>` or
491
- * `@/skills/<name>/<rel>` shape. `rel` is absent for the bare form.
384
+ * Process-global eval queue. Serializes all evalCodeAsync calls across
385
+ * sessions to enforce the asyncify one-at-a-time constraint.
492
386
  */
493
- function parseSkillSpecifier(specifier) {
494
- if (!specifier.startsWith("@/skills/")) return;
495
- const tail = specifier.slice(9);
496
- const slashIdx = tail.indexOf("/");
497
- const name = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
498
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return;
499
- const rel = slashIdx === -1 ? void 0 : tail.slice(slashIdx + 1);
500
- if (rel !== void 0 && rel === "") return;
501
- return {
502
- name,
503
- rel
504
- };
505
- }
387
+ const sharedEvalQueue = new AsyncEvalQueue();
506
388
  /**
507
- * Return the `@/skills/<name>` prefix for the skill that owns `base`, or `undefined`.
389
+ * Process-global WASM module shared by all sessions.
390
+ *
391
+ * Each session creates its own runtime and context on this module,
392
+ * providing full isolation for globals, heap, and stack. The module
393
+ * itself is stateless between runtimes — only the compiled WASM code
394
+ * and Emscripten infrastructure are shared.
395
+ *
396
+ * This is safe because:
397
+ * - The module loader is synchronous (preloaded skill cache), so
398
+ * imports don't cause asyncify suspensions.
399
+ * - Tool injection uses the promise-based pattern (newFunction +
400
+ * newPromise), not newAsyncifiedFunction, so tool calls don't
401
+ * cause asyncify suspensions.
402
+ * - The eval queue serializes evalCodeAsync calls to satisfy the
403
+ * one-concurrent-async-call-per-module constraint.
508
404
  */
509
- function matchSkillPrefix(base) {
510
- const parsed = parseSkillSpecifier(base);
511
- if (parsed === void 0) return;
512
- return `@/skills/${parsed.name}`;
405
+ let sharedModulePromise;
406
+ function getSharedModule() {
407
+ if (!sharedModulePromise) sharedModulePromise = (async () => {
408
+ const variant = await variantImport;
409
+ return (0, quickjs_emscripten_core.newQuickJSAsyncWASMModuleFromVariant)(variant.default ?? variant);
410
+ })();
411
+ return sharedModulePromise;
513
412
  }
514
413
  /**
515
- * Return the directory portion of a slash-separated specifier path.
414
+ * Unwrap a PTC tool result to a plain string for use inside QuickJS.
415
+ *
416
+ * Tool results may arrive as a raw string, or as an array of LangChain
417
+ * content blocks (`{ type: "text", text: "..." }`). Blocks are joined
418
+ * with newlines; non-text block types are silently skipped. Anything
419
+ * else (objects, nulls) is JSON-serialised as a fallback.
420
+ *
421
+ * @param result - Raw return value from `tool.invoke()`.
422
+ * @returns Plain string representation of the tool output.
516
423
  */
517
- function posixDirname(p) {
518
- const idx = p.lastIndexOf("/");
519
- if (idx === -1) return "";
520
- return p.slice(0, idx);
424
+ function extractToolText(result) {
425
+ if (typeof result === "string") return result;
426
+ if (Array.isArray(result)) {
427
+ const texts = [];
428
+ for (const block of result) if (typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string") texts.push(block.text);
429
+ if (texts.length > 0) return texts.join("\n");
430
+ }
431
+ return JSON.stringify(result);
521
432
  }
522
433
  /**
523
- * POSIX join for slash-separated specifiers. Avoids `node:path/posix`
524
- * since session.ts is consumed in browser bundles.
434
+ * Remove the `cat -n` line-number prefix from every line of a string.
435
+ *
436
+ * The filesystem backend formats file content with line numbers in the
437
+ * form `" N\t"` so human readers can navigate by line. That prefix
438
+ * is useful for the agent but noise for QuickJS code that parses the
439
+ * text programmatically (e.g. swarm reading `/context.txt`).
440
+ *
441
+ * The function is conservative: if any non-empty line lacks the prefix,
442
+ * the text is returned unchanged so nothing is silently corrupted.
443
+ *
444
+ * @param text - Raw file content, possibly line-number prefixed.
445
+ * @returns Content with line-number prefixes stripped, or the original
446
+ * text if it doesn't match the expected format throughout.
525
447
  */
526
- function posixJoin(base, rel) {
527
- const out = [];
528
- const segments = `${base}/${rel}`.split("/");
529
- for (const segment of segments) {
530
- if (segment === "" || segment === ".") continue;
531
- if (segment === "..") {
532
- out.pop();
533
- continue;
534
- }
535
- out.push(segment);
536
- }
537
- return out.join("/");
448
+ function stripLineNumbers(text) {
449
+ const lines = text.split("\n");
450
+ if (lines.length === 0) return text;
451
+ if (!lines.every((l) => l === "" || LINE_NUMBER_RE.test(l))) return text;
452
+ return lines.map((l) => l.replace(LINE_NUMBER_RE, "")).join("\n");
538
453
  }
539
454
  /**
540
455
  * Fixed-size character buffer for capturing console output from the QuickJS VM.
@@ -596,11 +511,26 @@ var ReplSession = class ReplSession {
596
511
  context = null;
597
512
  consoleBuffer = new ConsoleBuffer(DEFAULT_MAX_RESULTS_CHARS);
598
513
  options;
599
- skillsContext;
600
- skillsLoaded = /* @__PURE__ */ new Map();
601
- skillsFailed = /* @__PURE__ */ new Map();
602
514
  maxPtcCalls;
603
515
  ptcCallsRemaining = null;
516
+ subagentQueue = null;
517
+ bridgeDispatchRef = null;
518
+ /** Allowed keys in the subagent input object. */
519
+ static SUBAGENT_ALLOWED_KEYS = new Set([
520
+ "description",
521
+ "subagentType",
522
+ "responseSchema"
523
+ ]);
524
+ /**
525
+ * Reset the shared WASM module. Forces the next session to instantiate
526
+ * a fresh module. Only needed in tests where module state must be
527
+ * isolated between test files.
528
+ *
529
+ * @internal
530
+ */
531
+ static resetSharedModule() {
532
+ sharedModulePromise = void 0;
533
+ }
604
534
  constructor(id, options = {}) {
605
535
  this.id = id;
606
536
  this.options = options;
@@ -608,8 +538,8 @@ var ReplSession = class ReplSession {
608
538
  }
609
539
  async ensureStarted() {
610
540
  if (this.runtime) return;
611
- const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, skillsEnabled = false, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, captureConsole = true } = this.options;
612
- const runtime = (await newAsyncModule()).newRuntime();
541
+ const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, captureConsole = true } = this.options;
542
+ const runtime = (await getSharedModule()).newRuntime();
613
543
  runtime.setMemoryLimit(memoryLimitBytes);
614
544
  runtime.setMaxStackSize(maxStackSizeBytes);
615
545
  const context = runtime.newContext();
@@ -618,67 +548,15 @@ var ReplSession = class ReplSession {
618
548
  this.consoleBuffer = new ConsoleBuffer(maxResultChars);
619
549
  if (captureConsole) this.setupConsole();
620
550
  if (tools !== void 0 && tools.length > 0) this.injectTools(tools);
621
- if (skillsEnabled) this.installModuleLoader();
622
- }
623
- /**
624
- * Load the skill into cache on first access and replay cached errors.
625
- */
626
- async ensureSkillLoaded(name) {
627
- const cached = this.skillsLoaded.get(name);
628
- if (cached !== void 0) return cached;
629
- const cachedError = this.skillsFailed.get(name);
630
- if (cachedError !== void 0) throw cachedError;
631
- const ctx = this.skillsContext;
632
- if (ctx === void 0) throw new Error(`Skill '${name}' referenced but skills are not configured for this session`);
633
- const metadata = ctx.metadata.find((m) => m.name === name);
634
- if (metadata === void 0) throw new Error(`Skill '${name}' referenced but not available on this agent`);
635
- try {
636
- const loaded = await loadSkill(metadata, ctx.backend);
637
- this.skillsLoaded.set(name, loaded);
638
- return loaded;
639
- } catch (err) {
640
- this.skillsFailed.set(name, err);
641
- throw err;
642
- }
643
- }
644
- async resolveSpecifier(specifier) {
645
- const parsed = parseSkillSpecifier(specifier);
646
- if (parsed === void 0) return makeErrorSource(`Module not found: ${specifier}`);
647
- let loaded;
648
- try {
649
- loaded = await this.ensureSkillLoaded(parsed.name);
650
- } catch (err) {
651
- return makeErrorSource(err.message ?? String(err));
652
- }
653
- if (parsed.rel === void 0) {
654
- const source = loaded.files.get(loaded.entryRel);
655
- if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': entrypoint '${loaded.entryRel}' missing from bundle`);
656
- return source;
551
+ const { subagentBridge } = this.options;
552
+ if (subagentBridge) {
553
+ this.subagentQueue = new p_queue.default({ concurrency: subagentBridge.maxConcurrency });
554
+ this.injectSubagentBridge(subagentBridge.dispatch);
657
555
  }
658
- const source = loaded.files.get(parsed.rel);
659
- if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': '${parsed.rel}' not found in bundle`);
660
- return source;
661
- }
662
- /**
663
- * Canonicalize an `import` specifier. Bare specifiers pass through;
664
- * relative specifiers are resolved against the importing module's path.
665
- * Traversal out of a skill's `@/skills/<name>/` namespace is rejected.
666
- */
667
- normalizeSpecifier(base, requested) {
668
- if (!(requested.startsWith("./") || requested.startsWith("../"))) return requested;
669
- const parsed = parseSkillSpecifier(base);
670
- const resolved = posixJoin(parsed !== void 0 && parsed.rel === void 0 ? base : posixDirname(base), requested);
671
- const skillPrefix = matchSkillPrefix(base);
672
- if (skillPrefix === void 0) return resolved;
673
- if (!resolved.startsWith(`${skillPrefix}/`)) return `__resolve_error__:${requested} escapes ${skillPrefix}`;
674
- return resolved;
675
- }
676
- /**
677
- * Wire the QuickJS module loader and normalizer on this session's runtime.
678
- */
679
- installModuleLoader() {
680
- if (this.runtime === null) return;
681
- this.runtime.setModuleLoader(async (specifier) => this.resolveSpecifier(specifier), (base, requested) => this.normalizeSpecifier(base, requested));
556
+ const sessionId = this.options.sessionId ?? "default";
557
+ const sessionIdHandle = context.newString(sessionId);
558
+ context.setProp(context.global, "__sessionId__", sessionIdHandle);
559
+ sessionIdHandle.dispose();
682
560
  }
683
561
  /**
684
562
  * Initialise the per-eval PTC counter. Called at the top of every `eval()`.
@@ -741,14 +619,6 @@ var ReplSession = class ReplSession {
741
619
  if (session) session.dispose();
742
620
  }
743
621
  /**
744
- * Push the current skills metadata + backend into the session.
745
- * Called by the middleware once per `eval` invocation, before eval runs.
746
- * Pass `undefined` to clear the context (no skill imports will resolve).
747
- */
748
- setSkillsContext(ctx) {
749
- this.skillsContext = ctx;
750
- }
751
- /**
752
622
  * Evaluate code in this session.
753
623
  *
754
624
  * Lazily starts the QuickJS runtime on the first call. Code is
@@ -773,7 +643,7 @@ var ReplSession = class ReplSession {
773
643
  if (timeoutMs >= 0) runtime.setInterruptHandler((0, quickjs_emscripten.shouldInterruptAfterDeadline)(Date.now() + timeoutMs));
774
644
  else runtime.setInterruptHandler(() => false);
775
645
  const transformed = transformForEval(code);
776
- const result = await context.evalCodeAsync(transformed);
646
+ const result = await sharedEvalQueue.enqueue(() => context.evalCodeAsync(transformed));
777
647
  if (result.error) {
778
648
  const error = context.dump(result.error);
779
649
  result.error.dispose();
@@ -908,8 +778,9 @@ var ReplSession = class ReplSession {
908
778
  try {
909
779
  this.consumePtcBudget(camelName);
910
780
  const rawInput = typeof input === "object" && input !== null ? input : {};
911
- const result = await t.invoke(rawInput);
912
- const val = context.newString(typeof result === "string" ? result : JSON.stringify(result));
781
+ let text = extractToolText(await t.invoke(rawInput));
782
+ if (t.name === "read_file") text = stripLineNumbers(text);
783
+ const val = context.newString(text);
913
784
  promise.resolve(val);
914
785
  val.dispose();
915
786
  } catch (e) {
@@ -928,11 +799,116 @@ var ReplSession = class ReplSession {
928
799
  context.setProp(context.global, "tools", toolsNs);
929
800
  toolsNs.dispose();
930
801
  }
802
+ /**
803
+ * Install the `task` global on the QuickJS context.
804
+ *
805
+ * Registers the host function directly as `globalThis.task`,
806
+ * then freezes it via `evalCode`. Structured results (when
807
+ * responseSchema is provided) are marshaled into native QuickJS
808
+ * objects on the host side — no JS wrapper needed.
809
+ */
810
+ /**
811
+ * Replace the active bridge dispatch with a fresh one.
812
+ *
813
+ * Call this before each eval so the dispatch closure carries
814
+ * the current invocation's config (tracing callbacks, run ID, etc.)
815
+ * rather than the stale config from session creation.
816
+ */
817
+ updateBridgeDispatch(dispatch) {
818
+ if (this.bridgeDispatchRef) this.bridgeDispatchRef.current = dispatch;
819
+ }
820
+ injectSubagentBridge(dispatch) {
821
+ const context = this.context;
822
+ const queue = this.subagentQueue;
823
+ this.bridgeDispatchRef = { current: dispatch };
824
+ const ref = this.bridgeDispatchRef;
825
+ const hostFn = context.newFunction("task", (inputHandle) => {
826
+ const input = context.dump(inputHandle);
827
+ const promise = context.newPromise();
828
+ (async () => {
829
+ try {
830
+ if (input == null || typeof input !== "object" || Array.isArray(input)) throw new Error("task: expected an object argument");
831
+ const obj = { ...input };
832
+ if ("subagent_type" in obj) {
833
+ obj.subagentType ??= obj.subagent_type;
834
+ delete obj.subagent_type;
835
+ }
836
+ if ("response_schema" in obj) {
837
+ obj.responseSchema ??= obj.response_schema;
838
+ delete obj.response_schema;
839
+ }
840
+ const unknownKeys = Object.keys(obj).filter((k) => !ReplSession.SUBAGENT_ALLOWED_KEYS.has(k));
841
+ if (unknownKeys.length > 0) throw new Error(`task: unknown keys: ${unknownKeys.join(", ")}. Allowed: ${[...ReplSession.SUBAGENT_ALLOWED_KEYS].join(", ")}`);
842
+ const { description, subagentType, responseSchema } = obj;
843
+ if (typeof description !== "string" || description.length === 0) throw new Error("task: 'description' is required and must be a non-empty string");
844
+ if (typeof subagentType !== "string" || subagentType.length === 0) throw new Error("task: 'subagentType' is required and must be a non-empty string");
845
+ if (responseSchema !== void 0 && (responseSchema == null || typeof responseSchema !== "object" || Array.isArray(responseSchema))) throw new Error("task: 'responseSchema' must be a plain object (JSON Schema) when provided");
846
+ const result = await queue.add(() => ref.current({
847
+ description,
848
+ subagentType,
849
+ ...responseSchema !== void 0 && { responseSchema }
850
+ }));
851
+ if (typeof result === "string") {
852
+ const val = context.newString(result);
853
+ promise.resolve(val);
854
+ val.dispose();
855
+ } else {
856
+ const jsonResult = context.evalCode(`(${JSON.stringify(result)})`);
857
+ if (jsonResult.error) {
858
+ const errDump = context.dump(jsonResult.error);
859
+ jsonResult.error.dispose();
860
+ throw new Error(`task: failed to marshal structured response: ${JSON.stringify(errDump)}`);
861
+ }
862
+ promise.resolve(jsonResult.value);
863
+ jsonResult.value.dispose();
864
+ }
865
+ } catch (e) {
866
+ const msg = e != null && typeof e.message === "string" ? e.message : String(e);
867
+ const err = context.newError(msg);
868
+ promise.reject(err);
869
+ err.dispose();
870
+ }
871
+ promise.settled.then(context.runtime.executePendingJobs);
872
+ })();
873
+ return promise.handle;
874
+ });
875
+ context.setProp(context.global, "task", hostFn);
876
+ hostFn.dispose();
877
+ context.evalCode("Object.freeze(globalThis.task);Object.defineProperty(globalThis, 'task', { value: globalThis.task, writable: false, configurable: false,}); undefined");
878
+ }
931
879
  };
932
880
  //#endregion
881
+ //#region src/subagent-dispatch.ts
882
+ const SCHEMA_MAX_BYTES = 4096;
883
+ const SCHEMA_MAX_DEPTH = 5;
884
+ const SCHEMA_MAX_PROPERTIES = 32;
885
+ /**
886
+ * Validate that a response schema does not exceed size, depth, or
887
+ * property-count limits.
888
+ *
889
+ * @throws Error if any limit is exceeded.
890
+ */
891
+ function validateResponseSchema(schema) {
892
+ const serialized = JSON.stringify(schema);
893
+ if (serialized.length > SCHEMA_MAX_BYTES) throw new Error(`responseSchema exceeds ${SCHEMA_MAX_BYTES} byte limit (${serialized.length} bytes)`);
894
+ function check(node, depth, propCount) {
895
+ if (depth > SCHEMA_MAX_DEPTH) throw new Error(`responseSchema exceeds maximum nesting depth of ${SCHEMA_MAX_DEPTH}`);
896
+ const props = node.properties;
897
+ if (props != null && typeof props === "object" && !Array.isArray(props)) {
898
+ const propObj = props;
899
+ propCount.value += Object.keys(propObj).length;
900
+ if (propCount.value > SCHEMA_MAX_PROPERTIES) throw new Error(`responseSchema exceeds maximum of ${SCHEMA_MAX_PROPERTIES} properties`);
901
+ for (const value of Object.values(propObj)) if (value != null && typeof value === "object" && !Array.isArray(value)) check(value, depth + 1, propCount);
902
+ }
903
+ const items = node.items;
904
+ if (items != null && typeof items === "object" && !Array.isArray(items)) check(items, depth + 1, propCount);
905
+ }
906
+ check(schema, 0, { value: 0 });
907
+ }
908
+ //#endregion
933
909
  //#region src/middleware.ts
934
910
  /**
935
- * REPL middleware for deepagents.
911
+ * Code Interpreter middleware for deepagents.
936
912
  *
937
913
  * Provides an `eval` tool that runs JavaScript in a WASM-sandboxed QuickJS
938
914
  * interpreter. Supports:
@@ -940,6 +916,216 @@ var ReplSession = class ReplSession {
940
916
  * - Programmatic tool calling (PTC) — expose agent or custom tools inside the REPL
941
917
  */
942
918
  const DEFAULT_TOOL_NAME = "eval";
919
+ /**
920
+ * Render the subagent dispatch prompt section for the system message.
921
+ * Ported from the Python `_SUBAGENT_SYSTEM_PROMPT_TEMPLATE`.
922
+ */
923
+ function renderSubagentPrompt(toolName) {
924
+ return dedent.default`
925
+
926
+ ### Dispatching Subagents with \`task\`
927
+
928
+ \`task\` is your primitive for running configured subagents from inside the
929
+ JavaScript REPL. You orchestrate everything else - fan-out, filtering,
930
+ deduplication, multi-stage flow, and synthesis - in plain JavaScript.
931
+
932
+ #### The primitive
933
+
934
+ \`\`\`javascript
935
+ await task({
936
+ description, // full autonomous task prompt
937
+ subagentType, // configured subagent name
938
+ responseSchema, // optional JSON Schema for structured output
939
+ }); // -> Promise<unknown>
940
+ \`\`\`
941
+
942
+ \`task\` runs a full agentic loop for the selected configured subagent. The
943
+ subagent can use whatever tools it was configured with, iterate, inspect
944
+ context, and return one final result. \`subagentType\` is required; use one of
945
+ the configured subagent names.
946
+
947
+ \`description\` is the only prompt the subagent receives for this dispatch. Make
948
+ it complete: include the goal, constraints, relevant context, what to inspect,
949
+ and the exact shape or level of detail you expect back. Each dispatch is
950
+ stateless from the caller's perspective; you cannot send follow-up messages to
951
+ the same subagent run.
952
+
953
+ \`responseSchema\` is optional. When provided, the resolved value is already a
954
+ typed JavaScript value matching the schema. Do not call \`JSON.parse\` unless the
955
+ subagent intentionally returned a JSON string. Dynamic schemas work for
956
+ declarative subagents; runnable-backed subagents reject dynamic schemas because
957
+ their runnable is already compiled.
958
+
959
+ #### Approval model
960
+
961
+ \`task\` dispatches from inside the already-running \`${toolName}\` call. It
962
+ does not route through the parent agent's \`ToolNode\`-managed \`task\` tool and
963
+ does not trigger parent-level \`interrupt_on\` / HITL approval for each dispatch.
964
+ Declarative subagents still honor approval middleware configured inside their
965
+ own spec. If you need approval before launching a subagent from the parent, use
966
+ the normal \`task\` tool outside JavaScript or ensure the \`${toolName}\` call
967
+ itself is approval-gated.
968
+
969
+ #### Mental model
970
+
971
+ Hold your work in JS: an array of items in, an array of results out. Merge each
972
+ dispatch result back onto its item. Multi-stage analysis means: run a pass,
973
+ filter or regroup the array in JS, then run another pass over the survivors.
974
+
975
+ Prefer one \`${toolName}\` call that performs the whole workflow. Splitting the
976
+ workflow across multiple \`${toolName}\` calls costs model turns and forces you to
977
+ re-establish state.
978
+
979
+ #### Fan out with bounded concurrency
980
+
981
+ Dispatch independent work in parallel with \`Promise.all\`, but in explicit
982
+ batches around 10 so you do not launch hundreds of subagents at once. The bridge
983
+ enforces a hard per-REPL cap of 32 concurrent subagent calls.
984
+
985
+ \`\`\`javascript
986
+ const batchSize = 10;
987
+ const reviewed = [];
988
+ for (let i = 0; i < items.length; i += batchSize) {
989
+ const batch = items.slice(i, i + batchSize);
990
+ reviewed.push(...(await Promise.all(batch.map(async (it) => {
991
+ const result = await task({
992
+ description: "Review " + it.file + " for SQL injection. Cite line numbers.",
993
+ subagentType: "reviewer",
994
+ responseSchema: {
995
+ type: "object",
996
+ properties: {
997
+ vulnerabilities: {
998
+ type: "array",
999
+ items: {
1000
+ type: "object",
1001
+ properties: {
1002
+ type: { type: "string" },
1003
+ line: { type: "number" },
1004
+ evidence: { type: "string" },
1005
+ },
1006
+ required: ["type", "line", "evidence"],
1007
+ },
1008
+ },
1009
+ },
1010
+ required: ["vulnerabilities"],
1011
+ },
1012
+ });
1013
+ return { ...it, ...result };
1014
+ }))));
1015
+ }
1016
+ \`\`\`
1017
+
1018
+ #### Use parent JS for cheap work; use subagents for agentic work
1019
+
1020
+ Use JavaScript in the parent REPL for deterministic orchestration: joining
1021
+ arrays, deduping, sorting, filtering, grouping, batching, and merging results.
1022
+ If the \`tools.*\` namespace is exposed, also use it to pre-read files or collect
1023
+ shared data once, then pass only the relevant content to each subagent in
1024
+ \`description\`.
1025
+
1026
+ Use \`task\` for work that benefits from an autonomous agentic loop: reading
1027
+ or searching with the subagent's own tools, inspecting multiple files, following
1028
+ leads, making judgment calls, or producing a final synthesized report.
1029
+
1030
+ #### Pre-read shared context in the parent when useful
1031
+
1032
+ If many subagents need the same source list or file content and \`tools.*\` is
1033
+ available, gather that context once in the parent REPL before dispatching:
1034
+
1035
+ \`\`\`javascript
1036
+ const files = (await tools.glob({ pattern: "src/**/*.ts" }))
1037
+ .split("\\n")
1038
+ .filter(Boolean);
1039
+
1040
+ const items = await Promise.all(files.map(async (file) => {
1041
+ const content = await tools.readFile({ file_path: file });
1042
+ return { file, content };
1043
+ }));
1044
+
1045
+ const batchSize = 10;
1046
+ const results = [];
1047
+ for (let i = 0; i < items.length; i += batchSize) {
1048
+ const batch = items.slice(i, i + batchSize);
1049
+ results.push(...(await Promise.all(batch.map(async (it) => {
1050
+ const finding = await task({
1051
+ description:
1052
+ "Review this file for auth bypasses. Return concrete findings only.\\n\\n" +
1053
+ "File: " + it.file + "\\n\\n" +
1054
+ it.content,
1055
+ subagentType: "reviewer",
1056
+ responseSchema: {
1057
+ type: "object",
1058
+ properties: {
1059
+ findings: { type: "array", items: { type: "object" } },
1060
+ },
1061
+ required: ["findings"],
1062
+ },
1063
+ });
1064
+ return { ...it, ...finding };
1065
+ }))));
1066
+ }
1067
+ \`\`\`
1068
+
1069
+ #### Compose multiple stages
1070
+
1071
+ Filter the array in JS between passes. For example: first ask subagents for a
1072
+ cheap classification, filter to the risky items, then dispatch deeper reviews
1073
+ only for those items.
1074
+
1075
+ \`\`\`javascript
1076
+ const tagged = [];
1077
+ for (let i = 0; i < items.length; i += 10) {
1078
+ const batch = items.slice(i, i + 10);
1079
+ tagged.push(...(await Promise.all(batch.map(async (it) => {
1080
+ const tag = await task({
1081
+ description: "Classify " + it.file + " as handler, util, test, or config.",
1082
+ subagentType: "reviewer",
1083
+ responseSchema: {
1084
+ type: "object",
1085
+ properties: { kind: { type: "string" }, risky: { type: "boolean" } },
1086
+ required: ["kind", "risky"],
1087
+ },
1088
+ });
1089
+ return { ...it, ...tag };
1090
+ }))));
1091
+ }
1092
+
1093
+ const riskyHandlers = tagged.filter((it) => it.kind === "handler" && it.risky);
1094
+ const deepReviews = [];
1095
+ for (let i = 0; i < riskyHandlers.length; i += 10) {
1096
+ const batch = riskyHandlers.slice(i, i + 10);
1097
+ deepReviews.push(...(await Promise.all(batch.map(async (it) => {
1098
+ const review = await task({
1099
+ description: "Deep security review of " + it.file + ". Cite line numbers.",
1100
+ subagentType: "reviewer",
1101
+ });
1102
+ return { ...it, review };
1103
+ }))));
1104
+ }
1105
+ \`\`\`
1106
+
1107
+ #### Get results out without flooding your context
1108
+
1109
+ Keep large result sets in JS variables. Do not \`console.log\` the full result set.
1110
+ If \`tools.writeFile\` is exposed, persist structured output from inside the eval:
1111
+
1112
+ \`\`\`javascript
1113
+ await tools.writeFile({
1114
+ file_path: "/results/subagent-output.json",
1115
+ content: JSON.stringify(deepReviews),
1116
+ });
1117
+ \`\`\`
1118
+
1119
+ Otherwise return a compact summary or a small slice of the results, not the
1120
+ entire intermediate dataset.
1121
+
1122
+ #### Across evals
1123
+
1124
+ Variables persist according to the interpreter persistence mode above, but
1125
+ re-establish what you need in each eval. Doing the whole workflow in one
1126
+ \`${toolName}\` call is usually simplest.
1127
+ `;
1128
+ }
943
1129
  function renderReplSystemPrompt(opts) {
944
1130
  return dedent.default`
945
1131
  ### Interpreter
@@ -947,7 +1133,7 @@ function renderReplSystemPrompt(opts) {
947
1133
  An \`${opts.toolName}\` tool is available. It runs JavaScript in a persistent REPL.
948
1134
  - State (variables, functions) persists across tool calls within a single turn of conversation. They DO NOT persist across multiple turns.
949
1135
  - Top-level \`await\` works; Promises resolve before the call returns.
950
- - Sandboxed: no filesystem, no stdlib, no network, no real clock, no \`fetch\`, no \`require\`.
1136
+ - Runtime sandbox: no built-in filesystem, network, stdlib, or wall-clock APIs (\`fetch\`, \`require\`, \`fs\`, \`process\`, real \`Date.now()\` are unavailable or stubbed). External side effects from inside the REPL are only reachable via the \`tools.*\` namespace when it is exposed (see below); without it, the REPL is pure computation.
951
1137
  - Timeout: ${opts.timeout}s per call. Memory: ${opts.memoryLimitMb} MB total.
952
1138
  - \`console.log\` output is captured and returned alongside the result.
953
1139
  `;
@@ -1004,34 +1190,11 @@ function resolveToolList(items, agentTools) {
1004
1190
  });
1005
1191
  }
1006
1192
  /**
1007
- * Pull `skillsMetadata` from the task input, resolve the backend, and push
1008
- * both into the session. Short-circuits with a `SkillNotAvailable` error if
1009
- * the source references skills the agent doesn't have.
1010
- */
1011
- async function prepareSkillsForEval(session, skillsBackend, code) {
1012
- const taskInput = (0, _langchain_langgraph.getCurrentTaskInput)();
1013
- const metadata = taskInput?.skillsMetadata ?? [];
1014
- const referenced = scanSkillReferences(code);
1015
- if (referenced.size > 0) {
1016
- const known = new Set(metadata.map((m) => m.name));
1017
- const missing = [];
1018
- for (const name of referenced) if (!known.has(name)) missing.push(name);
1019
- if (missing.length > 0) {
1020
- session.setSkillsContext(void 0);
1021
- return formatSkillNotAvailable(missing);
1022
- }
1023
- }
1024
- const resolved = await (0, deepagents.resolveBackend)(skillsBackend, { state: taskInput });
1025
- session.setSkillsContext({
1026
- metadata,
1027
- backend: resolved
1028
- });
1029
- }
1030
- /**
1031
- * Create the REPL middleware.
1193
+ * Create the Code Interpreter middleware.
1032
1194
  */
1033
- function createREPLMiddleware(options = {}) {
1034
- const { ptc, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null, skillsBackend, maxPtcCalls = 256, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, toolName = DEFAULT_TOOL_NAME, captureConsole = true } = options;
1195
+ function createCodeInterpreterMiddleware(options = {}) {
1196
+ const { ptc, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null, maxPtcCalls = 256, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, toolName = DEFAULT_TOOL_NAME, captureConsole = true, subagents = true } = options;
1197
+ const maxSubagentConcurrency = subagents ? 32 : 0;
1035
1198
  if (maxPtcCalls !== null && maxPtcCalls !== void 0 && maxPtcCalls < 1) throw new Error("`maxPtcCalls` must be >= 1 or null");
1036
1199
  const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
1037
1200
  toolName,
@@ -1041,27 +1204,56 @@ function createREPLMiddleware(options = {}) {
1041
1204
  const middlewareId = crypto.randomUUID();
1042
1205
  let cachedPtcPrompt = null;
1043
1206
  let ptcTools = [];
1207
+ let taskTool = null;
1044
1208
  function filterToolsForPtc(allTools) {
1045
1209
  if (!ptc) return [];
1046
1210
  return resolveToolList(ptc, allTools.filter((t) => t.name !== toolName));
1047
1211
  }
1212
+ function findTaskTool(tools) {
1213
+ return tools.find((t) => t.name === "task") ?? null;
1214
+ }
1215
+ function createBridgeDispatch(subagentTaskTool, config) {
1216
+ return async (input) => {
1217
+ const hasSchema = input.responseSchema != null;
1218
+ if (hasSchema) validateResponseSchema(input.responseSchema);
1219
+ const toolConfig = {
1220
+ ...config,
1221
+ configurable: {
1222
+ ...config.configurable,
1223
+ ...hasSchema && { [deepagents.SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY]: input.responseSchema }
1224
+ }
1225
+ };
1226
+ const result = await subagentTaskTool.invoke({
1227
+ description: input.description,
1228
+ subagent_type: input.subagentType
1229
+ }, toolConfig);
1230
+ if (hasSchema && typeof result === "string") try {
1231
+ return JSON.parse(result);
1232
+ } catch {
1233
+ return result;
1234
+ }
1235
+ return result;
1236
+ };
1237
+ }
1048
1238
  return (0, langchain.createMiddleware)({
1049
- name: "REPLMiddleware",
1239
+ name: "CodeInterpreterMiddleware",
1050
1240
  tools: [(0, langchain.tool)(async (input, config) => {
1051
- const sessionKey = `${config.configurable?.thread_id || "__default__"}:${middlewareId}`;
1241
+ const threadId = config.configurable?.thread_id || "__default__";
1242
+ const sessionKey = `${threadId}:${middlewareId}`;
1052
1243
  const session = ReplSession.getOrCreate(sessionKey, {
1053
1244
  memoryLimitBytes,
1054
1245
  maxStackSizeBytes,
1055
1246
  maxPtcCalls,
1056
1247
  tools: ptcTools,
1057
- skillsEnabled: skillsBackend !== void 0,
1058
1248
  maxResultChars,
1059
- captureConsole
1249
+ captureConsole,
1250
+ sessionId: threadId,
1251
+ subagentBridge: taskTool && maxSubagentConcurrency > 0 ? {
1252
+ dispatch: createBridgeDispatch(taskTool, config),
1253
+ maxConcurrency: maxSubagentConcurrency
1254
+ } : void 0
1060
1255
  });
1061
- if (skillsBackend !== void 0) {
1062
- const setupError = await prepareSkillsForEval(session, skillsBackend, input.code);
1063
- if (setupError !== void 0) return setupError;
1064
- }
1256
+ if (taskTool && maxSubagentConcurrency > 0) session.updateBridgeDispatch(createBridgeDispatch(taskTool, config));
1065
1257
  return formatReplResult(await session.eval(input.code, executionTimeoutMs));
1066
1258
  }, {
1067
1259
  name: toolName,
@@ -1069,15 +1261,17 @@ function createREPLMiddleware(options = {}) {
1069
1261
  Evaluate TypeScript/JavaScript code in a sandboxed REPL. State persists across calls.
1070
1262
  Use console.log() for output. Returns the result of the last expression.
1071
1263
  If file or other tools are available, call them via the tools namespace: await tools.readFile({ path }).
1072
- If skills are configured, dynamically import them: await import("@/skills/<name>").
1073
1264
  `,
1074
1265
  metadata: { ls_code_input_language: "javascript" },
1075
1266
  schema: zod_v4.z.object({ code: zod_v4.z.string().describe("TypeScript/JavaScript code to evaluate in the sandboxed REPL") })
1076
1267
  })],
1077
1268
  wrapModelCall: async (request, handler) => {
1078
- ptcTools = filterToolsForPtc(request.tools || []);
1269
+ const agentTools = request.tools || [];
1270
+ ptcTools = filterToolsForPtc(agentTools);
1271
+ if (!taskTool && maxSubagentConcurrency > 0) taskTool = findTaskTool(agentTools);
1079
1272
  if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
1080
- const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(cachedPtcPrompt || "");
1273
+ const subagentPrompt = taskTool && maxSubagentConcurrency > 0 ? renderSubagentPrompt(toolName) : "";
1274
+ const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(subagentPrompt).concat(cachedPtcPrompt || "");
1081
1275
  return handler({
1082
1276
  ...request,
1083
1277
  systemMessage
@@ -1094,17 +1288,13 @@ exports.DEFAULT_EXECUTION_TIMEOUT = DEFAULT_EXECUTION_TIMEOUT;
1094
1288
  exports.DEFAULT_MAX_PTC_CALLS = DEFAULT_MAX_PTC_CALLS;
1095
1289
  exports.DEFAULT_MAX_STACK_SIZE = DEFAULT_MAX_STACK_SIZE;
1096
1290
  exports.DEFAULT_MEMORY_LIMIT = DEFAULT_MEMORY_LIMIT;
1097
- exports.MAX_SKILL_BUNDLE_BYTES = MAX_SKILL_BUNDLE_BYTES;
1098
1291
  exports.PTCCallBudgetExceededError = PTCCallBudgetExceededError;
1099
1292
  exports.ReplSession = ReplSession;
1100
- exports.SKILL_MODULE_EXTENSIONS = SKILL_MODULE_EXTENSIONS;
1101
- exports.createREPLMiddleware = createREPLMiddleware;
1293
+ exports.createCodeInterpreterMiddleware = createCodeInterpreterMiddleware;
1102
1294
  exports.formatReplResult = formatReplResult;
1103
- exports.formatSkillNotAvailable = formatSkillNotAvailable;
1104
- exports.loadSkill = loadSkill;
1105
- exports.scanSkillReferences = scanSkillReferences;
1106
1295
  exports.stripTypeSyntax = stripTypeSyntax;
1107
1296
  exports.toCamelCase = toCamelCase;
1108
1297
  exports.transformForEval = transformForEval;
1298
+ exports.validateResponseSchema = validateResponseSchema;
1109
1299
 
1110
1300
  //# sourceMappingURL=index.cjs.map