@langchain/quickjs 0.2.5 → 0.2.6

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,82 +23,21 @@ 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");
27
26
  let dedent = require("dedent");
28
27
  dedent = __toESM(dedent);
28
+ let _langchain_langgraph = require("@langchain/langgraph");
29
+ let deepagents = require("deepagents");
29
30
  let quickjs_emscripten = require("quickjs-emscripten");
30
31
  let quickjs_emscripten_core = require("quickjs-emscripten-core");
31
- let json_schema_to_typescript = require("json-schema-to-typescript");
32
- let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
32
+ let node_path_posix = require("node:path/posix");
33
+ node_path_posix = __toESM(node_path_posix);
33
34
  let acorn = require("acorn");
34
35
  let _sveltejs_acorn_typescript = require("@sveltejs/acorn-typescript");
35
36
  let estree_walker = require("estree-walker");
36
37
  let magic_string = require("magic-string");
37
38
  magic_string = __toESM(magic_string);
38
- let _langchain_langgraph = require("@langchain/langgraph");
39
- //#region src/utils.ts
40
- /**
41
- * Convert a snake_case or kebab-case string to camelCase.
42
- */
43
- function toCamelCase(name) {
44
- return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
45
- }
46
- /**
47
- * Format the result of a REPL evaluation for the agent.
48
- */
49
- function formatReplResult(result) {
50
- const parts = [];
51
- if (result.logs.length > 0) parts.push(result.logs.join("\n"));
52
- if (result.ok) {
53
- if (result.value !== void 0) {
54
- const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
55
- parts.push(`→ ${formatted}`);
56
- }
57
- } else if (result.error) {
58
- const errName = result.error.name || "Error";
59
- const errMsg = result.error.message || "Unknown error";
60
- parts.push(`${errName}: ${errMsg}`);
61
- if (result.error.stack) parts.push(result.error.stack);
62
- }
63
- return parts.join("\n") || "(no output)";
64
- }
65
- function safeToJsonSchema(schema) {
66
- try {
67
- return (0, _langchain_core_utils_json_schema.toJsonSchema)(schema);
68
- } catch {
69
- return;
70
- }
71
- }
72
- async function schemaToInterface(jsonSchema, interfaceName) {
73
- return (await (0, json_schema_to_typescript.compile)({
74
- ...jsonSchema,
75
- additionalProperties: false
76
- }, interfaceName, {
77
- bannerComment: "",
78
- additionalProperties: false
79
- })).replace(/^export /, "").trimEnd();
80
- }
81
- function capitalize(s) {
82
- return s.charAt(0).toUpperCase() + s.slice(1);
83
- }
84
- async function toolToTypeSignature(name, description, jsonSchema) {
85
- const inputType = `${capitalize(name)}Input`;
86
- if (!jsonSchema || !jsonSchema.properties) return dedent.default`
87
- /**
88
- * ${description}
89
- */
90
- async tools.${name}(input: Record<string, unknown>): Promise<string>
91
- `;
92
- return dedent.default`
93
- ${await schemaToInterface(jsonSchema, inputType)}
94
-
95
- /**
96
- * ${description}
97
- */
98
- async tools.${name}(input: ${inputType}): Promise<string>
99
- `;
100
- }
101
- //#endregion
39
+ let json_schema_to_typescript = require("json-schema-to-typescript");
40
+ let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
102
41
  //#region src/transform.ts
103
42
  /**
104
43
  * AST-based code transform pipeline for the REPL.
@@ -259,6 +198,260 @@ function findLastNonEmptyNode(nodes, s) {
259
198
  function isExpression(node) {
260
199
  return node.type === "ExpressionStatement";
261
200
  }
201
+ /**
202
+ * Strip TypeScript type syntax from an ES-module source so QuickJS can
203
+ * evaluate it as a standard JS module.
204
+ *
205
+ * Unlike `transformForEval`, this keeps `import`/`export` declarations,
206
+ * does not hoist to `globalThis`, and does not wrap in an IIFE.
207
+ * On parse failure the original source is returned unchanged.
208
+ */
209
+ function stripTypeSyntax(code) {
210
+ let ast;
211
+ try {
212
+ ast = TSParser.parse(code, {
213
+ ecmaVersion: "latest",
214
+ sourceType: "module",
215
+ locations: true
216
+ });
217
+ } catch {
218
+ return code;
219
+ }
220
+ const magicString = new magic_string.default(code);
221
+ const program = ast;
222
+ for (const node of program.body) {
223
+ if (isTSOnlyNode(node)) {
224
+ magicString.remove(node.start, node.end);
225
+ continue;
226
+ }
227
+ (0, estree_walker.walk)(node, { enter(n) {
228
+ stripTypeAnnotationFromNode(magicString, n);
229
+ } });
230
+ }
231
+ return magicString.toString();
232
+ }
233
+ //#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
+ }
262
455
  //#endregion
263
456
  //#region src/session.ts
