@langchain/quickjs 0.4.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,6 @@ 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
- }
302
- /**
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.
354
- *
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.
358
- */
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;
380
- }
381
- };
382
- //#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
325
  //#region src/eval-queue.ts
457
326
  /**
458
327
  * Serializes async operations on a shared WASM module.
@@ -509,6 +378,7 @@ const DEFAULT_MAX_STACK_SIZE = 320 * 1024;
509
378
  const DEFAULT_EXECUTION_TIMEOUT = 5e3;
510
379
  const DEFAULT_MAX_PTC_CALLS = 256;
511
380
  const DEFAULT_MAX_RESULTS_CHARS = 4e3;
381
+ const LINE_NUMBER_RE = /^\s*\d+(?:\.\d+)?\t/;
512
382
  const variantImport = import("@jitl/quickjs-ng-wasmfile-release-asyncify");
513
383
  /**
514
384
  * Process-global eval queue. Serializes all evalCodeAsync calls across
@@ -540,59 +410,46 @@ function getSharedModule() {
540
410
  })();
541
411
  return sharedModulePromise;
542
412
  }
543
- function makeErrorSource(message) {
544
- return `throw { name: "Error", message: ${JSON.stringify(message)} };`;
545
- }
546
- /**
547
- * Parse a canonicalized skill specifier into `{ name, rel }`.
548
- * Returns `undefined` for anything that isn't a valid `@/skills/<name>` or
549
- * `@/skills/<name>/<rel>` shape. `rel` is absent for the bare form.
550
- */
551
- function parseSkillSpecifier(specifier) {
552
- if (!specifier.startsWith("@/skills/")) return;
553
- const tail = specifier.slice(9);
554
- const slashIdx = tail.indexOf("/");
555
- const name = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
556
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return;
557
- const rel = slashIdx === -1 ? void 0 : tail.slice(slashIdx + 1);
558
- if (rel !== void 0 && rel === "") return;
559
- return {
560
- name,
561
- rel
562
- };
563
- }
564
413
  /**
565
- * Return the `@/skills/<name>` prefix for the skill that owns `base`, or `undefined`.
566
- */
567
- function matchSkillPrefix(base) {
568
- const parsed = parseSkillSpecifier(base);
569
- if (parsed === void 0) return;
570
- return `@/skills/${parsed.name}`;
571
- }
572
- /**
573
- * 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.
574
423
  */
575
- function posixDirname(p) {
576
- const idx = p.lastIndexOf("/");
577
- if (idx === -1) return "";
578
- 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);
579
432
  }
580
433
  /**
581
- * POSIX join for slash-separated specifiers. Avoids `node:path/posix`
582
- * 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.
583
447
  */
584
- function posixJoin(base, rel) {
585
- const out = [];
586
- const segments = `${base}/${rel}`.split("/");
587
- for (const segment of segments) {
588
- if (segment === "" || segment === ".") continue;
589
- if (segment === "..") {
590
- out.pop();
591
- continue;
592
- }
593
- out.push(segment);
594
- }
595
- 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");
596
453
  }
597
454
  /**
598
455
  * Fixed-size character buffer for capturing console output from the QuickJS VM.
@@ -654,11 +511,16 @@ var ReplSession = class ReplSession {
654
511
  context = null;
655
512
  consoleBuffer = new ConsoleBuffer(DEFAULT_MAX_RESULTS_CHARS);
656
513
  options;
657
- skillsContext;
658
- skillsLoaded = /* @__PURE__ */ new Map();
659
- skillsFailed = /* @__PURE__ */ new Map();
660
514
  maxPtcCalls;
