@langchain/quickjs 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,7 +100,9 @@ PTC configuration is progressive:
100
100
 
101
101
  ### Recursive Language Model (RLM)
102
102
 
103
- When the `task` tool is exposed via PTC, the agent can spawn sub-agents in parallel from within the REPL:
103
+ When the agent has subagents configured, a `task()` global is available inside
104
+ the REPL (no PTC needed), so the agent can spawn sub-agents in parallel from
105
+ within the REPL:
104
106
 
105
107
  ```typescript
106
108
  const agent = createDeepAgent({
@@ -112,7 +114,7 @@ const agent = createDeepAgent({
112
114
  systemPrompt: "...",
113
115
  },
114
116
  ],
115
- middleware: [createQuickJSMiddleware({ ptc: ["task"] })],
117
+ middleware: [createQuickJSMiddleware()],
116
118
  });
117
119
  ```
118
120
 
@@ -122,16 +124,21 @@ The agent then writes code like:
122
124
  const topics = ["quantum computing", "fusion energy", "CRISPR"];
123
125
  const results = await Promise.all(
124
126
  topics.map((topic) =>
125
- tools.task({
127
+ task({
126
128
  description: `Research ${topic} in depth`,
127
129
  subagentType: "general-purpose",
128
130
  }),
129
131
  ),
130
132
  );
133
+ // Aggregate and return the report from the eval; the agent then writes it to a
134
+ // file with its own write_file tool.
131
135
  const report = topics.map((t, i) => `## ${t}\n${results[i]}`).join("\n\n");
132
- await writeFile("/research.md", report);
136
+ report;
133
137
  ```
134
138
 
139
+ > `task` cannot be exposed via `ptc` — it is reserved for the `task()` global,
140
+ > so passing `ptc: ["task"]` throws.
141
+
135
142
  ## API
136
143
 
137
144
  ### `createQuickJSMiddleware(options?)`
package/dist/index.cjs CHANGED
@@ -30,6 +30,8 @@ let quickjs_emscripten = require("quickjs-emscripten");
30
30
  let quickjs_emscripten_core = require("quickjs-emscripten-core");
31
31
  let json_schema_to_typescript = require("json-schema-to-typescript");
32
32
  let _langchain_core_utils_json_schema = require("@langchain/core/utils/json_schema");
33
+ let _langchain_langgraph = require("@langchain/langgraph");
34
+ let _langchain_core_messages = require("@langchain/core/messages");
33
35
  let acorn = require("acorn");
34
36
  let _sveltejs_acorn_typescript = require("@sveltejs/acorn-typescript");
35
37
  let estree_walker = require("estree-walker");
@@ -121,6 +123,59 @@ async function toolToTypeSignature(name, description, jsonSchema) {
121
123
  `;
122
124
  }
123
125
  //#endregion
126
+ //#region src/coerce.ts
127
+ /**
128
+ * Coercion of tool / subagent return values for the QuickJS bridge.
129
+ *
130
+ * The deepagents `task` tool resolves to a LangGraph `Command` whose payload
131
+ * carries the subagent's final message(s) under `update.messages`; some tools
132
+ * return a `ToolMessage` or a list of messages. The interpreter bridges need
133
+ * the underlying output, not the envelope, so this unwraps those shapes to the
134
+ * content the model actually cares about.
135
+ */
136
+ /**
137
+ * Return the trailing message content from a `Command`'s `update.messages`,
138
+ * scanning from the end for the last message that actually has content. Returns
139
+ * the command unchanged when it has no message-shaped payload.
140
+ */
141
+ function extractCommandContent(command) {
142
+ const update = command.update;
143
+ const messages = update !== null && typeof update === "object" ? update.messages : void 0;
144
+ if (Array.isArray(messages)) for (let i = messages.length - 1; i >= 0; i--) {
145
+ const message = messages[i];
146
+ if (_langchain_core_messages.BaseMessage.isInstance(message) && message.content != null) return message.content;
147
+ }
148
+ return command;
149
+ }
150
+ /**
151
+ * Unwrap a LangChain `Command` / `ToolMessage` / message-list envelope to the
152
+ * underlying content. Non-envelope values (strings, content-block arrays, plain
153
+ * objects) are returned unchanged.
154
+ *
155
+ * @param value The raw value returned by a tool or subagent dispatch.
156
+ * @returns The unwrapped content, or `value` itself when it isn't an envelope.
157
+ */
158
+ function unwrapToolEnvelope(value) {
159
+ if (typeof value === "string") return value;
160
+ if ((0, _langchain_langgraph.isCommand)(value)) {
161
+ const inner = extractCommandContent(value);
162
+ return inner === value ? value : unwrapToolEnvelope(inner);
163
+ }
164
+ if (_langchain_core_messages.BaseMessage.isInstance(value)) return unwrapToolEnvelope(value.content);
165
+ if (Array.isArray(value)) {
166
+ for (let i = value.length - 1; i >= 0; i--) {
167
+ const entry = value[i];
168
+ if (_langchain_core_messages.BaseMessage.isInstance(entry)) return unwrapToolEnvelope(entry.content);
169
+ if ((0, _langchain_langgraph.isCommand)(entry)) {
170
+ const inner = extractCommandContent(entry);
171
+ if (inner !== entry) return unwrapToolEnvelope(inner);
172
+ }
173
+ }
174
+ return value;
175
+ }
176
+ return value;
177
+ }
178
+ //#endregion
124
179
  //#region src/transform.ts
125
180
  /**
126
181
  * AST-based code transform pipeline for the REPL.
@@ -422,6 +477,7 @@ function getSharedModule() {
422
477
  * @returns Plain string representation of the tool output.
423
478
  */
424
479
  function extractToolText(result) {
480
+ result = unwrapToolEnvelope(result);
425
481
  if (typeof result === "string") return result;
426
482
  if (Array.isArray(result)) {
427
483
  const texts = [];
@@ -516,7 +572,7 @@ var ReplSession = class ReplSession {
516
572
  subagentQueue = null;
517
573
  bridgeDispatchRef = null;
518
574
  /** Allowed keys in the subagent input object. */
519
- static SUBAGENT_ALLOWED_KEYS = new Set([
575
+ static SUBAGENT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
520
576
  "description",
521
577
  "subagentType",
522
578
  "responseSchema"
@@ -926,8 +982,10 @@ function renderSubagentPrompt(toolName) {
926
982
  ### Dispatching Subagents with \`task\`
927
983
 
928
984
  \`task\` is your primitive for running configured subagents from inside the
929
- JavaScript REPL. You orchestrate everything else - fan-out, filtering,
930
- deduplication, multi-stage flow, and synthesis - in plain JavaScript.
985
+ JavaScript REPL. Your job here is to DISTRIBUTE work, not to do it yourself:
986
+ write JavaScript that fans work out to subagents and assembles their results.
987
+ You handle the orchestration - fan-out, filtering, deduplication, multi-stage
988
+ flow, and synthesis - in plain JavaScript.
931
989
 
932
990
  #### The primitive
933
991
 
@@ -945,13 +1003,19 @@ function renderSubagentPrompt(toolName) {
945
1003
  the configured subagent names.
946
1004
 
947
1005
  \`description\` is the only prompt the subagent receives for this dispatch. Make
948
- it complete: include the goal, constraints, relevant context, what to inspect,
949
- and the exact shape or level of detail you expect back. Each dispatch is
950
- stateless from the caller's perspective; you cannot send follow-up messages to
951
- the same subagent run.
1006
+ it complete: the goal, the constraints, what to inspect, and the exact shape
1007
+ or level of detail you expect back. Give context as locators — file paths and
1008
+ symbol names not as pasted file contents. If you already read a file while
1009
+ exploring, still pass its path and let the subagent read it; do not paste back
1010
+ what you read. Each dispatch is stateless from the caller's perspective; you
1011
+ cannot send follow-up messages to the same subagent run.
952
1012
 
953
- \`responseSchema\` is optional. When provided, the resolved value is already a
954
- typed JavaScript value matching the schema. Do not call \`JSON.parse\` unless the
1013
+ \`responseSchema\` is optional, but set it on any dispatch whose result feeds
1014
+ later code. A deterministic, typed shape is what lets you compose the next
1015
+ stage reliably — index it, sort it, compare fields, branch on it, merge it —
1016
+ instead of parsing free-form text. This is what makes a whole workflow
1017
+ composable as one script. When provided, the resolved value is already a typed
1018
+ JavaScript value matching the schema; do not call \`JSON.parse\` unless the
955
1019
  subagent intentionally returned a JSON string. Dynamic schemas work for
956
1020
  declarative subagents; runnable-backed subagents reject dynamic schemas because
957
1021
  their runnable is already compiled.
@@ -972,9 +1036,12 @@ function renderSubagentPrompt(toolName) {
972
1036
  dispatch result back onto its item. Multi-stage analysis means: run a pass,
973
1037
  filter or regroup the array in JS, then run another pass over the survivors.
974
1038
 
975
- Prefer one \`${toolName}\` call that performs the whole workflow. Splitting the
976
- workflow across multiple \`${toolName}\` calls costs model turns and forces you to
977
- re-establish state.
1039
+ You can run the whole workflow in one \`${toolName}\` call or split it across
1040
+ several both are fine. A single end-to-end script (generate, compare, pick a
1041
+ winner; or review every item, then synthesize) is clean when you can write it
1042
+ in one go; splitting is also fine when you want to inspect results between
1043
+ stages. Either way, don't redo work across calls — reuse what is already in
1044
+ scope (see "Reuse what earlier evals left in scope" below).
978
1045
 
979
1046
  #### Fan out with bounded concurrency
980
1047
 
@@ -983,13 +1050,15 @@ function renderSubagentPrompt(toolName) {
983
1050
  enforces a hard per-REPL cap of 32 concurrent subagent calls.
984
1051
 
985
1052
  \`\`\`javascript
1053
+ const files = ["/src/a.ts", "/src/b.ts", "/src/c.ts"]; // found while exploring
986
1054
  const batchSize = 10;
987
1055
  const reviewed = [];
988
- for (let i = 0; i < items.length; i += batchSize) {
989
- const batch = items.slice(i, i + batchSize);
990
- reviewed.push(...(await Promise.all(batch.map(async (it) => {
1056
+ for (let i = 0; i < files.length; i += batchSize) {
1057
+ const batch = files.slice(i, i + batchSize);
1058
+ reviewed.push(...(await Promise.all(batch.map(async (file) => {
991
1059
  const result = await task({
992
- description: "Review " + it.file + " for SQL injection. Cite line numbers.",
1060
+ description: "Read " + file + " and review it for SQL injection. " +
1061
+ "Cite line numbers.",
993
1062
  subagentType: "reviewer",
994
1063
  responseSchema: {
995
1064
  type: "object",
@@ -1010,61 +1079,44 @@ function renderSubagentPrompt(toolName) {
1010
1079
  required: ["vulnerabilities"],
1011
1080
  },
1012
1081
  });
1013
- return { ...it, ...result };
1082
+ return { file, ...result };
1014
1083
  }))));
1015
1084
  }
1016
1085
  \`\`\`
1017
1086
 
1018
- #### Use parent JS for cheap work; use subagents for agentic work
1087
+ #### Explore with your own tools first, then distribute
1019
1088
 
1020
- Use JavaScript in the parent REPL for deterministic orchestration: joining
1021
- arrays, deduping, sorting, filtering, grouping, batching, and merging results.
1022
- If the \`tools.*\` namespace is exposed, also use it to pre-read files or collect
1023
- shared data once, then pass only the relevant content to each subagent in
1024
- \`description\`.
1089
+ You already have your normal tools for reading, listing, globbing, and
1090
+ grepping files. Use them to explore and understand the task BEFORE you write
1091
+ the orchestration script. These are ordinary tool calls, separate from the
1092
+ \`${toolName}\` tool: read the data file, list or glob the directory, grep for
1093
+ what matters, then decide how to split the work.
1025
1094
 
1026
- Use \`task\` for work that benefits from an autonomous agentic loop: reading
1027
- or searching with the subagent's own tools, inspecting multiple files, following
1028
- leads, making judgment calls, or producing a final synthesized report.
1095
+ Never write \`${toolName}\` code that spawns a subagent just to read or parse a
1096
+ file or list a directory. That is a deterministic step you do yourself with a
1097
+ direct tool call; spending a whole agent loop on it is wasteful.
1029
1098
 
1030
- #### Pre-read shared context in the parent when useful
1099
+ Once you understand the shape of the work, you have creative freedom in how
1100
+ you split it:
1031
1101
 
1032
- If many subagents need the same source list or file content and \`tools.*\` is
1033
- available, gather that context once in the parent REPL before dispatching:
1102
+ - One dispatch per file or per record, when the items are already separate.
1103
+ - Chunk a large input yourself read it, split it, optionally write a small
1104
+ input file per chunk — and dispatch one subagent per chunk.
1105
+ - A cheap classification pass first, then deeper dispatches only for the items
1106
+ that warrant them.
1034
1107
 
1035
- \`\`\`javascript
1036
- const files = (await tools.glob({ pattern: "src/**/*.ts" }))
1037
- .split("\\n")
1038
- .filter(Boolean);
1108
+ Then write JavaScript in the \`${toolName}\` tool that distributes the heavy,
1109
+ agentic work to subagents with \`task()\`: analyzing file contents, exploring a
1110
+ codebase, making judgment calls, rewriting code, or synthesizing a report.
1039
1111
 
1040
- const items = await Promise.all(files.map(async (file) => {
1041
- const content = await tools.readFile({ file_path: file });
1042
- return { file, content };
1043
- }));
1044
-
1045
- const batchSize = 10;
1046
- const results = [];
1047
- for (let i = 0; i < items.length; i += batchSize) {
1048
- const batch = items.slice(i, i + batchSize);
1049
- results.push(...(await Promise.all(batch.map(async (it) => {
1050
- const finding = await task({
1051
- description:
1052
- "Review this file for auth bypasses. Return concrete findings only.\\n\\n" +
1053
- "File: " + it.file + "\\n\\n" +
1054
- it.content,
1055
- subagentType: "reviewer",
1056
- responseSchema: {
1057
- type: "object",
1058
- properties: {
1059
- findings: { type: "array", items: { type: "object" } },
1060
- },
1061
- required: ["findings"],
1062
- },
1063
- });
1064
- return { ...it, ...finding };
1065
- }))));
1066
- }
1067
- \`\`\`
1112
+ Hand each subagent a locator, not a payload. Subagents have their own file
1113
+ tools, so for anything that lives in a file — a file to review, rewrite, or
1114
+ audit pass the path and let the subagent read it. Do NOT read a whole file
1115
+ just to paste its contents into the description; that bloats every dispatch
1116
+ and duplicates the file across them. Reserve inline content for small or
1117
+ derived data that has no path of its own: a single parsed record, or a chunk
1118
+ you split out of a larger input (write the chunk to its own file and pass that
1119
+ path if it is large). Assemble the results in JS.
1068
1120
 
1069
1121
  #### Compose multiple stages
1070
1122
 
@@ -1073,67 +1125,89 @@ function renderSubagentPrompt(toolName) {
1073
1125
  only for those items.
1074
1126
 
1075
1127
  \`\`\`javascript
1076
- const tagged = [];
1077
- for (let i = 0; i < items.length; i += 10) {
1078
- const batch = items.slice(i, i + 10);
1079
- tagged.push(...(await Promise.all(batch.map(async (it) => {
1080
- const tag = await task({
1081
- description: "Classify " + it.file + " as handler, util, test, or config.",
1082
- subagentType: "reviewer",
1083
- responseSchema: {
1084
- type: "object",
1085
- properties: { kind: { type: "string" }, risky: { type: "boolean" } },
1086
- required: ["kind", "risky"],
1087
- },
1088
- });
1089
- return { ...it, ...tag };
1090
- }))));
1091
- }
1128
+ const tagged = await Promise.all(files.map((file) =>
1129
+ task({
1130
+ description: "Read " + file + " and classify it as handler, util, " +
1131
+ "test, or config.",
1132
+ subagentType: "reviewer",
1133
+ responseSchema: {
1134
+ type: "object",
1135
+ properties: { kind: { type: "string" }, risky: { type: "boolean" } },
1136
+ required: ["kind", "risky"],
1137
+ },
1138
+ }).then((tag) => ({ file, ...tag }))
1139
+ ));
1092
1140
 
1093
1141
  const riskyHandlers = tagged.filter((it) => it.kind === "handler" && it.risky);
1094
- const deepReviews = [];
1095
- for (let i = 0; i < riskyHandlers.length; i += 10) {
1096
- const batch = riskyHandlers.slice(i, i + 10);
1097
- deepReviews.push(...(await Promise.all(batch.map(async (it) => {
1098
- const review = await task({
1099
- description: "Deep security review of " + it.file + ". Cite line numbers.",
1100
- subagentType: "reviewer",
1101
- });
1102
- return { ...it, review };
1103
- }))));
1104
- }
1142
+ const deepReviews = await Promise.all(riskyHandlers.map((it) =>
1143
+ task({
1144
+ description: "Deep security review of " + it.file + ". Cite line numbers.",
1145
+ subagentType: "reviewer",
1146
+ }).then((review) => ({ ...it, review }))
1147
+ ));
1105
1148
  \`\`\`
1106
1149
 
1107
- #### Get results out without flooding your context
1150
+ #### Return results via the last expression, not \`console.log\`
1151
+
1152
+ The value of the last expression in an \`${toolName}\` call (or a resolved
1153
+ top-level \`await\`) is returned to you as the result. Make that final
1154
+ expression the variable holding your result and read it from there.
1155
+ \`console.log\` is only for incidental debugging: its output is capped and
1156
+ truncated, while the returned value is not, so never \`console.log\` your
1157
+ actual results.
1158
+
1159
+ Keep large intermediate sets in JS variables and return only a compact
1160
+ summary or a small slice, not the entire dataset. To persist full output,
1161
+ have a subagent write it, or write it with your own file tool outside the
1162
+ \`${toolName}\` call.
1163
+
1164
+ #### Reuse what earlier evals left in scope
1108
1165
 
1109
- Keep large result sets in JS variables. Do not \`console.log\` the full result set.
1110
- If \`tools.writeFile\` is exposed, persist structured output from inside the eval:
1166
+ The REPL is persistent within a turn: every top-level variable, function, and
1167
+ class you declare is kept and is available in your next \`${toolName}\` call
1168
+ (each is hoisted to global scope). So if a later step needs something an
1169
+ earlier eval produced or bound, **reference that variable by name** — do not
1170
+ write a new literal that re-types data a previous eval already returned or
1171
+ computed.
1172
+
1173
+ If you catch yourself pasting a big array or object of values you produced in
1174
+ an earlier call, that is the tell: the variable is still in scope, so use it.
1175
+ Re-typing prior results as a fresh literal wastes tokens and drifts from what
1176
+ actually ran.
1111
1177
 
1112
1178
  \`\`\`javascript
1113
- await tools.writeFile({
1114
- file_path: "/results/subagent-output.json",
1115
- content: JSON.stringify(deepReviews),
1116
- });
1117
- \`\`\`
1179
+ // An earlier eval bound this:
1180
+ // const auditResults = await Promise.all(files.map(/* ...audit... */));
1118
1181
 
1119
- Otherwise return a compact summary or a small slice of the results, not the
1120
- entire intermediate dataset.
1182
+ // A later eval reference it; do NOT paste the findings back in as a literal:
1183
+ const findings = auditResults.flatMap((r) =>
1184
+ r.findings.map((f) => ({ ...f, file: r.file }))
1185
+ );
1186
+ const verified = await Promise.all(findings.map((f) =>
1187
+ task({ description: "Verify this finding: " + f.evidence, subagentType: "verifier" })
1188
+ .then((v) => ({ ...f, ...v }))
1189
+ ));
1190
+ \`\`\`
1121
1191
 
1122
- #### Across evals
1192
+ #### When the user asks for a "workflow"
1123
1193
 
1124
- Variables persist according to the interpreter persistence mode above, but
1125
- re-establish what you need in each eval. Doing the whole workflow in one
1126
- \`${toolName}\` call is usually simplest.
1194
+ If the user's request mentions running a "workflow" (or otherwise uses the
1195
+ word "workflow"), fan the work out to subagents rather than doing it all
1196
+ yourself. Explore with your own tools first as needed, then write JavaScript
1197
+ in the \`${toolName}\` tool that dispatches subagents with \`task()\` and
1198
+ assembles their results. The point is to distribute the heavy work in
1199
+ parallel, not to grind through it one tool call at a time.
1127
1200
  `;
1128
1201
  }
1129
1202
  function renderReplSystemPrompt(opts) {
1203
+ 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.";
1130
1204
  return dedent.default`
1131
1205
  ### Interpreter
1132
1206
 
1133
1207
  An \`${opts.toolName}\` tool is available. It runs JavaScript in a persistent REPL.
1134
1208
  - State (variables, functions) persists across tool calls within a single turn of conversation. They DO NOT persist across multiple turns.
1135
1209
  - Top-level \`await\` works; Promises resolve before the call returns.
1136
- - Runtime sandbox: no built-in filesystem, network, stdlib, or wall-clock APIs (\`fetch\`, \`require\`, \`fs\`, \`process\`, real \`Date.now()\` are unavailable or stubbed). External side effects from inside the REPL are only reachable via the \`tools.*\` namespace when it is exposed (see below); without it, the REPL is pure computation.
1210
+ - Runtime sandbox: no built-in filesystem, network, stdlib, or wall-clock APIs (\`fetch\`, \`require\`, \`fs\`, \`process\`, real \`Date.now()\` are unavailable or stubbed).${sideEffects}
1137
1211
  - Timeout: ${opts.timeout}s per call. Memory: ${opts.memoryLimitMb} MB total.
1138
1212
  - \`console.log\` output is captured and returned alongside the result.
1139
1213
  `;
@@ -1178,10 +1252,14 @@ async function generatePtcPrompt(tools) {
1178
1252
  * StructuredToolInterface objects. Strings are looked up by name in agentTools;
1179
1253
  * instances are included directly without requiring agent registration. Strings
1180
1254
  * that don't match any agent tool are silently omitted.
1255
+ *
1256
+ * Throws if the subagent `task` tool is requested (by name or instance): it is
1257
+ * reserved for the `task()` global and cannot be a `tools.*` PTC member.
1181
1258
  */
1182
1259
  function resolveToolList(items, agentTools) {
1183
1260
  const agentByName = new Map(agentTools.map((t) => [t.name, t]));
1184
1261
  return items.flatMap((item) => {
1262
+ if ((typeof item === "string" ? item : item.name) === "task") throw new Error("The subagent `task` tool cannot be exposed via `ptc`. It is always available as the top-level `task()` global inside the REPL (with `subagentType` and `responseSchema` support); exposing it through the `tools.*` namespace would create a second, conflicting dispatch path that drops `responseSchema`. Remove \"task\" from `ptc`.");
1185
1263
  if (typeof item === "string") {
1186
1264
  const found = agentByName.get(item);
1187
1265
  return found ? [found] : [];
@@ -1196,11 +1274,6 @@ function createCodeInterpreterMiddleware(options = {}) {
1196
1274
  const { ptc, memoryLimitBytes = DEFAULT_MEMORY_LIMIT, maxStackSizeBytes = DEFAULT_MAX_STACK_SIZE, executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT, systemPrompt: customSystemPrompt = null, maxPtcCalls = 256, maxResultChars = DEFAULT_MAX_RESULTS_CHARS, toolName = DEFAULT_TOOL_NAME, captureConsole = true, subagents = true } = options;
1197
1275
  const maxSubagentConcurrency = subagents ? 32 : 0;
1198
1276
  if (maxPtcCalls !== null && maxPtcCalls !== void 0 && maxPtcCalls < 1) throw new Error("`maxPtcCalls` must be >= 1 or null");
1199
- const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
1200
- toolName,
1201
- timeout: executionTimeoutMs / 1e3,
1202
- memoryLimitMb: Math.floor(memoryLimitBytes / (1024 * 1024))
1203
- });
1204
1277
  const middlewareId = crypto.randomUUID();
1205
1278
  let cachedPtcPrompt = null;
1206
1279
  let ptcTools = [];
@@ -1223,16 +1296,16 @@ function createCodeInterpreterMiddleware(options = {}) {
1223
1296
  ...hasSchema && { [deepagents.SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY]: input.responseSchema }
1224
1297
  }
1225
1298
  };
1226
- const result = await subagentTaskTool.invoke({
1299
+ const content = unwrapToolEnvelope(await subagentTaskTool.invoke({
1227
1300
  description: input.description,
1228
1301
  subagent_type: input.subagentType
1229
- }, toolConfig);
1230
- if (hasSchema && typeof result === "string") try {
1231
- return JSON.parse(result);
1302
+ }, toolConfig));
1303
+ if (hasSchema && typeof content === "string") try {
1304
+ return JSON.parse(content);
1232
1305
  } catch {
1233
- return result;
1306
+ return content;
1234
1307
  }
1235
- return result;
1308
+ return content;
1236
1309
  };
1237
1310
  }
1238
1311
  return (0, langchain.createMiddleware)({
@@ -1270,6 +1343,12 @@ function createCodeInterpreterMiddleware(options = {}) {
1270
1343
  ptcTools = filterToolsForPtc(agentTools);
1271
1344
  if (!taskTool && maxSubagentConcurrency > 0) taskTool = findTaskTool(agentTools);
1272
1345
  if (ptcTools.length > 0 && !cachedPtcPrompt) cachedPtcPrompt = await generatePtcPrompt(ptcTools);
1346
+ const baseSystemPrompt = customSystemPrompt || renderReplSystemPrompt({
1347
+ toolName,
1348
+ timeout: executionTimeoutMs / 1e3,
1349
+ memoryLimitMb: Math.floor(memoryLimitBytes / (1024 * 1024)),
1350
+ hasPtc: ptcTools.length > 0
1351
+ });
1273
1352
  const subagentPrompt = taskTool && maxSubagentConcurrency > 0 ? renderSubagentPrompt(toolName) : "";
1274
1353
  const systemMessage = request.systemMessage.concat(baseSystemPrompt).concat(subagentPrompt).concat(cachedPtcPrompt || "");
1275
1354
  return handler({