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
|
@@ -2,7 +2,7 @@ import { AIMessage, HumanMessage, SystemMessage, ToolMessage, anthropicPromptCac
|
|
|
2
2
|
import { Command, REMOVE_ALL_MESSAGES, ReducedValue, StateSchema, getConfig, getCurrentTaskInput, getStore, isCommand } from "@langchain/langgraph";
|
|
3
3
|
import { z } from "zod/v4";
|
|
4
4
|
import micromatch from "micromatch";
|
|
5
|
-
import { AIMessage as AIMessage$1, HumanMessage as HumanMessage$1, RemoveMessage, getBufferString } from "@langchain/core/messages";
|
|
5
|
+
import { AIMessage as AIMessage$1, HumanMessage as HumanMessage$1, RemoveMessage, SystemMessage as SystemMessage$1, ToolMessage as ToolMessage$1, getBufferString } from "@langchain/core/messages";
|
|
6
6
|
import * as z$2 from "zod";
|
|
7
7
|
import { z as z$1 } from "zod";
|
|
8
8
|
import { ContextOverflowError } from "@langchain/core/errors";
|
|
@@ -23,6 +23,22 @@ const EMPTY_CONTENT_WARNING = "System reminder: File exists but has empty conten
|
|
|
23
23
|
const MAX_LINE_LENGTH = 5e3;
|
|
24
24
|
const TOOL_RESULT_TOKEN_LIMIT = 2e4;
|
|
25
25
|
const TRUNCATION_GUIDANCE = "... [results truncated, try being more specific with your parameters]";
|
|
26
|
+
/**
|
|
27
|
+
* Normalize model- or caller-supplied text pagination bounds.
|
|
28
|
+
*
|
|
29
|
+
* Every backend must slice content and calculate pagination metadata from the
|
|
30
|
+
* same normalized values. Otherwise a fractional or negative argument could
|
|
31
|
+
* return one window while advertising a different `nextOffset`.
|
|
32
|
+
*
|
|
33
|
+
* Binary reads do not use this helper because their backend contract ignores
|
|
34
|
+
* line-based offset and limit values.
|
|
35
|
+
*/
|
|
36
|
+
function normalizeReadPagination(offset, limit) {
|
|
37
|
+
return {
|
|
38
|
+
offset: Number.isFinite(offset) ? Math.max(0, Math.floor(offset)) : 0,
|
|
39
|
+
limit: Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0
|
|
40
|
+
};
|
|
41
|
+
}
|
|
26
42
|
const MIME_TYPES = {
|
|
27
43
|
".png": "image/png",
|
|
28
44
|
".jpg": "image/jpeg",
|
|
@@ -134,40 +150,60 @@ function sanitizeToolCallId(toolCallId) {
|
|
|
134
150
|
return toolCallId.replace(/\./g, "_").replace(/\//g, "_").replace(/\\/g, "_");
|
|
135
151
|
}
|
|
136
152
|
/**
|
|
137
|
-
* Format file content with line numbers
|
|
138
|
-
*
|
|
139
|
-
* Chunks lines longer than MAX_LINE_LENGTH with continuation markers (e.g., 5.1, 5.2).
|
|
153
|
+
* Format file content with line numbers and structured source-line boundaries.
|
|
140
154
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
155
|
+
* The boundaries let downstream size limiting truncate only after complete
|
|
156
|
+
* source lines without reparsing the rendered gutter. This keeps presentation
|
|
157
|
+
* details (padding, tab separators, and continuation labels) encapsulated in
|
|
158
|
+
* the formatter that creates them.
|
|
144
159
|
*/
|
|
145
|
-
function
|
|
160
|
+
function formatContentWithLineNumbersAndBoundaries(content, startLine = 1) {
|
|
146
161
|
let lines;
|
|
147
162
|
if (typeof content === "string") {
|
|
148
163
|
lines = content.split("\n");
|
|
149
164
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
150
165
|
} else lines = content;
|
|
151
166
|
const resultLines = [];
|
|
167
|
+
const sourceLineBoundaries = [];
|
|
168
|
+
let renderedLength = 0;
|
|
169
|
+
const appendRow = (row, sourceLine, completesSourceLine) => {
|
|
170
|
+
if (resultLines.length > 0) renderedLength += 1;
|
|
171
|
+
resultLines.push(row);
|
|
172
|
+
renderedLength += row.length;
|
|
173
|
+
if (completesSourceLine) sourceLineBoundaries.push({
|
|
174
|
+
sourceLine,
|
|
175
|
+
endOffset: renderedLength
|
|
176
|
+
});
|
|
177
|
+
};
|
|
152
178
|
for (let i = 0; i < lines.length; i++) {
|
|
153
179
|
const line = lines[i];
|
|
154
180
|
const lineNum = i + startLine;
|
|
155
|
-
if (line.length <= 5e3)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
resultLines.push(`${continuationMarker.padStart(6)}\t${chunk}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
181
|
+
if (line.length <= 5e3) {
|
|
182
|
+
appendRow(`${lineNum.toString().padStart(6)}\t${line}`, lineNum, true);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const numChunks = Math.ceil(line.length / MAX_LINE_LENGTH);
|
|
186
|
+
for (let chunkIdx = 0; chunkIdx < numChunks; chunkIdx++) {
|
|
187
|
+
const start = chunkIdx * MAX_LINE_LENGTH;
|
|
188
|
+
const end = Math.min(start + MAX_LINE_LENGTH, line.length);
|
|
189
|
+
const chunk = line.substring(start, end);
|
|
190
|
+
appendRow(`${(chunkIdx === 0 ? `${lineNum}` : `${lineNum}.${chunkIdx}`).padStart(6)}\t${chunk}`, lineNum, chunkIdx === numChunks - 1);
|
|
168
191
|
}
|
|
169
192
|
}
|
|
170
|
-
return
|
|
193
|
+
return {
|
|
194
|
+
text: resultLines.join("\n"),
|
|
195
|
+
sourceLineBoundaries
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Format file content with line numbers (cat -n style).
|
|
200
|
+
*
|
|
201
|
+
* Lines longer than `MAX_LINE_LENGTH` are split into continuation rows such as
|
|
202
|
+
* `5.1` and `5.2`. Use `formatContentWithLineNumbersAndBoundaries` when a
|
|
203
|
+
* caller also needs safe source-line truncation points.
|
|
204
|
+
*/
|
|
205
|
+
function formatContentWithLineNumbers(content, startLine = 1) {
|
|
206
|
+
return formatContentWithLineNumbersAndBoundaries(content, startLine).text;
|
|
171
207
|
}
|
|
172
208
|
/**
|
|
173
209
|
* Check if content is empty and return warning message.
|
|
@@ -841,10 +877,23 @@ var StateBackend = class {
|
|
|
841
877
|
mimeType: fileDataV2.mimeType
|
|
842
878
|
};
|
|
843
879
|
if (typeof fileDataV2.content !== "string") return { error: `File '${filePath}' has binary content but text MIME type` };
|
|
844
|
-
|
|
845
|
-
|
|
880
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
881
|
+
const lines = fileDataV2.content.split("\n");
|
|
882
|
+
const totalLines = lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
883
|
+
const selected = lines.slice(normalizedOffset, normalizedOffset + normalizedLimit);
|
|
884
|
+
if (selected.length === 0 || normalizedOffset >= totalLines || normalizedLimit === 0) return {
|
|
885
|
+
content: selected.join("\n"),
|
|
846
886
|
mimeType: fileDataV2.mimeType
|
|
847
887
|
};
|
|
888
|
+
const endOffset = Math.min(normalizedOffset + selected.length, totalLines);
|
|
889
|
+
return {
|
|
890
|
+
content: selected.join("\n"),
|
|
891
|
+
mimeType: fileDataV2.mimeType,
|
|
892
|
+
totalLines,
|
|
893
|
+
startLine: normalizedOffset + 1,
|
|
894
|
+
endLine: endOffset,
|
|
895
|
+
nextOffset: endOffset < totalLines ? endOffset : void 0
|
|
896
|
+
};
|
|
848
897
|
}
|
|
849
898
|
/**
|
|
850
899
|
* Read file content as raw FileData.
|
|
@@ -1560,6 +1609,73 @@ const READ_FILE_TRUNCATION_MSG = `
|
|
|
1560
1609
|
|
|
1561
1610
|
[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.]`;
|
|
1562
1611
|
/**
|
|
1612
|
+
* Render backend pagination metadata as guidance for the model.
|
|
1613
|
+
*
|
|
1614
|
+
* Backends own source-level pagination because only they know how much of the
|
|
1615
|
+
* file was read. The middleware owns presentation: it line-numbers the text
|
|
1616
|
+
* and turns optional metadata into a human-readable footer. Keeping the fields
|
|
1617
|
+
* optional preserves compatibility with custom backends that predate this
|
|
1618
|
+
* contract; those reads simply receive no pagination footer.
|
|
1619
|
+
*
|
|
1620
|
+
* `nextOffset` is the signal that the read is partial. A result at EOF omits it,
|
|
1621
|
+
* so complete reads retain their previous output shape.
|
|
1622
|
+
*/
|
|
1623
|
+
function remainingLinesNotice(readResult) {
|
|
1624
|
+
const { startLine, endLine, nextOffset, totalLines } = readResult;
|
|
1625
|
+
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 "";
|
|
1626
|
+
const readCount = endLine - startLine + 1;
|
|
1627
|
+
const readUnit = readCount === 1 ? "line" : "lines";
|
|
1628
|
+
if (totalLines === void 0) return `\n\n[Read ${readCount} ${readUnit} (lines ${startLine}-${endLine}). More lines remain from offset ${nextOffset}.]`;
|
|
1629
|
+
if (endLine >= totalLines) return "";
|
|
1630
|
+
const remaining = totalLines - endLine;
|
|
1631
|
+
return `\n\n[Read ${readCount} ${readUnit} (lines ${startLine}-${endLine} of ${totalLines} total). ${remaining} ${remaining === 1 ? "line" : "lines"} remaining from offset ${nextOffset}.]`;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Fit a line-numbered read into the middleware's output budget without
|
|
1635
|
+
* publishing a resume offset that skips content the model did not see.
|
|
1636
|
+
*
|
|
1637
|
+
* There are two independent forms of limiting:
|
|
1638
|
+
*
|
|
1639
|
+
* 1. The backend paginates the source file with `offset` and `limit`.
|
|
1640
|
+
* 2. The middleware may further shorten that page to fit its token budget.
|
|
1641
|
+
*
|
|
1642
|
+
* If the backend returned lines 1-100 but the middleware only displayed lines
|
|
1643
|
+
* 1-30, forwarding the backend's original `nextOffset: 100` would silently skip
|
|
1644
|
+
* lines 31-100 on the next read. This function therefore truncates only after a
|
|
1645
|
+
* complete source line and rebuilds the remaining-lines notice using the last
|
|
1646
|
+
* line actually displayed.
|
|
1647
|
+
*
|
|
1648
|
+
* Long source lines may occupy several formatted rows (`12`, `12.1`, ...). The
|
|
1649
|
+
* formatter records a structured boundary only after the final chunk, so this
|
|
1650
|
+
* function does not need to inspect or understand the gutter representation.
|
|
1651
|
+
* If no complete source line can fit beside the truncation message, the function
|
|
1652
|
+
* falls back to character truncation and omits pagination guidance rather than
|
|
1653
|
+
* advertising an unsafe offset.
|
|
1654
|
+
*/
|
|
1655
|
+
function truncatePaginatedRead(formatted, filePath, readResult, tokenLimit) {
|
|
1656
|
+
const content = formatted.text;
|
|
1657
|
+
const notice = remainingLinesNotice(readResult);
|
|
1658
|
+
if (!tokenLimit || content.length + notice.length < 4 * tokenLimit) return content + notice;
|
|
1659
|
+
const truncationMsg = READ_FILE_TRUNCATION_MSG.replace("{file_path}", filePath);
|
|
1660
|
+
const threshold = 4 * tokenLimit;
|
|
1661
|
+
if (readResult.startLine !== void 0 && readResult.endLine !== void 0) {
|
|
1662
|
+
const finalSourceLine = readResult.endLine;
|
|
1663
|
+
const boundaries = formatted.sourceLineBoundaries.filter((boundary) => boundary.sourceLine <= finalSourceLine);
|
|
1664
|
+
for (let index = boundaries.length - 1; index >= 0; index -= 1) {
|
|
1665
|
+
const boundary = boundaries[index];
|
|
1666
|
+
const adjustedNotice = remainingLinesNotice({
|
|
1667
|
+
totalLines: readResult.totalLines,
|
|
1668
|
+
startLine: readResult.startLine,
|
|
1669
|
+
endLine: boundary.sourceLine,
|
|
1670
|
+
nextOffset: boundary.sourceLine
|
|
1671
|
+
});
|
|
1672
|
+
if (boundary.endOffset + truncationMsg.length + adjustedNotice.length <= threshold) return content.slice(0, boundary.endOffset) + truncationMsg + adjustedNotice;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
const maxContentLength = Math.max(0, threshold - truncationMsg.length);
|
|
1676
|
+
return content.substring(0, maxContentLength) + truncationMsg;
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1563
1679
|
* Note appended to grep results that were cut short by the match-count cap.
|
|
1564
1680
|
*/
|
|
1565
1681
|
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.";
|
|
@@ -2061,7 +2177,8 @@ function createReadFileTool(backend, options) {
|
|
|
2061
2177
|
const permissionError = checkPermission(permissions, "read", input.file_path);
|
|
2062
2178
|
if (permissionError !== void 0) return toolError(runtime, "read_file", permissionError);
|
|
2063
2179
|
const resolvedBackend = await resolveBackend(backend, runtime);
|
|
2064
|
-
const { file_path, offset = 0, limit = 100 } = input;
|
|
2180
|
+
const { file_path, offset: requestedOffset = 0, limit: requestedLimit = 100 } = input;
|
|
2181
|
+
const { offset, limit } = normalizeReadPagination(requestedOffset, requestedLimit);
|
|
2065
2182
|
const readResult = await resolvedBackend.read(file_path, offset, limit);
|
|
2066
2183
|
if (readResult.error) return [{
|
|
2067
2184
|
type: "text",
|
|
@@ -2109,16 +2226,21 @@ function createReadFileTool(backend, options) {
|
|
|
2109
2226
|
}
|
|
2110
2227
|
let content = typeof readResult.content === "string" ? readResult.content : "";
|
|
2111
2228
|
const lines = content.split("\n");
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2229
|
+
let paginationResult = readResult;
|
|
2230
|
+
if (lines.length > limit) {
|
|
2231
|
+
content = lines.slice(0, limit).join("\n");
|
|
2232
|
+
if (limit > 0 && readResult.startLine !== void 0 && readResult.endLine !== void 0) {
|
|
2233
|
+
const endLine = Math.min(readResult.startLine + limit - 1, readResult.endLine, readResult.totalLines ?? Number.POSITIVE_INFINITY);
|
|
2234
|
+
paginationResult = {
|
|
2235
|
+
...readResult,
|
|
2236
|
+
endLine,
|
|
2237
|
+
nextOffset: endLine
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2118
2240
|
}
|
|
2119
2241
|
return [{
|
|
2120
2242
|
type: "text",
|
|
2121
|
-
text:
|
|
2243
|
+
text: truncatePaginatedRead(formatContentWithLineNumbersAndBoundaries(content, paginationResult.startLine ?? offset + 1), file_path, paginationResult, toolTokenLimitBeforeEvict)
|
|
2122
2244
|
}];
|
|
2123
2245
|
}, {
|
|
2124
2246
|
name: "read_file",
|
|
@@ -3236,6 +3358,83 @@ function createSummarizationMiddleware(options) {
|
|
|
3236
3358
|
});
|
|
3237
3359
|
}
|
|
3238
3360
|
//#endregion
|
|
3361
|
+
//#region src/middleware/utils.ts
|
|
3362
|
+
/**
|
|
3363
|
+
* Utility functions for middleware.
|
|
3364
|
+
*
|
|
3365
|
+
* This module provides shared helpers used across middleware implementations.
|
|
3366
|
+
*/
|
|
3367
|
+
/**
|
|
3368
|
+
* Merge custom middleware into an assembled stack by `.name`.
|
|
3369
|
+
*
|
|
3370
|
+
* Matching custom middleware replaces the existing entry in place. New
|
|
3371
|
+
* middleware is appended after the base stack in caller-provided order.
|
|
3372
|
+
*/
|
|
3373
|
+
function mergeMiddleware$1(base, custom) {
|
|
3374
|
+
const merged = new Map(base.map((middleware) => [middleware.name, middleware]));
|
|
3375
|
+
for (const middleware of custom) merged.set(middleware.name, middleware);
|
|
3376
|
+
return [...merged.values()];
|
|
3377
|
+
}
|
|
3378
|
+
function middlewareNames(middleware) {
|
|
3379
|
+
return new Set(middleware.map((entry) => entry.name));
|
|
3380
|
+
}
|
|
3381
|
+
function matchingMiddleware(middleware, names) {
|
|
3382
|
+
return middleware.filter((entry) => names.has(entry.name));
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Merge custom middleware into default and tail middleware segments.
|
|
3386
|
+
*
|
|
3387
|
+
* Same-name custom entries replace matching defaults in either segment. Novel
|
|
3388
|
+
* custom entries are inserted between the default and tail segments unless
|
|
3389
|
+
* `appendNew` is false.
|
|
3390
|
+
*/
|
|
3391
|
+
function mergeMiddlewareStack(defaultMiddleware, customMiddleware, tailMiddleware = [], options = {}) {
|
|
3392
|
+
const defaultMiddlewareNames = middlewareNames(defaultMiddleware);
|
|
3393
|
+
const tailMiddlewareNames = middlewareNames(tailMiddleware);
|
|
3394
|
+
const knownMiddlewareNames = /* @__PURE__ */ new Set([...defaultMiddlewareNames, ...tailMiddlewareNames]);
|
|
3395
|
+
const novelMiddleware = options.appendNew === false ? [] : customMiddleware.filter((entry) => !knownMiddlewareNames.has(entry.name));
|
|
3396
|
+
return [
|
|
3397
|
+
...mergeMiddleware$1(defaultMiddleware, matchingMiddleware(customMiddleware, defaultMiddlewareNames)),
|
|
3398
|
+
...novelMiddleware,
|
|
3399
|
+
...mergeMiddleware$1(tailMiddleware, matchingMiddleware(customMiddleware, tailMiddlewareNames))
|
|
3400
|
+
];
|
|
3401
|
+
}
|
|
3402
|
+
/**
|
|
3403
|
+
* Append text to a system message.
|
|
3404
|
+
*
|
|
3405
|
+
* Creates a new SystemMessage with the text appended to the existing content.
|
|
3406
|
+
* If the original message has content, the new text is separated by two newlines.
|
|
3407
|
+
*
|
|
3408
|
+
* @param systemMessage - Existing system message or null/undefined.
|
|
3409
|
+
* @param text - Text to add to the system message.
|
|
3410
|
+
* @returns New SystemMessage with the text appended.
|
|
3411
|
+
*
|
|
3412
|
+
* @example
|
|
3413
|
+
* ```typescript
|
|
3414
|
+
* const original = new SystemMessage({ content: "You are a helpful assistant." });
|
|
3415
|
+
* const updated = appendToSystemMessage(original, "Always be concise.");
|
|
3416
|
+
* // Result: SystemMessage with content "You are a helpful assistant.\n\nAlways be concise."
|
|
3417
|
+
* ```
|
|
3418
|
+
*/
|
|
3419
|
+
function appendToSystemMessage(systemMessage, text) {
|
|
3420
|
+
if (!systemMessage) return new SystemMessage$1({ content: text });
|
|
3421
|
+
const existingContent = systemMessage.content;
|
|
3422
|
+
if (typeof existingContent === "string") {
|
|
3423
|
+
const newContent = existingContent ? `${existingContent}\n\n${text}` : text;
|
|
3424
|
+
return new SystemMessage$1({ content: newContent });
|
|
3425
|
+
}
|
|
3426
|
+
if (Array.isArray(existingContent)) {
|
|
3427
|
+
const newContent = [...existingContent];
|
|
3428
|
+
const textToAdd = newContent.length > 0 ? `\n\n${text}` : text;
|
|
3429
|
+
newContent.push({
|
|
3430
|
+
type: "text",
|
|
3431
|
+
text: textToAdd
|
|
3432
|
+
});
|
|
3433
|
+
return new SystemMessage$1({ content: newContent });
|
|
3434
|
+
}
|
|
3435
|
+
return new SystemMessage$1({ content: text });
|
|
3436
|
+
}
|
|
3437
|
+
//#endregion
|
|
3239
3438
|
//#region src/middleware/subagents.ts
|
|
3240
3439
|
/**
|
|
3241
3440
|
* Config key used by task-tool callers to request dynamic response format.
|
|
@@ -3249,6 +3448,8 @@ const SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY = "__deepagents_subagent_response_form
|
|
|
3249
3448
|
* Provides a minimal base prompt that can be extended by specific subagent configurations.
|
|
3250
3449
|
*/
|
|
3251
3450
|
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.";
|
|
3451
|
+
const FORKED_CONTEXT_KEY = "_deepagentsForkedContext";
|
|
3452
|
+
const FORK_RECURSION_REFUSAL = "You are a subagent and cannot delegate to another subagent. Complete this task yourself instead of calling this tool again.";
|
|
3252
3453
|
/**
|
|
3253
3454
|
* State keys excluded when passing state to subagents and when returning
|
|
3254
3455
|
* updates from subagents. Summarization keys are excluded because their
|
|
@@ -3261,6 +3462,18 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
3261
3462
|
"skillsMetadata",
|
|
3262
3463
|
"memoryContents",
|
|
3263
3464
|
"_summarizationEvent",
|
|
3465
|
+
"_summarizationSessionId",
|
|
3466
|
+
FORKED_CONTEXT_KEY
|
|
3467
|
+
];
|
|
3468
|
+
/**
|
|
3469
|
+
* State keys excluded when inheriting state into a declarative fork.
|
|
3470
|
+
* Narrower than `EXCLUDED_STATE_KEYS`: a fork's mirrored middleware needs
|
|
3471
|
+
* the parent's private channels (skills metadata, memory contents, etc.)
|
|
3472
|
+
* to rebuild an equivalent prompt.
|
|
3473
|
+
*/
|
|
3474
|
+
const FORK_EXCLUDED_STATE_KEYS = [
|
|
3475
|
+
"structuredResponse",
|
|
3476
|
+
"_summarizationEvent",
|
|
3264
3477
|
"_summarizationSessionId"
|
|
3265
3478
|
];
|
|
3266
3479
|
/**
|
|
@@ -3270,25 +3483,37 @@ const EXCLUDED_STATE_KEYS = [
|
|
|
3270
3483
|
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.";
|
|
3271
3484
|
function getTaskToolDescription(subagentDescriptions) {
|
|
3272
3485
|
return context`
|
|
3273
|
-
Launch an ephemeral subagent to handle a complex, multi-step task
|
|
3486
|
+
Launch an ephemeral subagent to handle a complex, multi-step task.
|
|
3274
3487
|
|
|
3275
3488
|
Available agent types and the tools they have access to:
|
|
3276
3489
|
${subagentDescriptions.join("\n")}
|
|
3277
3490
|
|
|
3278
3491
|
Specify subagent_type to select the agent. Usage notes:
|
|
3279
3492
|
- Launch multiple agents concurrently when their tasks are independent, using a single message with multiple tool calls.
|
|
3280
|
-
- 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.
|
|
3493
|
+
- 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.
|
|
3281
3494
|
- The agent's report is not shown to the user; relay a summary yourself.
|
|
3282
|
-
- Tell the agent whether to create content, analyze, or only research, since it
|
|
3495
|
+
- 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.
|
|
3283
3496
|
- If an agent's description says to use it proactively, do so without waiting to be asked.
|
|
3284
3497
|
- When only general-purpose is available, use it for any complex, context-heavy task; it has the same capabilities as the main agent.
|
|
3285
3498
|
`;
|
|
3286
3499
|
}
|
|
3500
|
+
const FORKED_SUBAGENT_TOOL_NOTE = " (inherits your full conversation and system prompt — no need to restate context here)";
|
|
3501
|
+
const COMPILED_FORKED_SUBAGENT_TOOL_NOTE = " (inherits your conversation history — its system prompt is fixed in its own runnable)";
|
|
3502
|
+
/** Render one subagent's listing line for the task tool description. */
|
|
3503
|
+
function describeSubagentForTool(name, description, forked, compiled = false) {
|
|
3504
|
+
return `- ${name}: ${description}${forked ? compiled ? COMPILED_FORKED_SUBAGENT_TOOL_NOTE : FORKED_SUBAGENT_TOOL_NOTE : ""}`;
|
|
3505
|
+
}
|
|
3506
|
+
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";
|
|
3507
|
+
/**
|
|
3508
|
+
* Whether a declarative subagent spec has `mode: "fork"` set.
|
|
3509
|
+
*
|
|
3510
|
+
* A plain boolean, not a type predicate: `SubAgent` covers both `"fork"` and
|
|
3511
|
+
* `"isolated"`, so there's no distinct type left to narrow to.
|
|
3512
|
+
*/
|
|
3287
3513
|
function isForkedSubAgent(value) {
|
|
3288
3514
|
if (typeof value !== "object" || value == null) return false;
|
|
3289
3515
|
if (!("mode" in value)) return false;
|
|
3290
|
-
|
|
3291
|
-
return true;
|
|
3516
|
+
return value.mode === "fork";
|
|
3292
3517
|
}
|
|
3293
3518
|
/**
|
|
3294
3519
|
* Base specification for the general-purpose subagent.
|
|
@@ -3331,15 +3556,26 @@ const GENERAL_PURPOSE_SUBAGENT = {
|
|
|
3331
3556
|
name: "general-purpose",
|
|
3332
3557
|
description: DEFAULT_GENERAL_PURPOSE_DESCRIPTION,
|
|
3333
3558
|
systemPrompt: DEFAULT_SUBAGENT_PROMPT,
|
|
3334
|
-
mode: "
|
|
3559
|
+
mode: "isolated"
|
|
3335
3560
|
};
|
|
3561
|
+
function filterState(state, excludedKeys) {
|
|
3562
|
+
const filtered = {};
|
|
3563
|
+
for (const [key, value] of Object.entries(state)) if (!excludedKeys.includes(key)) filtered[key] = value;
|
|
3564
|
+
return filtered;
|
|
3565
|
+
}
|
|
3336
3566
|
/**
|
|
3337
3567
|
* Filter state to exclude certain keys when passing to subagents
|
|
3338
3568
|
*/
|
|
3339
3569
|
function filterStateForSubagent(state) {
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3570
|
+
return filterState(state, EXCLUDED_STATE_KEYS);
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Filter state to exclude only the keys a declarative fork must not resume
|
|
3574
|
+
* (structured response, summarization event/session) — see
|
|
3575
|
+
* `FORK_EXCLUDED_STATE_KEYS`.
|
|
3576
|
+
*/
|
|
3577
|
+
function filterStateForFork(state) {
|
|
3578
|
+
return filterState(state, FORK_EXCLUDED_STATE_KEYS);
|
|
3343
3579
|
}
|
|
3344
3580
|
/**
|
|
3345
3581
|
* Invalid tool message block types
|
|
@@ -3383,6 +3619,15 @@ function stripInFlightAIMessage(messages) {
|
|
|
3383
3619
|
const last = messages.at(-1);
|
|
3384
3620
|
return AIMessage$1.isInstance(last) && (last.tool_calls?.length ?? 0) > 0 ? messages.slice(0, -1) : messages;
|
|
3385
3621
|
}
|
|
3622
|
+
const ForkedContextStateSchema = z.object({ [FORKED_CONTEXT_KEY]: z.boolean().optional() });
|
|
3623
|
+
function createForkTaskToolMiddleware(taskTool) {
|
|
3624
|
+
return createMiddleware({
|
|
3625
|
+
name: "forkTaskToolMiddleware",
|
|
3626
|
+
stateSchema: ForkedContextStateSchema,
|
|
3627
|
+
tools: [taskTool],
|
|
3628
|
+
beforeAgent: () => ({ [FORKED_CONTEXT_KEY]: true })
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3386
3631
|
/**
|
|
3387
3632
|
* Create a runnable agent from a declarative `SubAgent` spec.
|
|
3388
3633
|
*
|
|
@@ -3412,6 +3657,17 @@ function createSubAgent(spec, options) {
|
|
|
3412
3657
|
});
|
|
3413
3658
|
}
|
|
3414
3659
|
/**
|
|
3660
|
+
* Resolve a fork's system prompt: the parent's inherited prompt, with the
|
|
3661
|
+
* fork's own systemPrompt (if any) appended as an addendum rather than
|
|
3662
|
+
* replacing it.
|
|
3663
|
+
*/
|
|
3664
|
+
function resolveForkSystemPrompt(parentSystemPrompt, forkAddendum) {
|
|
3665
|
+
if (!forkAddendum) return parentSystemPrompt ?? "";
|
|
3666
|
+
const addendumText = typeof forkAddendum === "string" ? forkAddendum : forkAddendum.text;
|
|
3667
|
+
if (SystemMessage.isInstance(parentSystemPrompt)) return appendToSystemMessage(parentSystemPrompt, addendumText);
|
|
3668
|
+
return parentSystemPrompt ? `${parentSystemPrompt}\n\n${addendumText}` : addendumText;
|
|
3669
|
+
}
|
|
3670
|
+
/**
|
|
3415
3671
|
* Create subagent instances from specifications.
|
|
3416
3672
|
*
|
|
3417
3673
|
* Returns compiled agents, raw specs keyed by name (for on-demand
|
|
@@ -3419,13 +3675,18 @@ function createSubAgent(spec, options) {
|
|
|
3419
3675
|
* of names that should fork the parent's conversation.
|
|
3420
3676
|
*/
|
|
3421
3677
|
function getSubagents(options) {
|
|
3422
|
-
const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware: gpMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, parentSystemPrompt = null } = options;
|
|
3678
|
+
const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware: gpMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, parentSystemPrompt = null, mirroredTaskTool } = options;
|
|
3423
3679
|
const defaultSubagentMiddleware = defaultMiddleware || [];
|
|
3424
3680
|
const generalPurposeMiddlewareBase = gpMiddleware || defaultSubagentMiddleware;
|
|
3425
3681
|
const agents = {};
|
|
3426
3682
|
const specsByName = {};
|
|
3427
3683
|
const subagentDescriptions = [];
|
|
3428
3684
|
const forkModeNames = /* @__PURE__ */ new Set();
|
|
3685
|
+
const seenNames = new Set(generalPurposeAgent ? ["general-purpose"] : []);
|
|
3686
|
+
for (const agentParams of subagents) {
|
|
3687
|
+
if (seenNames.has(agentParams.name)) throw new Error(`Duplicate subagent name '${agentParams.name}'; each subagent must have a unique name.`);
|
|
3688
|
+
seenNames.add(agentParams.name);
|
|
3689
|
+
}
|
|
3429
3690
|
if (generalPurposeAgent) {
|
|
3430
3691
|
const generalPurposeMiddleware = [...generalPurposeMiddlewareBase];
|
|
3431
3692
|
if (defaultInterruptOn) generalPurposeMiddleware.push(humanInTheLoopMiddleware({ interruptOn: defaultInterruptOn }));
|
|
@@ -3439,24 +3700,34 @@ function getSubagents(options) {
|
|
|
3439
3700
|
};
|
|
3440
3701
|
agents["general-purpose"] = createSubAgent(gpSpec);
|
|
3441
3702
|
specsByName["general-purpose"] = gpSpec;
|
|
3442
|
-
subagentDescriptions.push(
|
|
3703
|
+
subagentDescriptions.push(describeSubagentForTool("general-purpose", DEFAULT_GENERAL_PURPOSE_DESCRIPTION, false));
|
|
3443
3704
|
}
|
|
3444
3705
|
for (const agentParams of subagents) {
|
|
3445
3706
|
const rawMode = agentParams.mode;
|
|
3446
|
-
if (rawMode != null && rawMode !== "
|
|
3447
|
-
|
|
3707
|
+
if (rawMode != null && rawMode !== "isolated" && rawMode !== "fork" && rawMode !== "handoff") throw new Error(`SubAgent '${agentParams.name}' has invalid mode '${rawMode}' — must be "isolated" or "fork".`);
|
|
3708
|
+
const forked = isForkedSubAgent(agentParams);
|
|
3709
|
+
const compiled = "runnable" in agentParams;
|
|
3710
|
+
subagentDescriptions.push(describeSubagentForTool(agentParams.name, agentParams.description, forked, compiled));
|
|
3448
3711
|
if ("runnable" in agentParams) {
|
|
3449
3712
|
agents[agentParams.name] = agentParams.runnable;
|
|
3450
3713
|
specsByName[agentParams.name] = agentParams;
|
|
3451
|
-
if (
|
|
3452
|
-
|
|
3714
|
+
if (forked) forkModeNames.add(agentParams.name);
|
|
3715
|
+
continue;
|
|
3716
|
+
}
|
|
3717
|
+
const subagentMiddleware = [...defaultSubagentMiddleware, ...agentParams.middleware ?? []];
|
|
3718
|
+
if (forked) {
|
|
3719
|
+
const rawSkills = agentParams.skills;
|
|
3720
|
+
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.`);
|
|
3721
|
+
const resolvedSystemPrompt = resolveForkSystemPrompt(parentSystemPrompt, agentParams.systemPrompt);
|
|
3722
|
+
const fsIndex = subagentMiddleware.findIndex((m) => m.name === "FilesystemMiddleware");
|
|
3723
|
+
subagentMiddleware.splice(fsIndex + 1, 0, createForkTaskToolMiddleware(mirroredTaskTool));
|
|
3453
3724
|
const resolvedSpec = {
|
|
3454
3725
|
...agentParams,
|
|
3455
|
-
systemPrompt:
|
|
3726
|
+
systemPrompt: resolvedSystemPrompt,
|
|
3456
3727
|
mode: void 0,
|
|
3457
3728
|
model: agentParams.model ?? defaultModel,
|
|
3458
3729
|
tools: agentParams.tools ?? defaultTools,
|
|
3459
|
-
middleware:
|
|
3730
|
+
middleware: subagentMiddleware,
|
|
3460
3731
|
interruptOn: agentParams.interruptOn ?? defaultInterruptOn ?? void 0
|
|
3461
3732
|
};
|
|
3462
3733
|
agents[agentParams.name] = createSubAgent(resolvedSpec);
|
|
@@ -3465,10 +3736,10 @@ function getSubagents(options) {
|
|
|
3465
3736
|
} else {
|
|
3466
3737
|
const resolvedSpec = {
|
|
3467
3738
|
...agentParams,
|
|
3468
|
-
mode: "
|
|
3739
|
+
mode: "isolated",
|
|
3469
3740
|
model: agentParams.model ?? defaultModel,
|
|
3470
3741
|
tools: agentParams.tools ?? defaultTools,
|
|
3471
|
-
middleware:
|
|
3742
|
+
middleware: subagentMiddleware,
|
|
3472
3743
|
interruptOn: agentParams.interruptOn ?? defaultInterruptOn ?? void 0
|
|
3473
3744
|
};
|
|
3474
3745
|
agents[agentParams.name] = createSubAgent(resolvedSpec);
|
|
@@ -3487,16 +3758,12 @@ function getSubagents(options) {
|
|
|
3487
3758
|
*/
|
|
3488
3759
|
function createTaskTool(options) {
|
|
3489
3760
|
const { defaultModel, defaultTools, defaultMiddleware, generalPurposeMiddleware, defaultInterruptOn, subagents, generalPurposeAgent, taskDescription, parentSystemPrompt = null } = options;
|
|
3490
|
-
const
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
subagents,
|
|
3497
|
-
generalPurposeAgent,
|
|
3498
|
-
parentSystemPrompt
|
|
3499
|
-
});
|
|
3761
|
+
const subagentNames = [...generalPurposeAgent ? ["general-purpose"] : [], ...subagents.map((spec) => spec.name)];
|
|
3762
|
+
const subagentDescriptions = [...generalPurposeAgent ? [describeSubagentForTool("general-purpose", DEFAULT_GENERAL_PURPOSE_DESCRIPTION, false)] : [], ...subagents.map((spec) => describeSubagentForTool(spec.name, spec.description, isForkedSubAgent(spec), "runnable" in spec))];
|
|
3763
|
+
const finalTaskDescription = taskDescription ? taskDescription : getTaskToolDescription(subagentDescriptions);
|
|
3764
|
+
let subagentGraphs = {};
|
|
3765
|
+
let specsByName = {};
|
|
3766
|
+
let forkModeNames = /* @__PURE__ */ new Set();
|
|
3500
3767
|
function selectSubagent(subagentType, config) {
|
|
3501
3768
|
const spec = specsByName[subagentType];
|
|
3502
3769
|
const responseFormat = config.configurable?.[SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY];
|
|
@@ -3504,18 +3771,19 @@ function createTaskTool(options) {
|
|
|
3504
3771
|
if ("runnable" in spec || responseFormat == null) return subagentGraphs[subagentType];
|
|
3505
3772
|
return createSubAgent(spec, { responseFormat });
|
|
3506
3773
|
}
|
|
3507
|
-
|
|
3508
|
-
return tool(async (input, config) => {
|
|
3774
|
+
async function runTask(input, config) {
|
|
3509
3775
|
const { description, subagent_type } = input;
|
|
3776
|
+
const currentState = getCurrentTaskInput();
|
|
3777
|
+
if (currentState[FORKED_CONTEXT_KEY]) return FORK_RECURSION_REFUSAL;
|
|
3510
3778
|
if (!(subagent_type in subagentGraphs)) {
|
|
3511
3779
|
const allowedTypes = Object.keys(subagentGraphs).map((k) => `\`${k}\``).join(", ");
|
|
3512
3780
|
throw new Error(`Error: invoked agent of type ${subagent_type}, the only allowed types are ${allowedTypes}`);
|
|
3513
3781
|
}
|
|
3514
3782
|
const shouldFork = forkModeNames.has(subagent_type);
|
|
3515
3783
|
const subagent = selectSubagent(subagent_type, config);
|
|
3516
|
-
const
|
|
3517
|
-
const subagentState = filterStateForSubagent(currentState);
|
|
3518
|
-
if (shouldFork) subagentState.messages = [...getEffectiveMessages(stripInFlightAIMessage(currentState.messages ?? []), currentState), new HumanMessage$1({ content: description })];
|
|
3784
|
+
const spec = specsByName[subagent_type];
|
|
3785
|
+
const subagentState = shouldFork && !("runnable" in spec) ? filterStateForFork(currentState) : filterStateForSubagent(currentState);
|
|
3786
|
+
if (shouldFork) subagentState.messages = [...getEffectiveMessages(stripInFlightAIMessage(currentState.messages ?? []), currentState), new HumanMessage$1({ content: FORK_TASK_PREAMBLE + description })];
|
|
3519
3787
|
else subagentState.messages = [new HumanMessage$1({ content: description })];
|
|
3520
3788
|
subagentState._summarizationSessionId = `session_${crypto.randomUUID().substring(0, 8)}`;
|
|
3521
3789
|
const subagentConfig = {
|
|
@@ -3542,14 +3810,35 @@ function createTaskTool(options) {
|
|
|
3542
3810
|
return content;
|
|
3543
3811
|
}
|
|
3544
3812
|
return returnCommandWithStateUpdate(result, config.toolCall.id);
|
|
3545
|
-
}
|
|
3813
|
+
}
|
|
3814
|
+
const taskToolSchema = z.object({
|
|
3815
|
+
description: z.string().describe("The task to execute with the selected agent"),
|
|
3816
|
+
subagent_type: z.string().describe(`Name of the agent to use. Available: ${subagentNames.join(", ")}`)
|
|
3817
|
+
});
|
|
3818
|
+
const taskTool = tool(runTask, {
|
|
3546
3819
|
name: "task",
|
|
3547
3820
|
description: finalTaskDescription,
|
|
3548
|
-
schema:
|
|
3549
|
-
|
|
3550
|
-
|
|
3821
|
+
schema: taskToolSchema
|
|
3822
|
+
});
|
|
3823
|
+
const { agents, specsByName: resolvedSpecsByName, forkModeNames: resolvedForkModeNames } = getSubagents({
|
|
3824
|
+
defaultModel,
|
|
3825
|
+
defaultTools,
|
|
3826
|
+
defaultMiddleware,
|
|
3827
|
+
generalPurposeMiddleware,
|
|
3828
|
+
defaultInterruptOn,
|
|
3829
|
+
subagents,
|
|
3830
|
+
generalPurposeAgent,
|
|
3831
|
+
parentSystemPrompt,
|
|
3832
|
+
mirroredTaskTool: tool(runTask, {
|
|
3833
|
+
name: "task",
|
|
3834
|
+
description: finalTaskDescription,
|
|
3835
|
+
schema: taskToolSchema
|
|
3551
3836
|
})
|
|
3552
3837
|
});
|
|
3838
|
+
subagentGraphs = agents;
|
|
3839
|
+
specsByName = resolvedSpecsByName;
|
|
3840
|
+
forkModeNames = resolvedForkModeNames;
|
|
3841
|
+
return taskTool;
|
|
3553
3842
|
}
|
|
3554
3843
|
/**
|
|
3555
3844
|
* Create subagent middleware with task tool
|
|
@@ -4551,43 +4840,6 @@ function createSkillsMiddleware(options) {
|
|
|
4551
4840
|
});
|
|
4552
4841
|
}
|
|
4553
4842
|
//#endregion
|
|
4554
|
-
//#region src/middleware/utils.ts
|
|
4555
|
-
/**
|
|
4556
|
-
* Merge custom middleware into an assembled stack by `.name`.
|
|
4557
|
-
*
|
|
4558
|
-
* Matching custom middleware replaces the existing entry in place. New
|
|
4559
|
-
* middleware is appended after the base stack in caller-provided order.
|
|
4560
|
-
*/
|
|
4561
|
-
function mergeMiddleware$1(base, custom) {
|
|
4562
|
-
const merged = new Map(base.map((middleware) => [middleware.name, middleware]));
|
|
4563
|
-
for (const middleware of custom) merged.set(middleware.name, middleware);
|
|
4564
|
-
return [...merged.values()];
|
|
4565
|
-
}
|
|
4566
|
-
function middlewareNames(middleware) {
|
|
4567
|
-
return new Set(middleware.map((entry) => entry.name));
|
|
4568
|
-
}
|
|
4569
|
-
function matchingMiddleware(middleware, names) {
|
|
4570
|
-
return middleware.filter((entry) => names.has(entry.name));
|
|
4571
|
-
}
|
|
4572
|
-
/**
|
|
4573
|
-
* Merge custom middleware into default and tail middleware segments.
|
|
4574
|
-
*
|
|
4575
|
-
* Same-name custom entries replace matching defaults in either segment. Novel
|
|
4576
|
-
* custom entries are inserted between the default and tail segments unless
|
|
4577
|
-
* `appendNew` is false.
|
|
4578
|
-
*/
|
|
4579
|
-
function mergeMiddlewareStack(defaultMiddleware, customMiddleware, tailMiddleware = [], options = {}) {
|
|
4580
|
-
const defaultMiddlewareNames = middlewareNames(defaultMiddleware);
|
|
4581
|
-
const tailMiddlewareNames = middlewareNames(tailMiddleware);
|
|
4582
|
-
const knownMiddlewareNames = /* @__PURE__ */ new Set([...defaultMiddlewareNames, ...tailMiddlewareNames]);
|
|
4583
|
-
const novelMiddleware = options.appendNew === false ? [] : customMiddleware.filter((entry) => !knownMiddlewareNames.has(entry.name));
|
|
4584
|
-
return [
|
|
4585
|
-
...mergeMiddleware$1(defaultMiddleware, matchingMiddleware(customMiddleware, defaultMiddlewareNames)),
|
|
4586
|
-
...novelMiddleware,
|
|
4587
|
-
...mergeMiddleware$1(tailMiddleware, matchingMiddleware(customMiddleware, tailMiddlewareNames))
|
|
4588
|
-
];
|
|
4589
|
-
}
|
|
4590
|
-
//#endregion
|
|
4591
4843
|
//#region src/middleware/completion_callback.ts
|
|
4592
4844
|
/**
|
|
4593
4845
|
* Callback middleware for async subagents.
|
|
@@ -5447,8 +5699,9 @@ function hasToolName(tool) {
|
|
|
5447
5699
|
return tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string";
|
|
5448
5700
|
}
|
|
5449
5701
|
/**
|
|
5450
|
-
* Create middleware that
|
|
5451
|
-
*
|
|
5702
|
+
* Create middleware that hides excluded tools from the model and rejects calls
|
|
5703
|
+
* to them. Exclusions calibrate the agent per model; they are not a security
|
|
5704
|
+
* boundary.
|
|
5452
5705
|
*
|
|
5453
5706
|
* @internal
|
|
5454
5707
|
*/
|
|
@@ -5460,6 +5713,16 @@ function createToolExclusionMiddleware(excludedTools) {
|
|
|
5460
5713
|
...request,
|
|
5461
5714
|
tools: request.tools?.filter((tool) => !hasToolName(tool) || !excludedTools.has(tool.name))
|
|
5462
5715
|
});
|
|
5716
|
+
},
|
|
5717
|
+
wrapToolCall(request, handler) {
|
|
5718
|
+
const { name, id } = request.toolCall;
|
|
5719
|
+
if (!excludedTools.has(name)) return handler(request);
|
|
5720
|
+
return new ToolMessage$1({
|
|
5721
|
+
content: `Error: ${name} is not available.`,
|
|
5722
|
+
tool_call_id: id ?? "",
|
|
5723
|
+
name,
|
|
5724
|
+
status: "error"
|
|
5725
|
+
});
|
|
5463
5726
|
}
|
|
5464
5727
|
});
|
|
5465
5728
|
}
|
|
@@ -6205,8 +6468,18 @@ function createDeepAgent(params = {}) {
|
|
|
6205
6468
|
providerHint: getModelProvider(model),
|
|
6206
6469
|
identifierHint: getModelIdentifier(model)
|
|
6207
6470
|
});
|
|
6208
|
-
const
|
|
6209
|
-
|
|
6471
|
+
const computeProfileFilesystemTools = (profile) => {
|
|
6472
|
+
const filesystemTools = FILESYSTEM_TOOL_NAMES.filter((toolName) => !profile.excludedTools.has(toolName));
|
|
6473
|
+
return filesystemTools.length === FILESYSTEM_TOOL_NAMES.length ? void 0 : filesystemTools.includes("read_file") ? filesystemTools : ["read_file", ...filesystemTools];
|
|
6474
|
+
};
|
|
6475
|
+
const profileFilesystemTools = computeProfileFilesystemTools(harnessProfile);
|
|
6476
|
+
const resolveSubagentProfile = (subagentModel) => {
|
|
6477
|
+
if (subagentModel == null || subagentModel === model) return harnessProfile;
|
|
6478
|
+
return typeof subagentModel === "string" ? resolveHarnessProfile({ spec: subagentModel }) : resolveHarnessProfile({
|
|
6479
|
+
providerHint: getModelProvider(subagentModel),
|
|
6480
|
+
identifierHint: getModelIdentifier(subagentModel)
|
|
6481
|
+
});
|
|
6482
|
+
};
|
|
6210
6483
|
const toolOverrides = harnessProfile.toolDescriptionOverrides;
|
|
6211
6484
|
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;
|
|
6212
6485
|
const anthropicModel = isAnthropicModel(model);
|
|
@@ -6242,44 +6515,50 @@ function createDeepAgent(params = {}) {
|
|
|
6242
6515
|
* Only the general-purpose subagent inherits the main agent's skills.
|
|
6243
6516
|
* If a custom subagent needs skills, it must specify its own `skills` array.
|
|
6244
6517
|
*/
|
|
6245
|
-
const createSubagentDefaultMiddleware = (input) => {
|
|
6518
|
+
const createSubagentDefaultMiddleware = (input, subagentProfile, forked) => {
|
|
6246
6519
|
const effectivePermissions = input.permissions ?? permissions;
|
|
6247
6520
|
return [
|
|
6248
6521
|
createFilesystemMiddleware({
|
|
6249
6522
|
backend,
|
|
6250
6523
|
permissions: effectivePermissions,
|
|
6251
|
-
tools:
|
|
6524
|
+
tools: computeProfileFilesystemTools(subagentProfile)
|
|
6252
6525
|
}),
|
|
6253
6526
|
createSummarizationMiddleware({ backend }),
|
|
6254
6527
|
createPatchToolCallsMiddleware(),
|
|
6255
|
-
|
|
6528
|
+
...!forked && input.skills != null && input.skills.length > 0 ? [createSkillsMiddleware({
|
|
6256
6529
|
backend,
|
|
6257
6530
|
sources: input.skills
|
|
6258
6531
|
})] : []
|
|
6259
6532
|
];
|
|
6260
6533
|
};
|
|
6261
|
-
const buildSubagentMiddleware = (input
|
|
6262
|
-
|
|
6263
|
-
|
|
6534
|
+
const buildSubagentMiddleware = (input) => {
|
|
6535
|
+
const subagentProfile = resolveSubagentProfile(input.model);
|
|
6536
|
+
const forked = isForkedSubAgent(input);
|
|
6537
|
+
const subagentDefaultMiddleware = createSubagentDefaultMiddleware(input, subagentProfile, forked);
|
|
6538
|
+
if (forked && skills != null && skills.length > 0) subagentDefaultMiddleware.unshift(createSkillsMiddleware({
|
|
6539
|
+
backend,
|
|
6540
|
+
sources: skills
|
|
6541
|
+
}));
|
|
6542
|
+
let subagentMiddleware = mergeMiddlewareStack(subagentDefaultMiddleware, forked && customMiddleware.length > 0 ? mergeMiddleware$1(customMiddleware, input.middleware ?? []) : input.middleware ?? [], [
|
|
6543
|
+
...resolveMiddleware(subagentProfile.extraMiddleware),
|
|
6264
6544
|
...cacheMiddleware,
|
|
6265
|
-
...
|
|
6545
|
+
...forked && memory != null && memory.length > 0 ? [createMemoryMiddleware({
|
|
6546
|
+
backend,
|
|
6547
|
+
sources: memory,
|
|
6548
|
+
addCacheControl: anthropicModel
|
|
6549
|
+
})] : []
|
|
6266
6550
|
]);
|
|
6267
|
-
if (
|
|
6551
|
+
if (subagentProfile.excludedMiddleware.size > 0) subagentMiddleware = subagentMiddleware.filter((middleware) => !subagentProfile.excludedMiddleware.has(middleware.name));
|
|
6552
|
+
if (subagentProfile.excludedTools.size > 0) subagentMiddleware.push(createToolExclusionMiddleware(subagentProfile.excludedTools));
|
|
6268
6553
|
return subagentMiddleware;
|
|
6269
6554
|
};
|
|
6270
6555
|
const normalizeSubagentSpec = (input) => ({
|
|
6271
6556
|
...input,
|
|
6272
|
-
|
|
6273
|
-
middleware: buildSubagentMiddleware(input, false)
|
|
6274
|
-
});
|
|
6275
|
-
const normalizeForkedSubagentSpec = (input) => ({
|
|
6276
|
-
...input,
|
|
6277
|
-
tools: input.tools ?? [],
|
|
6278
|
-
middleware: buildSubagentMiddleware(input, true)
|
|
6557
|
+
middleware: buildSubagentMiddleware(input)
|
|
6279
6558
|
});
|
|
6280
6559
|
const allSubagents = subagents;
|
|
6281
6560
|
const asyncSubAgents = allSubagents.filter((item) => isAsyncSubAgent(item));
|
|
6282
|
-
const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item :
|
|
6561
|
+
const inlineSubagents = allSubagents.filter((item) => !isAsyncSubAgent(item)).map((item) => "runnable" in item ? item : normalizeSubagentSpec(item));
|
|
6283
6562
|
const gpConfig = harnessProfile.generalPurposeSubagent;
|
|
6284
6563
|
if (!(gpConfig?.enabled === false) && !inlineSubagents.some((item) => item.name === GENERAL_PURPOSE_SUBAGENT["name"])) {
|
|
6285
6564
|
const gpSystemPrompt = gpConfig?.systemPrompt ?? applyProfilePrompt(harnessProfile, GENERAL_PURPOSE_SUBAGENT.systemPrompt);
|
|
@@ -6771,10 +7050,23 @@ var StoreBackend = class {
|
|
|
6771
7050
|
mimeType: fileDataV2.mimeType
|
|
6772
7051
|
};
|
|
6773
7052
|
if (typeof fileDataV2.content !== "string") return { error: `File '${filePath}' has binary content but text MIME type` };
|
|
6774
|
-
|
|
6775
|
-
|
|
7053
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
7054
|
+
const lines = fileDataV2.content.split("\n");
|
|
7055
|
+
const totalLines = lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
7056
|
+
const selected = lines.slice(normalizedOffset, normalizedOffset + normalizedLimit);
|
|
7057
|
+
if (selected.length === 0 || normalizedOffset >= totalLines || normalizedLimit === 0) return {
|
|
7058
|
+
content: selected.join("\n"),
|
|
6776
7059
|
mimeType: fileDataV2.mimeType
|
|
6777
7060
|
};
|
|
7061
|
+
const endOffset = Math.min(normalizedOffset + selected.length, totalLines);
|
|
7062
|
+
return {
|
|
7063
|
+
content: selected.join("\n"),
|
|
7064
|
+
mimeType: fileDataV2.mimeType,
|
|
7065
|
+
totalLines,
|
|
7066
|
+
startLine: normalizedOffset + 1,
|
|
7067
|
+
endLine: endOffset,
|
|
7068
|
+
nextOffset: endOffset < totalLines ? endOffset : void 0
|
|
7069
|
+
};
|
|
6778
7070
|
} catch (e) {
|
|
6779
7071
|
return { error: e.message };
|
|
6780
7072
|
}
|
|
@@ -7040,12 +7332,20 @@ function splitLinesKeepEnds(content) {
|
|
|
7040
7332
|
return lines;
|
|
7041
7333
|
}
|
|
7042
7334
|
function sliceReadContent(content, offset, limit) {
|
|
7043
|
-
if (!content
|
|
7335
|
+
if (!content) return { content };
|
|
7044
7336
|
const lines = splitLinesKeepEnds(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
7045
7337
|
const startIndex = offset;
|
|
7046
7338
|
const endIndex = Math.min(startIndex + limit, lines.length);
|
|
7047
7339
|
if (startIndex >= lines.length) return { error: `Line offset ${offset} exceeds file length (${lines.length} lines)` };
|
|
7048
|
-
|
|
7340
|
+
const selected = lines.slice(startIndex, endIndex);
|
|
7341
|
+
if (selected.length === 0 || offset < 0 || limit <= 0) return { content: selected.join("") };
|
|
7342
|
+
return {
|
|
7343
|
+
content: selected.join(""),
|
|
7344
|
+
totalLines: lines.length,
|
|
7345
|
+
startLine: startIndex + 1,
|
|
7346
|
+
endLine: endIndex,
|
|
7347
|
+
nextOffset: endIndex < lines.length ? endIndex : void 0
|
|
7348
|
+
};
|
|
7049
7349
|
}
|
|
7050
7350
|
function isLangSmithNotFoundError(error) {
|
|
7051
7351
|
if (typeof error !== "object" || error === null) return false;
|
|
@@ -7524,9 +7824,11 @@ var ContextHubBackend = class ContextHubBackend {
|
|
|
7524
7824
|
}
|
|
7525
7825
|
const content = cache[hubPath];
|
|
7526
7826
|
if (content === void 0) return { error: `File '${filePath}' not found` };
|
|
7527
|
-
const
|
|
7827
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
7828
|
+
const sliced = sliceReadContent(content, normalizedOffset, normalizedLimit);
|
|
7528
7829
|
if (sliced.error) return { error: sliced.error };
|
|
7529
7830
|
return {
|
|
7831
|
+
...sliced,
|
|
7530
7832
|
content: sliced.content ?? "",
|
|
7531
7833
|
mimeType: TEXT_MIME_TYPE
|
|
7532
7834
|
};
|
|
@@ -7865,6 +8167,7 @@ function buildFindCommand(searchPath) {
|
|
|
7865
8167
|
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}`;
|
|
7866
8168
|
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`;
|
|
7867
8169
|
}
|
|
8170
|
+
const READ_METADATA_PREFIX = "__DEEPAGENTS_READ_METADATA__";
|
|
7868
8171
|
/**
|
|
7869
8172
|
* Pure POSIX shell command for reading files with line numbers.
|
|
7870
8173
|
* Uses awk for line numbering with offset/limit — works on any Linux including Alpine.
|
|
@@ -7878,9 +8181,32 @@ function buildReadCommand(filePath, offset, limit) {
|
|
|
7878
8181
|
return [
|
|
7879
8182
|
`if [ ! -f ${quotedPath} ]; then echo "Error: File not found"; exit 1; fi`,
|
|
7880
8183
|
`if [ ! -s ${quotedPath} ]; then echo "System reminder: File exists but has empty contents"; exit 0; fi`,
|
|
7881
|
-
`awk 'NR >= ${start} && NR <= ${end} { printf "%6d\\t%s\\n", NR, $0 }' ${quotedPath}`
|
|
8184
|
+
`awk 'NR >= ${start} && NR <= ${end} { printf "%6d\\t%s\\n", NR, $0 } END { printf "${READ_METADATA_PREFIX}\\t%d\\n", NR }' ${quotedPath}`
|
|
7882
8185
|
].join("; ");
|
|
7883
8186
|
}
|
|
8187
|
+
function parseReadOutput(output, offset, limit) {
|
|
8188
|
+
const rows = output.split("\n");
|
|
8189
|
+
let metadataIndex = -1;
|
|
8190
|
+
for (let index = rows.length - 1; index >= 0; index -= 1) if (rows[index].startsWith(`${READ_METADATA_PREFIX}\t`)) {
|
|
8191
|
+
metadataIndex = index;
|
|
8192
|
+
break;
|
|
8193
|
+
}
|
|
8194
|
+
if (metadataIndex === -1) return { content: output };
|
|
8195
|
+
const totalLines = Number(rows[metadataIndex].slice(29));
|
|
8196
|
+
if (!Number.isSafeInteger(totalLines) || totalLines < 0) return { content: output };
|
|
8197
|
+
const contentRows = rows.slice(0, metadataIndex);
|
|
8198
|
+
const content = contentRows.length > 0 ? `${contentRows.join("\n")}\n` : "";
|
|
8199
|
+
const startOffset = Math.floor(offset);
|
|
8200
|
+
const endOffset = Math.min(startOffset + Math.floor(limit), totalLines);
|
|
8201
|
+
if (startOffset >= totalLines || endOffset <= startOffset) return { content };
|
|
8202
|
+
return {
|
|
8203
|
+
content,
|
|
8204
|
+
totalLines,
|
|
8205
|
+
startLine: startOffset + 1,
|
|
8206
|
+
endLine: endOffset,
|
|
8207
|
+
nextOffset: endOffset < totalLines ? endOffset : void 0
|
|
8208
|
+
};
|
|
8209
|
+
}
|
|
7884
8210
|
/**
|
|
7885
8211
|
* Build a grep command for literal (fixed-string) search.
|
|
7886
8212
|
* Uses grep -rHnF for recursive, with-filename, with-line-number, fixed-string search.
|
|
@@ -7958,15 +8284,17 @@ var BaseSandbox = class {
|
|
|
7958
8284
|
mimeType
|
|
7959
8285
|
};
|
|
7960
8286
|
}
|
|
7961
|
-
|
|
8287
|
+
const { offset: normalizedOffset, limit: normalizedLimit } = normalizeReadPagination(offset, limit);
|
|
8288
|
+
if (normalizedLimit === 0) return {
|
|
7962
8289
|
content: "",
|
|
7963
8290
|
mimeType
|
|
7964
8291
|
};
|
|
7965
|
-
const command = buildReadCommand(filePath,
|
|
8292
|
+
const command = buildReadCommand(filePath, normalizedOffset, normalizedLimit);
|
|
7966
8293
|
const result = await this.execute(command);
|
|
7967
8294
|
if (result.exitCode !== 0) return { error: `File '${filePath}' not found` };
|
|
8295
|
+
const parsed = parseReadOutput(result.output, normalizedOffset, normalizedLimit);
|
|
7968
8296
|
return {
|
|
7969
|
-
content:
|
|
8297
|
+
...result.truncated ? { content: parsed.content } : parsed,
|
|
7970
8298
|
mimeType
|
|
7971
8299
|
};
|
|
7972
8300
|
}
|
|
@@ -8401,6 +8729,6 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
|
|
|
8401
8729
|
}
|
|
8402
8730
|
};
|
|
8403
8731
|
//#endregion
|
|
8404
|
-
export { DEFAULT_GENERAL_PURPOSE_DESCRIPTION as A, StateBackend as B, MAX_SKILL_DESCRIPTION_LENGTH as C, createMemoryMiddleware as D, createSkillsMiddleware as E, createSubAgentMiddleware as F, resolveBackend as G, applyGrepMaxCount as H, computeSummarizationDefaults as I, checkEmptyContent as J, adaptBackendProtocol as K, createSummarizationMiddleware as L, GENERAL_PURPOSE_SUBAGENT as M, SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY as N, filesValue as O, createSubAgent as P, createFilesystemMiddleware as R, createCompletionCallbackMiddleware as S, MAX_SKILL_NAME_LENGTH as T, isSandboxBackend as U, SandboxError as V, isSandboxProtocol as W, isTextMimeType as X, getMimeType as Y,
|
|
8732
|
+
export { DEFAULT_GENERAL_PURPOSE_DESCRIPTION as A, StateBackend as B, MAX_SKILL_DESCRIPTION_LENGTH as C, createMemoryMiddleware as D, createSkillsMiddleware as E, createSubAgentMiddleware as F, resolveBackend as G, applyGrepMaxCount as H, computeSummarizationDefaults as I, checkEmptyContent as J, adaptBackendProtocol as K, createSummarizationMiddleware as L, GENERAL_PURPOSE_SUBAGENT as M, SUBAGENT_RESPONSE_FORMAT_CONFIG_KEY as N, filesValue as O, createSubAgent as P, performStringReplacement as Q, createFilesystemMiddleware as R, createCompletionCallbackMiddleware as S, MAX_SKILL_NAME_LENGTH as T, isSandboxBackend as U, SandboxError as V, isSandboxProtocol as W, isTextMimeType as X, getMimeType as Y, normalizeReadPagination as Z, createHarnessProfile as _, ASYNC_TASK_SYSTEM_PROMPT as a, createAsyncSubAgentMiddleware as b, TASK_SYSTEM_PROMPT as c, registerHarnessProfile as d, generalPurposeSubagentConfigSchema as f, EMPTY_HARNESS_PROFILE as g, serializeProfile as h, StoreBackend as i, DEFAULT_SUBAGENT_PROMPT as j, createPatchToolCallsMiddleware as k, createDeepAgent as l, parseHarnessProfileConfig as m, BaseSandbox as n, BASE_AGENT_PROMPT as o, harnessProfileConfigSchema as p, adaptSandboxProtocol as q, ContextHubBackend as r, EXECUTION_SYSTEM_PROMPT as s, LangSmithSandbox as t, getHarnessProfile as u, REQUIRED_MIDDLEWARE_NAMES as v, MAX_SKILL_FILE_SIZE as w, isAsyncSubAgent as x, ConfigurationError as y, CompositeBackend as z };
|
|
8405
8733
|
|
|
8406
|
-
//# sourceMappingURL=langsmith-
|
|
8734
|
+
//# sourceMappingURL=langsmith-BYWZnEVh.js.map
|