@langchain/quickjs 0.4.0 → 0.5.1
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 +595 -429
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -115
- package/dist/index.d.ts +68 -115
- package/dist/index.js +594 -424
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,17 +1,155 @@
|
|
|
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
|
|
7
|
+
import { compile } from "json-schema-to-typescript";
|
|
8
|
+
import { toJsonSchema } from "@langchain/core/utils/json_schema";
|
|
9
|
+
import { isCommand } from "@langchain/langgraph";
|
|
10
|
+
import { BaseMessage } from "@langchain/core/messages";
|
|
9
11
|
import { Parser } from "acorn";
|
|
10
12
|
import { tsPlugin } from "@sveltejs/acorn-typescript";
|
|
11
13
|
import { walk } from "estree-walker";
|
|
12
14
|
import MagicString from "magic-string";
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
+
import PQueue from "p-queue";
|
|
16
|
+
//#region src/errors.ts
|
|
17
|
+
/**
|
|
18
|
+
* Thrown when a single eval exhausts its configured PTC call budget.
|
|
19
|
+
*/
|
|
20
|
+
var PTCCallBudgetExceededError = class extends Error {
|
|
21
|
+
limit;
|
|
22
|
+
attempted;
|
|
23
|
+
functionName;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
super(`PTC call budget exceeded (limit=${options.limit}, attempted=${options.attempted}, function=${options.functionName})`);
|
|
26
|
+
this.name = "PTCCallBudgetExceededError";
|
|
27
|
+
this.limit = options.limit;
|
|
28
|
+
this.attempted = options.attempted;
|
|
29
|
+
this.functionName = options.functionName;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/utils.ts
|
|
34
|
+
/**
|
|
35
|
+
* Convert a snake_case or kebab-case string to camelCase.
|
|
36
|
+
*/
|
|
37
|
+
function toCamelCase(name) {
|
|
38
|
+
return name.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format the result of a REPL evaluation for the agent.
|
|
42
|
+
*/
|
|
43
|
+
function formatReplResult(result) {
|
|
44
|
+
const parts = [];
|
|
45
|
+
if (result.logs.length > 0) {
|
|
46
|
+
let logsText = result.logs.join("\n");
|
|
47
|
+
if (result.logsDroppedChars > 0) logsText += `\n[truncated ${result.logsDroppedChars} chars]`;
|
|
48
|
+
parts.push(logsText);
|
|
49
|
+
}
|
|
50
|
+
if (result.ok) {
|
|
51
|
+
if (result.value !== void 0) {
|
|
52
|
+
const formatted = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
|
|
53
|
+
parts.push(`→ ${formatted}`);
|
|
54
|
+
}
|
|
55
|
+
} else if (result.error) {
|
|
56
|
+
const errName = result.error.name || "Error";
|
|
57
|
+
const errMsg = result.error.message || "Unknown error";
|
|
58
|
+
parts.push(`${errName}: ${errMsg}`);
|
|
59
|
+
if (result.error.stack) parts.push(result.error.stack);
|
|
60
|
+
}
|
|
61
|
+
return parts.join("\n") || "(no output)";
|
|
62
|
+
}
|
|
63
|
+
function safeToJsonSchema(schema) {
|
|
64
|
+
try {
|
|
65
|
+
return toJsonSchema(schema);
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function schemaToInterface(jsonSchema, interfaceName) {
|
|
71
|
+
return (await compile({
|
|
72
|
+
...jsonSchema,
|
|
73
|
+
additionalProperties: false
|
|
74
|
+
}, interfaceName, {
|
|
75
|
+
bannerComment: "",
|
|
76
|
+
additionalProperties: false
|
|
77
|
+
})).replace(/^export /, "").trimEnd();
|
|
78
|
+
}
|
|
79
|
+
function capitalize(s) {
|
|
80
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
81
|
+
}
|
|
82
|
+
async function toolToTypeSignature(name, description, jsonSchema) {
|
|
83
|
+
const inputType = `${capitalize(name)}Input`;
|
|
84
|
+
if (!jsonSchema || !jsonSchema.properties) return dedent`
|
|
85
|
+
/**
|
|
86
|
+
* ${description}
|
|
87
|
+
*/
|
|
88
|
+
async tools.${name}(input: Record<string, unknown>): Promise<string>
|
|
89
|
+
`;
|
|
90
|
+
return dedent`
|
|
91
|
+
${await schemaToInterface(jsonSchema, inputType)}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* ${description}
|
|
95
|
+
*/
|
|
96
|
+
async tools.${name}(input: ${inputType}): Promise<string>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/coerce.ts
|
|
101
|
+
/**
|
|
102
|
+
* Coercion of tool / subagent return values for the QuickJS bridge.
|
|
103
|
+
*
|
|
104
|
+
* The deepagents `task` tool resolves to a LangGraph `Command` whose payload
|
|
105
|
+
* carries the subagent's final message(s) under `update.messages`; some tools
|
|
106
|
+
* return a `ToolMessage` or a list of messages. The interpreter bridges need
|
|
107
|
+
* the underlying output, not the envelope, so this unwraps those shapes to the
|
|
108
|
+
* content the model actually cares about.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* Return the trailing message content from a `Command`'s `update.messages`,
|
|
112
|
+
* scanning from the end for the last message that actually has content. Returns
|
|
113
|
+
* the command unchanged when it has no message-shaped payload.
|
|
114
|
+
*/
|
|
115
|
+
function extractCommandContent(command) {
|
|
116
|
+
const update = command.update;
|
|
117
|
+
const messages = update !== null && typeof update === "object" ? update.messages : void 0;
|
|
118
|
+
if (Array.isArray(messages)) for (let i = messages.length - 1; i >= 0; i--) {
|
|
119
|
+
const message = messages[i];
|
|
120
|
+
if (BaseMessage.isInstance(message) && message.content != null) return message.content;
|
|
121
|
+
}
|
|
122
|
+
return command;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Unwrap a LangChain `Command` / `ToolMessage` / message-list envelope to the
|
|
126
|
+
* underlying content. Non-envelope values (strings, content-block arrays, plain
|
|
127
|
+
* objects) are returned unchanged.
|
|
128
|
+
*
|
|
129
|
+
* @param value The raw value returned by a tool or subagent dispatch.
|
|
130
|
+
* @returns The unwrapped content, or `value` itself when it isn't an envelope.
|
|
131
|
+
*/
|
|
132
|
+
function unwrapToolEnvelope(value) {
|
|
133
|
+
if (typeof value === "string") return value;
|
|
134
|
+
if (isCommand(value)) {
|
|
135
|
+
const inner = extractCommandContent(value);
|
|
136
|
+
return inner === value ? value : unwrapToolEnvelope(inner);
|
|
137
|
+
}
|
|
138
|
+
if (BaseMessage.isInstance(value)) return unwrapToolEnvelope(value.content);
|
|
139
|
+
if (Array.isArray(value)) {
|
|
140
|
+
for (let i = value.length - 1; i >= 0; i--) {
|
|
141
|
+
const entry = value[i];
|
|
142
|
+
if (BaseMessage.isInstance(entry)) return unwrapToolEnvelope(entry.content);
|
|
143
|
+
if (isCommand(entry)) {
|
|
144
|
+
const inner = extractCommandContent(entry);
|
|
145
|
+
if (inner !== entry) return unwrapToolEnvelope(inner);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
15
153
|
//#region src/transform.ts
|
|
16
154
|
/**
|
|
17
155
|
* AST-based code transform pipeline for the REPL.
|
|
@@ -87,7 +225,11 @@ function transformForEval(code) {
|
|
|
87
225
|
}
|
|
88
226
|
function isTSOnlyNode(node) {
|
|
89
227
|
const t = node.type;
|
|
90
|
-
|
|
228
|
+
if (t === "TSTypeAliasDeclaration" || t === "TSInterfaceDeclaration" || t === "TSEnumDeclaration" || t === "TSModuleDeclaration" || t === "TSDeclareFunction" || t.startsWith("TS")) return true;
|
|
229
|
+
if (t === "VariableDeclaration" && node.declare === true) return true;
|
|
230
|
+
if (t === "ImportDeclaration" && node.importKind === "type") return true;
|
|
231
|
+
if (t === "ExportNamedDeclaration" && node.exportKind === "type") return true;
|
|
232
|
+
return false;
|
|
91
233
|
}
|
|
92
234
|
/**
|
|
93
235
|
* Rewrite a top-level VariableDeclaration to globalThis assignments.
|
|
@@ -139,7 +281,11 @@ function stripTypeAnnotations(s, node) {
|
|
|
139
281
|
} });
|
|
140
282
|
}
|
|
141
283
|
function stripTypeAnnotationFromNode(s, n, offset = 0) {
|
|
142
|
-
if (n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - offset, n.typeAnnotation.end - offset);
|
|
284
|
+
if (n.optional === true && n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - 1 - offset, n.typeAnnotation.end - offset);
|
|
285
|
+
else if (n.optional === true && !n.typeAnnotation) {
|
|
286
|
+
const nameEnd = n.type === "Identifier" && typeof n.name === "string" ? n.start + n.name.length : null;
|
|
287
|
+
if (nameEnd != null) s.remove(nameEnd - offset, nameEnd + 1 - offset);
|
|
288
|
+
} else if (n.typeAnnotation && n.typeAnnotation.start != null) s.remove(n.typeAnnotation.start - offset, n.typeAnnotation.end - offset);
|
|
143
289
|
if (n.returnType && n.returnType.start != null) s.remove(n.returnType.start - offset, n.returnType.end - offset);
|
|
144
290
|
if (n.typeParameters && n.typeParameters.start != null) s.remove(n.typeParameters.start - offset, n.typeParameters.end - offset);
|
|
145
291
|
if (n.typeArguments && n.typeArguments.start != null) s.remove(n.typeArguments.start - offset, n.typeArguments.end - offset);
|
|
@@ -205,228 +351,6 @@ function stripTypeSyntax(code) {
|
|
|
205
351
|
return magicString.toString();
|
|
206
352
|
}
|
|
207
353
|
//#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
|
-
}
|
|
276
|
-
/**
|
|
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.
|
|
328
|
-
*
|
|
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.
|
|
332
|
-
*/
|
|
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;
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
//#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
354
|
//#region src/eval-queue.ts
|
|
431
355
|
/**
|
|
432
356
|
* Serializes async operations on a shared WASM module.
|
|
@@ -483,6 +407,7 @@ const DEFAULT_MAX_STACK_SIZE = 320 * 1024;
|
|
|
483
407
|
const DEFAULT_EXECUTION_TIMEOUT = 5e3;
|
|
484
408
|
const DEFAULT_MAX_PTC_CALLS = 256;
|
|
485
409
|
const DEFAULT_MAX_RESULTS_CHARS = 4e3;
|
|
410
|
+
const LINE_NUMBER_RE = /^\s*\d+(?:\.\d+)?\t/;
|
|
486
411
|
const variantImport = import("@jitl/quickjs-ng-wasmfile-release-asyncify");
|
|
487
412
|
/**
|
|
488
413
|
* Process-global eval queue. Serializes all evalCodeAsync calls across
|
|
@@ -514,59 +439,47 @@ function getSharedModule() {
|
|
|
514
439
|
})();
|
|
515
440
|
return sharedModulePromise;
|
|
516
441
|
}
|
|
517
|
-
function makeErrorSource(message) {
|
|
518
|
-
return `throw { name: "Error", message: ${JSON.stringify(message)} };`;
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
* Parse a canonicalized skill specifier into `{ name, rel }`.
|
|
522
|
-
* Returns `undefined` for anything that isn't a valid `@/skills/<name>` or
|
|
523
|
-
* `@/skills/<name>/<rel>` shape. `rel` is absent for the bare form.
|
|
524
|
-
*/
|
|
525
|
-
function parseSkillSpecifier(specifier) {
|
|
526
|
-
if (!specifier.startsWith("@/skills/")) return;
|
|
527
|
-
const tail = specifier.slice(9);
|
|
528
|
-
const slashIdx = tail.indexOf("/");
|
|
529
|
-
const name = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
|
|
530
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) return;
|
|
531
|
-
const rel = slashIdx === -1 ? void 0 : tail.slice(slashIdx + 1);
|
|
532
|
-
if (rel !== void 0 && rel === "") return;
|
|
533
|
-
return {
|
|
534
|
-
name,
|
|
535
|
-
rel
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Return the `@/skills/<name>` prefix for the skill that owns `base`, or `undefined`.
|
|
540
|
-
*/
|
|
541
|
-
function matchSkillPrefix(base) {
|
|
542
|
-
const parsed = parseSkillSpecifier(base);
|
|
543
|
-
if (parsed === void 0) return;
|
|
544
|
-
return `@/skills/${parsed.name}`;
|
|
545
|
-
}
|
|
546
442
|
/**
|
|
547
|
-
*
|
|
443
|
+
* Unwrap a PTC tool result to a plain string for use inside QuickJS.
|
|
444
|
+
*
|
|
445
|
+
* Tool results may arrive as a raw string, or as an array of LangChain
|
|
446
|
+
* content blocks (`{ type: "text", text: "..." }`). Blocks are joined
|
|
447
|
+
* with newlines; non-text block types are silently skipped. Anything
|
|
448
|
+
* else (objects, nulls) is JSON-serialised as a fallback.
|
|
449
|
+
*
|
|
450
|
+
* @param result - Raw return value from `tool.invoke()`.
|
|
451
|
+
* @returns Plain string representation of the tool output.
|
|
548
452
|
*/
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
if (
|
|
552
|
-
|
|
453
|
+
function extractToolText(result) {
|
|
454
|
+
result = unwrapToolEnvelope(result);
|
|
455
|
+
if (typeof result === "string") return result;
|
|
456
|
+
if (Array.isArray(result)) {
|
|
457
|
+
const texts = [];
|
|
458
|
+
for (const block of result) if (typeof block === "object" && block !== null && block.type === "text" && typeof block.text === "string") texts.push(block.text);
|
|
459
|
+
if (texts.length > 0) return texts.join("\n");
|
|
460
|
+
}
|
|
461
|
+
return JSON.stringify(result);
|
|
553
462
|
}
|
|
554
463
|
/**
|
|
555
|
-
*
|
|
556
|
-
*
|
|
464
|
+
* Remove the `cat -n` line-number prefix from every line of a string.
|
|
465
|
+
*
|
|
466
|
+
* The filesystem backend formats file content with line numbers in the
|
|
467
|
+
* form `" N\t"` so human readers can navigate by line. That prefix
|
|
468
|
+
* is useful for the agent but noise for QuickJS code that parses the
|
|
469
|
+
* text programmatically (e.g. swarm reading `/context.txt`).
|
|
470
|
+
*
|
|
471
|
+
* The function is conservative: if any non-empty line lacks the prefix,
|
|
472
|
+
* the text is returned unchanged so nothing is silently corrupted.
|
|
473
|
+
*
|
|
474
|
+
* @param text - Raw file content, possibly line-number prefixed.
|
|
475
|
+
* @returns Content with line-number prefixes stripped, or the original
|
|
476
|
+
* text if it doesn't match the expected format throughout.
|
|
557
477
|
*/
|
|
558
|
-
function
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (segment === "..") {
|
|
564
|
-
out.pop();
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
out.push(segment);
|
|
568
|
-
}
|
|
569
|
-
return out.join("/");
|
|
478
|
+
function stripLineNumbers(text) {
|
|
479
|
+
const lines = text.split("\n");
|
|
480
|
+
if (lines.length === 0) return text;
|
|
481
|
+
if (!lines.every((l) => l === "" || LINE_NUMBER_RE.test(l))) return text;
|
|
482
|
+
return lines.map((l) => l.replace(LINE_NUMBER_RE, "")).join("\n");
|
|
570
483
|
}
|
|
571
484
|
/**
|
|
572
485
|
* Fixed-size character buffer for capturing console output from the QuickJS VM.
|
|
@@ -628,11 +541,16 @@ var ReplSession = class ReplSession {
|
|
|
628
541
|
context = null;
|
|
629
542
|
consoleBuffer = new ConsoleBuffer(DEFAULT_MAX_RESULTS_CHARS);
|
|
630
543
|
options;
|
|
631
|
-
skillsContext;
|
|
632
|
-
skillsLoaded = /* @__PURE__ */ new Map();
|
|
633
|
-
skillsFailed = /* @__PURE__ */ new Map();
|
|
634
544
|
maxPtcCalls;
|
|
635
545
|
ptcCallsRemaining = null;
|
|
546
|
+
subagentQueue = null;
|
|
547
|
+
bridgeDispatchRef = null;
|
|
548
|
+
/** Allowed keys in the subagent input object. */
|
|
549
|
+
static SUBAGENT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
550
|
+
"description",
|
|
551
|
+
"subagentType",
|
|
552
|
+
"responseSchema"
|
|
553
|
+
]);
|
|
636
554
|
/**
|
|
637
555
|
* Reset the shared WASM module. Forces the next session to instantiate
|
|
638
556
|
* a fresh module. Only needed in tests where module state must be
|
|
@@ -650,7 +568,7 @@ var ReplSession = class ReplSession {
|
|
|
650
568
|
}
|
|
651
569
|
async ensureStarted() {
|
|
652
570
|
if (this.runtime) return;
|
|
653
|
-
const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools,
|
|
571
|
+
const { memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, tools, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, captureConsole = true } = this.options;
|
|
654
572
|
const runtime = (await getSharedModule()).newRuntime();
|
|
655
573
|
runtime.setMemoryLimit(memoryLimitBytes);
|
|
656
574
|
runtime.setMaxStackSize(maxStackSizeBytes);
|
|
@@ -660,97 +578,15 @@ var ReplSession = class ReplSession {
|
|
|
660
578
|
this.consoleBuffer = new ConsoleBuffer(maxResultChars);
|
|
661
579
|
if (captureConsole) this.setupConsole();
|
|
662
580
|
if (tools !== void 0 && tools.length > 0) this.injectTools(tools);
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
*/
|
|
668
|
-
async ensureSkillLoaded(name) {
|
|
669
|
-
const cached = this.skillsLoaded.get(name);
|
|
670
|
-
if (cached !== void 0) return cached;
|
|
671
|
-
const cachedError = this.skillsFailed.get(name);
|
|
672
|
-
if (cachedError !== void 0) throw cachedError;
|
|
673
|
-
const ctx = this.skillsContext;
|
|
674
|
-
if (ctx === void 0) throw new Error(`Skill '${name}' referenced but skills are not configured for this session`);
|
|
675
|
-
const metadata = ctx.metadata.find((m) => m.name === name);
|
|
676
|
-
if (metadata === void 0) throw new Error(`Skill '${name}' referenced but not available on this agent`);
|
|
677
|
-
try {
|
|
678
|
-
const loaded = await loadSkill(metadata, ctx.backend);
|
|
679
|
-
this.skillsLoaded.set(name, loaded);
|
|
680
|
-
return loaded;
|
|
681
|
-
} catch (err) {
|
|
682
|
-
this.skillsFailed.set(name, err);
|
|
683
|
-
throw err;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Pre-load all skills referenced in source code into the in-memory
|
|
688
|
-
* cache. Must be called before `evalCodeAsync` so the module loader
|
|
689
|
-
* can resolve synchronously. An async loader would cause asyncify
|
|
690
|
-
* suspensions on each import, which is incompatible with the shared
|
|
691
|
-
* WASM module used by all sessions.
|
|
692
|
-
*/
|
|
693
|
-
async preloadReferencedSkills(code) {
|
|
694
|
-
const refs = scanSkillReferences(code);
|
|
695
|
-
for (const name of refs) {
|
|
696
|
-
if (this.skillsLoaded.has(name) || this.skillsFailed.has(name)) continue;
|
|
697
|
-
try {
|
|
698
|
-
await this.ensureSkillLoaded(name);
|
|
699
|
-
} catch (err) {
|
|
700
|
-
if (!this.skillsFailed.has(name)) this.skillsFailed.set(name, err);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Resolve a module specifier to source code. Strictly synchronous —
|
|
706
|
-
* only reads from the in-memory skill cache populated by
|
|
707
|
-
* `preloadReferencedSkills`. Returns error source (not a thrown
|
|
708
|
-
* exception) for missing or failed skills so QuickJS reports the
|
|
709
|
-
* error inside the VM.
|
|
710
|
-
*/
|
|
711
|
-
resolveSpecifier(specifier) {
|
|
712
|
-
const parsed = parseSkillSpecifier(specifier);
|
|
713
|
-
if (parsed === void 0) return makeErrorSource(`Module not found: ${specifier}`);
|
|
714
|
-
const cachedError = this.skillsFailed.get(parsed.name);
|
|
715
|
-
if (cachedError !== void 0) return makeErrorSource(cachedError.message ?? String(cachedError));
|
|
716
|
-
const loaded = this.skillsLoaded.get(parsed.name);
|
|
717
|
-
if (loaded === void 0) return makeErrorSource(`Skill '${parsed.name}' was not preloaded. Ensure the import specifier is a static string literal (dynamic specifiers like \`import("@/skills/" + name)\` are not supported).`);
|
|
718
|
-
if (parsed.rel === void 0) {
|
|
719
|
-
const source = loaded.files.get(loaded.entryRel);
|
|
720
|
-
if (source === void 0) return makeErrorSource(`Skill '${parsed.name}': entrypoint '${loaded.entryRel}' missing from bundle`);
|
|
721
|
-
return source;
|
|
581
|
+
const { subagentBridge } = this.options;
|
|
582
|
+
if (subagentBridge) {
|
|
583
|
+
this.subagentQueue = new PQueue({ concurrency: subagentBridge.maxConcurrency });
|
|
584
|
+
this.injectSubagentBridge(subagentBridge.dispatch);
|
|
722
585
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Canonicalize an `import` specifier. Bare specifiers pass through;
|
|
730
|
-
* relative specifiers are resolved against the importing module's path.
|
|
731
|
-
* Traversal out of a skill's `@/skills/<name>/` namespace is rejected.
|
|
732
|
-
*/
|
|
733
|
-
normalizeSpecifier(base, requested) {
|
|
734
|
-
if (!(requested.startsWith("./") || requested.startsWith("../"))) return requested;
|
|
735
|
-
const parsed = parseSkillSpecifier(base);
|
|
736
|
-
const resolved = posixJoin(parsed !== void 0 && parsed.rel === void 0 ? base : posixDirname(base), requested);
|
|
737
|
-
const skillPrefix = matchSkillPrefix(base);
|
|
738
|
-
if (skillPrefix === void 0) return resolved;
|
|
739
|
-
if (!resolved.startsWith(`${skillPrefix}/`)) return `__resolve_error__:${requested} escapes ${skillPrefix}`;
|
|
740
|
-
return resolved;
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* Wire the QuickJS module loader and normalizer on this session's runtime.
|
|
744
|
-
*
|
|
745
|
-
* The loader is strictly synchronous — it reads from the in-memory skill
|
|
746
|
-
* cache populated by `preloadReferencedSkills`. This is critical: an async
|
|
747
|
-
* module loader causes asyncify suspensions on each import, and disposing
|
|
748
|
-
* a runtime after multi-file imports corrupts the shared module's asyncify
|
|
749
|
-
* state, silently breaking the loader for all subsequent sessions.
|
|
750
|
-
*/
|
|
751
|
-
installModuleLoader() {
|
|
752
|
-
if (this.runtime === null) return;
|
|
753
|
-
this.runtime.setModuleLoader((specifier) => this.resolveSpecifier(specifier), (base, requested) => this.normalizeSpecifier(base, requested));
|
|
586
|
+
const sessionId = this.options.sessionId ?? "default";
|
|
587
|
+
const sessionIdHandle = context.newString(sessionId);
|
|
588
|
+
context.setProp(context.global, "__sessionId__", sessionIdHandle);
|
|
589
|
+
sessionIdHandle.dispose();
|
|
754
590
|
}
|
|
755
591
|
/**
|
|
756
592
|
* Initialise the per-eval PTC counter. Called at the top of every `eval()`.
|
|
@@ -813,14 +649,6 @@ var ReplSession = class ReplSession {
|
|
|
813
649
|
if (session) session.dispose();
|
|
814
650
|
}
|
|
815
651
|
/**
|
|
816
|
-
* Push the current skills metadata + backend into the session.
|
|
817
|
-
* Called by the middleware once per `eval` invocation, before eval runs.
|
|
818
|
-
* Pass `undefined` to clear the context (no skill imports will resolve).
|
|
819
|
-
*/
|
|
820
|
-
setSkillsContext(ctx) {
|
|
821
|
-
this.skillsContext = ctx;
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
652
|
* Evaluate code in this session.
|
|
825
653
|
*
|
|
826
654
|
* Lazily starts the QuickJS runtime on the first call. Code is
|
|
@@ -833,7 +661,6 @@ var ReplSession = class ReplSession {
|
|
|
833
661
|
await this.ensureStarted();
|
|
834
662
|
const runtime = this.runtime;
|
|
835
663
|
const context = this.context;
|
|
836
|
-
await this.preloadReferencedSkills(code);
|
|
837
664
|
const drainLogs = () => {
|
|
838
665
|
const [raw, dropped] = this.consoleBuffer.drain();
|
|
839
666
|
return {
|
|
@@ -981,8 +808,9 @@ var ReplSession = class ReplSession {
|
|
|
981
808
|
try {
|
|
982
809
|
this.consumePtcBudget(camelName);
|
|
983
810
|
const rawInput = typeof input === "object" && input !== null ? input : {};
|
|
984
|
-
|
|
985
|
-
|
|
811
|
+
let text = extractToolText(await t.invoke(rawInput));
|
|
812
|
+
if (t.name === "read_file") text = stripLineNumbers(text);
|
|
813
|
+
const val = context.newString(text);
|
|
986
814
|
promise.resolve(val);
|
|
987
815
|
val.dispose();
|
|
988
816
|
} catch (e) {
|
|
@@ -1001,8 +829,113 @@ var ReplSession = class ReplSession {
|
|
|
1001
829
|
context.setProp(context.global, "tools", toolsNs);
|
|
1002
830
|
toolsNs.dispose();
|
|
1003
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Install the `task` global on the QuickJS context.
|
|
834
|
+
*
|
|
835
|
+
* Registers the host function directly as `globalThis.task`,
|
|
836
|
+
* then freezes it via `evalCode`. Structured results (when
|
|
837
|
+
* responseSchema is provided) are marshaled into native QuickJS
|
|
838
|
+
* objects on the host side — no JS wrapper needed.
|
|
839
|
+
*/
|
|
840
|
+
/**
|
|
841
|
+
* Replace the active bridge dispatch with a fresh one.
|
|
842
|
+
*
|
|
843
|
+
* Call this before each eval so the dispatch closure carries
|
|
844
|
+
* the current invocation's config (tracing callbacks, run ID, etc.)
|
|
845
|
+
* rather than the stale config from session creation.
|
|
846
|
+
*/
|
|
847
|
+
updateBridgeDispatch(dispatch) {
|
|
848
|
+
if (this.bridgeDispatchRef) this.bridgeDispatchRef.current = dispatch;
|
|
849
|
+
}
|
|
850
|
+
injectSubagentBridge(dispatch) {
|
|
851
|
+
const context = this.context;
|
|
852
|
+
const queue = this.subagentQueue;
|
|
853
|
+
this.bridgeDispatchRef = { current: dispatch };
|
|
854
|
+
const ref = this.bridgeDispatchRef;
|
|
855
|
+
const hostFn = context.newFunction("task", (inputHandle) => {
|
|
856
|
+
const input = context.dump(inputHandle);
|
|
857
|
+
const promise = context.newPromise();
|
|
858
|
+
(async () => {
|
|
859
|
+
try {
|
|
860
|
+
if (input == null || typeof input !== "object" || Array.isArray(input)) throw new Error("task: expected an object argument");
|
|
861
|
+
const obj = { ...input };
|
|
862
|
+
if ("subagent_type" in obj) {
|
|
863
|
+
obj.subagentType ??= obj.subagent_type;
|
|
864
|
+
delete obj.subagent_type;
|
|
865
|
+
}
|
|
866
|
+
if ("response_schema" in obj) {
|
|
867
|
+
obj.responseSchema ??= obj.response_schema;
|
|
868
|
+
delete obj.response_schema;
|
|
869
|
+
}
|
|
870
|
+
const unknownKeys = Object.keys(obj).filter((k) => !ReplSession.SUBAGENT_ALLOWED_KEYS.has(k));
|
|
871
|
+
if (unknownKeys.length > 0) throw new Error(`task: unknown keys: ${unknownKeys.join(", ")}. Allowed: ${[...ReplSession.SUBAGENT_ALLOWED_KEYS].join(", ")}`);
|
|
872
|
+
const { description, subagentType, responseSchema } = obj;
|
|
873
|
+
if (typeof description !== "string" || description.length === 0) throw new Error("task: 'description' is required and must be a non-empty string");
|
|
874
|
+
if (typeof subagentType !== "string" || subagentType.length === 0) throw new Error("task: 'subagentType' is required and must be a non-empty string");
|
|
875
|
+
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");
|
|
876
|
+
const result = await queue.add(() => ref.current({
|
|
877
|
+
description,
|
|
878
|
+
subagentType,
|
|
879
|
+
...responseSchema !== void 0 && { responseSchema }
|
|
880
|
+
}));
|
|
881
|
+
if (typeof result === "string") {
|
|
882
|
+
const val = context.newString(result);
|
|
883
|
+
promise.resolve(val);
|
|
884
|
+
val.dispose();
|
|
885
|
+
} else {
|
|
886
|
+
const jsonResult = context.evalCode(`(${JSON.stringify(result)})`);
|
|
887
|
+
if (jsonResult.error) {
|
|
888
|
+
const errDump = context.dump(jsonResult.error);
|
|
889
|
+
jsonResult.error.dispose();
|
|
890
|
+
throw new Error(`task: failed to marshal structured response: ${JSON.stringify(errDump)}`);
|
|
891
|
+
}
|
|
892
|
+
promise.resolve(jsonResult.value);
|
|
893
|
+
jsonResult.value.dispose();
|
|
894
|
+
}
|
|
895
|
+
} catch (e) {
|
|
896
|
+
const msg = e != null && typeof e.message === "string" ? e.message : String(e);
|
|
897
|
+
const err = context.newError(msg);
|
|
898
|
+
promise.reject(err);
|
|
899
|
+
err.dispose();
|
|
900
|
+
}
|
|
901
|
+
promise.settled.then(context.runtime.executePendingJobs);
|
|
902
|
+
})();
|
|
903
|
+
return promise.handle;
|
|
904
|
+
});
|
|
905
|
+
context.setProp(context.global, "task", hostFn);
|
|
906
|
+
hostFn.dispose();
|
|
907
|
+
context.evalCode("Object.freeze(globalThis.task);Object.defineProperty(globalThis, 'task', { value: globalThis.task, writable: false, configurable: false,}); undefined");
|
|
908
|
+
}
|
|
1004
909
|
};
|
|
1005
910
|
//#endregion
|
|
911
|
+
//#region src/subagent-dispatch.ts
|
|
912
|
+
const SCHEMA_MAX_BYTES = 4096;
|
|
913
|
+
const SCHEMA_MAX_DEPTH = 5;
|
|
914
|
+
const SCHEMA_MAX_PROPERTIES = 32;
|
|
915
|
+
/**
|
|
916
|
+
* Validate that a response schema does not exceed size, depth, or
|
|
917
|
+
* property-count limits.
|
|
918
|
+
*
|
|
919
|
+
* @throws Error if any limit is exceeded.
|
|
920
|
+
*/
|
|
921
|
+
function validateResponseSchema(schema) {
|
|
922
|
+
const serialized = JSON.stringify(schema);
|
|
923
|
+
if (serialized.length > SCHEMA_MAX_BYTES) throw new Error(`responseSchema exceeds ${SCHEMA_MAX_BYTES} byte limit (${serialized.length} bytes)`);
|
|
924
|
+
function check(node, depth, propCount) {
|
|
925
|
+
if (depth > SCHEMA_MAX_DEPTH) throw new Error(`responseSchema exceeds maximum nesting depth of ${SCHEMA_MAX_DEPTH}`);
|
|
926
|
+
const props = node.properties;
|
|
927
|
+
if (props != null && typeof props === "object" && !Array.isArray(props)) {
|
|
928
|
+
const propObj = props;
|
|
929
|
+
propCount.value += Object.keys(propObj).length;
|
|
930
|
+
if (propCount.value > SCHEMA_MAX_PROPERTIES) throw new Error(`responseSchema exceeds maximum of ${SCHEMA_MAX_PROPERTIES} properties`);
|
|
931
|
+
for (const value of Object.values(propObj)) if (value != null && typeof value === "object" && !Array.isArray(value)) check(value, depth + 1, propCount);
|
|
932
|
+
}
|
|
933
|
+
const items = node.items;
|
|
934
|
+
if (items != null && typeof items === "object" && !Array.isArray(items)) check(items, depth + 1, propCount);
|
|
935
|
+
}
|
|
936
|
+
check(schema, 0, { value: 0 });
|
|
937
|
+
}
|
|
938
|
+
//#endregion
|
|
1006
939
|
//#region src/middleware.ts
|
|
1007
940
|
/**
|
|
1008
941
|
* Code Interpreter middleware for deepagents.
|
|
@@ -1013,14 +946,242 @@ var ReplSession = class ReplSession {
|
|
|
1013
946
|
* - Programmatic tool calling (PTC) — expose agent or custom tools inside the REPL
|
|
1014
947
|
*/
|
|
1015
948
|
const DEFAULT_TOOL_NAME = "eval";
|
|
949
|
+
/**
|
|
950
|
+
* Render the subagent dispatch prompt section for the system message.
|
|
951
|
+
* Ported from the Python `_SUBAGENT_SYSTEM_PROMPT_TEMPLATE`.
|
|
952
|
+
*/
|
|
953
|
+
function renderSubagentPrompt(toolName) {
|
|
954
|
+
return dedent`
|
|
955
|
+
|
|
956
|
+
### Dispatching Subagents with \`task\`
|
|
957
|
+
|
|
958
|
+
\`task\` is your primitive for running configured subagents from inside the
|
|
959
|
+
JavaScript REPL. Your job here is to DISTRIBUTE work, not to do it yourself:
|
|
960
|
+
write JavaScript that fans work out to subagents and assembles their results.
|
|
961
|
+
You handle the orchestration - fan-out, filtering, deduplication, multi-stage
|
|
962
|
+
flow, and synthesis - in plain JavaScript.
|
|
963
|
+
|
|
964
|
+
#### The primitive
|
|
965
|
+
|
|
966
|
+
\`\`\`javascript
|
|
967
|
+
await task({
|
|
968
|
+
description, // full autonomous task prompt
|
|
969
|
+
subagentType, // configured subagent name
|
|
970
|
+
responseSchema, // optional JSON Schema for structured output
|
|
971
|
+
}); // -> Promise<unknown>
|
|
972
|
+
\`\`\`
|
|
973
|
+
|
|
974
|
+
\`task\` runs a full agentic loop for the selected configured subagent. The
|
|
975
|
+
subagent can use whatever tools it was configured with, iterate, inspect
|
|
976
|
+
context, and return one final result. \`subagentType\` is required; use one of
|
|
977
|
+
the configured subagent names.
|
|
978
|
+
|
|
979
|
+
\`description\` is the only prompt the subagent receives for this dispatch. Make
|
|
980
|
+
it complete: the goal, the constraints, what to inspect, and the exact shape
|
|
981
|
+
or level of detail you expect back. Give context as locators — file paths and
|
|
982
|
+
symbol names — not as pasted file contents. If you already read a file while
|
|
983
|
+
exploring, still pass its path and let the subagent read it; do not paste back
|
|
984
|
+
what you read. Each dispatch is stateless from the caller's perspective; you
|
|
985
|
+
cannot send follow-up messages to the same subagent run.
|
|
986
|
+
|
|
987
|
+
\`responseSchema\` is optional, but set it on any dispatch whose result feeds
|
|
988
|
+
later code. A deterministic, typed shape is what lets you compose the next
|
|
989
|
+
stage reliably — index it, sort it, compare fields, branch on it, merge it —
|
|
990
|
+
instead of parsing free-form text. This is what makes a whole workflow
|
|
991
|
+
composable as one script. When provided, the resolved value is already a typed
|
|
992
|
+
JavaScript value matching the schema; do not call \`JSON.parse\` unless the
|
|
993
|
+
subagent intentionally returned a JSON string. Dynamic schemas work for
|
|
994
|
+
declarative subagents; runnable-backed subagents reject dynamic schemas because
|
|
995
|
+
their runnable is already compiled.
|
|
996
|
+
|
|
997
|
+
#### Approval model
|
|
998
|
+
|
|
999
|
+
\`task\` dispatches from inside the already-running \`${toolName}\` call. It
|
|
1000
|
+
does not route through the parent agent's \`ToolNode\`-managed \`task\` tool and
|
|
1001
|
+
does not trigger parent-level \`interrupt_on\` / HITL approval for each dispatch.
|
|
1002
|
+
Declarative subagents still honor approval middleware configured inside their
|
|
1003
|
+
own spec. If you need approval before launching a subagent from the parent, use
|
|
1004
|
+
the normal \`task\` tool outside JavaScript or ensure the \`${toolName}\` call
|
|
1005
|
+
itself is approval-gated.
|
|
1006
|
+
|
|
1007
|
+
#### Mental model
|
|
1008
|
+
|
|
1009
|
+
Hold your work in JS: an array of items in, an array of results out. Merge each
|
|
1010
|
+
dispatch result back onto its item. Multi-stage analysis means: run a pass,
|
|
1011
|
+
filter or regroup the array in JS, then run another pass over the survivors.
|
|
1012
|
+
|
|
1013
|
+
You can run the whole workflow in one \`${toolName}\` call or split it across
|
|
1014
|
+
several — both are fine. A single end-to-end script (generate, compare, pick a
|
|
1015
|
+
winner; or review every item, then synthesize) is clean when you can write it
|
|
1016
|
+
in one go; splitting is also fine when you want to inspect results between
|
|
1017
|
+
stages. Either way, don't redo work across calls — reuse what is already in
|
|
1018
|
+
scope (see "Reuse what earlier evals left in scope" below).
|
|
1019
|
+
|
|
1020
|
+
#### Fan out with bounded concurrency
|
|
1021
|
+
|
|
1022
|
+
Dispatch independent work in parallel with \`Promise.all\`, but in explicit
|
|
1023
|
+
batches around 10 so you do not launch hundreds of subagents at once. The bridge
|
|
1024
|
+
enforces a hard per-REPL cap of 32 concurrent subagent calls.
|
|
1025
|
+
|
|
1026
|
+
\`\`\`javascript
|
|
1027
|
+
const files = ["/src/a.ts", "/src/b.ts", "/src/c.ts"]; // found while exploring
|
|
1028
|
+
const batchSize = 10;
|
|
1029
|
+
const reviewed = [];
|
|
1030
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
1031
|
+
const batch = files.slice(i, i + batchSize);
|
|
1032
|
+
reviewed.push(...(await Promise.all(batch.map(async (file) => {
|
|
1033
|
+
const result = await task({
|
|
1034
|
+
description: "Read " + file + " and review it for SQL injection. " +
|
|
1035
|
+
"Cite line numbers.",
|
|
1036
|
+
subagentType: "reviewer",
|
|
1037
|
+
responseSchema: {
|
|
1038
|
+
type: "object",
|
|
1039
|
+
properties: {
|
|
1040
|
+
vulnerabilities: {
|
|
1041
|
+
type: "array",
|
|
1042
|
+
items: {
|
|
1043
|
+
type: "object",
|
|
1044
|
+
properties: {
|
|
1045
|
+
type: { type: "string" },
|
|
1046
|
+
line: { type: "number" },
|
|
1047
|
+
evidence: { type: "string" },
|
|
1048
|
+
},
|
|
1049
|
+
required: ["type", "line", "evidence"],
|
|
1050
|
+
},
|
|
1051
|
+
},
|
|
1052
|
+
},
|
|
1053
|
+
required: ["vulnerabilities"],
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1056
|
+
return { file, ...result };
|
|
1057
|
+
}))));
|
|
1058
|
+
}
|
|
1059
|
+
\`\`\`
|
|
1060
|
+
|
|
1061
|
+
#### Explore with your own tools first, then distribute
|
|
1062
|
+
|
|
1063
|
+
You already have your normal tools for reading, listing, globbing, and
|
|
1064
|
+
grepping files. Use them to explore and understand the task BEFORE you write
|
|
1065
|
+
the orchestration script. These are ordinary tool calls, separate from the
|
|
1066
|
+
\`${toolName}\` tool: read the data file, list or glob the directory, grep for
|
|
1067
|
+
what matters, then decide how to split the work.
|
|
1068
|
+
|
|
1069
|
+
Never write \`${toolName}\` code that spawns a subagent just to read or parse a
|
|
1070
|
+
file or list a directory. That is a deterministic step you do yourself with a
|
|
1071
|
+
direct tool call; spending a whole agent loop on it is wasteful.
|
|
1072
|
+
|
|
1073
|
+
Once you understand the shape of the work, you have creative freedom in how
|
|
1074
|
+
you split it:
|
|
1075
|
+
|
|
1076
|
+
- One dispatch per file or per record, when the items are already separate.
|
|
1077
|
+
- Chunk a large input yourself — read it, split it, optionally write a small
|
|
1078
|
+
input file per chunk — and dispatch one subagent per chunk.
|
|
1079
|
+
- A cheap classification pass first, then deeper dispatches only for the items
|
|
1080
|
+
that warrant them.
|
|
1081
|
+
|
|
1082
|
+
Then write JavaScript in the \`${toolName}\` tool that distributes the heavy,
|
|
1083
|
+
agentic work to subagents with \`task()\`: analyzing file contents, exploring a
|
|
1084
|
+
codebase, making judgment calls, rewriting code, or synthesizing a report.
|
|
1085
|
+
|
|
1086
|
+
Hand each subagent a locator, not a payload. Subagents have their own file
|
|
1087
|
+
tools, so for anything that lives in a file — a file to review, rewrite, or
|
|
1088
|
+
audit — pass the path and let the subagent read it. Do NOT read a whole file
|
|
1089
|
+
just to paste its contents into the description; that bloats every dispatch
|
|
1090
|
+
and duplicates the file across them. Reserve inline content for small or
|
|
1091
|
+
derived data that has no path of its own: a single parsed record, or a chunk
|
|
1092
|
+
you split out of a larger input (write the chunk to its own file and pass that
|
|
1093
|
+
path if it is large). Assemble the results in JS.
|
|
1094
|
+
|
|
1095
|
+
#### Compose multiple stages
|
|
1096
|
+
|
|
1097
|
+
Filter the array in JS between passes. For example: first ask subagents for a
|
|
1098
|
+
cheap classification, filter to the risky items, then dispatch deeper reviews
|
|
1099
|
+
only for those items.
|
|
1100
|
+
|
|
1101
|
+
\`\`\`javascript
|
|
1102
|
+
const tagged = await Promise.all(files.map((file) =>
|
|
1103
|
+
task({
|
|
1104
|
+
description: "Read " + file + " and classify it as handler, util, " +
|
|
1105
|
+
"test, or config.",
|
|
1106
|
+
subagentType: "reviewer",
|
|
1107
|
+
responseSchema: {
|
|
1108
|
+
type: "object",
|
|
1109
|
+
properties: { kind: { type: "string" }, risky: { type: "boolean" } },
|
|
1110
|
+
required: ["kind", "risky"],
|
|
1111
|
+
},
|
|
1112
|
+
}).then((tag) => ({ file, ...tag }))
|
|
1113
|
+
));
|
|
1114
|
+
|
|
1115
|
+
const riskyHandlers = tagged.filter((it) => it.kind === "handler" && it.risky);
|
|
1116
|
+
const deepReviews = await Promise.all(riskyHandlers.map((it) =>
|
|
1117
|
+
task({
|
|
1118
|
+
description: "Deep security review of " + it.file + ". Cite line numbers.",
|
|
1119
|
+
subagentType: "reviewer",
|
|
1120
|
+
}).then((review) => ({ ...it, review }))
|
|
1121
|
+
));
|
|
1122
|
+
\`\`\`
|
|
1123
|
+
|
|
1124
|
+
#### Return results via the last expression, not \`console.log\`
|
|
1125
|
+
|
|
1126
|
+
The value of the last expression in an \`${toolName}\` call (or a resolved
|
|
1127
|
+
top-level \`await\`) is returned to you as the result. Make that final
|
|
1128
|
+
expression the variable holding your result and read it from there.
|
|
1129
|
+
\`console.log\` is only for incidental debugging: its output is capped and
|
|
1130
|
+
truncated, while the returned value is not, so never \`console.log\` your
|
|
1131
|
+
actual results.
|
|
1132
|
+
|
|
1133
|
+
Keep large intermediate sets in JS variables and return only a compact
|
|
1134
|
+
summary or a small slice, not the entire dataset. To persist full output,
|
|
1135
|
+
have a subagent write it, or write it with your own file tool outside the
|
|
1136
|
+
\`${toolName}\` call.
|
|
1137
|
+
|
|
1138
|
+
#### Reuse what earlier evals left in scope
|
|
1139
|
+
|
|
1140
|
+
The REPL is persistent within a turn: every top-level variable, function, and
|
|
1141
|
+
class you declare is kept and is available in your next \`${toolName}\` call
|
|
1142
|
+
(each is hoisted to global scope). So if a later step needs something an
|
|
1143
|
+
earlier eval produced or bound, **reference that variable by name** — do not
|
|
1144
|
+
write a new literal that re-types data a previous eval already returned or
|
|
1145
|
+
computed.
|
|
1146
|
+
|
|
1147
|
+
If you catch yourself pasting a big array or object of values you produced in
|
|
1148
|
+
an earlier call, that is the tell: the variable is still in scope, so use it.
|
|
1149
|
+
Re-typing prior results as a fresh literal wastes tokens and drifts from what
|
|
1150
|
+
actually ran.
|
|
1151
|
+
|
|
1152
|
+
\`\`\`javascript
|
|
1153
|
+
// An earlier eval bound this:
|
|
1154
|
+
// const auditResults = await Promise.all(files.map(/* ...audit... */));
|
|
1155
|
+
|
|
1156
|
+
// A later eval — reference it; do NOT paste the findings back in as a literal:
|
|
1157
|
+
const findings = auditResults.flatMap((r) =>
|
|
1158
|
+
r.findings.map((f) => ({ ...f, file: r.file }))
|
|
1159
|
+
);
|
|
1160
|
+
const verified = await Promise.all(findings.map((f) =>
|
|
1161
|
+
task({ description: "Verify this finding: " + f.evidence, subagentType: "verifier" })
|
|
1162
|
+
.then((v) => ({ ...f, ...v }))
|
|
1163
|
+
));
|
|
1164
|
+
\`\`\`
|
|
1165
|
+
|
|
1166
|
+
#### When the user asks for a "workflow"
|
|
1167
|
+
|
|
1168
|
+
If the user's request mentions running a "workflow" (or otherwise uses the
|
|
1169
|
+
word "workflow"), fan the work out to subagents rather than doing it all
|
|
1170
|
+
yourself. Explore with your own tools first as needed, then write JavaScript
|
|
1171
|
+
in the \`${toolName}\` tool that dispatches subagents with \`task()\` and
|
|
1172
|
+
assembles their results. The point is to distribute the heavy work in
|
|
1173
|
+
parallel, not to grind through it one tool call at a time.
|
|
1174
|
+
`;
|
|
1175
|
+
}
|
|
1016
1176
|
function renderReplSystemPrompt(opts) {
|
|
1177
|
+
const sideEffects = opts.hasPtc ? " External side effects from inside the REPL are reachable only via the `tools.*` namespace documented below." : " The REPL is pure computation; do any filesystem or other I/O with your normal tools, outside this tool.";
|
|
1017
1178
|
return dedent`
|
|
1018
1179
|
### Interpreter
|
|
1019
1180
|
|
|
1020
1181
|
An \`${opts.toolName}\` tool is available. It runs JavaScript in a persistent REPL.
|
|
1021
1182
|
- State (variables, functions) persists across tool calls within a single turn of conversation. They DO NOT persist across multiple turns.
|
|
1022
1183
|
- Top-level \`await\` works; Promises resolve before the call returns.
|
|
1023
|
-
-
|
|
1184
|
+
- Runtime sandbox: no built-in filesystem, network, stdlib, or wall-clock APIs (\`fetch\`, \`require\`, \`fs\`, \`process\`, real \`Date.now()\` are unavailable or stubbed).${sideEffects}
|
|
1024
1185
|
- Timeout: ${opts.timeout}s per call. Memory: ${opts.memoryLimitMb} MB total.
|
|
1025
1186
|
- \`console.log\` output is captured and returned alongside the result.
|
|
1026
1187
|
`;
|
|
@@ -1077,64 +1238,65 @@ function resolveToolList(items, agentTools) {
|
|
|
1077
1238
|
});
|
|
1078
1239
|
}
|
|
1079
1240
|
/**
|
|
1080
|
-
* Pull `skillsMetadata` from the task input, resolve the backend, and push
|
|
1081
|
-
* both into the session. Short-circuits with a `SkillNotAvailable` error if
|
|
1082
|
-
* the source references skills the agent doesn't have.
|
|
1083
|
-
*/
|
|
1084
|
-
async function prepareSkillsForEval(session, skillsBackend, code) {
|
|
1085
|
-
const taskInput = getCurrentTaskInput();
|
|
1086
|
-
const metadata = taskInput?.skillsMetadata ?? [];
|
|
1087
|
-
const referenced = scanSkillReferences(code);
|
|
1088
|
-
if (referenced.size > 0) {
|
|
1089
|
-
const known = new Set(metadata.map((m) => m.name));
|
|
1090
|
-
const missing = [];
|
|
1091
|
-
for (const name of referenced) if (!known.has(name)) missing.push(name);
|
|
1092
|
-
if (missing.length > 0) {
|
|
1093
|
-
session.setSkillsContext(void 0);
|
|
1094
|
-
return formatSkillNotAvailable(missing);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
const resolved = await resolveBackend(skillsBackend, { state: taskInput });
|
|
1098
|
-
session.setSkillsContext({
|
|
1099
|
-
metadata,
|
|
1100
|
-
backend: resolved
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
1241
|
* Create the Code Interpreter middleware.
|
|
1105
1242
|
*/
|
|
1106
1243
|
function createCodeInterpreterMiddleware(options = {}) {
|
|
1107
|
-
const { ptc, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null,
|
|
1244
|
+
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;
|
|
1245
|
+
const maxSubagentConcurrency = subagents ? 32 : 0;
|
|
1108
1246
|
if (maxPtcCalls !== null && maxPtcCalls !== void 0 && maxPtcCalls < 1) throw new Error("`maxPtcCalls` must be >= 1 or null");
|
|
1109
|
-
const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
|
|
1110
|
-
toolName,
|
|
1111
|
-
timeout: executionTimeoutMs / 1e3,
|
|
1112
|
-
memoryLimitMb: Math.floor(memoryLimitBytes / (1024 * 1024))
|
|
1113
|
-
});
|
|
1114
1247
|
const middlewareId = crypto.randomUUID();
|
|
1115
1248
|
let cachedPtcPrompt = null;
|
|
1116
1249
|
let ptcTools = [];
|
|
1250
|
+
let taskTool = null;
|
|
1117
1251
|
function filterToolsForPtc(allTools) {
|
|
1118
1252
|
if (!ptc) return [];
|
|
1119
1253
|
return resolveToolList(ptc, allTools.filter((t) => t.name !== toolName));
|
|
1120
1254
|
}
|
|
1255
|
+
function findTaskTool(tools) {
|
|
1256
|
+
return tools.find((t) => t.name === "task") ?? null;
|
|
1257
|
+
}
|
|
1258
|
+
function createBridgeDispatch(subagentTaskTool, config) {
|
|
1259
|
+
return async (input) => {
|
|
1260
|
+
const hasSchema = input.responseSchema != null;
|
|
1261
|
+
if (hasSchema) validateResponseSchema(input.responseSchema);
|
|
1262
|
+
const toolConfig = {
|
|
1263
|
+
...config,
|
|
1264
|
+
configurable: {
|
|
1265
|
+
...config.configurable,
|
|
1266
|
+
...hasSchema && { [SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY]: input.responseSchema }
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
const content = unwrapToolEnvelope(await subagentTaskTool.invoke({
|
|
1270
|
+
description: input.description,
|
|
1271
|
+
subagent_type: input.subagentType
|
|
1272
|
+
}, toolConfig));
|
|
1273
|
+
if (hasSchema && typeof content === "string") try {
|
|
1274
|
+
return JSON.parse(content);
|
|
1275
|
+
} catch {
|
|
1276
|
+
return content;
|
|
1277
|
+
}
|
|
1278
|
+
return content;
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1121
1281
|
return createMiddleware({
|
|
1122
1282
|
name: "CodeInterpreterMiddleware",
|
|
1123
1283
|
tools: [tool(async (input, config) => {
|
|
1124
|
-
const
|
|
1284
|
+
const threadId = config.configurable?.thread_id || "__default__";
|
|
1285
|
+
const sessionKey = `${threadId}:${middlewareId}`;
|
|
1125
1286
|
const session = ReplSession.getOrCreate(sessionKey, {
|
|
1126
1287
|
memoryLimitBytes,
|
|
1127
1288
|
maxStackSizeBytes,
|
|
1128
1289
|
maxPtcCalls,
|
|
1129
1290
|
tools: ptcTools,
|
|
1130
|
-
skillsEnabled: skillsBackend !== void 0,
|
|
1131
1291
|
maxResultChars,
|
|
1132
|
-
captureConsole
|
|
1292
|
+
captureConsole,
|
|
1293
|
+
sessionId: threadId,
|
|
1294
|
+
subagentBridge: taskTool && maxSubagentConcurrency > 0 ? {
|
|
1295
|
+
dispatch: createBridgeDispatch(taskTool, config),
|
|
1296
|
+
maxConcurrency: maxSubagentConcurrency
|
|
1297
|
+
} : void 0
|
|
1133
1298
|
});
|
|
1134
|
-
if (
|
|
1135
|
-
const setupError = await prepareSkillsForEval(session, skillsBackend, input.code);
|
|
1136
|
-
if (setupError !== void 0) return setupError;
|
|
1137
|
-
}
|
|
1299
|
+
if (taskTool && maxSubagentConcurrency > 0) session.updateBridgeDispatch(createBridgeDispatch(taskTool, config));
|
|
1138
1300
|
return formatReplResult(await session.eval(input.code, executionTimeoutMs));
|
|
1139
1301
|
}, {
|
|
1140
1302
|
name: toolName,
|
|
@@ -1142,15 +1304,23 @@ function createCodeInterpreterMiddleware(options = {}) {
|
|
|
1142
1304
|
Evaluate TypeScript/JavaScript code in a sandboxed REPL. State persists across calls.
|
|
1143
1305
|
Use console.log() for output. Returns the result of the last expression.
|
|
1144
1306
|
If file or other tools are available, call them via the tools namespace: await tools.readFile({ path }).
|
|
1145
|
-
If skills are configured, dynamically import them: await import("@/skills/<name>").
|
|
1146
1307
|
`,
|
|
1147
1308
|
metadata: { ls_code_input_language: "javascript" },
|
|
1148
1309
|
schema: z.object({ code: z.string().describe("TypeScript/JavaScript code to evaluate in the sandboxed REPL") })
|
|
1149
1310
|
})],
|
|
1150
1311
|
wrapModelCall: async (request, handler) => {
|
|
1151
|
-
|
|
1312
|
+
const agentTools = request.tools || [];
|
|
1313
|
+
ptcTools = filterToolsForPtc(agentTools);
|
|
1314
|
+
if (!taskTool && maxSubagentConcurrency > 0) taskTool = findTaskTool(agentTools);
|
|
1152
1315
|
if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
|
|
1153
|
-
const
|
|
1316
|
+
const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
|
|
1317
|
+
toolName,
|
|
1318
|
+
timeout: executionTimeoutMs / 1e3,
|
|
1319
|
+
memoryLimitMb: Math.floor(memoryLimitBytes / (1024 * 1024)),
|
|
1320
|
+
hasPtc: ptcTools.length > 0
|
|
1321
|
+
});
|
|
1322
|
+
const subagentPrompt = taskTool && maxSubagentConcurrency > 0 ? renderSubagentPrompt(toolName) : "";
|
|
1323
|
+
const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(subagentPrompt).concat(cachedPtcPrompt || "");
|
|
1154
1324
|
return handler({
|
|
1155
1325
|
...request,
|
|
1156
1326
|
systemMessage
|
|
@@ -1163,6 +1333,6 @@ function createCodeInterpreterMiddleware(options = {}) {
|
|
|
1163
1333
|
});
|
|
1164
1334
|
}
|
|
1165
1335
|
//#endregion
|
|
1166
|
-
export { DEFAULT_EXECUTION_TIMEOUT, DEFAULT_MAX_PTC_CALLS, DEFAULT_MAX_STACK_SIZE, DEFAULT_MEMORY_LIMIT,
|
|
1336
|
+
export { DEFAULT_EXECUTION_TIMEOUT, DEFAULT_MAX_PTC_CALLS, DEFAULT_MAX_STACK_SIZE, DEFAULT_MEMORY_LIMIT, PTCCallBudgetExceededError, ReplSession, createCodeInterpreterMiddleware, formatReplResult, stripTypeSyntax, toCamelCase, transformForEval, validateResponseSchema };
|
|
1167
1337
|
|
|
1168
1338
|
//# sourceMappingURL=index.js.map
|