264
457
  /**
@@ -278,65 +471,237 @@ function isExpression(node) {
278
471
  * It holds an `id` that keys into a static session map. The heavy QuickJS
279
472
  * runtime is lazily started on the first `.eval()` call, making the session
280
473
  * safe across graph interrupts and checkpointing.
281
- *
282
- * File writes inside the REPL are buffered (`pendingWrites`) and only
283
- * flushed to the backend after a script finishes executing. Call
284
- * `session.flushWrites(backend)` after eval to persist them.
285
474
  */
286
475
  const DEFAULT_MEMORY_LIMIT = 50 * 1024 * 1024;
287
476
  const DEFAULT_MAX_STACK_SIZE = 320 * 1024;
288
477
  const DEFAULT_EXECUTION_TIMEOUT = 3e4;
289
- let asyncModulePromise;
290
- async function getAsyncModule() {
291
- if (!asyncModulePromise) asyncModulePromise = (async () => {
292
- const variant = await import("@jitl/quickjs-ng-wasmfile-release-asyncify");
293
- return (0, quickjs_emscripten_core.newQuickJSAsyncWASMModuleFromVariant)(variant.default ?? variant);
294
- })();
295
- return asyncModulePromise;
478
+ const DEFAULT_MAX_PTC_CALLS = 256;
479
+ const DEFAULT_MAX_RESULTS_CHARS = 4e3;
480
+ const variantImport = import("@jitl/quickjs-ng-wasmfile-release-asyncify");
481
+ async function newAsyncModule() {
482
+ const variant = await variantImport;
483
+ return (0, quickjs_emscripten_core.newQuickJSAsyncWASMModuleFromVariant)(variant.default ?? variant);
484
+ }
485
+ function makeErrorSource(message) {
486
+ return `throw { name: "Error", message: ${JSON.stringify(message)} };`;
487
+ }
488
+ /**
489
+ * Parse a canonicalized skill specifier into `{ name, rel }`.
490
+ * Returns `undefined` for anything that isn't a valid `@/skills/<name>` or
491
+ * `@/skills/<name>/<rel>` shape. `rel` is absent for the bare form.
492
+ */
493
+ function parseSkillSpecifier(specifier) {
494
+ if (!specifier.startsWith("@/skills/")) return;
495
+ const tail = specifier.slice(9);
496
+ const slashIdx = tail.indexOf("/");
497
+ const name = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
498
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return;
499
+ const rel = slashIdx === -1 ? void 0 : tail.slice(slashIdx + 1);
500
+ if (rel !== void 0 && rel === "") return;
501
+ return {
502
+ name,
503
+ rel
504
+ };
296
505
  }
297
506
  /**
507
+ * Return the `@/skills/<name>` prefix for the skill that owns `base`, or `undefined`.
508
+ */
509
+ function matchSkillPrefix(base) {
510
+ const parsed = parseSkillSpecifier(base);
511
+ if (parsed === void 0) return;
512
+ return `@/skills/${parsed.name}`;
513
+ }
514
+ /**
515
+ * Return the directory portion of a slash-separated specifier path.
516
+ */
517
+ function posixDirname(p) {
518
+ const idx = p.lastIndexOf("/");
519
+ if (idx === -1) return "";
520
+ return p.slice(0, idx);
521
+ }
522
+ /**
523
+ * POSIX join for slash-separated specifiers. Avoids `node:path/posix`
524
+ * since session.ts is consumed in browser bundles.
525
+ */
526
+ function posixJoin(base, rel) {
527
+ const out = [];
528
+ const segments = `${base}/${rel}`.split("/");
529
+ for (const segment of segments) {
530
+ if (segment === "" || segment === ".") continue;
531
+ if (segment === "..") {
532
+ out.pop();
533
+ continue;
534
+ }
535
+ out.push(segment);
536
+ }
537
+ return out.join("/");
538
+ }
539
+ /**
540
+ * Fixed-size character buffer for capturing console output from the QuickJS VM.
541
+ *
542
+ * Lines are accumulated up to `maxChars`. Once the cap is reached, excess
543
+ * characters are counted as dropped rather than silently discarded without
544
+ * attribution, so callers can surface a truncation notice to the user.
545
+ */
546
+ var ConsoleBuffer = class {
547
+ maxChars;
548
+ buffer = "";
549
+ droppedChars = 0;
550
+ constructor(maxChars) {
551
+ this.maxChars = Math.max(maxChars, 0);
552
+ }
553
+ /**
554
+ * Append `line` to the buffer.
555
+ *
556
+ * If the buffer is already full the entire line is counted as dropped.
557
+ * If `line` partially fits, the fitting prefix is stored and the remainder
558
+ * is counted as dropped.
559
+ */
560
+ append(line) {
561
+ const remaining = this.maxChars - this.buffer.length;
562
+ if (remaining <= 0) {
563
+ this.droppedChars += line.length;
564
+ return;
565
+ }
566
+ if (line.length <= remaining) this.buffer += line;
567
+ else {
568
+ this.buffer += line.slice(0, remaining);
569
+ this.droppedChars += line.length - remaining;
570
+ }
571
+ }
572
+ /**
573
+ * Return the buffered output and dropped-character count as `[buffered,
574
+ * droppedChars]`, then reset both to zero.
575
+ */
576
+ drain() {
577
+ const out = this.buffer;
578
+ const dropped = this.droppedChars;
579
+ this.buffer = "";
580
+ this.droppedChars = 0;
581
+ return [out, dropped];
582
+ }
583
+ };
584
+ /**
298
585
  * Sandboxed JavaScript REPL session backed by QuickJS WASM.
299
586
  *
300
587
  * Serializable — holds an `id` that keys into a static session map.
301
588
  * The QuickJS runtime is lazily started on the first `.eval()` call
302
589
  * and reconnected if a session with the same id already exists.
303
590
  * This makes it safe to store in LangGraph state across interrupts.
304
- *
305
- * File writes are buffered during execution and flushed via
306
- * `flushWrites(backend)` after eval completes.
307
591
  */
308
592
  var ReplSession = class ReplSession {
309
593
  static sessions = /* @__PURE__ */ new Map();
310
594
  id;
311
- pendingWrites = [];
312
595
  runtime = null;
313
596
  context = null;
314
- logs = [];
315
- _options;
316
- _backend = null;
597
+ consoleBuffer = new ConsoleBuffer(DEFAULT_MAX_RESULTS_CHARS);
598
+ options;
599
+ skillsContext;
600
+ skillsLoaded = /* @__PURE__ */ new Map();
601
+ skillsFailed = /* @__PURE__ */ new Map();
602
+ maxPtcCalls;
603
+ ptcCallsRemaining = null;
317
604
  constructor(id, options = {}) {
318
605
  this.id = id;
319
- this._options = options;
320
- }
321
- get backend() {
322
- return this._backend;
323
- }
324
- set backend(b) {
325
- this._backend = b ? (0, deepagents.adaptBackendProtocol)(b) : null;
606
+ this.options = options;
607
+ this.maxPtcCalls = options.maxPtcCalls !== void 0 ? options.maxPtcCalls : 256;
326
608
  }
327
609
  async ensureStarted() {
328
610
  if (this.runtime) return;
329
- const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, backend, tools } = this._options;
330
- const runtime = (await getAsyncModule()).newRuntime();
611
+ const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, skillsEnabled = false, maxResultChars = DEFAULT_MAX_RESULTS_CHARS } = this.options;
612
+ const runtime = (await newAsyncModule()).newRuntime();
331
613
  runtime.setMemoryLimit(memoryLimitBytes);
332
614
  runtime.setMaxStackSize(maxStackSizeBytes);
333
615
  const context = runtime.newContext();
334
616
  this.runtime = runtime;
335
617
  this.context = context;
618
+ this.consoleBuffer = new ConsoleBuffer(maxResultChars);
336
619
  this.setupConsole();
337
- if (backend) this._backend = (0, deepagents.adaptBackendProtocol)(backend);
338
- this.injectVfs();
339
- if (tools && tools.length > 0) this.injectTools(tools);
620
+ if (tools !== void 0 && tools.length > 0) this.injectTools(tools);
621
+ if (skillsEnabled) this.installModuleLoader();
622
+ }
623
+ /**
624
+ * Load the skill into cache on first access and replay cached errors.
625
+ */
626
+ async ensureSkillLoaded(name) {
627
+ const cached = this.skillsLoaded.get(name);
628
+ if (cached !== void 0) return cached;
629
+ const cachedError = this.skillsFailed.get(name);
630
+ if (cachedError !== void 0) throw cachedError;
631
+ const ctx = this.skillsContext;
632
+ if (ctx === void 0) throw new Error(`Skill '${name}' referenced but skills are not configured for this session`);
633
+ const metadata = ctx.metadata.find((m) => m.name === name);
634
+ if (metadata === void 0) throw new Error(`Skill '${name}' referenced but not available on this agent`);
635
+ try {
636
+ const loaded = await loadSkill(metadata, ctx.backend);
637
+ this.skillsLoaded.set(name, loaded);
638
+ return loaded;
639
+ } catch (err) {
640
+ this.skillsFailed.set(name, err);
641
+ throw err;
642
+ }
643
+ }
644
+ async resolveSpecifier(specifier) {
645
+ const parsed = parseSkillSpecifier(specifier);
646
+ if (parsed === void 0) return makeErrorSource(`Module not found: ${specifier}`);
647
+ let loaded;
648
+ try {
649
+ loaded = await this.ensureSkillLoaded(parsed.name);
650
+ } catch (err) {
651
+ return makeErrorSource(err.message ?? String(err));
652
+ }
653
+ if (parsed.rel === void 0) {
654
+ const source = loaded.files.get(loaded.entryRel);
655
+ if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': entrypoint '${loaded.entryRel}' missing from bundle`);
656
+ return source;
657
+ }
658
+ const source = loaded.files.get(parsed.rel);
659
+ if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': '${parsed.rel}' not found in bundle`);
660
+ return source;
661
+ }
662
+ /**
663
+ * Canonicalize an `import` specifier. Bare specifiers pass through;
664
+ * relative specifiers are resolved against the importing module's path.
665
+ * Traversal out of a skill's `@/skills/<name>/` namespace is rejected.
666
+ */
667
+ normalizeSpecifier(base, requested) {
668
+ if (!(requested.startsWith("./") || requested.startsWith("../"))) return requested;
669
+ const parsed = parseSkillSpecifier(base);
670
+ const resolved = posixJoin(parsed !== void 0 && parsed.rel === void 0 ? base : posixDirname(base), requested);
671
+ const skillPrefix = matchSkillPrefix(base);
672
+ if (skillPrefix === void 0) return resolved;
673
+ if (!resolved.startsWith(`${skillPrefix}/`)) return `__resolve_error__:${requested} escapes ${skillPrefix}`;
674
+ return resolved;
675
+ }
676
+ /**
677
+ * Wire the QuickJS module loader and normalizer on this session's runtime.
678
+ */
679
+ installModuleLoader() {
680
+ if (this.runtime === null) return;
681
+ this.runtime.setModuleLoader(async (specifier) => this.resolveSpecifier(specifier), (base, requested) => this.normalizeSpecifier(base, requested));
682
+ }
683
+ /**
684
+ * Initialise the per-eval PTC counter. Called at the top of every `eval()`.
685
+ */
686
+ resetPtcBudget() {
687
+ this.ptcCallsRemaining = this.maxPtcCalls === null ? null : this.maxPtcCalls;
688
+ }
689
+ /**
690
+ * Decrement the PTC call counter and throw if the budget is exhausted.
691
+ * `null` budget means unlimited — returns immediately without decrementing.
692
+ */
693
+ consumePtcBudget(functionName) {
694
+ if (this.ptcCallsRemaining === null) return;
695
+ if (this.ptcCallsRemaining > 0) {
696
+ this.ptcCallsRemaining--;
697
+ return;
698
+ }
699
+ const limit = this.maxPtcCalls ?? 0;
700
+ throw new PTCCallBudgetExceededError({
701
+ limit,
702
+ attempted: limit + 1,
703
+ functionName
704
+ });
340
705
  }
