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