661
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
+ ]);
662
524
  /**
663
525
  * Reset the shared WASM module. Forces the next session to instantiate
664
526
  * a fresh module. Only needed in tests where module state must be
@@ -676,7 +538,7 @@ var ReplSession = class ReplSession {
676
538
  }
677
539
  async ensureStarted() {
678
540
  if (this.runtime) return;
679
- const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, skillsEnabled = false, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, captureConsole = true } = this.options;
541
+ const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, captureConsole = true } = this.options;
680
542
  const runtime = (await getSharedModule()).newRuntime();
681
543
  runtime.setMemoryLimit(memoryLimitBytes);
682
544
  runtime.setMaxStackSize(maxStackSizeBytes);
@@ -686,97 +548,15 @@ var ReplSession = class ReplSession {
686
548
  this.consoleBuffer = new ConsoleBuffer(maxResultChars);
687
549
  if (captureConsole) this.setupConsole();
688
550
  if (tools !== void 0 && tools.length > 0) this.injectTools(tools);
689
- if (skillsEnabled) this.installModuleLoader();
690
- }
691
- /**
692
- * Load the skill into cache on first access and replay cached errors.
693
- */
694
- async ensureSkillLoaded(name) {
695
- const cached = this.skillsLoaded.get(name);
696
- if (cached !== void 0) return cached;
697
- const cachedError = this.skillsFailed.get(name);
698
- if (cachedError !== void 0) throw cachedError;
699
- const ctx = this.skillsContext;
700
- if (ctx === void 0) throw new Error(`Skill '${name}' referenced but skills are not configured for this session`);
701
- const metadata = ctx.metadata.find((m) => m.name === name);
702
- if (metadata === void 0) throw new Error(`Skill '${name}' referenced but not available on this agent`);
703
- try {
704
- const loaded = await loadSkill(metadata, ctx.backend);
705
- this.skillsLoaded.set(name, loaded);
706
- return loaded;
707
- } catch (err) {
708
- this.skillsFailed.set(name, err);
709
- throw err;
710
- }
711
- }
712
- /**
713
- * Pre-load all skills referenced in source code into the in-memory
714
- * cache. Must be called before `evalCodeAsync` so the module loader
715
- * can resolve synchronously. An async loader would cause asyncify
716
- * suspensions on each import, which is incompatible with the shared
717
- * WASM module used by all sessions.
718
- */
719
- async preloadReferencedSkills(code) {
720
- const refs = scanSkillReferences(code);
721
- for (const name of refs) {
722
- if (this.skillsLoaded.has(name) || this.skillsFailed.has(name)) continue;
723
- try {
724
- await this.ensureSkillLoaded(name);
725
- } catch (err) {
726
- if (!this.skillsFailed.has(name)) this.skillsFailed.set(name, err);
727
- }
728
- }
729
- }
730
- /**
731
- * Resolve a module specifier to source code. Strictly synchronous —
732
- * only reads from the in-memory skill cache populated by
733
- * `preloadReferencedSkills`. Returns error source (not a thrown
734
- * exception) for missing or failed skills so QuickJS reports the
735
- * error inside the VM.
736
- */
737
- resolveSpecifier(specifier) {
738
- const parsed = parseSkillSpecifier(specifier);
739
- if (parsed === void 0) return makeErrorSource(`Module not found: ${specifier}`);
740
- const cachedError = this.skillsFailed.get(parsed.name);
741
- if (cachedError !== void 0) return makeErrorSource(cachedError.message ?? String(cachedError));
742
- const loaded = this.skillsLoaded.get(parsed.name);
743
- if (loaded === void 0) return makeErrorSource(`Skill '${parsed.name}' was not preloaded. Ensure the import specifier is a static string literal (dynamic specifiers like \`import("@/skills/" + name)\` are not supported).`);
744
- if (parsed.rel === void 0) {
745
- const source = loaded.files.get(loaded.entryRel);
746
- if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': entrypoint '${loaded.entryRel}' missing from bundle`);
747
- 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);
748
555
  }