341
706
  /**
342
707
  * Get or create a session for the given id.
@@ -347,10 +712,7 @@ var ReplSession = class ReplSession {
347
712
  */
348
713
  static getOrCreate(id, options = {}) {
349
714
  const existing = ReplSession.sessions.get(id);
350
- if (existing) {
351
- if (options.backend) existing._backend = (0, deepagents.adaptBackendProtocol)(options.backend);
352
- return existing;
353
- }
715
+ if (existing) return existing;
354
716
  const session = new ReplSession(id, options);
355
717
  ReplSession.sessions.set(id, session);
356
718
  return session;
@@ -362,6 +724,31 @@ var ReplSession = class ReplSession {
362
724
  return ReplSession.sessions.get(id) ?? null;
363
725
  }
364
726
  /**
727
+ * Returns true if any session exists whose key equals `threadId` or starts
728
+ * with `threadId:`. Useful for tests that need to confirm a session was
729
+ * created without knowing the full `threadId:middlewareId` key.
730
+ */
731
+ static hasAnyForThread(threadId) {
732
+ const prefix = `${threadId}:`;
733
+ for (const key of ReplSession.sessions.keys()) if (key === threadId || key.startsWith(prefix)) return true;
734
+ return false;
735
+ }
736
+ /**
737
+ * Dispose and remove the session with the given key, if it exists.
738
+ */
739
+ static deleteSession(key) {
740
+ const session = ReplSession.sessions.get(key);
741
+ if (session) session.dispose();
742
+ }
743
+ /**
744
+ * Push the current skills metadata + backend into the session.
745
+ * Called by the middleware once per `js_eval` invocation, before eval runs.
746
+ * Pass `undefined` to clear the context (no skill imports will resolve).
747
+ */
748
+ setSkillsContext(ctx) {
749
+ this.skillsContext = ctx;
750
+ }
751
+ /**
365
752
  * Evaluate code in this session.
366
753
  *
367
754
  * Lazily starts the QuickJS runtime on the first call. Code is
@@ -374,88 +761,94 @@ var ReplSession = class ReplSession {
374
761
  await this.ensureStarted();
375
762
  const runtime = this.runtime;
376
763
  const context = this.context;
377
- this.logs.length = 0;
378
- if (timeoutMs >= 0) runtime.setInterruptHandler((0, quickjs_emscripten.shouldInterruptAfterDeadline)(Date.now() + timeoutMs));
379
- else runtime.setInterruptHandler(() => false);
380
- const transformed = transformForEval(code);
381
- const result = await context.evalCodeAsync(transformed);
382
- if (result.error) {
383
- const error = context.dump(result.error);
384
- result.error.dispose();
764
+ const drainLogs = () => {
765
+ const [raw, dropped] = this.consoleBuffer.drain();
385
766
  return {
386
- ok: false,
387
- error,
388
- logs: [...this.logs]
767
+ logs: raw.length > 0 ? raw.split("\n").filter((l) => l.length > 0) : [],
768
+ logsDroppedChars: dropped
389
769
  };
390
- }
391
- const promiseState = context.getPromiseState(result.value);
392
- if (promiseState.type === "fulfilled") {
393
- if (promiseState.notAPromise) {
394
- const value = context.dump(result.value);
395
- result.value.dispose();
770
+ };
771
+ this.resetPtcBudget();
772
+ try {
773
+ if (timeoutMs >= 0) runtime.setInterruptHandler((0, quickjs_emscripten.shouldInterruptAfterDeadline)(Date.now() + timeoutMs));
774
+ else runtime.setInterruptHandler(() => false);
775
+ const transformed = transformForEval(code);
776
+ const result = await context.evalCodeAsync(transformed);
777
+ if (result.error) {
778
+ const error = context.dump(result.error);
779
+ result.error.dispose();
396
780
  return {
397
- ok: true,
398
- value,
399
- logs: [...this.logs]
781
+ ok: false,
782
+ error,
783
+ ...drainLogs()
400
784
  };
401
785
  }
402
- const value = context.dump(promiseState.value);
403
- promiseState.value.dispose();
404
- result.value.dispose();
405
- return {
406
- ok: true,
407
- value,
408
- logs: [...this.logs]
409
- };
410
- }
411
- if (promiseState.type === "rejected") {
412
- const error = context.dump(promiseState.error);
413
- promiseState.error.dispose();
414
- result.value.dispose();
415
- return {
416
- ok: false,
417
- error,
418
- logs: [...this.logs]
419
- };
420
- }
421
- const noTimeout = timeoutMs < 0;
422
- const deadline = noTimeout ? Infinity : Date.now() + timeoutMs;
423
- while (noTimeout || Date.now() < deadline) {
424
- context.runtime.executePendingJobs();
425
- const state = context.getPromiseState(result.value);
426
- if (state.type === "fulfilled") {
427
- const value = context.dump(state.value);
428
- state.value.dispose();
786
+ const promiseState = context.getPromiseState(result.value);
787
+ if (promiseState.type === "fulfilled") {
788
+ if (promiseState.notAPromise) {
789
+ const value = context.dump(result.value);
790
+ result.value.dispose();
791
+ return {
792
+ ok: true,
793
+ value,
794
+ ...drainLogs()
795
+ };
796
+ }
797
+ const value = context.dump(promiseState.value);
798
+ promiseState.value.dispose();
429
799
  result.value.dispose();
430
800
  return {
431
801
  ok: true,
432
802
  value,
433
- logs: [...this.logs]
803
+ ...drainLogs()
434
804
  };
435
805
  }
436
- if (state.type === "rejected") {
437
- const error = context.dump(state.error);
438
- state.error.dispose();
806
+ if (promiseState.type === "rejected") {
807
+ const error = context.dump(promiseState.error);
808
+ promiseState.error.dispose();
439
809
  result.value.dispose();
440
810
  return {
441
811
  ok: false,
442
812
  error,
443
- logs: [...this.logs]
813
+ ...drainLogs()
444
814
  };
445
815
  }
446
- await new Promise((r) => setTimeout(r, 1));
816
+ const noTimeout = timeoutMs < 0;
817
+ const deadline = noTimeout ? Infinity : Date.now() + timeoutMs;
818
+ while (noTimeout || Date.now() < deadline) {
819
+ context.runtime.executePendingJobs();
820
+ const state = context.getPromiseState(result.value);
821
+ if (state.type === "fulfilled") {
822
+ const value = context.dump(state.value);
823
+ state.value.dispose();
824
+ result.value.dispose();
825
+ return {
826
+ ok: true,
827
+ value,
828
+ ...drainLogs()
829
+ };
830
+ }
831
+ if (state.type === "rejected") {
832
+ const error = context.dump(state.error);
833
+ state.error.dispose();
834
+ result.value.dispose();
835
+ return {
836
+ ok: false,
837
+ error,
838
+ ...drainLogs()
839
+ };
840
+ }
841
+ await new Promise((r) => setTimeout(r, 1));
842
+ }
843
+ result.value.dispose();
844
+ return {
845
+ ok: false,
846
+ error: { message: "Promise timed out — execution interrupted" },
847
+ ...drainLogs()
848
+ };
849
+ } finally {
850
+ this.ptcCallsRemaining = null;
447
851
  }
448
- result.value.dispose();
449
- return {
450
- ok: false,
451
- error: { message: "Promise timed out — execution interrupted" },
452
- logs: [...this.logs]
453
- };
454
- }
455
- async flushWrites(backend) {
456
- const adapted = (0, deepagents.adaptBackendProtocol)(backend);
457
- const writes = this.pendingWrites.splice(0);
458
- for (const { path, content } of writes) await adapted.write(path, content);
459
852
  }
460
853
  dispose() {
461
854
  try {
@@ -484,7 +877,6 @@ var ReplSession = class ReplSession {
484
877
  }
485
878
  setupConsole() {
486
879
  const context = this.context;
487
- const logs = this.logs;
488
880
  const consoleHandle = context.newObject();
489
881
  for (const method of [
490
882
  "log",
@@ -495,7 +887,8 @@ var ReplSession = class ReplSession {
495
887
  ]) {
496
888
  const fnHandle = context.newFunction(method, (...args) => {
497
889
  const formatted = args.map((a) => context.dump(a)).map((a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)).join(" ");
498
- logs.push(method === "log" || method === "info" || method === "debug" ? formatted : `[${method}] ${formatted}`);
890
+ const line = method === "log" || method === "info" || method === "debug" ? formatted : `[${method}] ${formatted}`;
891
+ this.consoleBuffer.append(line + "\n");
499
892
  });
500
893
  context.setProp(consoleHandle, method, fnHandle);
501
894
  fnHandle.dispose();
@@ -503,67 +896,6 @@ var ReplSession = class ReplSession {
503
896
  context.setProp(context.global, "console", consoleHandle);
504
897
  consoleHandle.dispose();
505
898
  }
506
- injectVfs() {
507
- const context = this.context;
508
- const getBackend = () => this._backend;
509
- const { pendingWrites } = this;
510
- const readFileHandle = context.newFunction("readFile", (pathHandle) => {
511
- const backend = getBackend();
512
- if (!backend) {
513
- const promise = context.newPromise();
514
- const err = context.newError("Backend not available");
515
- promise.reject(err);
516
- err.dispose();
517
- promise.settled.then(context.runtime.executePendingJobs);
518
- return promise.handle;
519
- }
520
- const path = context.getString(pathHandle);
521
- const promise = context.newPromise();
522
- (async () => {
523
- try {
524
- const result = await backend.readRaw(path);
525
- if (result.error || !result.data) {
526
- const err = context.newError(`ENOENT: no such file or directory '${path}'.`);
527
- promise.reject(err);
528
- err.dispose();
529
- } else {
530
- const content = Array.isArray(result.data.content) ? result.data.content.join("\n") : typeof result.data.content === "string" ? result.data.content : null;
531
- if (content === null) {
532
- const err = context.newError(`Cannot read binary file '${path}' as text.`);
533
- promise.reject(err);
534
- err.dispose();
535
- return;
536
- }
537
- const val = context.newString(content);
538
- promise.resolve(val);
539
- val.dispose();
540
- }
541
- } catch {
542
- const err = context.newError(`ENOENT: no such file or directory '${path}'.`);
543
- promise.reject(err);
544
- err.dispose();
545
- }
546
- promise.settled.then(context.runtime.executePendingJobs);
547
- })();
548
- return promise.handle;
549
- });
550
- context.setProp(context.global, "readFile", readFileHandle);
551
- readFileHandle.dispose();
552
- const writeFileHandle = context.newFunction("writeFile", (pathHandle, contentHandle) => {
553
- const path = context.getString(pathHandle);
554
- const content = context.getString(contentHandle);
555
- const promise = context.newPromise();
556
- pendingWrites.push({
557
- path,
558
- content
559
- });
560
- promise.resolve(context.undefined);
561
- promise.settled.then(context.runtime.executePendingJobs);
562
- return promise.handle;
563
- });
564
- context.setProp(context.global, "writeFile", writeFileHandle);
565
- writeFileHandle.dispose();
566
- }
567
899
  injectTools(tools) {
568
900
  const context = this.context;
569
901
  const toolsNs = context.newObject();
@@ -574,6 +906,7 @@ var ReplSession = class ReplSession {
574
906
  const promise = context.newPromise();
575
907
  (async () => {
576
908
  try {
909
+ this.consumePtcBudget(camelName);
577
910
  const rawInput = typeof input === "object" && input !== null ? input : {};
578
911
  const result = await t.invoke(rawInput);
579
912
  const val = context.newString(typeof result === "string" ? result : JSON.stringify(result));
@@ -604,23 +937,8 @@ var ReplSession = class ReplSession {
604
937
  * Provides a `js_eval` tool that runs JavaScript in a WASM-sandboxed QuickJS
605
938
  * interpreter. Supports:
606
939
  * - Persistent state across evaluations (true REPL)
607
- * - VFS integration via readFile/writeFile
608
- * - Programmatic tool calling (PTC)
609
- */
610
- /**
611
- * Backend-provided tools excluded from PTC by default.
612
- * These are redundant inside the REPL since VFS helpers (readFile/writeFile)
613
- * already cover file I/O against the agent's in-memory working set.
614
- */
615
- const DEFAULT_PTC_EXCLUDED_TOOLS = [
616
- "ls",
617
- "read_file",
618
- "write_file",
619
- "edit_file",
620
- "glob",
621
- "grep",
622
- "execute"
623
- ];
940
+ * - Programmatic tool calling (PTC) — expose agent or custom tools inside the REPL
941
+ */
624
942
  const REPL_SYSTEM_PROMPT = dedent.default`
625
943
  ## TypeScript/JavaScript REPL (\`js_eval\`)
626
944
 
@@ -630,37 +948,11 @@ const REPL_SYSTEM_PROMPT = dedent.default`
630
948
 
631
949
  ### Hard rules
632
950
 
633
- - **No network, no filesystem** — only the helpers below. Do not attempt \`fetch\`, \`require\`, or \`import\`.
951
+ - **No network, no direct filesystem** — only through tools provided in the \`tools\` namespace below.
634
952
  - **Cite your sources** — when reporting values from files, include the path and key/index so the user can verify.
635
953
  - **Use console.log()** for output — it is captured and returned. \`console.warn()\` and \`console.error()\` are also available.
636
954
  - **Reuse state from previous cells** — variables, functions, and results from earlier \`js_eval\` calls persist across calls. Reference them by name in follow-up cells instead of re-embedding data as inline JSON literals.
637
955
 
638
- ### First-time usage
639
-
640
- \`\`\`typescript
641
- // Read a file from the agent's virtual filesystem
642
- const raw: string = await readFile("/data.json");
643
- const data = JSON.parse(raw) as { n: number };
644
- console.log(data);
645
-
646
- // Write results back
647
- await writeFile("/output.txt", JSON.stringify({ result: data.n }));
648
- \`\`\`
649
-
650
- ### API Reference — built-in globals
651
-
652
- \`\`\`typescript
653
- /**
654
- * Read a file from the agent's virtual filesystem. Throws if the file does not exist.
655
- */
656
- async readFile(path: string): Promise<string>
657
-
658
- /**
659
- * Write a file to the agent's virtual filesystem.
660
- */
661
- async writeFile(path: string, content: string): Promise<void>
662
- \`\`\`
663
-
664
956
  ### Limitations
665
957
 
666
958
  - ES2023+ syntax with TypeScript support. No Node.js APIs, no \`require\`, no \`import\`.
@@ -703,81 +995,117 @@ async function generatePtcPrompt(tools) {
703
995
  `;
704
996
  }
