deepagents 1.13.1 → 1.13.3
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/{agent-D50BBbJT.d.ts → agent-CVBXKZAu.d.ts} +97 -87
- package/dist/{agent-HW8KptlB.d.cts → agent-D7nWARfg.d.cts} +96 -87
- package/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/index.cjs +3 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/{langsmith-zm0ILQsV.js → langsmith-BYWZnEVh.js} +466 -138
- package/dist/langsmith-BYWZnEVh.js.map +1 -0
- package/dist/{langsmith-Ck9t7AGW.cjs → langsmith-Ynj9VKxb.cjs} +470 -141
- package/dist/langsmith-Ynj9VKxb.cjs.map +1 -0
- package/dist/node.cjs +3 -2
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -3
- package/dist/{src-C9o7b5C6.js → src-Blh1ZEYd.js} +18 -7
- package/dist/src-Blh1ZEYd.js.map +1 -0
- package/dist/{src-C97zNx7s.cjs → src-DRMLI0-x.cjs} +18 -7
- package/dist/src-DRMLI0-x.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/langsmith-Ck9t7AGW.cjs.map +0 -1
- package/dist/langsmith-zm0ILQsV.js.map +0 -1
- package/dist/src-C97zNx7s.cjs.map +0 -1
- package/dist/src-C9o7b5C6.js.map +0 -1
|
@@ -47,6 +47,22 @@ const EMPTY_CONTENT_WARNING = "System reminder: File exists but has empty conten
|
|
|
47
47
|
const MAX_LINE_LENGTH = 5e3;
|
|
48
48
|
const TOOL_RESULT_TOKEN_LIMIT = 2e4;
|
|
49
49
|
const TRUNCATION_GUIDANCE = "... [results truncated, try being more specific with your parameters]";
|
|
50
|
+
/**
|
|
51
|
+
* Normalize model- or caller-supplied text pagination bounds.
|
|
52
|
+
*
|
|
53
|
+
* Every backend must slice content and calculate pagination metadata from the
|
|
54
|
+
* same normalized values. Otherwise a fractional or negative argument could
|
|
55
|
+
* return one window while advertising a different `nextOffset`.
|
|
56
|
+
*
|
|
57
|
+
* Binary reads do not use this helper because their backend contract ignores
|
|
58
|
+
* line-based offset and limit values.
|
|
59
|
+
*/
|
|
60
|
+
function normalizeReadPagination(offset, limit) {
|
|
61
|
+
return {
|
|
62
|
+
offset: Number.isFinite(offset) ? Math.max(0, Math.floor(offset)) : 0,
|
|
63
|
+
limit: Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0
|
|
64
|
+
};
|
|
65
|
+
}
|
|
50
66
|
const MIME_TYPES = {
|
|
51
67
|
".png": "image/png",
|
|
52
68
|
".jpg": "image/jpeg",
|
|
@@ -158,40 +174,60 @@ function sanitizeToolCallId(toolCallId) {
|
|
|
158
174
|
return toolCallId.replace(/\./g, "_").replace(/\//g, "_").replace(/\\/g, "_");
|
|
159
175
|
}
|
|
160
176
|
/**
|
|
161
|
-
* Format file content with line numbers
|
|
162
|
-
*
|
|
163
|
-
* Chunks lines longer than MAX_LINE_LENGTH with continuation markers (e.g., 5.1, 5.2).
|
|
177
|
+
* Format file content with line numbers and structured source-line boundaries.
|
|
164
178
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
179
|
+
* The boundaries let downstream size limiting truncate only after complete
|
|
180
|
+
* source lines without reparsing the rendered gutter. This keeps presentation
|
|
181
|
+
* details (padding, tab separators, and continuation labels) encapsulated in
|
|
182
|
+
* the formatter that creates them.
|
|
168
183
|
*/
|
|
169
|
-
function
|
|
184
|
+
function formatContentWithLineNumbersAndBoundaries(content, startLine = 1) {
|
|
170
185
|
let lines;
|
|
171
186
|
if (typeof content === "string") {
|
|
172
187
|
lines = content.split("\n");
|
|
173
188
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
174
189
|
} else lines = content;
|
|
175
190
|
const resultLines = [];
|
|
191
|
+
const sourceLineBoundaries = [];
|
|
192
|
+
let renderedLength = 0;
|
|
193
|
+
const appendRow = (row, sourceLine, completesSourceLine) => {
|
|
194
|
+
if (resultLines.length > 0) renderedLength += 1;
|
|
195
|
+
resultLines.push(row);
|
|
196
|
+
renderedLength += row.length;
|
|
197
|
+
if (completesSourceLine) sourceLineBoundaries.push({
|
|
198
|
+
sourceLine,
|
|
199
|
+
endOffset: renderedLength
|
|
200
|
+
});
|
|
201
|
+
};
|
|
176
202
|
for (let i = 0; i < lines.length; i++) {
|
|
177
203
|
const line = lines[i];
|
|
178
204
|
const lineNum = i + startLine;
|
|
179
|
-
if (line.length <= 5e3)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
resultLines.push(`${continuationMarker.padStart(6)}\t${chunk}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
205
|
+
if (line.length <= 5e3) {
|
|
206
|
+
appendRow(`${lineNum.toString().padStart(6)}\t${line}`, lineNum, true);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);
|
|
210
|
+
for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
|
|
211
|
+
const start = chunkIdx * MAX_LINE_LENGTH;
|
|
212
|
+
const end = Math.min(start + MAX_LINE_LENGTH, line.length);
|
|
213
|
+
const chunk = line.substring(start, end);
|
|
214
|
+
appendRow(`${(chunkIdx === 0 ? `${lineNum}` : `${lineNum}.${chunkIdx}`).padStart(6)}\t${chunk}`, lineNum, chunkIdx === numChunks - 1);
|
|
192
215
|
}
|
|
193
216
|
}
|
|
194
|
-
return
|
|
217
|
+
return {
|
|
218
|
+
text: resultLines.join("\n"),
|
|
219
|
+
sourceLineBoundaries
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Format file content with line numbers (cat -n style).
|
|
224
|
+
*
|
|
225
|
+
* Lines longer than `MAX_LINE_LENGTH` are split into continuation rows such as
|
|
226
|
+
* `5.1` and `5.2`. Use `formatContentWithLineNumbersAndBoundaries` when a
|
|
227
|
+
* caller also needs safe source-line truncation points.
|
|
228
|
+
*/
|
|
229
|
+
function formatContentWithLineNumbers(content, startLine = 1) {
|
|
230
|
+
return formatContentWithLineNumbersAndBoundaries(content, startLine).text;
|
|
195
231
|
}
|
|
196
232
|
/**
|
|
197
233
|
* Check if content is empty and return warning message.
|
|
@@ -865,10 +901,23 @@ var StateBackend = class {
|
|
|
865
901
|
mimeType: fileDataV2.mimeType
|
|
866
902
|
};
|
|
867
903
|
if (typeof fileDataV2.content !== "string") return { error: `File '${filePath}' has binary content but text MIME type` };
|
|
868
|
-
|
|
869
|
-
|
|
904
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
905
|
+
const lines = fileDataV2.content.split("\n");
|
|
906
|
+
const totalLines = lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
907
|
+
const selected = lines.slice(normalizedOffset, normalizedOffset + normalizedLimit);
|
|
908
|
+
if (selected.length === 0 || normalizedOffset >= totalLines || normalizedLimit === 0) return {
|
|
909
|
+
content: selected.join("\n"),
|
|
870
910
|
mimeType: fileDataV2.mimeType
|
|
871
911
|
};
|
|
912
|
+
const endOffset = Math.min(normalizedOffset + selected.length, totalLines);
|
|
913
|
+
return {
|
|
914
|
+
content: selected.join("\n"),
|
|
915
|
+
mimeType: fileDataV2.mimeType,
|
|
916
|
+
totalLines,
|
|
917
|
+
startLine: normalizedOffset + 1,
|
|
918
|
+
endLine: endOffset,
|
|
919
|
+
nextOffset: endOffset < totalLines ? endOffset : void 0
|
|
920
|
+
};
|
|
872
921
|
}
|
|
873
922
|
/**
|
|
874
923
|
* Read file content as raw FileData.
|
|
@@ -1584,6 +1633,73 @@ const READ_FILE_TRUNCATION_MSG = `
|
|
|
1584
1633
|
|
|
1585
1634
|
[Output was truncated due to size limits. The file content is very large. Consider reformatting the file to make it easier to navigate. For example, if this is JSON, use execute(command='jq . {file_path}') to pretty-print it with line breaks. For other formats, you can use appropriate formatting tools to split long lines.]`;
|
|
1586
1635
|
/**
|
|
1636
|
+
* Render backend pagination metadata as guidance for the model.
|
|
1637
|
+
*
|
|
1638
|
+
* Backends own source-level pagination because only they know how much of the
|
|
1639
|
+
* file was read. The middleware owns presentation: it line-numbers the text
|
|
1640
|
+
* and turns optional metadata into a human-readable footer. Keeping the fields
|
|
1641
|
+
* optional preserves compatibility with custom backends that predate this
|
|
1642
|
+
* contract; those reads simply receive no pagination footer.
|
|
1643
|
+
*
|
|
1644
|
+
* `nextOffset` is the signal that the read is partial. A result at EOF omits it,
|
|
1645
|
+
* so complete reads retain their previous output shape.
|
|
1646
|
+
*/
|
|
1647
|
+
function remainingLinesNotice(readResult) {
|
|
1648
|
+
const { startLine, endLine, nextOffset, totalLines } = readResult;
|
|
1649
|
+
if (startLine === void 0 || endLine === void 0 || nextOffset === void 0 || !Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || !Number.isSafeInteger(nextOffset) || startLine < 1 || endLine < startLine || nextOffset !== endLine || totalLines !== void 0 && (!Number.isSafeInteger(totalLines) || totalLines < endLine)) return "";
|
|
1650
|
+
const readCount = endLine - startLine + 1;
|
|
1651
|
+
const readUnit = readCount === 1 ? "line" : "lines";
|
|
1652
|
+
if (totalLines === void 0) return `\n\n[Read ${readCount} ${readUnit} (lines ${startLine}-${endLine}). More lines remain from offset ${nextOffset}.]`;
|
|
1653
|
+
if (endLine >= totalLines) return "";
|
|
1654
|
+
const remaining = totalLines - endLine;
|
|
1655
|
+
return `\n\n[Read ${readCount} ${readUnit} (lines ${startLine}-${endLine} of ${totalLines} total). ${remaining} ${remaining === 1 ? "line" : "lines"} remaining from offset ${nextOffset}.]`;
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Fit a line-numbered read into the middleware's output budget without
|
|
1659
|
+
* publishing a resume offset that skips content the model did not see.
|
|
1660
|
+
*
|
|
1661
|
+
* There are two independent forms of limiting:
|
|
1662
|
+
*
|
|
1663
|
+
* 1. The backend paginates the source file with `offset` and `limit`.
|
|
1664
|
+
* 2. The middleware may further shorten that page to fit its token budget.
|
|
1665
|
+
*
|
|
1666
|
+
* If the backend returned lines 1-100 but the middleware only displayed lines
|
|
1667
|
+
* 1-30, forwarding the backend's original `nextOffset: 100` would silently skip
|
|
1668
|
+
* lines 31-100 on the next read. This function therefore truncates only after a
|
|
1669
|
+
* complete source line and rebuilds the remaining-lines notice using the last
|
|
1670
|
+
* line actually displayed.
|
|
1671
|
+
*
|
|
1672
|
+
* Long source lines may occupy several formatted rows (`12`, `12.1`, ...). The
|
|
1673
|
+
* formatter records a structured boundary only after the final chunk, so this
|
|
1674
|
+
* function does not need to inspect or understand the gutter representation.
|
|
1675
|
+
* If no complete source line can fit beside the truncation message, the function
|
|
1676
|
+
* falls back to character truncation and omits pagination guidance rather than
|
|
1677
|
+
* advertising an unsafe offset.
|
|
1678
|
+
*/
|
|
1679
|
+
function truncatePaginatedRead(formatted, filePath, readResult, tokenLimit) {
|
|
1680
|
+
const content = formatted.text;
|
|
1681
|
+
const notice = remainingLinesNotice(readResult);
|
|
1682
|
+
if (!tokenLimit || content.length + notice.length < 4 * tokenLimit) return content + notice;
|
|
1683
|
+
const truncationMsg = READ_FILE_TRUNCATION_MSG.replace("{file_path}", filePath);
|
|
1684
|
+
const threshold = 4 * tokenLimit;
|
|
1685
|
+
if (readResult.startLine !== void 0 && readResult.endLine !== void 0) {
|
|
1686
|
+
const finalSourceLine = readResult.endLine;
|
|
1687
|
+
const boundaries = formatted.sourceLineBoundaries.filter((boundary) => boundary.sourceLine <= finalSourceLine);
|
|
1688
|
+
for (let index = boundaries.length - 1; index >= 0; index -= 1) {
|
|
1689
|
+
const boundary = boundaries[index];
|
|
1690
|
+
const adjustedNotice = remainingLinesNotice({
|
|
1691
|
+
totalLines: readResult.totalLines,
|
|
1692
|
+
startLine: readResult.startLine,
|
|
1693
|
+
endLine: boundary.sourceLine,
|
|
1694
|
+
nextOffset: boundary.sourceLine
|
|
1695
|
+
});
|
|
1696
|
+
if (boundary.endOffset + truncationMsg.length + adjustedNotice.length <= threshold) return content.slice(0, boundary.endOffset) + truncationMsg + adjustedNotice;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
const maxContentLength = Math.max(0, threshold - truncationMsg.length);
|
|
1700
|
+
return content.substring(0, maxContentLength) + truncationMsg;
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1587
1703
|
* Note appended to grep results that were cut short by the match-count cap.
|
|
1588
1704
|
*/
|
|
1589
1705
|
const GREP_TRUNCATION_NOTE = "Note: the search stopped early because it hit the maximum match count. The matches above are valid but incomplete. Narrow the search (a more specific pattern or a narrower path), or raise max_count, to see the rest.";
|
|
@@ -2085,7 +2201,8 @@ function createReadFileTool(backend, options) {
|
|
|
2085
2201
|
const permissionError = checkPermission(permissions, "read", input.file_path);
|
|
2086
2202
|
if (permissionError !== void 0) return toolError(runtime, "read_file", permissionError);
|
|
2087
2203
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2088
|
-
const { file_path, offset = 0, limit = 100 } = input;
|
|
2204
|
+
const { file_path, offset: requestedOffset = 0, limit: requestedLimit = 100 } = input;
|
|
2205
|
+
const { offset, limit } = normalizeReadPagination(requestedOffset, requestedLimit);
|
|
2089
2206
|
const readResult = await resolvedBackend.read(file_path, offset, limit);
|
|
2090
2207
|
if (readResult.error) return [{
|
|
2091
2208
|
type: "text",
|
|
@@ -2133,16 +2250,21 @@ function createReadFileTool(backend, options) {
|
|
|
2133
2250
|
}
|
|
2134
2251
|
let content = typeof readResult.content === "string" ? readResult.content : "";
|
|
2135
2252
|
const lines = content.split("\n");
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2253
|
+
let paginationResult = readResult;
|
|
2254
|
+
if (lines.length > limit) {
|
|
2255
|
+
content = lines.slice(0, limit).join("\n");
|
|
2256
|
+
if (limit > 0 && readResult.startLine !== void 0 && readResult.endLine !== void 0) {
|
|
2257
|
+
const endLine = Math.min(readResult.startLine + limit - 1, readResult.endLine, readResult.totalLines ?? Number.POSITIVE_INFINITY);
|
|
2258
|
+
paginationResult = {
|
|
2259
|
+
...readResult,
|
|
2260
|
+
endLine,
|
|
2261
|
+
nextOffset: endLine
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2142
2264
|
}
|
|
2143
2265
|
return [{
|
|
2144
2266
|
type: "text",
|
|
2145
|
-
text:
|
|
2267
|
+
text: truncatePaginatedRead(formatContentWithLineNumbersAndBoundaries(content, paginationResult.startLine ?? offset + 1), file_path, paginationResult, toolTokenLimitBeforeEvict)
|
|
2146
2268
|
}];
|
|
2147
2269
|
}, {
|
|
2148
2270
|
name: "read_file",
|
|
@@ -3260,6 +3382,83 @@ function createSummarizationMiddleware(options) {
|
|
|
3260
3382
|
});
|
|
3261
3383
|
}
|
|
3262
3384
|
//#endregion
|
|
3385
|
+
//#region src/middleware/utils.ts
|
|
3386
|
+
/**
|
|
3387
|
+
* Utility functions for middleware.
|
|
3388
|
+
*
|
|
3389
|
+
* This module provides shared helpers used across middleware implementations.
|
|
3390
|
+
*/
|
|
3391
|
+
/**
|
|
3392
|
+
* Merge custom middleware into an assembled stack by `.name`.
|
|
3393
|
+
*
|
|
3394
|
+
* Matching custom middleware replaces the existing entry in place. New
|
|
3395
|
+
* middleware is appended after the base stack in caller-provided order.
|
|
3396
|
+
*/
|
|
3397
|
+
function mergeMiddleware$1(base, custom) {
|
|
3398
|
+
const merged = new Map(base.map((middleware) => [middleware.name, middleware]));
|
|
3399
|
+
for (const middleware of custom) merged.set(middleware.name, middleware);
|
|
3400
|
+
return [...merged.values()];
|
|
3401
|
+
}
|
|
3402
|
+
function middlewareNames(middleware) {
|
|
3403
|
+
return new Set(middleware.map((entry) => entry.name));
|
|
3404
|
+
}
|
|
3405
|
+
function matchingMiddleware(middleware, names) {
|
|
3406
|
+
return middleware.filter((entry) => names.has(entry.name));
|
|
3407
|
+
}
|
|
3408
|
+
/**
|
|
3409
|
+
* Merge custom middleware into default and tail middleware segments.
|
|
3410
|
+
*
|
|
3411
|
+
* Same-name custom entries replace matching defaults in either segment. Novel
|
|
3412
|
+
* custom entries are inserted between the default and tail segments unless
|
|
3413
|
+
* `appendNew` is false.
|
|
3414
|
+
*/
|
|
3415
|
+
function mergeMiddlewareStack(defaultMiddleware, customMiddleware, tailMiddleware = [], options = {}) {
|
|
3416
|
+
const defaultMiddlewareNames = middlewareNames(defaultMiddleware);
|
|
3417
|
+
const tailMiddlewareNames = middlewareNames(tailMiddleware);
|
|
3418
|
+
const knownMiddlewareNames = /* @__PURE__ */ new Set([...defaultMiddlewareNames, ...tailMiddlewareNames]);
|
|
3419
|
+
const novelMiddleware = options.appendNew === false ? [] : customMiddleware.filter((entry) => !knownMiddlewareNames.has(entry.name));
|
|
3420
|
+
return [
|
|
3421
|
+
...mergeMiddleware$1(defaultMiddleware, matchingMiddleware(customMiddleware, defaultMiddlewareNames)),
|
|
3422
|
+
...novelMiddleware,
|
|
3423
|
+
...mergeMiddleware$1(tailMiddleware, matchingMiddleware(customMiddleware, tailMiddlewareNames))
|
|
3424
|
+
];
|
|
3425
|
+
}
|
|
3426
|
+
/**
|
|
3427
|
+
* Append text to a system message.
|
|
3428
|
+
*
|
|
3429
|
+
* Creates a new SystemMessage with the text appended to the existing content.
|
|
3430
|
+
* If the original message has content, the new text is separated by two newlines.
|
|
3431
|
+
*
|
|
3432
|
+
* @param systemMessage - Existing system message or null/undefined.
|
|
3433
|
+
* @param text - Text to add to the system message.
|
|
3434
|
+
* @returns New SystemMessage with the text appended.
|
|
3435
|
+
*
|
|
3436
|
+
* @example
|
|
3437
|
+
* ```typescript
|
|
3438
|
+
* const original = new SystemMessage({ content: "You are a helpful assistant." });
|
|
3439
|
+
* const updated = appendToSystemMessage(original, "Always be concise.");
|
|
3440
|
+
* // Result: SystemMessage with content "You are a helpful assistant.\n\nAlways be concise."
|
|
3441
|
+
* ```
|
|
3442
|
+
*/
|
|
3443
|
+
function appendToSystemMessage(systemMessage, text) {
|
|
3444
|
+
if (!systemMessage) return new _langchain_core_messages.SystemMessage({ content: text });
|
|
3445
|
+
const existingContent = systemMessage.content;
|
|
3446
|
+
if (typeof existingContent === "string") {
|
|
3447
|
+
const newContent = existingContent ? `${existingContent}\n\n${text}` : text;
|
|
3448
|
+
return new _langchain_core_messages.SystemMessage({ content: newContent });
|
|
3449
|
+
}
|
|
3450
|
+
if (Array.isArray(existingContent)) {
|
|
3451
|
+
const newContent = [...existingContent];
|
|
3452
|
+
const textToAdd = newContent.length > 0 ? `\n\n${text}` : text;
|
|
3453
|
+
newContent.push({
|
|
3454
|
+
type: "text",
|
|
3455
|
+
text: textToAdd
|
|
3456
|
+
});
|
|
3457
|
+
return new _langchain_core_messages.SystemMessage({ content: newContent });
|
|
3458
|
+
}
|
|
3459
|
+
return new _langchain_core_messages.SystemMessage({ content: text });
|
|
3460
|
+
}
|
|
3461
|
+
//#endregion
|
|
3263
3462
|
//#region src/middleware/subagents.ts
|
|
3264
3463
|
/**
|
|
3265
3464
|
* Config key used by task-tool callers to request dynamic response format.
|
|
@@ -3273,6 +3472,8 @@ const SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY = "__deepagents_subagent_response_form
|
|
|
3273
3472
|
* Provides a minimal base prompt that can be extended by specific subagent configurations.
|
|
3274
3473
|
*/
|
|
3275
3474
|
const DEFAULT_SUBAGENT_PROMPT = "In order to complete the objective that the user asks of you, you have access to a number of standard tools.";
|
|
3475
|
+
const FORKED_CONTEXT_KEY = "_deepagentsForkedContext";
|
|
3476
|
+
const FORK_RECURSION_REFUSAL = "You are a subagent and cannot delegate to another subagent. Complete this task yourself instead of calling this tool again.";
|
|
3276
3477
|
/**
|
|
3277
3478
|
* State keys excluded when passing state to subagents and when returning
|
|
3278
3479
|
* updates from subagents. Summarization keys are excluded because their
|
|
@@ -3285,6 +3486,18 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
3285
3486
|
"skillsMetadata",
|
|
3286
3487
|
"memoryContents",
|
|
3287
3488
|
"_summarizationEvent",
|
|
3489
|
+
"_summarizationSessionId",
|
|
3490
|
+
FORKED_CONTEXT_KEY
|
|
3491
|
+
];
|
|
3492
|
+
/**
|
|
3493
|
+
* State keys excluded when inheriting state into a declarative fork.
|
|
3494
|
+
* Narrower than `EXCLUDED_STATE_KEYS`: a fork's mirrored middleware needs
|
|
3495
|
+
* the parent's private channels (skills metadata, memory contents, etc.)
|
|
3496
|
+
* to rebuild an equivalent prompt.
|
|
3497
|
+
*/
|
|
3498
|
+
const FORK_EXCLUDED_STATE_KEYS = [
|
|
3499
|
+
"structuredResponse",
|
|
3500
|
+
"_summarizationEvent",
|
|
3288
3501
|
"_summarizationSessionId"
|
|
3289
3502
|
];
|
|
3290
3503
|
/**
|
|
@@ -3294,25 +3507,37 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
3294
3507
|
const DEFAULT_GENERAL_PURPOSE_DESCRIPTION = "General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.";
|
|
3295
3508
|
function getTaskToolDescription(subagentDescriptions) {
|
|
3296
3509
|
return langchain.context`
|
|
3297
|
-
Launch an ephemeral subagent to handle a complex, multi-step task
|
|
3510
|
+
Launch an ephemeral subagent to handle a complex, multi-step task.
|
|
3298
3511
|
|
|
3299
3512
|
Available agent types and the tools they have access to:
|
|
3300
3513
|
${subagentDescriptions.join("\n")}
|
|
3301
3514
|
|
|
3302
3515
|
Specify subagent_type to select the agent. Usage notes:
|
|
3303
3516
|
- Launch multiple agents concurrently when their tasks are independent, using a single message with multiple tool calls.
|
|
3304
|
-
- Each invocation is stateless: the agent sees only the prompt you give it and returns a single final report. Put full detail in the prompt and state exactly what it should return.
|
|
3517
|
+
- Each invocation is stateless by default: the agent sees only the prompt you give it and returns a single final report. Put full detail in the prompt and state exactly what it should return — unless an agent type below says it inherits your conversation instead.
|
|
3305
3518
|
- The agent's report is not shown to the user; relay a summary yourself.
|
|
3306
|
-
- Tell the agent whether to create content, analyze, or only research, since it
|
|
3519
|
+
- Tell the agent whether to create content, analyze, or only research, since it can't necessarily see the user's intent unless it inherits your conversation, as noted per agent type below.
|
|
3307
3520
|
- If an agent's description says to use it proactively, do so without waiting to be asked.
|
|
3308
3521
|
- When only general-purpose is available, use it for any complex, context-heavy task; it has the same capabilities as the main agent.
|
|
3309
3522
|
`;
|
|
3310
3523
|
}
|
|
3524
|
+
const FORKED_SUBAGENT_TOOL_NOTE = " (inherits your full conversation and system prompt — no need to restate context here)";
|
|
3525
|
+
const COMPILED_FORKED_SUBAGENT_TOOL_NOTE = " (inherits your conversation history — its system prompt is fixed in its own runnable)";
|
|
3526
|
+
/** Render one subagent's listing line for the task tool description. */
|
|
3527
|
+
function describeSubagentForTool(name, description, forked, compiled = false) {
|
|
3528
|
+
return `- ${name}: ${description}${forked ? compiled ? COMPILED_FORKED_SUBAGENT_TOOL_NOTE : FORKED_SUBAGENT_TOOL_NOTE : ""}`;
|
|
3529
|
+
}
|
|
3530
|
+
const FORK_TASK_PREAMBLE = "[The messages above are a prior conversation you are continuing as the subagent that was just invoked. Any mention in them of delegating to a subagent already happened — you are that subagent, not the one being asked to delegate further. If you try to delegate to another subagent yourself, it will be refused — complete this task directly. Use the specific facts, figures, and identifiers already established in that conversation when completing the task below — do not answer generically when exact details are already available above. Your actual task is below.]\n\n";
|
|
3531
|
+
/**
|
|
3532
|
+
* Whether a declarative subagent spec has `mode: "fork"` set.
|
|
3533
|
+
*
|
|
3534
|
+
* A plain boolean, not a type predicate: `SubAgent` covers both `"fork"` and
|
|
3535
|
+
* `"isolated"`, so there's no distinct type left to narrow to.
|
|
3536
|
+
*/
|
|
3311
3537
|
function isForkedSubAgent(value) {
|
|
3312
3538
|
if (typeof value !== "object" || value == null) return false;
|
|
3313
3539
|
if (!("mode" in value)) return false;
|
|
3314
|
-
|
|
3315
|
-
return true;
|
|
3540
|
+
return value.mode === "fork";
|
|
3316
3541
|
}
|
|
3317
3542
|
/**
|
|
3318
3543
|
* Base specification for the general-purpose subagent.
|
|
@@ -3355,15 +3580,26 @@ const GENERAL_PURPOSE_SUBAGENT = {
|
|
|
3355
3580
|
name: "general-purpose",
|
|
3356
3581
|
description: DEFAULT_GENERAL_PURPOSE_DESCRIPTION,
|
|
3357
3582
|
systemPrompt: DEFAULT_SUBAGENT_PROMPT,
|
|
3358
|
-
mode: "
|
|
3583
|
+
mode: "isolated"
|
|
3359
3584
|
};
|
|
3585
|
+
function filterState(state, excludedKeys) {
|
|
3586
|
+
const filtered = {};
|
|
3587
|
+
for (const [key, value] of Object.entries(state)) if (!excludedKeys.includes(key)) filtered[key] = value;
|
|
3588
|
+
return filtered;
|
|
3589
|
+
}
|
|
3360
3590
|
/**
|
|
3361
3591
|
* Filter state to exclude certain keys when passing to subagents
|
|
3362
3592
|
*/
|
|
3363
3593
|
function filterStateForSubagent(state) {
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3594
|
+
return filterState(state, EXCLUDED_STATE_KEYS);
|
|
3595
|
+
}
|
|
3596
|
+
/**
|
|
3597
|
+
* Filter state to exclude only the keys a declarative fork must not resume
|
|
3598
|
+
* (structured response, summarization event/session) — see
|
|
3599
|
+
* `FORK_EXCLUDED_STATE_KEYS`.
|
|
3600
|
+
*/
|
|
3601
|
+
function filterStateForFork(state) {
|
|
3602
|
+
return filterState(state, FORK_EXCLUDED_STATE_KEYS);
|
|
3367
3603
|
}
|
|
3368
3604
|
/**
|
|
3369
3605
|
* Invalid tool message block types
|
|
@@ -3407,6 +3643,15 @@ function stripInFlightAIMessage(messages) {
|
|
|
3407
3643
|
const last = messages.at(-1);
|
|
3408
3644
|
return _langchain_core_messages.AIMessage.isInstance(last) && (last.tool_calls?.length ?? 0) > 0 ? messages.slice(0, -1) : messages;
|
|
3409
3645
|
}
|
|
3646
|
+
const ForkedContextStateSchema = zod_v4.z.object({ [FORKED_CONTEXT_KEY]: zod_v4.z.boolean().optional() });
|
|
3647
|
+
function createForkTaskToolMiddleware(taskTool) {
|
|
3648
|
+
return (0, langchain.createMiddleware)({
|
|
3649
|
+
name: "forkTaskToolMiddleware",
|
|
3650
|
+
stateSchema: ForkedContextStateSchema,
|
|
3651
|
+
tools: [taskTool],
|
|
3652
|
+
beforeAgent: () => ({ [FORKED_CONTEXT_KEY]: true })
|
|
3653
|
+
});
|
|
3654
|
+
}
|
|
3410
3655
|
/**
|
|
3411
3656
|
* Create a runnable agent from a declarative `SubAgent` spec.
|
|
3412
3657
|
*
|
|
@@ -3436,6 +3681,17 @@ function createSubAgent(spec, options) {
|
|
|
3436
3681
|
});
|
|
3437
3682
|
}
|
|
3438
3683
|
/**
|
|
3684
|
+
* Resolve a fork's system prompt: the parent's inherited prompt, with the
|
|
3685
|
+
* fork's own systemPrompt (if any) appended as an addendum rather than
|
|
3686
|
+
* replacing it.
|
|
3687
|
+
*/
|
|
3688
|
+
function resolveForkSystemPrompt(parentSystemPrompt, forkAddendum) {
|
|
3689
|
+
if (!forkAddendum) return parentSystemPrompt ?? "";
|
|
3690
|
+
const addendumText = typeof forkAddendum === "string" ? forkAddendum : forkAddendum.text;
|
|
3691
|
+
if (langchain.SystemMessage.isInstance(parentSystemPrompt)) return appendToSystemMessage(parentSystemPrompt, addendumText);
|
|
3692
|
+
return parentSystemPrompt ? `${parentSystemPrompt}\n\n${addendumText}` : addendumText;
|
|
3693
|
+
}
|
|
3694
|
+
/**
|
|
3439
3695
|
* Create subagent instances from specifications.
|
|
3440
3696
|
*
|
|
3441
3697
|
* Returns compiled agents, raw specs keyed by name (for on-demand
|
|
@@ -3443,13 +3699,18 @@ function createSubAgent(spec, options) {
|
|
|
3443
3699
|
* of names that should fork the parent's conversation.
|
|
3444
3700
|
*/
|
|
3445
3701
|
function getSubagents(options) {
|
|
3446
|
-
const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware: gpMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, parentSystemPrompt = null } = options;
|
|
3702
|
+
const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware: gpMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, parentSystemPrompt = null, mirroredTaskTool } = options;
|
|
3447
3703
|
const defaultSubagentMiddleware = defaultMiddleware || [];
|
|
3448
3704
|
const generalPurposeMiddlewareBase = gpMiddleware || defaultSubagentMiddleware;
|
|
3449
3705
|
const agents = {};
|
|
3450
3706
|
const specsByName = {};
|
|
3451
3707
|
const subagentDescriptions = [];
|
|
3452
3708
|
const forkModeNames = /* @__PURE__ */ new Set();
|
|
3709
|
+
const seenNames = new Set(generalPurposeAgent ? ["general-purpose"] : []);
|
|
3710
|
+
for (const agentParams of subagents) {
|
|
3711
|
+
if (seenNames.has(agentParams.name)) throw new Error(`Duplicate subagent name '${agentParams.name}'; each subagent must have a unique name.`);
|
|
3712
|
+
seenNames.add(agentParams.name);
|
|
3713
|
+
}
|
|
3453
3714
|
if (generalPurposeAgent) {
|
|
3454
3715
|
const generalPurposeMiddleware = [...generalPurposeMiddlewareBase];
|
|
3455
3716
|
if (defaultInterruptOn) generalPurposeMiddleware.push((0, langchain.humanInTheLoopMiddleware)({ interruptOn: defaultInterruptOn }));
|
|
@@ -3463,24 +3724,34 @@ function getSubagents(options) {
|
|
|
3463
3724
|
};
|
|
3464
3725
|
agents["general-purpose"] = createSubAgent(gpSpec);
|
|
3465
3726
|
specsByName["general-purpose"] = gpSpec;
|
|
3466
|
-
subagentDescriptions.push(
|
|
3727
|
+
subagentDescriptions.push(describeSubagentForTool("general-purpose", DEFAULT_GENERAL_PURPOSE_DESCRIPTION, false));
|
|
3467
3728
|
}
|
|
3468
3729
|
for (const agentParams of subagents) {
|
|
3469
3730
|
const rawMode = agentParams.mode;
|
|
3470
|
-
if (rawMode != null && rawMode !== "
|
|
3471
|
-
|
|
3731
|
+
if (rawMode != null && rawMode !== "isolated" && rawMode !== "fork" && rawMode !== "handoff") throw new Error(`SubAgent '${agentParams.name}' has invalid mode '${rawMode}' — must be "isolated" or "fork".`);
|
|
3732
|
+
const forked = isForkedSubAgent(agentParams);
|
|
3733
|
+
const compiled = "runnable" in agentParams;
|
|
3734
|
+
subagentDescriptions.push(describeSubagentForTool(agentParams.name, agentParams.description, forked, compiled));
|
|
3472
3735
|
if ("runnable" in agentParams) {
|
|
3473
3736
|
agents[agentParams.name] = agentParams.runnable;
|
|
3474
3737
|
specsByName[agentParams.name] = agentParams;
|
|
3475
|
-
if (
|
|
3476
|
-
|
|
3738
|
+
if (forked) forkModeNames.add(agentParams.name);
|
|
3739
|
+
continue;
|
|
3740
|
+
}
|
|
3741
|
+
const subagentMiddleware = [...defaultSubagentMiddleware, ...agentParams.middleware ?? []];
|
|
3742
|
+
if (forked) {
|
|
3743
|
+
const rawSkills = agentParams.skills;
|
|
3744
|
+
if (Array.isArray(rawSkills) && rawSkills.length > 0) throw new Error(`SubAgent '${agentParams.name}' cannot set skills under mode: "fork"; the parent's skills are inherited instead.`);
|
|
3745
|
+
const resolvedSystemPrompt = resolveForkSystemPrompt(parentSystemPrompt, agentParams.systemPrompt);
|
|
3746
|
+
const fsIndex = subagentMiddleware.findIndex((m) => m.name === "FilesystemMiddleware");
|
|
3747
|
+
subagentMiddleware.splice(fsIndex + 1, 0, createForkTaskToolMiddleware(mirroredTaskTool));
|
|
3477
3748
|
const resolvedSpec = {
|
|
3478
3749
|
...agentParams,
|
|
3479
|
-
systemPrompt:
|
|
3750
|
+
systemPrompt: resolvedSystemPrompt,
|
|
3480
3751
|
mode: void 0,
|
|
3481
3752
|
model: agentParams.model ?? defaultModel,
|
|
3482
3753
|
tools: agentParams.tools ?? defaultTools,
|
|
3483
|
-
middleware:
|
|
3754
|
+
middleware: subagentMiddleware,
|
|
3484
3755
|
interruptOn: agentParams.interruptOn ?? defaultInterruptOn ?? void 0
|
|
3485
3756
|
};
|
|
3486
3757
|
agents[agentParams.name] = createSubAgent(resolvedSpec);
|
|
@@ -3489,10 +3760,10 @@ function getSubagents(options) {
|
|
|
3489
3760
|
} else {
|
|
3490
3761
|
const resolvedSpec = {
|
|
3491
3762
|
...agentParams,
|
|
3492
|
-
mode: "
|
|
3763
|
+
mode: "isolated",
|
|
3493
3764
|
model: agentParams.model ?? defaultModel,
|
|
3494
3765
|
tools: agentParams.tools ?? defaultTools,
|
|
3495
|
-
middleware:
|
|
3766
|
+
middleware: subagentMiddleware,
|
|
3496
3767
|
interruptOn: agentParams.interruptOn ?? defaultInterruptOn ?? void 0
|
|
3497
3768
|
};
|
|
3498
3769
|
agents[agentParams.name] = createSubAgent(resolvedSpec);
|
|
@@ -3511,16 +3782,12 @@ function getSubagents(options) {
|
|
|
3511
3782
|
*/
|
|
3512
3783
|
function createTaskTool(options) {
|
|
3513
3784
|
const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, taskDescription, parentSystemPrompt = null } = options;
|
|
3514
|
-
const
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
subagents,
|
|
3521
|
-
generalPurposeAgent,
|
|
3522
|
-
parentSystemPrompt
|
|
3523
|
-
});
|
|
3785
|
+
const subagentNames = [...generalPurposeAgent ? ["general-purpose"] : [], ...subagents.map((spec) => spec.name)];
|
|
3786
|
+
const subagentDescriptions = [...generalPurposeAgent ? [describeSubagentForTool("general-purpose", DEFAULT_GENERAL_PURPOSE_DESCRIPTION, false)] : [], ...subagents.map((spec) => describeSubagentForTool(spec.name, spec.description, isForkedSubAgent(spec), "runnable" in spec))];
|
|
3787
|
+
const finalTaskDescription = taskDescription ? taskDescription : getTaskToolDescription(subagentDescriptions);
|
|
3788
|
+
let subagentGraphs = {};
|
|
3789
|
+
let specsByName = {};
|
|
3790
|
+
let forkModeNames = /* @__PURE__ */ new Set();
|
|
3524
3791
|
function selectSubagent(subagentType, config) {
|
|
3525
3792
|
const spec = specsByName[subagentType];
|
|
3526
3793
|
const responseFormat = config.configurable?.[SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY];
|
|
@@ -3528,18 +3795,19 @@ function createTaskTool(options) {
|
|
|
3528
3795
|
if ("runnable" in spec || responseFormat == null) return subagentGraphs[subagentType];
|
|
3529
3796
|
return createSubAgent(spec, { responseFormat });
|
|
3530
3797
|
}
|
|
3531
|
-
|
|
3532
|
-
return (0, langchain.tool)(async (input, config) => {
|
|
3798
|
+
async function runTask(input, config) {
|
|
3533
3799
|
const { description, subagent_type } = input;
|
|
3800
|
+
const currentState = (0, _langchain_langgraph.getCurrentTaskInput)();
|
|
3801
|
+
if (currentState[FORKED_CONTEXT_KEY]) return FORK_RECURSION_REFUSAL;
|
|
3534
3802
|
if (!(subagent_type in subagentGraphs)) {
|
|
3535
3803
|
const allowedTypes = Object.keys(subagentGraphs).map((k) => `\`${k}\``).join(", ");
|
|
3536
3804
|
throw new Error(`Error: invoked agent of type ${subagent_type}, the only allowed types are ${allowedTypes}`);
|
|
3537
3805
|
}
|
|
3538
3806
|
const shouldFork = forkModeNames.has(subagent_type);
|
|
3539
3807
|
const subagent = selectSubagent(subagent_type, config);
|
|
3540
|
-
const
|
|
3541
|
-
const subagentState = filterStateForSubagent(currentState);
|
|
3542
|
-
if (shouldFork) subagentState.messages = [...getEffectiveMessages(stripInFlightAIMessage(currentState.messages ?? []), currentState), new _langchain_core_messages.HumanMessage({ content: description })];
|
|
3808
|
+
const spec = specsByName[subagent_type];
|
|
3809
|
+
const subagentState = shouldFork && !("runnable" in spec) ? filterStateForFork(currentState) : filterStateForSubagent(currentState);
|
|
3810
|
+
if (shouldFork) subagentState.messages = [...getEffectiveMessages(stripInFlightAIMessage(currentState.messages ?? []), currentState), new _langchain_core_messages.HumanMessage({ content: FORK_TASK_PREAMBLE + description })];
|
|
3543
3811
|
else subagentState.messages = [new _langchain_core_messages.HumanMessage({ content: description })];
|
|
3544
3812
|
subagentState._summarizationSessionId = `session_${crypto.randomUUID().substring(0, 8)}`;
|
|
3545
3813
|
const subagentConfig = {
|
|
@@ -3566,14 +3834,35 @@ function createTaskTool(options) {
|
|
|
3566
3834
|
return content;
|
|
3567
3835
|
}
|
|
3568
3836
|
return returnCommandWithStateUpdate(result, config.toolCall.id);
|
|
3569
|
-
}
|
|
3837
|
+
}
|
|
3838
|
+
const taskToolSchema = zod_v4.z.object({
|
|
3839
|
+
description: zod_v4.z.string().describe("The task to execute with the selected agent"),
|
|
3840
|
+
subagent_type: zod_v4.z.string().describe(`Name of the agent to use. Available: ${subagentNames.join(", ")}`)
|
|
3841
|
+
});
|
|
3842
|
+
const taskTool = (0, langchain.tool)(runTask, {
|
|
3570
3843
|
name: "task",
|
|
3571
3844
|
description: finalTaskDescription,
|
|
3572
|
-
schema:
|
|
3573
|
-
|
|
3574
|
-
|
|
3845
|
+
schema: taskToolSchema
|
|
3846
|
+
});
|
|
3847
|
+
const { agents, specsByName: resolvedSpecsByName, forkModeNames: resolvedForkModeNames } = getSubagents({
|
|
3848
|
+
defaultModel,
|
|
3849
|
+
defaultTools,
|
|
3850
|
+
defaultMiddleware,
|
|
3851
|
+
generalPurposeMiddleware,
|
|
3852
|
+
defaultInterruptOn,
|
|
3853
|
+
subagents,
|
|
3854
|
+
generalPurposeAgent,
|
|
3855
|
+
parentSystemPrompt,
|
|
3856
|
+
mirroredTaskTool: (0, langchain.tool)(runTask, {
|
|
3857
|
+
name: "task",
|
|
3858
|
+
description: finalTaskDescription,
|
|
3859
|
+
schema: taskToolSchema
|
|
3575
3860
|
})
|
|
3576
3861
|
});
|
|
3862
|
+
subagentGraphs = agents;
|
|
3863
|
+
specsByName = resolvedSpecsByName;
|
|
3864
|
+
forkModeNames = resolvedForkModeNames;
|
|
3865
|
+
return taskTool;
|
|
3577
3866
|
}
|
|
3578
3867
|
/**
|
|
3579
3868
|
* Create subagent middleware with task tool
|
|
@@ -4575,48 +4864,6 @@ function createSkillsMiddleware(options) {
|
|
|
4575
4864
|
});
|
|
4576
4865
|
}
|
|
4577
4866
|
//#endregion
|
|
4578
|
-
//#region src/middleware/utils.ts
|
|
4579
|
-
/**
|
|
4580
|
-
* Utility functions for middleware.
|
|
4581
|
-
*
|
|
4582
|
-
* This module provides shared helpers used across middleware implementations.
|
|
4583
|
-
*/
|
|
4584
|
-
/**
|
|
4585
|
-
* Merge custom middleware into an assembled stack by `.name`.
|
|
4586
|
-
*
|
|
4587
|
-
* Matching custom middleware replaces the existing entry in place. New
|
|
4588
|
-
* middleware is appended after the base stack in caller-provided order.
|
|
4589
|
-
*/
|
|
4590
|
-
function mergeMiddleware$1(base, custom) {
|
|
4591
|
-
const merged = new Map(base.map((middleware) => [middleware.name, middleware]));
|
|
4592
|
-
for (const middleware of custom) merged.set(middleware.name, middleware);
|
|
4593
|
-
return [...merged.values()];
|
|
4594
|
-
}
|
|
4595
|
-
function middlewareNames(middleware) {
|
|
4596
|
-
return new Set(middleware.map((entry) => entry.name));
|
|
4597
|
-
}
|
|
4598
|
-
function matchingMiddleware(middleware, names) {
|
|
4599
|
-
return middleware.filter((entry) => names.has(entry.name));
|
|
4600
|
-
}
|
|
4601
|
-
/**
|
|
4602
|
-
* Merge custom middleware into default and tail middleware segments.
|
|
4603
|
-
*
|
|
4604
|
-
* Same-name custom entries replace matching defaults in either segment. Novel
|
|
4605
|
-
* custom entries are inserted between the default and tail segments unless
|
|
4606
|
-
* `appendNew` is false.
|
|
4607
|
-
*/
|
|
4608
|
-
function mergeMiddlewareStack(defaultMiddleware, customMiddleware, tailMiddleware = [], options = {}) {
|
|
4609
|
-
const defaultMiddlewareNames = middlewareNames(defaultMiddleware);
|
|
4610
|
-
const tailMiddlewareNames = middlewareNames(tailMiddleware);
|
|
4611
|
-
const knownMiddlewareNames = /* @__PURE__ */ new Set([...defaultMiddlewareNames, ...tailMiddlewareNames]);
|
|
4612
|
-
const novelMiddleware = options.appendNew === false ? [] : customMiddleware.filter((entry) => !knownMiddlewareNames.has(entry.name));
|
|
4613
|
-
return [
|
|
4614
|
-
...mergeMiddleware$1(defaultMiddleware, matchingMiddleware(customMiddleware, defaultMiddlewareNames)),
|
|
4615
|
-
...novelMiddleware,
|
|
4616
|
-
...mergeMiddleware$1(tailMiddleware, matchingMiddleware(customMiddleware, tailMiddlewareNames))
|
|
4617
|
-
];
|
|
4618
|
-
}
|
|
4619
|
-
//#endregion
|
|
4620
4867
|
//#region src/middleware/completion_callback.ts
|
|
4621
4868
|
/**
|
|
4622
4869
|
* Callback middleware for async subagents.
|
|
@@ -5476,8 +5723,9 @@ function hasToolName(tool) {
|
|
|
5476
5723
|
return tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string";
|
|
5477
5724
|
}
|
|
5478
5725
|
/**
|
|
5479
|
-
* Create middleware that
|
|
5480
|
-
*
|
|
5726
|
+
* Create middleware that hides excluded tools from the model and rejects calls
|
|
5727
|
+
* to them. Exclusions calibrate the agent per model; they are not a security
|
|
5728
|
+
* boundary.
|
|
5481
5729
|
*
|
|
5482
5730
|
* @internal
|
|
5483
5731
|
*/
|
|
@@ -5489,6 +5737,16 @@ function createToolExclusionMiddleware(excludedTools) {
|
|
|
5489
5737
|
...request,
|
|
5490
5738
|
tools: request.tools?.filter((tool) => !hasToolName(tool) || !excludedTools.has(tool.name))
|
|
5491
5739
|
});
|
|
5740
|
+
},
|
|
5741
|
+
wrapToolCall(request, handler) {
|
|
5742
|
+
const { name, id } = request.toolCall;
|
|
5743
|
+
if (!excludedTools.has(name)) return handler(request);
|
|
5744
|
+
return new _langchain_core_messages.ToolMessage({
|
|
5745
|
+
content: `Error: ${name} is not available.`,
|
|
5746
|
+
tool_call_id: id ?? "",
|
|
5747
|
+
name,
|
|
5748
|
+
status: "error"
|
|
5749
|
+
});
|
|
5492
5750
|
}
|
|
5493
5751
|
});
|
|
5494
5752
|
}
|
|
@@ -6234,8 +6492,18 @@ function createDeepAgent(params = {}) {
|
|
|
6234
6492
|
providerHint: getModelProvider(model),
|
|
6235
6493
|
identifierHint: getModelIdentifier(model)
|
|
6236
6494
|
});
|
|
6237
|
-
const
|
|
6238
|
-
|
|
6495
|
+
const computeProfileFilesystemTools = (profile) => {
|
|
6496
|
+
const filesystemTools = FILESYSTEM_TOOL_NAMES.filter((toolName) => !profile.excludedTools.has(toolName));
|
|
6497
|
+
return filesystemTools.length === FILESYSTEM_TOOL_NAMES.length ? void 0 : filesystemTools.includes("read_file") ? filesystemTools : ["read_file", ...filesystemTools];
|
|
6498
|
+
};
|
|
6499
|
+
const profileFilesystemTools = computeProfileFilesystemTools(harnessProfile);
|
|
6500
|
+
const resolveSubagentProfile = (subagentModel) => {
|
|
6501
|
+
if (subagentModel == null || subagentModel === model) return harnessProfile;
|
|
6502
|
+
return typeof subagentModel === "string" ? resolveHarnessProfile({ spec: subagentModel }) : resolveHarnessProfile({
|
|
6503
|
+
providerHint: getModelProvider(subagentModel),
|
|
6504
|
+
identifierHint: getModelIdentifier(subagentModel)
|
|
6505
|
+
});
|
|
6506
|
+
};
|
|
6239
6507
|
const toolOverrides = harnessProfile.toolDescriptionOverrides;
|
|
6240
6508
|
const effectiveTools = Object.keys(toolOverrides).length > 0 ? tools.map((t) => t.name in toolOverrides ? Object.assign(Object.create(Object.getPrototypeOf(t)), t, { description: toolOverrides[t.name] }) : t) : tools;
|
|
6241
6509
|
const anthropicModel = isAnthropicModel(model);
|
|
@@ -6271,44 +6539,50 @@ function createDeepAgent(params = {}) {
|
|
|
6271
6539
|
* Only the general-purpose subagent inherits the main agent's skills.
|
|
6272
6540
|
* If a custom subagent needs skills, it must specify its own `skills` array.
|
|
6273
6541
|
*/
|
|
6274
|
-
const createSubagentDefaultMiddleware = (input) => {
|
|
6542
|
+
const createSubagentDefaultMiddleware = (input, subagentProfile, forked) => {
|
|
6275
6543
|
const effectivePermissions = input.permissions ?? permissions;
|
|
6276
6544
|
return [
|
|
6277
6545
|
createFilesystemMiddleware({
|
|
6278
6546
|
backend,
|
|
6279
6547
|
permissions: effectivePermissions,
|
|
6280
|
-
tools:
|
|
6548
|
+
tools: computeProfileFilesystemTools(subagentProfile)
|
|
6281
6549
|
}),
|
|
6282
6550
|
createSummarizationMiddleware({ backend }),
|
|
6283
6551
|
createPatchToolCallsMiddleware(),
|
|
6284
|
-
|
|
6552
|
+
...!forked && input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
|
|
6285
6553
|
backend,
|
|
6286
6554
|
sources: input.skills
|
|
6287
6555
|
})] : []
|
|
6288
6556
|
];
|
|
6289
6557
|
};
|
|
6290
|
-
const buildSubagentMiddleware = (input
|
|
6291
|
-
|
|
6292
|
-
|
|
6558
|
+
const buildSubagentMiddleware = (input) => {
|
|
6559
|
+
const subagentProfile = resolveSubagentProfile(input.model);
|
|
6560
|
+
const forked = isForkedSubAgent(input);
|
|
6561
|
+
const subagentDefaultMiddleware = createSubagentDefaultMiddleware(input, subagentProfile, forked);
|
|
6562
|
+
if (forked && skills != null && skills.length > 0) subagentDefaultMiddleware.unshift(createSkillsMiddleware({
|
|
6563
|
+
backend,
|
|
6564
|
+
sources: skills
|
|
6565
|
+
}));
|
|
6566
|
+
let subagentMiddleware = mergeMiddlewareStack(subagentDefaultMiddleware, forked && customMiddleware.length > 0 ? mergeMiddleware$1(customMiddleware, input.middleware ?? []) : input.middleware ?? [], [
|
|
6567
|
+
...resolveMiddleware(subagentProfile.extraMiddleware),
|
|
6293
6568
|
...cacheMiddleware,
|
|
6294
|
-
...
|
|
6569
|
+
...forked && memory != null && memory.length > 0 ? [createMemoryMiddleware({
|
|
6570
|
+
backend,
|
|
6571
|
+
sources: memory,
|
|
6572
|
+
addCacheControl: anthropicModel
|
|
6573
|
+
})] : []
|
|
6295
6574
|
]);
|
|
6296
|
-
if (
|
|
6575
|
+
if (subagentProfile.excludedMiddleware.size > 0) subagentMiddleware = subagentMiddleware.filter((middleware) => !subagentProfile.excludedMiddleware.has(middleware.name));
|
|
6576
|
+
if (subagentProfile.excludedTools.size > 0) subagentMiddleware.push(createToolExclusionMiddleware(subagentProfile.excludedTools));
|
|
6297
6577
|
return subagentMiddleware;
|
|
6298
6578
|
};
|
|
6299
6579
|
const normalizeSubagentSpec = (input) => ({
|
|
6300
6580
|
...input,
|
|
6301
|
-
|
|
6302
|
-
middleware: buildSubagentMiddleware(input, false)
|
|
6303
|
-
});
|
|
6304
|
-
const normalizeForkedSubagentSpec = (input) => ({
|
|
6305
|
-
...input,
|
|
6306
|
-
tools: input.tools ?? [],
|
|
6307
|
-
middleware: buildSubagentMiddleware(input, true)
|
|
6581
|
+
middleware: buildSubagentMiddleware(input)
|
|
6308
6582
|
});
|
|
6309
6583
|
const allSubagents = subagents;
|
|
6310
6584
|
const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
|
|
6311
|
-
const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item :
|
|
6585
|
+
const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : normalizeSubagentSpec(item));
|
|
6312
6586
|
const gpConfig = harnessProfile.generalPurposeSubagent;
|
|
6313
6587
|
if (!(gpConfig?.enabled === false) && !inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
|
|
6314
6588
|
const gpSystemPrompt = gpConfig?.systemPrompt ?? applyProfilePrompt(harnessProfile, GENERAL_PURPOSE_SUBAGENT.systemPrompt);
|
|
@@ -6800,10 +7074,23 @@ var StoreBackend = class {
|
|
|
6800
7074
|
mimeType: fileDataV2.mimeType
|
|
6801
7075
|
};
|
|
6802
7076
|
if (typeof fileDataV2.content !== "string") return { error: `File '${filePath}' has binary content but text MIME type` };
|
|
6803
|
-
|
|
6804
|
-
|
|
7077
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
7078
|
+
const lines = fileDataV2.content.split("\n");
|
|
7079
|
+
const totalLines = lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
7080
|
+
const selected = lines.slice(normalizedOffset, normalizedOffset + normalizedLimit);
|
|
7081
|
+
if (selected.length === 0 || normalizedOffset >= totalLines || normalizedLimit === 0) return {
|
|
7082
|
+
content: selected.join("\n"),
|
|
6805
7083
|
mimeType: fileDataV2.mimeType
|
|
6806
7084
|
};
|
|
7085
|
+
const endOffset = Math.min(normalizedOffset + selected.length, totalLines);
|
|
7086
|
+
return {
|
|
7087
|
+
content: selected.join("\n"),
|
|
7088
|
+
mimeType: fileDataV2.mimeType,
|
|
7089
|
+
totalLines,
|
|
7090
|
+
startLine: normalizedOffset + 1,
|
|
7091
|
+
endLine: endOffset,
|
|
7092
|
+
nextOffset: endOffset < totalLines ? endOffset : void 0
|
|
7093
|
+
};
|
|
6807
7094
|
} catch (e) {
|
|
6808
7095
|
return { error: e.message };
|
|
6809
7096
|
}
|
|
@@ -7069,12 +7356,20 @@ function splitLinesKeepEnds(content) {
|
|
|
7069
7356
|
return lines;
|
|
7070
7357
|
}
|
|
7071
7358
|
function sliceReadContent(content, offset, limit) {
|
|
7072
|
-
if (!content
|
|
7359
|
+
if (!content) return { content };
|
|
7073
7360
|
const lines = splitLinesKeepEnds(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
7074
7361
|
const startIndex = offset;
|
|
7075
7362
|
const endIndex = Math.min(startIndex + limit, lines.length);
|
|
7076
7363
|
if (startIndex >= lines.length) return { error: `Line offset ${offset} exceeds file length (${lines.length} lines)` };
|
|
7077
|
-
|
|
7364
|
+
const selected = lines.slice(startIndex, endIndex);
|
|
7365
|
+
if (selected.length === 0 || offset < 0 || limit <= 0) return { content: selected.join("") };
|
|
7366
|
+
return {
|
|
7367
|
+
content: selected.join(""),
|
|
7368
|
+
totalLines: lines.length,
|
|
7369
|
+
startLine: startIndex + 1,
|
|
7370
|
+
endLine: endIndex,
|
|
7371
|
+
nextOffset: endIndex < lines.length ? endIndex : void 0
|
|
7372
|
+
};
|
|
7078
7373
|
}
|
|
7079
7374
|
function isLangSmithNotFoundError(error) {
|
|
7080
7375
|
if (typeof error !== "object" || error === null) return false;
|
|
@@ -7553,9 +7848,11 @@ var ContextHubBackend = class ContextHubBackend {
|
|
|
7553
7848
|
}
|
|
7554
7849
|
const content = cache[hubPath];
|
|
7555
7850
|
if (content === void 0) return { error: `File '${filePath}' not found` };
|
|
7556
|
-
const
|
|
7851
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
7852
|
+
const sliced = sliceReadContent(content, normalizedOffset, normalizedLimit);
|
|
7557
7853
|
if (sliced.error) return { error: sliced.error };
|
|
7558
7854
|
return {
|
|
7855
|
+
...sliced,
|
|
7559
7856
|
content: sliced.content ?? "",
|
|
7560
7857
|
mimeType: TEXT_MIME_TYPE
|
|
7561
7858
|
};
|
|
@@ -7894,6 +8191,7 @@ function buildFindCommand(searchPath) {
|
|
|
7894
8191
|
const findBase = `find -L ${quotedPath} ${`\\( -path /proc -o -path /sys -o -path /dev -o -path /run -o -path ${quotedPath}/proc -o -path ${quotedPath}/sys -o -path ${quotedPath}/dev -o -path ${quotedPath}/run \\) -prune`} -o -not -path ${quotedPath}`;
|
|
7895
8192
|
return `{ ${`if find /dev/null -maxdepth 0 -printf '' 2>/dev/null; then ${findBase} -printf '%s\\t%T@\\t%y\\t%p\\n' 2>/dev/null; elif stat -c %s /dev/null >/dev/null 2>&1; then ${findBase} -exec sh -c '${STAT_C_SCRIPT}' _ {} +; else ${findBase} -exec stat -f '%z\t%m\t%Sp\t%N' {} + 2>/dev/null; fi || true`}; } | head -n 50001`;
|
|
7896
8193
|
}
|
|
8194
|
+
const READ_METADATA_PREFIX = "__DEEPAGENTS_READ_METADATA__";
|
|
7897
8195
|
/**
|
|
7898
8196
|
* Pure POSIX shell command for reading files with line numbers.
|
|
7899
8197
|
* Uses awk for line numbering with offset/limit — works on any Linux including Alpine.
|
|
@@ -7907,9 +8205,32 @@ function buildReadCommand(filePath, offset, limit) {
|
|
|
7907
8205
|
return [
|
|
7908
8206
|
`if [ ! -f ${quotedPath} ]; then echo "Error: File not found"; exit 1; fi`,
|
|
7909
8207
|
`if [ ! -s ${quotedPath} ]; then echo "System reminder: File exists but has empty contents"; exit 0; fi`,
|
|
7910
|
-
`awk 'NR >= ${start} && NR <= ${end} { printf "%6d\\t%s\\n", NR, $0 }' ${quotedPath}`
|
|
8208
|
+
`awk 'NR >= ${start} && NR <= ${end} { printf "%6d\\t%s\\n", NR, $0 } END { printf "${READ_METADATA_PREFIX}\\t%d\\n", NR }' ${quotedPath}`
|
|
7911
8209
|
].join("; ");
|
|
7912
8210
|
}
|
|
8211
|
+
function parseReadOutput(output, offset, limit) {
|
|
8212
|
+
const rows = output.split("\n");
|
|
8213
|
+
let metadataIndex = -1;
|
|
8214
|
+
for (let index = rows.length - 1; index >= 0; index -= 1) if (rows[index].startsWith(`${READ_METADATA_PREFIX}\t`)) {
|
|
8215
|
+
metadataIndex = index;
|
|
8216
|
+
break;
|
|
8217
|
+
}
|
|
8218
|
+
if (metadataIndex === -1) return { content: output };
|
|
8219
|
+
const totalLines = Number(rows[metadataIndex].slice(29));
|
|
8220
|
+
if (!Number.isSafeInteger(totalLines) || totalLines < 0) return { content: output };
|
|
8221
|
+
const contentRows = rows.slice(0, metadataIndex);
|
|
8222
|
+
const content = contentRows.length > 0 ? `${contentRows.join("\n")}\n` : "";
|
|
8223
|
+
const startOffset = Math.floor(offset);
|
|
8224
|
+
const endOffset = Math.min(startOffset + Math.floor(limit), totalLines);
|
|
8225
|
+
if (startOffset >= totalLines || endOffset <= startOffset) return { content };
|
|
8226
|
+
return {
|
|
8227
|
+
content,
|
|
8228
|
+
totalLines,
|
|
8229
|
+
startLine: startOffset + 1,
|
|
8230
|
+
endLine: endOffset,
|
|
8231
|
+
nextOffset: endOffset < totalLines ? endOffset : void 0
|
|
8232
|
+
};
|
|
8233
|
+
}
|
|
7913
8234
|
/**
|
|
7914
8235
|
* Build a grep command for literal (fixed-string) search.
|
|
7915
8236
|
* Uses grep -rHnF for recursive, with-filename, with-line-number, fixed-string search.
|
|
@@ -7987,15 +8308,17 @@ var BaseSandbox = class {
|
|
|
7987
8308
|
mimeType
|
|
7988
8309
|
};
|
|
7989
8310
|
}
|
|
7990
|
-
|
|
8311
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
8312
|
+
if (normalizedLimit === 0) return {
|
|
7991
8313
|
content: "",
|
|
7992
8314
|
mimeType
|
|
7993
8315
|
};
|
|
7994
|
-
const command = buildReadCommand(filePath,
|
|
8316
|
+
const command = buildReadCommand(filePath, normalizedOffset, normalizedLimit);
|
|
7995
8317
|
const result = await this.execute(command);
|
|
7996
8318
|
if (result.exitCode !== 0) return { error: `File '${filePath}' not found` };
|
|
8319
|
+
const parsed = parseReadOutput(result.output, normalizedOffset, normalizedLimit);
|
|
7997
8320
|
return {
|
|
7998
|
-
content:
|
|
8321
|
+
...result.truncated ? { content: parsed.content } : parsed,
|
|
7999
8322
|
mimeType
|
|
8000
8323
|
};
|
|
8001
8324
|
}
|
|
@@ -8712,6 +9035,12 @@ Object.defineProperty(exports, "isTextMimeType", {
|
|
|
8712
9035
|
return isTextMimeType;
|
|
8713
9036
|
}
|
|
8714
9037
|
});
|
|
9038
|
+
Object.defineProperty(exports, "normalizeReadPagination", {
|
|
9039
|
+
enumerable: true,
|
|
9040
|
+
get: function() {
|
|
9041
|
+
return normalizeReadPagination;
|
|
9042
|
+
}
|
|
9043
|
+
});
|
|
8715
9044
|
Object.defineProperty(exports, "parseHarnessProfileConfig", {
|
|
8716
9045
|
enumerable: true,
|
|
8717
9046
|
get: function() {
|
|
@@ -8743,4 +9072,4 @@ Object.defineProperty(exports, "serializeProfile", {
|
|
|
8743
9072
|
}
|
|
8744
9073
|
});
|
|
8745
9074
|
|
|
8746
|
-
//# sourceMappingURL=langsmith-
|
|
9075
|
+
//# sourceMappingURL=langsmith-Ynj9VKxb.cjs.map
|