749
- let source = loaded.files.get(parsed.rel);
750
- if (source === void 0 && parsed.rel.endsWith(".js")) source = loaded.files.get(parsed.rel.slice(0, -3) + ".ts");
751
- if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': '${parsed.rel}' not found in bundle`);
752
- return source;
753
- }
754
- /**
755
- * Canonicalize an `import` specifier. Bare specifiers pass through;
756
- * relative specifiers are resolved against the importing module's path.
757
- * Traversal out of a skill's `@/skills/<name>/` namespace is rejected.
758
- */
759
- normalizeSpecifier(base, requested) {
760
- if (!(requested.startsWith("./") || requested.startsWith("../"))) return requested;
761
- const parsed = parseSkillSpecifier(base);
762
- const resolved = posixJoin(parsed !== void 0 && parsed.rel === void 0 ? base : posixDirname(base), requested);
763
- const skillPrefix = matchSkillPrefix(base);
764
- if (skillPrefix === void 0) return resolved;
765
- if (!resolved.startsWith(`${skillPrefix}/`)) return `__resolve_error__:${requested} escapes ${skillPrefix}`;
766
- return resolved;
767
- }
768
- /**
769
- * Wire the QuickJS module loader and normalizer on this session's runtime.
770
- *
771
- * The loader is strictly synchronous — it reads from the in-memory skill
772
- * cache populated by `preloadReferencedSkills`. This is critical: an async
773
- * module loader causes asyncify suspensions on each import, and disposing
774
- * a runtime after multi-file imports corrupts the shared module's asyncify
775
- * state, silently breaking the loader for all subsequent sessions.
776
- */
777
- installModuleLoader() {
778
- if (this.runtime === null) return;
779
- this.runtime.setModuleLoader((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();
780
560
  }
781
561
  /**
782
562
  * Initialise the per-eval PTC counter. Called at the top of every `eval()`.
@@ -839,14 +619,6 @@ var ReplSession = class ReplSession {
839
619
  if (session) session.dispose();
840
620
  }
841
621
  /**
842
- * Push the current skills metadata + backend into the session.
843
- * Called by the middleware once per `eval` invocation, before eval runs.
844
- * Pass `undefined` to clear the context (no skill imports will resolve).
845
- */
846
- setSkillsContext(ctx) {
847
- this.skillsContext = ctx;
848
- }
849
- /**
850
622
  * Evaluate code in this session.
851
623
  *
852
624
  * Lazily starts the QuickJS runtime on the first call. Code is
@@ -859,7 +631,6 @@ var ReplSession = class ReplSession {
859
631
  await this.ensureStarted();
860
632
  const runtime = this.runtime;
861
633
  const context = this.context;
862
- await this.preloadReferencedSkills(code);
863
634
  const drainLogs = () => {
864
635
  const [raw, dropped] = this.consoleBuffer.drain();
865
636
  return {
@@ -1007,8 +778,9 @@ var ReplSession = class ReplSession {
1007
778
  try {
1008
779
  this.consumePtcBudget(camelName);
1009
780
  const rawInput = typeof input === "object" && input !== null ? input : {};
1010
- const result = await t.invoke(rawInput);
1011
- 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);
1012
784
  promise.resolve(val);
1013
785
  val.dispose();
1014
786
  } catch (e) {
@@ -1027,8 +799,113 @@ var ReplSession = class ReplSession {
1027
799
  context.setProp(context.global, "tools", toolsNs);
1028
800
  toolsNs.dispose();
1029
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
+ }
1030
879
  };
1031
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
1032
909
  //#region src/middleware.ts
1033
910
  /**
1034
911
  * Code Interpreter middleware for deepagents.
@@ -1039,6 +916,216 @@ var ReplSession = class ReplSession {
1039
916
  * - Programmatic tool calling (PTC) — expose agent or custom tools inside the REPL
1040
917
  */
1041
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
+ }
1042
1129
  function renderReplSystemPrompt(opts) {
1043
1130
  return dedent.default`
1044
1131
  ### Interpreter
@@ -1046,7 +1133,7 @@ function renderReplSystemPrompt(opts) {
1046
1133
  An \`${opts.toolName}\` tool is available. It runs JavaScript in a persistent REPL.
1047
1134
  - State (variables, functions) persists across tool calls within a single turn of conversation. They DO NOT persist across multiple turns.
1048
1135
  - Top-level \`await\` works; Promises resolve before the call returns.
1049
- - 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.
1050
1137
  - Timeout: ${opts.timeout}s per call. Memory: ${opts.memoryLimitMb} MB total.
1051
1138
  - \`console.log\` output is captured and returned alongside the result.
1052
1139
  `;
@@ -1103,34 +1190,11 @@ function resolveToolList(items, agentTools) {
1103
1190
  });
1104
1191
  }
1105
1192
  /**
1106
- * Pull `skillsMetadata` from the task input, resolve the backend, and push
1107
- * both into the session. Short-circuits with a `SkillNotAvailable` error if
1108
- * the source references skills the agent doesn't have.
1109
- */
1110
- async function prepareSkillsForEval(session, skillsBackend, code) {
1111
- const taskInput = (0, _langchain_langgraph.getCurrentTaskInput)();
1112
- const metadata = taskInput?.skillsMetadata ?? [];
1113
- const referenced = scanSkillReferences(code);
1114
- if (referenced.size > 0) {
1115
- const known = new Set(metadata.map((m) => m.name));
1116
- const missing = [];
1117
- for (const name of referenced) if (!known.has(name)) missing.push(name);
1118
- if (missing.length > 0) {
1119
- session.setSkillsContext(void 0);
1120
- return formatSkillNotAvailable(missing);
1121
- }
1122
- }
1123
- const resolved = await (0, deepagents.resolveBackend)(skillsBackend, { state: taskInput });
1124
- session.setSkillsContext({
1125
- metadata,
1126
- backend: resolved
1127
- });
1128
- }
1129
- /**
1130
1193
  * Create the Code Interpreter middleware.
1131
1194
  */