705
997
  /**
998
+ * Resolves a mixed list of tool names and tool instances into a flat list of
999
+ * StructuredToolInterface objects. Strings are looked up by name in agentTools;
1000
+ * instances are included directly without requiring agent registration. Strings
1001
+ * that don't match any agent tool are silently omitted.
1002
+ */
1003
+ function resolveToolList(items, agentTools) {
1004
+ const agentByName = new Map(agentTools.map((t) => [t.name, t]));
1005
+ return items.flatMap((item) => {
1006
+ if (typeof item === "string") {
1007
+ const found = agentByName.get(item);
1008
+ return found ? [found] : [];
1009
+ }
1010
+ return [item];
1011
+ });
1012
+ }
1013
+ /**
1014
+ * Pull `skillsMetadata` from the task input, resolve the backend, and push
1015
+ * both into the session. Short-circuits with a `SkillNotAvailable` error if
1016
+ * the source references skills the agent doesn't have.
1017
+ */
1018
+ async function prepareSkillsForEval(session, skillsBackend, code) {
1019
+ const taskInput = (0, _langchain_langgraph.getCurrentTaskInput)();
1020
+ const metadata = taskInput?.skillsMetadata ?? [];
1021
+ const referenced = scanSkillReferences(code);
1022
+ if (referenced.size > 0) {
1023
+ const known = new Set(metadata.map((m) => m.name));
1024
+ const missing = [];
1025
+ for (const name of referenced) if (!known.has(name)) missing.push(name);
1026
+ if (missing.length > 0) {
1027
+ session.setSkillsContext(void 0);
1028
+ return formatSkillNotAvailable(missing);
1029
+ }
1030
+ }
1031
+ const resolved = await (0, deepagents.resolveBackend)(skillsBackend, { state: taskInput });
1032
+ session.setSkillsContext({
1033
+ metadata,
1034
+ backend: resolved
1035
+ });
1036
+ }
1037
+ /**
706
1038
  * Create the QuickJS REPL middleware.
707
1039
  */
708
1040
  function createQuickJSMiddleware(options = {}) {
709
- const { backend = (runtime) => new deepagents.StateBackend(runtime), ptc = false, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null } = options;
710
- const usePtc = ptc !== false;
1041
+ 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 } = options;
1042
+ if (maxPtcCalls !== null && maxPtcCalls !== void 0 && maxPtcCalls < 1) throw new Error("`maxPtcCalls` must be >= 1 or null");
711
1043
  const baseSystemPrompt = customSystemPrompt || REPL_SYSTEM_PROMPT;
1044
+ const middlewareId = crypto.randomUUID();
712
1045
  let cachedPtcPrompt = null;
713
1046
  let ptcTools = [];
714
1047
  function filterToolsForPtc(allTools) {
715
- if (ptc === false) return [];
716
- const candidates = allTools.filter((t) => t.name !== "js_eval");
717
- if (ptc === true) {
718
- const excluded = new Set(DEFAULT_PTC_EXCLUDED_TOOLS);
719
- return candidates.filter((t) => !excluded.has(t.name));
720
- }
721
- if (Array.isArray(ptc)) {
722
- const included = new Set(ptc);
723
- return candidates.filter((t) => included.has(t.name));
724
- }
725
- if ("include" in ptc) {
726
- const included = new Set(ptc.include);
727
- return candidates.filter((t) => included.has(t.name));
728
- }
729
- if ("exclude" in ptc) {
730
- const excluded = new Set([...DEFAULT_PTC_EXCLUDED_TOOLS, ...ptc.exclude]);
731
- return candidates.filter((t) => !excluded.has(t.name));
732
- }
733
- return [];
1048
+ if (!ptc) return [];
1049
+ return resolveToolList(ptc, allTools.filter((t) => t.name !== "js_eval"));
734
1050
  }