1132
1195
  function createCodeInterpreterMiddleware(options = {}) {
1133
- 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;
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;
1134
1198
  if (maxPtcCalls !== null && maxPtcCalls !== void 0 && maxPtcCalls < 1) throw new Error("`maxPtcCalls` must be >= 1 or null");
1135
1199
  const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
1136
1200
  toolName,
@@ -1140,27 +1204,56 @@ function createCodeInterpreterMiddleware(options = {}) {
1140
1204
  const middlewareId = crypto.randomUUID();
1141
1205
  let cachedPtcPrompt = null;
1142
1206
  let ptcTools = [];
1207
+ let taskTool = null;
1143
1208
  function filterToolsForPtc(allTools) {
1144
1209
  if (!ptc) return [];
1145
1210
  return resolveToolList(ptc, allTools.filter((t) => t.name !== toolName));
1146
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
+ }
1147
1238
  return (0, langchain.createMiddleware)({
1148
1239
  name: "CodeInterpreterMiddleware",
1149
1240
  tools: [(0, langchain.tool)(async (input, config) => {
1150
- const sessionKey = `${config.configurable?.thread_id || "__default__"}:${middlewareId}`;
1241
+ const threadId = config.configurable?.thread_id || "__default__";
1242
+ const sessionKey = `${threadId}:${middlewareId}`;
1151
1243
  const session = ReplSession.getOrCreate(sessionKey, {
1152
1244
  memoryLimitBytes,
1153
1245
  maxStackSizeBytes,
1154
1246
  maxPtcCalls,
1155
1247
  tools: ptcTools,
1156
- skillsEnabled: skillsBackend !== void 0,
1157
1248
  maxResultChars,
1158
- captureConsole
1249
+ captureConsole,
1250
+ sessionId: threadId,
1251
+ subagentBridge: taskTool && maxSubagentConcurrency > 0 ? {
1252
+ dispatch: createBridgeDispatch(taskTool, config),
1253
+ maxConcurrency: maxSubagentConcurrency
1254
+ } : void 0
1159
1255
  });
1160
- if (skillsBackend !== void 0) {
1161
- const setupError = await prepareSkillsForEval(session, skillsBackend, input.code);
1162
- if (setupError !== void 0) return setupError;
1163
- }
1256
+ if (taskTool && maxSubagentConcurrency > 0) session.updateBridgeDispatch(createBridgeDispatch(taskTool, config));
1164
1257
  return formatReplResult(await session.eval(input.code, executionTimeoutMs));
1165
1258
  }, {
1166
1259
  name: toolName,
@@ -1168,15 +1261,17 @@ function createCodeInterpreterMiddleware(options = {}) {
1168
1261
  Evaluate TypeScript/JavaScript code in a sandboxed REPL. State persists across calls.
1169
1262
  Use console.log() for output. Returns the result of the last expression.
1170
1263
  If file or other tools are available, call them via the tools namespace: await tools.readFile({ path }).
1171
- If skills are configured, dynamically import them: await import("@/skills/<name>").
1172
1264
  `,
1173
1265
  metadata: { ls_code_input_language: "javascript" },
1174
1266
  schema: zod_v4.z.object({ code: zod_v4.z.string().describe("TypeScript/JavaScript code to evaluate in the sandboxed REPL") })
1175
1267
  })],
1176
1268
  wrapModelCall: async (request, handler) => {
1177
- ptcTools = filterToolsForPtc(request.tools || []);
1269
+ const agentTools = request.tools || [];
1270
+ ptcTools = filterToolsForPtc(agentTools);
1271
+ if (!taskTool && maxSubagentConcurrency > 0) taskTool = findTaskTool(agentTools);
1178
1272
  if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
1179
- 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 || "");
1180
1275
  return handler({
1181
1276
  ...request,
1182
1277
  systemMessage
@@ -1193,17 +1288,13 @@ exports.DEFAULT_EXECUTION_TIMEOUT = DEFAULT_EXECUTION_TIMEOUT;
1193
1288
  exports.DEFAULT_MAX_PTC_CALLS = DEFAULT_MAX_PTC_CALLS;
1194
1289
  exports.DEFAULT_MAX_STACK_SIZE = DEFAULT_MAX_STACK_SIZE;
1195
1290
  exports.DEFAULT_MEMORY_LIMIT = DEFAULT_MEMORY_LIMIT;
1196
- exports.MAX_SKILL_BUNDLE_BYTES = MAX_SKILL_BUNDLE_BYTES;
1197
1291
  exports.PTCCallBudgetExceededError = PTCCallBudgetExceededError;
1198
1292
  exports.ReplSession = ReplSession;
1199
- exports.SKILL_MODULE_EXTENSIONS = SKILL_MODULE_EXTENSIONS;
1200
1293
  exports.createCodeInterpreterMiddleware = createCodeInterpreterMiddleware;
1201
1294
  exports.formatReplResult = formatReplResult;
1202
- exports.formatSkillNotAvailable = formatSkillNotAvailable;
1203
- exports.loadSkill = loadSkill;
1204
- exports.scanSkillReferences = scanSkillReferences;
1205
1295
  exports.stripTypeSyntax = stripTypeSyntax;
1206
1296
  exports.toCamelCase = toCamelCase;
1207
1297
  exports.transformForEval = transformForEval;
1298
+ exports.validateResponseSchema = validateResponseSchema;
1208
1299
 
1209
1300
  //# sourceMappingURL=index.cjs.map