735
1051
  return (0, langchain.createMiddleware)({
736
1052
  name: "QuickJSMiddleware",
737
1053
  tools: [(0, langchain.tool)(async (input, config) => {
738
- const threadId = config.configurable?.thread_id || "__default__";
739
- const resolvedBackend = await (0, deepagents.resolveBackend)(backend, {
740
- ...config,
741
- state: (0, _langchain_langgraph.getCurrentTaskInput)(config) || {}
742
- });
743
- const session = ReplSession.getOrCreate(threadId, {
1054
+ const sessionKey = `${config.configurable?.thread_id || "__default__"}:${middlewareId}`;
1055
+ const session = ReplSession.getOrCreate(sessionKey, {
744
1056
  memoryLimitBytes,
745
1057
  maxStackSizeBytes,
746
- backend: resolvedBackend,
747
- tools: ptcTools
1058
+ maxPtcCalls,
1059
+ tools: ptcTools,
1060
+ skillsEnabled: skillsBackend !== void 0,
1061
+ maxResultChars
748
1062
  });
749
- const result = await session.eval(input.code, executionTimeoutMs);
750
- await session.flushWrites(resolvedBackend);
751
- return formatReplResult(result);
1063
+ if (skillsBackend !== void 0) {
1064
+ const setupError = await prepareSkillsForEval(session, skillsBackend, input.code);
1065
+ if (setupError !== void 0) return setupError;
1066
+ }
1067
+ return formatReplResult(await session.eval(input.code, executionTimeoutMs));
752
1068
  }, {
753
1069
  name: "js_eval",
754
1070
  description: dedent.default`
755
1071
  Evaluate TypeScript/JavaScript code in a sandboxed REPL. State persists across calls.
756
- Use readFile(path) and writeFile(path, content) for file access.
757
1072
  Use console.log() for output. Returns the result of the last expression.
1073
+ If file or other tools are available, call them via the tools namespace: await tools.readFile({ path }).
1074
+ If skills are configured, dynamically import them: await import("@/skills/<name>").
758
1075
  `,
1076
+ metadata: { ls_code_input_language: "javascript" },
759
1077
  schema: zod_v4.z.object({ code: zod_v4.z.string().describe("TypeScript/JavaScript code to evaluate in the sandboxed REPL") })
760
1078
  })],
761
1079
  wrapModelCall: async (request, handler) => {
762
- const agentTools = request.tools || [];
763
- ptcTools = usePtc ? filterToolsForPtc(agentTools) : [];
1080
+ ptcTools = filterToolsForPtc(request.tools || []);
764
1081
  if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
765
1082
  const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(cachedPtcPrompt || "");
766
1083
  return handler({
767
1084
  ...request,
768
1085
  systemMessage
769
1086
  });
1087
+ },
1088
+ afterAgent: async (_state, runtime) => {
1089
+ const sessionKey = `${runtime.configurable?.thread_id ?? "__default__"}:${middlewareId}`;
1090
+ ReplSession.deleteSession(sessionKey);
770
1091
  }
771
1092
  });
772
1093
  }
773
1094
  //#endregion
774
1095
  exports.DEFAULT_EXECUTION_TIMEOUT = DEFAULT_EXECUTION_TIMEOUT;
1096
+ exports.DEFAULT_MAX_PTC_CALLS = DEFAULT_MAX_PTC_CALLS;
775
1097
  exports.DEFAULT_MAX_STACK_SIZE = DEFAULT_MAX_STACK_SIZE;
776
1098
  exports.DEFAULT_MEMORY_LIMIT = DEFAULT_MEMORY_LIMIT;
777
- exports.DEFAULT_PTC_EXCLUDED_TOOLS = DEFAULT_PTC_EXCLUDED_TOOLS;
1099
+ exports.MAX_SKILL_BUNDLE_BYTES = MAX_SKILL_BUNDLE_BYTES;
1100
+ exports.PTCCallBudgetExceededError = PTCCallBudgetExceededError;
778
1101
  exports.ReplSession = ReplSession;
1102
+ exports.SKILL_MODULE_EXTENSIONS = SKILL_MODULE_EXTENSIONS;
779
1103
  exports.createQuickJSMiddleware = createQuickJSMiddleware;
780
1104
  exports.formatReplResult = formatReplResult;
1105
+ exports.formatSkillNotAvailable = formatSkillNotAvailable;
1106
+ exports.loadSkill = loadSkill;
1107
+ exports.scanSkillReferences = scanSkillReferences;
1108
+ exports.stripTypeSyntax = stripTypeSyntax;
781
1109
  exports.toCamelCase = toCamelCase;
782
1110
  exports.transformForEval = transformForEval;
783
1111