deepagents 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +418 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -88
- package/dist/index.d.ts +78 -59
- package/dist/index.js +411 -132
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,8 @@ let _langchain_core_messages = require("@langchain/core/messages");
|
|
|
35
35
|
let zod = require("zod");
|
|
36
36
|
let yaml = require("yaml");
|
|
37
37
|
yaml = __toESM(yaml);
|
|
38
|
+
require("uuid");
|
|
39
|
+
require("@langchain/openai");
|
|
38
40
|
let node_fs_promises = require("node:fs/promises");
|
|
39
41
|
node_fs_promises = __toESM(node_fs_promises);
|
|
40
42
|
let node_fs = require("node:fs");
|
|
@@ -199,14 +201,30 @@ function performStringReplacement(content, oldString, newString, replaceAll) {
|
|
|
199
201
|
return [content.split(oldString).join(newString), occurrences];
|
|
200
202
|
}
|
|
201
203
|
/**
|
|
202
|
-
* Validate and normalize a path.
|
|
204
|
+
* Validate and normalize a directory path.
|
|
205
|
+
*
|
|
206
|
+
* Ensures paths are safe to use by preventing directory traversal attacks
|
|
207
|
+
* and enforcing consistent formatting. All paths are normalized to use
|
|
208
|
+
* forward slashes and start with a leading slash.
|
|
209
|
+
*
|
|
210
|
+
* This function is designed for virtual filesystem paths and rejects
|
|
211
|
+
* Windows absolute paths (e.g., C:/..., F:/...) to maintain consistency
|
|
212
|
+
* and prevent path format ambiguity.
|
|
203
213
|
*
|
|
204
214
|
* @param path - Path to validate
|
|
205
215
|
* @returns Normalized path starting with / and ending with /
|
|
206
216
|
* @throws Error if path is invalid
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* validatePath("foo/bar") // Returns: "/foo/bar/"
|
|
221
|
+
* validatePath("/./foo//bar") // Returns: "/foo/bar/"
|
|
222
|
+
* validatePath("../etc/passwd") // Throws: Path traversal not allowed
|
|
223
|
+
* validatePath("C:\\Users\\file") // Throws: Windows absolute paths not supported
|
|
224
|
+
* ```
|
|
207
225
|
*/
|
|
208
|
-
function validatePath(path
|
|
209
|
-
const pathStr = path
|
|
226
|
+
function validatePath(path) {
|
|
227
|
+
const pathStr = path || "/";
|
|
210
228
|
if (!pathStr || pathStr.trim() === "") throw new Error("Path cannot be empty");
|
|
211
229
|
let normalized = pathStr.startsWith("/") ? pathStr : "/" + pathStr;
|
|
212
230
|
if (!normalized.endsWith("/")) normalized += "/";
|
|
@@ -228,10 +246,10 @@ function validatePath(path$4) {
|
|
|
228
246
|
* // Returns: "/test.py\n/src/main.py" (sorted by modified_at)
|
|
229
247
|
* ```
|
|
230
248
|
*/
|
|
231
|
-
function globSearchFiles(files, pattern, path
|
|
249
|
+
function globSearchFiles(files, pattern, path = "/") {
|
|
232
250
|
let normalizedPath;
|
|
233
251
|
try {
|
|
234
|
-
normalizedPath = validatePath(path
|
|
252
|
+
normalizedPath = validatePath(path);
|
|
235
253
|
} catch {
|
|
236
254
|
return "No files found";
|
|
237
255
|
}
|
|
@@ -261,7 +279,7 @@ function globSearchFiles(files, pattern, path$4 = "/") {
|
|
|
261
279
|
* (e.g., invalid regex). We deliberately do not raise here to keep backends
|
|
262
280
|
* non-throwing in tool contexts and preserve user-facing error messages.
|
|
263
281
|
*/
|
|
264
|
-
function grepMatchesFromFiles(files, pattern, path
|
|
282
|
+
function grepMatchesFromFiles(files, pattern, path = null, glob = null) {
|
|
265
283
|
let regex;
|
|
266
284
|
try {
|
|
267
285
|
regex = new RegExp(pattern);
|
|
@@ -270,7 +288,7 @@ function grepMatchesFromFiles(files, pattern, path$4 = null, glob = null) {
|
|
|
270
288
|
}
|
|
271
289
|
let normalizedPath;
|
|
272
290
|
try {
|
|
273
|
-
normalizedPath = validatePath(path
|
|
291
|
+
normalizedPath = validatePath(path);
|
|
274
292
|
} catch {
|
|
275
293
|
return [];
|
|
276
294
|
}
|
|
@@ -323,11 +341,11 @@ var StateBackend = class {
|
|
|
323
341
|
* @returns List of FileInfo objects for files and directories directly in the directory.
|
|
324
342
|
* Directories have a trailing / in their path and is_dir=true.
|
|
325
343
|
*/
|
|
326
|
-
lsInfo(path
|
|
344
|
+
lsInfo(path) {
|
|
327
345
|
const files = this.getFiles();
|
|
328
346
|
const infos = [];
|
|
329
347
|
const subdirs = /* @__PURE__ */ new Set();
|
|
330
|
-
const normalizedPath = path
|
|
348
|
+
const normalizedPath = path.endsWith("/") ? path : path + "/";
|
|
331
349
|
for (const [k, fd] of Object.entries(files)) {
|
|
332
350
|
if (!k.startsWith(normalizedPath)) continue;
|
|
333
351
|
const relative = k.substring(normalizedPath.length);
|
|
@@ -409,15 +427,15 @@ var StateBackend = class {
|
|
|
409
427
|
/**
|
|
410
428
|
* Structured search results or error string for invalid input.
|
|
411
429
|
*/
|
|
412
|
-
grepRaw(pattern, path
|
|
413
|
-
return grepMatchesFromFiles(this.getFiles(), pattern, path
|
|
430
|
+
grepRaw(pattern, path = "/", glob = null) {
|
|
431
|
+
return grepMatchesFromFiles(this.getFiles(), pattern, path, glob);
|
|
414
432
|
}
|
|
415
433
|
/**
|
|
416
434
|
* Structured glob matching returning FileInfo objects.
|
|
417
435
|
*/
|
|
418
|
-
globInfo(pattern, path
|
|
436
|
+
globInfo(pattern, path = "/") {
|
|
419
437
|
const files = this.getFiles();
|
|
420
|
-
const result = globSearchFiles(files, pattern, path
|
|
438
|
+
const result = globSearchFiles(files, pattern, path);
|
|
421
439
|
if (result === "No files found") return [];
|
|
422
440
|
const paths = result.split("\n");
|
|
423
441
|
const infos = [];
|
|
@@ -445,15 +463,15 @@ var StateBackend = class {
|
|
|
445
463
|
uploadFiles(files) {
|
|
446
464
|
const responses = [];
|
|
447
465
|
const updates = {};
|
|
448
|
-
for (const [path
|
|
449
|
-
updates[path
|
|
466
|
+
for (const [path, content] of files) try {
|
|
467
|
+
updates[path] = createFileData(new TextDecoder().decode(content));
|
|
450
468
|
responses.push({
|
|
451
|
-
path
|
|
469
|
+
path,
|
|
452
470
|
error: null
|
|
453
471
|
});
|
|
454
472
|
} catch {
|
|
455
473
|
responses.push({
|
|
456
|
-
path
|
|
474
|
+
path,
|
|
457
475
|
error: "invalid_path"
|
|
458
476
|
});
|
|
459
477
|
}
|
|
@@ -470,11 +488,11 @@ var StateBackend = class {
|
|
|
470
488
|
downloadFiles(paths) {
|
|
471
489
|
const files = this.getFiles();
|
|
472
490
|
const responses = [];
|
|
473
|
-
for (const path
|
|
474
|
-
const fileData = files[path
|
|
491
|
+
for (const path of paths) {
|
|
492
|
+
const fileData = files[path];
|
|
475
493
|
if (!fileData) {
|
|
476
494
|
responses.push({
|
|
477
|
-
path
|
|
495
|
+
path,
|
|
478
496
|
content: null,
|
|
479
497
|
error: "file_not_found"
|
|
480
498
|
});
|
|
@@ -483,7 +501,7 @@ var StateBackend = class {
|
|
|
483
501
|
const contentStr = fileDataToString(fileData);
|
|
484
502
|
const content = new TextEncoder().encode(contentStr);
|
|
485
503
|
responses.push({
|
|
486
|
-
path
|
|
504
|
+
path,
|
|
487
505
|
content,
|
|
488
506
|
error: null
|
|
489
507
|
});
|
|
@@ -502,6 +520,74 @@ var StateBackend = class {
|
|
|
502
520
|
* - Tool result eviction for large outputs
|
|
503
521
|
*/
|
|
504
522
|
/**
|
|
523
|
+
* Tools that should be excluded from the large result eviction logic.
|
|
524
|
+
*
|
|
525
|
+
* This array contains tools that should NOT have their results evicted to the filesystem
|
|
526
|
+
* when they exceed token limits. Tools are excluded for different reasons:
|
|
527
|
+
*
|
|
528
|
+
* 1. Tools with built-in truncation (ls, glob, grep):
|
|
529
|
+
* These tools truncate their own output when it becomes too large. When these tools
|
|
530
|
+
* produce truncated output due to many matches, it typically indicates the query
|
|
531
|
+
* needs refinement rather than full result preservation. In such cases, the truncated
|
|
532
|
+
* matches are potentially more like noise and the LLM should be prompted to narrow
|
|
533
|
+
* its search criteria instead.
|
|
534
|
+
*
|
|
535
|
+
* 2. Tools with problematic truncation behavior (read_file):
|
|
536
|
+
* read_file is tricky to handle as the failure mode here is single long lines
|
|
537
|
+
* (e.g., imagine a jsonl file with very long payloads on each line). If we try to
|
|
538
|
+
* truncate the result of read_file, the agent may then attempt to re-read the
|
|
539
|
+
* truncated file using read_file again, which won't help.
|
|
540
|
+
*
|
|
541
|
+
* 3. Tools that never exceed limits (edit_file, write_file):
|
|
542
|
+
* These tools return minimal confirmation messages and are never expected to produce
|
|
543
|
+
* output large enough to exceed token limits, so checking them would be unnecessary.
|
|
544
|
+
*/
|
|
545
|
+
const TOOLS_EXCLUDED_FROM_EVICTION = [
|
|
546
|
+
"ls",
|
|
547
|
+
"glob",
|
|
548
|
+
"grep",
|
|
549
|
+
"read_file",
|
|
550
|
+
"edit_file",
|
|
551
|
+
"write_file"
|
|
552
|
+
];
|
|
553
|
+
/**
|
|
554
|
+
* Approximate number of characters per token for truncation calculations.
|
|
555
|
+
* Using 4 chars per token as a conservative approximation (actual ratio varies by content)
|
|
556
|
+
* This errs on the high side to avoid premature eviction of content that might fit.
|
|
557
|
+
*/
|
|
558
|
+
const NUM_CHARS_PER_TOKEN = 4;
|
|
559
|
+
/**
|
|
560
|
+
* Message template for evicted tool results.
|
|
561
|
+
*/
|
|
562
|
+
const TOO_LARGE_TOOL_MSG = `Tool result too large, the result of this tool call {tool_call_id} was saved in the filesystem at this path: {file_path}
|
|
563
|
+
You can read the result from the filesystem by using the read_file tool, but make sure to only read part of the result at a time.
|
|
564
|
+
You can do this by specifying an offset and limit in the read_file tool call.
|
|
565
|
+
For example, to read the first 100 lines, you can use the read_file tool with offset=0 and limit=100.
|
|
566
|
+
|
|
567
|
+
Here is a preview showing the head and tail of the result (lines of the form
|
|
568
|
+
... [N lines truncated] ...
|
|
569
|
+
indicate omitted lines in the middle of the content):
|
|
570
|
+
|
|
571
|
+
{content_sample}`;
|
|
572
|
+
/**
|
|
573
|
+
* Create a preview of content showing head and tail with truncation marker.
|
|
574
|
+
*
|
|
575
|
+
* @param contentStr - The full content string to preview.
|
|
576
|
+
* @param headLines - Number of lines to show from the start (default: 5).
|
|
577
|
+
* @param tailLines - Number of lines to show from the end (default: 5).
|
|
578
|
+
* @returns Formatted preview string with line numbers.
|
|
579
|
+
*/
|
|
580
|
+
function createContentPreview(contentStr, headLines = 5, tailLines = 5) {
|
|
581
|
+
const lines = contentStr.split("\n");
|
|
582
|
+
if (lines.length <= headLines + tailLines) return formatContentWithLineNumbers(lines.map((line) => line.substring(0, 1e3)), 1);
|
|
583
|
+
const head = lines.slice(0, headLines).map((line) => line.substring(0, 1e3));
|
|
584
|
+
const tail = lines.slice(-tailLines).map((line) => line.substring(0, 1e3));
|
|
585
|
+
const headSample = formatContentWithLineNumbers(head, 1);
|
|
586
|
+
const truncationNotice = `\n... [${lines.length - headLines - tailLines} lines truncated] ...\n`;
|
|
587
|
+
const tailSample = formatContentWithLineNumbers(tail, lines.length - tailLines + 1);
|
|
588
|
+
return headSample + truncationNotice + tailSample;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
505
591
|
* Zod v3 schema for FileData (re-export from backends)
|
|
506
592
|
*/
|
|
507
593
|
const FileDataSchema = zod_v4.z.object({
|
|
@@ -510,16 +596,26 @@ const FileDataSchema = zod_v4.z.object({
|
|
|
510
596
|
modified_at: zod_v4.z.string()
|
|
511
597
|
});
|
|
512
598
|
/**
|
|
513
|
-
*
|
|
599
|
+
* Reducer for files state that merges file updates with support for deletions.
|
|
600
|
+
* When a file value is null, the file is deleted from state.
|
|
601
|
+
* When a file value is non-null, it is added or updated in state.
|
|
602
|
+
*
|
|
603
|
+
* This reducer enables concurrent updates from parallel subagents by properly
|
|
604
|
+
* merging their file changes instead of requiring LastValue semantics.
|
|
605
|
+
*
|
|
606
|
+
* @param current - The current files record (from state)
|
|
607
|
+
* @param update - The new files record (from a subagent update), with null values for deletions
|
|
608
|
+
* @returns Merged files record with deletions applied
|
|
514
609
|
*/
|
|
515
|
-
function fileDataReducer(
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
610
|
+
function fileDataReducer(current, update) {
|
|
611
|
+
if (update === void 0) return current || {};
|
|
612
|
+
if (current === void 0) {
|
|
613
|
+
const result = {};
|
|
614
|
+
for (const [key, value] of Object.entries(update)) if (value !== null) result[key] = value;
|
|
615
|
+
return result;
|
|
520
616
|
}
|
|
521
|
-
const result = { ...
|
|
522
|
-
for (const [key, value] of Object.entries(
|
|
617
|
+
const result = { ...current };
|
|
618
|
+
for (const [key, value] of Object.entries(update)) if (value === null) delete result[key];
|
|
523
619
|
else result[key] = value;
|
|
524
620
|
return result;
|
|
525
621
|
}
|
|
@@ -528,11 +624,13 @@ function fileDataReducer(left, right) {
|
|
|
528
624
|
* Defined at module level to ensure the same object identity is used across all agents,
|
|
529
625
|
* preventing "Channel already exists with different type" errors when multiple agents
|
|
530
626
|
* use createFilesystemMiddleware.
|
|
627
|
+
*
|
|
628
|
+
* Uses ReducedValue for files to allow concurrent updates from parallel subagents.
|
|
531
629
|
*/
|
|
532
|
-
const FilesystemStateSchema =
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
630
|
+
const FilesystemStateSchema = new _langchain_langgraph.StateSchema({ files: new _langchain_langgraph.ReducedValue(zod_v4.z.record(zod_v4.z.string(), FileDataSchema).default(() => ({})), {
|
|
631
|
+
inputSchema: zod_v4.z.record(zod_v4.z.string(), FileDataSchema.nullable()).optional(),
|
|
632
|
+
reducer: fileDataReducer
|
|
633
|
+
}) });
|
|
536
634
|
/**
|
|
537
635
|
* Resolve backend from factory or instance.
|
|
538
636
|
*
|
|
@@ -543,7 +641,10 @@ function getBackend(backend, stateAndStore) {
|
|
|
543
641
|
if (typeof backend === "function") return backend(stateAndStore);
|
|
544
642
|
return backend;
|
|
545
643
|
}
|
|
546
|
-
const FILESYSTEM_SYSTEM_PROMPT =
|
|
644
|
+
const FILESYSTEM_SYSTEM_PROMPT = `## Filesystem Tools \`ls\`, \`read_file\`, \`write_file\`, \`edit_file\`, \`glob\`, \`grep\`
|
|
645
|
+
|
|
646
|
+
You have access to a filesystem which you can interact with using these tools.
|
|
647
|
+
All file paths must start with a /.
|
|
547
648
|
|
|
548
649
|
- ls: list files in a directory (requires absolute path)
|
|
549
650
|
- read_file: read a file from the filesystem
|
|
@@ -551,31 +652,99 @@ const FILESYSTEM_SYSTEM_PROMPT = `You have access to a virtual filesystem. All f
|
|
|
551
652
|
- edit_file: edit a file in the filesystem
|
|
552
653
|
- glob: find files matching a pattern (e.g., "**/*.py")
|
|
553
654
|
- grep: search for text within files`;
|
|
554
|
-
const LS_TOOL_DESCRIPTION =
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
655
|
+
const LS_TOOL_DESCRIPTION = `Lists all files in a directory.
|
|
656
|
+
|
|
657
|
+
This is useful for exploring the filesystem and finding the right file to read or edit.
|
|
658
|
+
You should almost ALWAYS use this tool before using the read_file or edit_file tools.`;
|
|
659
|
+
const READ_FILE_TOOL_DESCRIPTION = `Reads a file from the filesystem.
|
|
660
|
+
|
|
661
|
+
Assume this tool is able to read all files. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
662
|
+
|
|
663
|
+
Usage:
|
|
664
|
+
- By default, it reads up to 500 lines starting from the beginning of the file
|
|
665
|
+
- **IMPORTANT for large files and codebase exploration**: Use pagination with offset and limit parameters to avoid context overflow
|
|
666
|
+
- First scan: read_file(path, limit=100) to see file structure
|
|
667
|
+
- Read more sections: read_file(path, offset=100, limit=200) for next 200 lines
|
|
668
|
+
- Only omit limit (read full file) when necessary for editing
|
|
669
|
+
- Specify offset and limit: read_file(path, offset=0, limit=100) reads first 100 lines
|
|
670
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
671
|
+
- Lines longer than 10,000 characters will be split into multiple lines with continuation markers (e.g., 5.1, 5.2, etc.). When you specify a limit, these continuation lines count towards the limit.
|
|
672
|
+
- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.
|
|
673
|
+
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.
|
|
674
|
+
- You should ALWAYS make sure a file has been read before editing it.`;
|
|
675
|
+
const WRITE_FILE_TOOL_DESCRIPTION = `Writes to a new file in the filesystem.
|
|
676
|
+
|
|
677
|
+
Usage:
|
|
678
|
+
- The write_file tool will create a new file.
|
|
679
|
+
- Prefer to edit existing files (with the edit_file tool) over creating new ones when possible.`;
|
|
680
|
+
const EDIT_FILE_TOOL_DESCRIPTION = `Performs exact string replacements in files.
|
|
681
|
+
|
|
682
|
+
Usage:
|
|
683
|
+
- You must read the file before editing. This tool will error if you attempt an edit without reading the file first.
|
|
684
|
+
- When editing, preserve the exact indentation (tabs/spaces) from the read output. Never include line number prefixes in old_string or new_string.
|
|
685
|
+
- ALWAYS prefer editing existing files over creating new ones.
|
|
686
|
+
- Only use emojis if the user explicitly requests it.`;
|
|
687
|
+
const GLOB_TOOL_DESCRIPTION = `Find files matching a glob pattern.
|
|
688
|
+
|
|
689
|
+
Supports standard glob patterns: \`*\` (any characters), \`**\` (any directories), \`?\` (single character).
|
|
690
|
+
Returns a list of absolute file paths that match the pattern.
|
|
691
|
+
|
|
692
|
+
Examples:
|
|
693
|
+
- \`**/*.py\` - Find all Python files
|
|
694
|
+
- \`*.txt\` - Find all text files in root
|
|
695
|
+
- \`/subdir/**/*.md\` - Find all markdown files under /subdir`;
|
|
696
|
+
const GREP_TOOL_DESCRIPTION = `Search for a text pattern across files.
|
|
697
|
+
|
|
698
|
+
Searches for literal text (not regex) and returns matching files or content based on output_mode.
|
|
699
|
+
|
|
700
|
+
Examples:
|
|
701
|
+
- Search all files: \`grep(pattern="TODO")\`
|
|
702
|
+
- Search Python files only: \`grep(pattern="import", glob="*.py")\`
|
|
703
|
+
- Show matching lines: \`grep(pattern="error", output_mode="content")\``;
|
|
704
|
+
const EXECUTE_TOOL_DESCRIPTION = `Executes a shell command in an isolated sandbox environment.
|
|
705
|
+
|
|
706
|
+
Usage:
|
|
707
|
+
Executes a given command in the sandbox environment with proper handling and security measures.
|
|
562
708
|
Before executing the command, please follow these steps:
|
|
563
709
|
|
|
564
710
|
1. Directory Verification:
|
|
565
|
-
- If the command will create new directories or files, first use the ls tool to verify the parent directory exists
|
|
711
|
+
- If the command will create new directories or files, first use the ls tool to verify the parent directory exists and is the correct location
|
|
712
|
+
- For example, before running "mkdir foo/bar", first use ls to check that "foo" exists and is the intended parent directory
|
|
566
713
|
|
|
567
714
|
2. Command Execution:
|
|
568
|
-
- Always quote file paths that contain spaces with double quotes
|
|
569
|
-
-
|
|
570
|
-
|
|
715
|
+
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
|
716
|
+
- Examples of proper quoting:
|
|
717
|
+
- cd "/Users/name/My Documents" (correct)
|
|
718
|
+
- cd /Users/name/My Documents (incorrect - will fail)
|
|
719
|
+
- python "/path/with spaces/script.py" (correct)
|
|
720
|
+
- python /path/with spaces/script.py (incorrect - will fail)
|
|
721
|
+
- After ensuring proper quoting, execute the command
|
|
722
|
+
- Capture the output of the command
|
|
571
723
|
|
|
572
724
|
Usage notes:
|
|
573
|
-
-
|
|
725
|
+
- Commands run in an isolated sandbox environment
|
|
726
|
+
- Returns combined stdout/stderr output with exit code
|
|
574
727
|
- If the output is very large, it may be truncated
|
|
575
|
-
- IMPORTANT:
|
|
576
|
-
-
|
|
577
|
-
|
|
578
|
-
|
|
728
|
+
- VERY IMPORTANT: You MUST avoid using search commands like find and grep. Instead use the grep, glob tools to search. You MUST avoid read tools like cat, head, tail, and use read_file to read files.
|
|
729
|
+
- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings)
|
|
730
|
+
- Use '&&' when commands depend on each other (e.g., "mkdir dir && cd dir")
|
|
731
|
+
- Use ';' only when you need to run commands sequentially but don't care if earlier commands fail
|
|
732
|
+
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of cd
|
|
733
|
+
|
|
734
|
+
Examples:
|
|
735
|
+
Good examples:
|
|
736
|
+
- execute(command="pytest /foo/bar/tests")
|
|
737
|
+
- execute(command="python /path/to/script.py")
|
|
738
|
+
- execute(command="npm install && npm test")
|
|
739
|
+
|
|
740
|
+
Bad examples (avoid these):
|
|
741
|
+
- execute(command="cd /foo/bar && pytest tests") # Use absolute path instead
|
|
742
|
+
- execute(command="cat file.txt") # Use read_file tool instead
|
|
743
|
+
- execute(command="find . -name '*.py'") # Use glob tool instead
|
|
744
|
+
- execute(command="grep -r 'pattern' .") # Use grep tool instead
|
|
745
|
+
|
|
746
|
+
Note: This tool is only available if the backend supports execution (SandboxBackendProtocol).
|
|
747
|
+
If execution is not supported, the tool will return an error message.`;
|
|
579
748
|
const EXECUTION_SYSTEM_PROMPT = `## Execute Tool \`execute\`
|
|
580
749
|
|
|
581
750
|
You have access to an \`execute\` tool for running shell commands in a sandboxed environment.
|
|
@@ -592,9 +761,9 @@ function createLsTool(backend, options) {
|
|
|
592
761
|
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
593
762
|
store: config.store
|
|
594
763
|
});
|
|
595
|
-
const path
|
|
596
|
-
const infos = await resolvedBackend.lsInfo(path
|
|
597
|
-
if (infos.length === 0) return `No files found in ${path
|
|
764
|
+
const path = input.path || "/";
|
|
765
|
+
const infos = await resolvedBackend.lsInfo(path);
|
|
766
|
+
if (infos.length === 0) return `No files found in ${path}`;
|
|
598
767
|
const lines = [];
|
|
599
768
|
for (const info of infos) if (info.is_dir) lines.push(`${info.path} (directory)`);
|
|
600
769
|
else {
|
|
@@ -708,8 +877,8 @@ function createGlobTool(backend, options) {
|
|
|
708
877
|
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
709
878
|
store: config.store
|
|
710
879
|
});
|
|
711
|
-
const { pattern, path
|
|
712
|
-
const infos = await resolvedBackend.globInfo(pattern, path
|
|
880
|
+
const { pattern, path = "/" } = input;
|
|
881
|
+
const infos = await resolvedBackend.globInfo(pattern, path);
|
|
713
882
|
if (infos.length === 0) return `No files found matching pattern '${pattern}'`;
|
|
714
883
|
return infos.map((info) => info.path).join("\n");
|
|
715
884
|
}, {
|
|
@@ -731,8 +900,8 @@ function createGrepTool(backend, options) {
|
|
|
731
900
|
state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
|
|
732
901
|
store: config.store
|
|
733
902
|
});
|
|
734
|
-
const { pattern, path
|
|
735
|
-
const result = await resolvedBackend.grepRaw(pattern, path
|
|
903
|
+
const { pattern, path = "/", glob = null } = input;
|
|
904
|
+
const result = await resolvedBackend.grepRaw(pattern, path, glob);
|
|
736
905
|
if (typeof result === "string") return result;
|
|
737
906
|
if (result.length === 0) return `No matches found for pattern '${pattern}'`;
|
|
738
907
|
const lines = [];
|
|
@@ -815,10 +984,13 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
815
984
|
systemPrompt: newSystemPrompt
|
|
816
985
|
});
|
|
817
986
|
},
|
|
818
|
-
wrapToolCall:
|
|
987
|
+
wrapToolCall: async (request, handler) => {
|
|
988
|
+
if (!toolTokenLimitBeforeEvict) return handler(request);
|
|
989
|
+
const toolName = request.toolCall?.name;
|
|
990
|
+
if (toolName && TOOLS_EXCLUDED_FROM_EVICTION.includes(toolName)) return handler(request);
|
|
819
991
|
const result = await handler(request);
|
|
820
|
-
async function processToolMessage(msg) {
|
|
821
|
-
if (typeof msg.content === "string" && msg.content.length > toolTokenLimitBeforeEvict *
|
|
992
|
+
async function processToolMessage(msg, toolTokenLimitBeforeEvict) {
|
|
993
|
+
if (typeof msg.content === "string" && msg.content.length > toolTokenLimitBeforeEvict * NUM_CHARS_PER_TOKEN) {
|
|
822
994
|
const resolvedBackend = getBackend(backend, {
|
|
823
995
|
state: request.state || {},
|
|
824
996
|
store: request.config?.store
|
|
@@ -829,9 +1001,10 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
829
1001
|
message: msg,
|
|
830
1002
|
filesUpdate: null
|
|
831
1003
|
};
|
|
1004
|
+
const contentSample = createContentPreview(msg.content);
|
|
832
1005
|
return {
|
|
833
1006
|
message: new langchain.ToolMessage({
|
|
834
|
-
content:
|
|
1007
|
+
content: TOO_LARGE_TOOL_MSG.replace("{tool_call_id}", msg.tool_call_id).replace("{file_path}", evictPath).replace("{content_sample}", contentSample),
|
|
835
1008
|
tool_call_id: msg.tool_call_id,
|
|
836
1009
|
name: msg.name
|
|
837
1010
|
}),
|
|
@@ -844,7 +1017,7 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
844
1017
|
};
|
|
845
1018
|
}
|
|
846
1019
|
if (langchain.ToolMessage.isInstance(result)) {
|
|
847
|
-
const processed = await processToolMessage(result);
|
|
1020
|
+
const processed = await processToolMessage(result, toolTokenLimitBeforeEvict);
|
|
848
1021
|
if (processed.filesUpdate) return new _langchain_langgraph.Command({ update: {
|
|
849
1022
|
files: processed.filesUpdate,
|
|
850
1023
|
messages: [processed.message]
|
|
@@ -855,10 +1028,10 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
855
1028
|
const update = result.update;
|
|
856
1029
|
if (!update?.messages) return result;
|
|
857
1030
|
let hasLargeResults = false;
|
|
858
|
-
const accumulatedFiles = { ...update.files
|
|
1031
|
+
const accumulatedFiles = update.files ? { ...update.files } : {};
|
|
859
1032
|
const processedMessages = [];
|
|
860
1033
|
for (const msg of update.messages) if (langchain.ToolMessage.isInstance(msg)) {
|
|
861
|
-
const processed = await processToolMessage(msg);
|
|
1034
|
+
const processed = await processToolMessage(msg, toolTokenLimitBeforeEvict);
|
|
862
1035
|
processedMessages.push(processed.message);
|
|
863
1036
|
if (processed.filesUpdate) {
|
|
864
1037
|
hasLargeResults = true;
|
|
@@ -872,7 +1045,7 @@ function createFilesystemMiddleware(options = {}) {
|
|
|
872
1045
|
} });
|
|
873
1046
|
}
|
|
874
1047
|
return result;
|
|
875
|
-
}
|
|
1048
|
+
}
|
|
876
1049
|
});
|
|
877
1050
|
}
|
|
878
1051
|
|
|
@@ -882,8 +1055,7 @@ const DEFAULT_SUBAGENT_PROMPT = "In order to complete the objective that the use
|
|
|
882
1055
|
const EXCLUDED_STATE_KEYS = [
|
|
883
1056
|
"messages",
|
|
884
1057
|
"todos",
|
|
885
|
-
"structuredResponse"
|
|
886
|
-
"files"
|
|
1058
|
+
"structuredResponse"
|
|
887
1059
|
];
|
|
888
1060
|
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.";
|
|
889
1061
|
function getTaskToolDescription(subagentDescriptions) {
|
|
@@ -1322,7 +1494,7 @@ const MEMORY_SYSTEM_PROMPT = `<agent_memory>
|
|
|
1322
1494
|
function formatMemoryContents(contents, sources) {
|
|
1323
1495
|
if (Object.keys(contents).length === 0) return "(No memory loaded)";
|
|
1324
1496
|
const sections = [];
|
|
1325
|
-
for (const path
|
|
1497
|
+
for (const path of sources) if (contents[path]) sections.push(`${path}\n${contents[path]}`);
|
|
1326
1498
|
if (sections.length === 0) return "(No memory loaded)";
|
|
1327
1499
|
return sections.join("\n\n");
|
|
1328
1500
|
}
|
|
@@ -1333,18 +1505,18 @@ function formatMemoryContents(contents, sources) {
|
|
|
1333
1505
|
* @param path - Path to the AGENTS.md file.
|
|
1334
1506
|
* @returns File content if found, null otherwise.
|
|
1335
1507
|
*/
|
|
1336
|
-
async function loadMemoryFromBackend(backend, path
|
|
1508
|
+
async function loadMemoryFromBackend(backend, path) {
|
|
1337
1509
|
if (!backend.downloadFiles) {
|
|
1338
|
-
const content = await backend.read(path
|
|
1510
|
+
const content = await backend.read(path);
|
|
1339
1511
|
if (content.startsWith("Error:")) return null;
|
|
1340
1512
|
return content;
|
|
1341
1513
|
}
|
|
1342
|
-
const results = await backend.downloadFiles([path
|
|
1343
|
-
if (results.length !== 1) throw new Error(`Expected 1 response for path ${path
|
|
1514
|
+
const results = await backend.downloadFiles([path]);
|
|
1515
|
+
if (results.length !== 1) throw new Error(`Expected 1 response for path ${path}, got ${results.length}`);
|
|
1344
1516
|
const response = results[0];
|
|
1345
1517
|
if (response.error != null) {
|
|
1346
1518
|
if (response.error === "file_not_found") return null;
|
|
1347
|
-
throw new Error(`Failed to download ${path
|
|
1519
|
+
throw new Error(`Failed to download ${path}: ${response.error}`);
|
|
1348
1520
|
}
|
|
1349
1521
|
if (response.content != null) return new TextDecoder().decode(response.content);
|
|
1350
1522
|
return null;
|
|
@@ -1374,7 +1546,7 @@ function createMemoryMiddleware(options) {
|
|
|
1374
1546
|
/**
|
|
1375
1547
|
* Resolve backend from instance or factory.
|
|
1376
1548
|
*/
|
|
1377
|
-
function getBackend
|
|
1549
|
+
function getBackend(state) {
|
|
1378
1550
|
if (typeof backend === "function") return backend({ state });
|
|
1379
1551
|
return backend;
|
|
1380
1552
|
}
|
|
@@ -1383,13 +1555,13 @@ function createMemoryMiddleware(options) {
|
|
|
1383
1555
|
stateSchema: MemoryStateSchema,
|
|
1384
1556
|
async beforeAgent(state) {
|
|
1385
1557
|
if ("memoryContents" in state && state.memoryContents != null) return;
|
|
1386
|
-
const resolvedBackend = getBackend
|
|
1558
|
+
const resolvedBackend = getBackend(state);
|
|
1387
1559
|
const contents = {};
|
|
1388
|
-
for (const path
|
|
1389
|
-
const content = await loadMemoryFromBackend(resolvedBackend, path
|
|
1390
|
-
if (content) contents[path
|
|
1560
|
+
for (const path of sources) try {
|
|
1561
|
+
const content = await loadMemoryFromBackend(resolvedBackend, path);
|
|
1562
|
+
if (content) contents[path] = content;
|
|
1391
1563
|
} catch (error) {
|
|
1392
|
-
console.debug(`Failed to load memory from ${path
|
|
1564
|
+
console.debug(`Failed to load memory from ${path}:`, error);
|
|
1393
1565
|
}
|
|
1394
1566
|
return { memoryContents: contents };
|
|
1395
1567
|
},
|
|
@@ -1452,9 +1624,9 @@ const MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024;
|
|
|
1452
1624
|
const MAX_SKILL_NAME_LENGTH = 64;
|
|
1453
1625
|
const MAX_SKILL_DESCRIPTION_LENGTH = 1024;
|
|
1454
1626
|
/**
|
|
1455
|
-
*
|
|
1627
|
+
* Zod schema for a single skill metadata entry.
|
|
1456
1628
|
*/
|
|
1457
|
-
const
|
|
1629
|
+
const SkillMetadataEntrySchema = zod.z.object({
|
|
1458
1630
|
name: zod.z.string(),
|
|
1459
1631
|
description: zod.z.string(),
|
|
1460
1632
|
path: zod.z.string(),
|
|
@@ -1462,7 +1634,31 @@ const SkillsStateSchema = zod.z.object({ skillsMetadata: zod.z.array(zod.z.objec
|
|
|
1462
1634
|
compatibility: zod.z.string().nullable().optional(),
|
|
1463
1635
|
metadata: zod.z.record(zod.z.string(), zod.z.string()).optional(),
|
|
1464
1636
|
allowedTools: zod.z.array(zod.z.string()).optional()
|
|
1465
|
-
})
|
|
1637
|
+
});
|
|
1638
|
+
/**
|
|
1639
|
+
* Reducer for skillsMetadata that merges arrays from parallel subagents.
|
|
1640
|
+
* Skills are deduplicated by name, with later values overriding earlier ones.
|
|
1641
|
+
*
|
|
1642
|
+
* @param current - The current skillsMetadata array (from state)
|
|
1643
|
+
* @param update - The new skillsMetadata array (from a subagent update)
|
|
1644
|
+
* @returns Merged array with duplicates resolved by name (later values win)
|
|
1645
|
+
*/
|
|
1646
|
+
function skillsMetadataReducer(current, update) {
|
|
1647
|
+
if (!update || update.length === 0) return current || [];
|
|
1648
|
+
if (!current || current.length === 0) return update;
|
|
1649
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1650
|
+
for (const skill of current) merged.set(skill.name, skill);
|
|
1651
|
+
for (const skill of update) merged.set(skill.name, skill);
|
|
1652
|
+
return Array.from(merged.values());
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* State schema for skills middleware.
|
|
1656
|
+
* Uses ReducedValue for skillsMetadata to allow concurrent updates from parallel subagents.
|
|
1657
|
+
*/
|
|
1658
|
+
const SkillsStateSchema = new _langchain_langgraph.StateSchema({ skillsMetadata: new _langchain_langgraph.ReducedValue(zod.z.array(SkillMetadataEntrySchema).default(() => []), {
|
|
1659
|
+
inputSchema: zod.z.array(SkillMetadataEntrySchema).optional(),
|
|
1660
|
+
reducer: skillsMetadataReducer
|
|
1661
|
+
}) });
|
|
1466
1662
|
/**
|
|
1467
1663
|
* Skills System Documentation prompt template.
|
|
1468
1664
|
*/
|
|
@@ -1589,7 +1785,7 @@ function parseSkillMetadataFromContent(content, skillPath, directoryName) {
|
|
|
1589
1785
|
*/
|
|
1590
1786
|
async function listSkillsFromBackend(backend, sourcePath) {
|
|
1591
1787
|
const skills = [];
|
|
1592
|
-
const normalizedPath = sourcePath.endsWith("/") ? sourcePath : `${sourcePath}/`;
|
|
1788
|
+
const normalizedPath = sourcePath.endsWith("/") || sourcePath.endsWith("\\") ? sourcePath : `${sourcePath}/`;
|
|
1593
1789
|
let fileInfos;
|
|
1594
1790
|
try {
|
|
1595
1791
|
fileInfos = await backend.lsInfo(normalizedPath);
|
|
@@ -1597,7 +1793,7 @@ async function listSkillsFromBackend(backend, sourcePath) {
|
|
|
1597
1793
|
return [];
|
|
1598
1794
|
}
|
|
1599
1795
|
const entries = fileInfos.map((info) => ({
|
|
1600
|
-
name: info.path.replace(
|
|
1796
|
+
name: info.path.replace(/[/\\]$/, "").split(/[/\\]/).pop() || "",
|
|
1601
1797
|
type: info.is_dir ? "directory" : "file"
|
|
1602
1798
|
}));
|
|
1603
1799
|
for (const entry of entries) {
|
|
@@ -1622,21 +1818,29 @@ async function listSkillsFromBackend(backend, sourcePath) {
|
|
|
1622
1818
|
}
|
|
1623
1819
|
/**
|
|
1624
1820
|
* Format skills locations for display in system prompt.
|
|
1821
|
+
* Shows priority indicator for the last source (highest priority).
|
|
1625
1822
|
*/
|
|
1626
1823
|
function formatSkillsLocations(sources) {
|
|
1627
1824
|
if (sources.length === 0) return "**Skills Sources:** None configured";
|
|
1628
|
-
const lines = [
|
|
1629
|
-
for (
|
|
1825
|
+
const lines = [];
|
|
1826
|
+
for (let i = 0; i < sources.length; i++) {
|
|
1827
|
+
const sourcePath = sources[i];
|
|
1828
|
+
const name = sourcePath.replace(/[/\\]$/, "").split(/[/\\]/).filter(Boolean).pop()?.replace(/^./, (c) => c.toUpperCase()) || "Skills";
|
|
1829
|
+
const suffix = i === sources.length - 1 ? " (higher priority)" : "";
|
|
1830
|
+
lines.push(`**${name} Skills**: \`${sourcePath}\`${suffix}`);
|
|
1831
|
+
}
|
|
1630
1832
|
return lines.join("\n");
|
|
1631
1833
|
}
|
|
1632
1834
|
/**
|
|
1633
1835
|
* Format skills metadata for display in system prompt.
|
|
1836
|
+
* Shows allowed tools for each skill if specified.
|
|
1634
1837
|
*/
|
|
1635
1838
|
function formatSkillsList(skills, sources) {
|
|
1636
|
-
if (skills.length === 0) return `(No skills available yet.
|
|
1839
|
+
if (skills.length === 0) return `(No skills available yet. You can create skills in ${sources.map((s) => `\`${s}\``).join(" or ")})`;
|
|
1637
1840
|
const lines = [];
|
|
1638
1841
|
for (const skill of skills) {
|
|
1639
1842
|
lines.push(`- **${skill.name}**: ${skill.description}`);
|
|
1843
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) lines.push(` → Allowed tools: ${skill.allowedTools.join(", ")}`);
|
|
1640
1844
|
lines.push(` → Read \`${skill.path}\` for full instructions`);
|
|
1641
1845
|
}
|
|
1642
1846
|
return lines.join("\n");
|
|
@@ -1666,7 +1870,7 @@ function createSkillsMiddleware(options) {
|
|
|
1666
1870
|
/**
|
|
1667
1871
|
* Resolve backend from instance or factory.
|
|
1668
1872
|
*/
|
|
1669
|
-
function getBackend
|
|
1873
|
+
function getBackend(state) {
|
|
1670
1874
|
if (typeof backend === "function") return backend({ state });
|
|
1671
1875
|
return backend;
|
|
1672
1876
|
}
|
|
@@ -1679,7 +1883,7 @@ function createSkillsMiddleware(options) {
|
|
|
1679
1883
|
loadedSkills = state.skillsMetadata;
|
|
1680
1884
|
return;
|
|
1681
1885
|
}
|
|
1682
|
-
const resolvedBackend = getBackend
|
|
1886
|
+
const resolvedBackend = getBackend(state);
|
|
1683
1887
|
const allSkills = /* @__PURE__ */ new Map();
|
|
1684
1888
|
for (const sourcePath of sources) try {
|
|
1685
1889
|
const skills = await listSkillsFromBackend(resolvedBackend, sourcePath);
|
|
@@ -1705,6 +1909,62 @@ function createSkillsMiddleware(options) {
|
|
|
1705
1909
|
});
|
|
1706
1910
|
}
|
|
1707
1911
|
|
|
1912
|
+
//#endregion
|
|
1913
|
+
//#region src/middleware/utils.ts
|
|
1914
|
+
/**
|
|
1915
|
+
* Utility functions for middleware.
|
|
1916
|
+
*
|
|
1917
|
+
* This module provides shared helpers used across middleware implementations.
|
|
1918
|
+
*/
|
|
1919
|
+
|
|
1920
|
+
//#endregion
|
|
1921
|
+
//#region src/middleware/summarization.ts
|
|
1922
|
+
/**
|
|
1923
|
+
* Summarization middleware with backend support for conversation history offloading.
|
|
1924
|
+
*
|
|
1925
|
+
* This module extends the base LangChain summarization middleware with additional
|
|
1926
|
+
* backend-based features for persisting conversation history before summarization.
|
|
1927
|
+
*
|
|
1928
|
+
* ## Usage
|
|
1929
|
+
*
|
|
1930
|
+
* ```typescript
|
|
1931
|
+
* import { createSummarizationMiddleware } from "@anthropic/deepagents";
|
|
1932
|
+
* import { FilesystemBackend } from "@anthropic/deepagents";
|
|
1933
|
+
*
|
|
1934
|
+
* const backend = new FilesystemBackend({ rootDir: "/data" });
|
|
1935
|
+
*
|
|
1936
|
+
* const middleware = createSummarizationMiddleware({
|
|
1937
|
+
* model: "gpt-4o-mini",
|
|
1938
|
+
* backend,
|
|
1939
|
+
* trigger: { type: "fraction", value: 0.85 },
|
|
1940
|
+
* keep: { type: "fraction", value: 0.10 },
|
|
1941
|
+
* });
|
|
1942
|
+
*
|
|
1943
|
+
* const agent = createDeepAgent({ middleware: [middleware] });
|
|
1944
|
+
* ```
|
|
1945
|
+
*
|
|
1946
|
+
* ## Storage
|
|
1947
|
+
*
|
|
1948
|
+
* Offloaded messages are stored as markdown at `/conversation_history/{thread_id}.md`.
|
|
1949
|
+
*
|
|
1950
|
+
* Each summarization event appends a new section to this file, creating a running log
|
|
1951
|
+
* of all evicted messages.
|
|
1952
|
+
*
|
|
1953
|
+
* ## Relationship to LangChain Summarization Middleware
|
|
1954
|
+
*
|
|
1955
|
+
* The base `summarizationMiddleware` from `langchain` provides core summarization
|
|
1956
|
+
* functionality. This middleware adds:
|
|
1957
|
+
* - Backend-based conversation history offloading
|
|
1958
|
+
* - Tool argument truncation for old messages
|
|
1959
|
+
*
|
|
1960
|
+
* For simple use cases without backend offloading, use `summarizationMiddleware`
|
|
1961
|
+
* from `langchain` directly.
|
|
1962
|
+
*/
|
|
1963
|
+
/**
|
|
1964
|
+
* State schema for summarization middleware.
|
|
1965
|
+
*/
|
|
1966
|
+
const SummarizationStateSchema = zod.z.object({ _summarizationSessionId: zod.z.string().optional() });
|
|
1967
|
+
|
|
1708
1968
|
//#endregion
|
|
1709
1969
|
//#region src/backends/store.ts
|
|
1710
1970
|
/**
|
|
@@ -1806,13 +2066,13 @@ var StoreBackend = class {
|
|
|
1806
2066
|
* @returns List of FileInfo objects for files and directories directly in the directory.
|
|
1807
2067
|
* Directories have a trailing / in their path and is_dir=true.
|
|
1808
2068
|
*/
|
|
1809
|
-
async lsInfo(path
|
|
2069
|
+
async lsInfo(path) {
|
|
1810
2070
|
const store = this.getStore();
|
|
1811
2071
|
const namespace = this.getNamespace();
|
|
1812
2072
|
const items = await this.searchStorePaginated(store, namespace);
|
|
1813
2073
|
const infos = [];
|
|
1814
2074
|
const subdirs = /* @__PURE__ */ new Set();
|
|
1815
|
-
const normalizedPath = path
|
|
2075
|
+
const normalizedPath = path.endsWith("/") ? path : path + "/";
|
|
1816
2076
|
for (const item of items) {
|
|
1817
2077
|
const itemKey = String(item.key);
|
|
1818
2078
|
if (!itemKey.startsWith(normalizedPath)) continue;
|
|
@@ -1917,7 +2177,7 @@ var StoreBackend = class {
|
|
|
1917
2177
|
/**
|
|
1918
2178
|
* Structured search results or error string for invalid input.
|
|
1919
2179
|
*/
|
|
1920
|
-
async grepRaw(pattern, path
|
|
2180
|
+
async grepRaw(pattern, path = "/", glob = null) {
|
|
1921
2181
|
const store = this.getStore();
|
|
1922
2182
|
const namespace = this.getNamespace();
|
|
1923
2183
|
const items = await this.searchStorePaginated(store, namespace);
|
|
@@ -1927,12 +2187,12 @@ var StoreBackend = class {
|
|
|
1927
2187
|
} catch {
|
|
1928
2188
|
continue;
|
|
1929
2189
|
}
|
|
1930
|
-
return grepMatchesFromFiles(files, pattern, path
|
|
2190
|
+
return grepMatchesFromFiles(files, pattern, path, glob);
|
|
1931
2191
|
}
|
|
1932
2192
|
/**
|
|
1933
2193
|
* Structured glob matching returning FileInfo objects.
|
|
1934
2194
|
*/
|
|
1935
|
-
async globInfo(pattern, path
|
|
2195
|
+
async globInfo(pattern, path = "/") {
|
|
1936
2196
|
const store = this.getStore();
|
|
1937
2197
|
const namespace = this.getNamespace();
|
|
1938
2198
|
const items = await this.searchStorePaginated(store, namespace);
|
|
@@ -1942,7 +2202,7 @@ var StoreBackend = class {
|
|
|
1942
2202
|
} catch {
|
|
1943
2203
|
continue;
|
|
1944
2204
|
}
|
|
1945
|
-
const result = globSearchFiles(files, pattern, path
|
|
2205
|
+
const result = globSearchFiles(files, pattern, path);
|
|
1946
2206
|
if (result === "No files found") return [];
|
|
1947
2207
|
const paths = result.split("\n");
|
|
1948
2208
|
const infos = [];
|
|
@@ -1968,17 +2228,17 @@ var StoreBackend = class {
|
|
|
1968
2228
|
const store = this.getStore();
|
|
1969
2229
|
const namespace = this.getNamespace();
|
|
1970
2230
|
const responses = [];
|
|
1971
|
-
for (const [path
|
|
2231
|
+
for (const [path, content] of files) try {
|
|
1972
2232
|
const fileData = createFileData(new TextDecoder().decode(content));
|
|
1973
2233
|
const storeValue = this.convertFileDataToStoreValue(fileData);
|
|
1974
|
-
await store.put(namespace, path
|
|
2234
|
+
await store.put(namespace, path, storeValue);
|
|
1975
2235
|
responses.push({
|
|
1976
|
-
path
|
|
2236
|
+
path,
|
|
1977
2237
|
error: null
|
|
1978
2238
|
});
|
|
1979
2239
|
} catch {
|
|
1980
2240
|
responses.push({
|
|
1981
|
-
path
|
|
2241
|
+
path,
|
|
1982
2242
|
error: "invalid_path"
|
|
1983
2243
|
});
|
|
1984
2244
|
}
|
|
@@ -1994,11 +2254,11 @@ var StoreBackend = class {
|
|
|
1994
2254
|
const store = this.getStore();
|
|
1995
2255
|
const namespace = this.getNamespace();
|
|
1996
2256
|
const responses = [];
|
|
1997
|
-
for (const path
|
|
1998
|
-
const item = await store.get(namespace, path
|
|
2257
|
+
for (const path of paths) try {
|
|
2258
|
+
const item = await store.get(namespace, path);
|
|
1999
2259
|
if (!item) {
|
|
2000
2260
|
responses.push({
|
|
2001
|
-
path
|
|
2261
|
+
path,
|
|
2002
2262
|
content: null,
|
|
2003
2263
|
error: "file_not_found"
|
|
2004
2264
|
});
|
|
@@ -2007,13 +2267,13 @@ var StoreBackend = class {
|
|
|
2007
2267
|
const contentStr = fileDataToString(this.convertStoreItemToFileData(item));
|
|
2008
2268
|
const content = new TextEncoder().encode(contentStr);
|
|
2009
2269
|
responses.push({
|
|
2010
|
-
path
|
|
2270
|
+
path,
|
|
2011
2271
|
content,
|
|
2012
2272
|
error: null
|
|
2013
2273
|
});
|
|
2014
2274
|
} catch {
|
|
2015
2275
|
responses.push({
|
|
2016
|
-
path
|
|
2276
|
+
path,
|
|
2017
2277
|
content: null,
|
|
2018
2278
|
error: "file_not_found"
|
|
2019
2279
|
});
|
|
@@ -2574,9 +2834,9 @@ var CompositeBackend = class {
|
|
|
2574
2834
|
* @returns List of FileInfo objects with route prefixes added, for files and directories
|
|
2575
2835
|
* directly in the directory. Directories have a trailing / in their path and is_dir=true.
|
|
2576
2836
|
*/
|
|
2577
|
-
async lsInfo(path
|
|
2578
|
-
for (const [routePrefix, backend] of this.sortedRoutes) if (path
|
|
2579
|
-
const suffix = path
|
|
2837
|
+
async lsInfo(path) {
|
|
2838
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (path.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
2839
|
+
const suffix = path.substring(routePrefix.length);
|
|
2580
2840
|
const searchPath = suffix ? "/" + suffix : "/";
|
|
2581
2841
|
const infos = await backend.lsInfo(searchPath);
|
|
2582
2842
|
const prefixed = [];
|
|
@@ -2586,9 +2846,9 @@ var CompositeBackend = class {
|
|
|
2586
2846
|
});
|
|
2587
2847
|
return prefixed;
|
|
2588
2848
|
}
|
|
2589
|
-
if (path
|
|
2849
|
+
if (path === "/") {
|
|
2590
2850
|
const results = [];
|
|
2591
|
-
const defaultInfos = await this.default.lsInfo(path
|
|
2851
|
+
const defaultInfos = await this.default.lsInfo(path);
|
|
2592
2852
|
results.push(...defaultInfos);
|
|
2593
2853
|
for (const [routePrefix] of this.sortedRoutes) results.push({
|
|
2594
2854
|
path: routePrefix,
|
|
@@ -2599,7 +2859,7 @@ var CompositeBackend = class {
|
|
|
2599
2859
|
results.sort((a, b) => a.path.localeCompare(b.path));
|
|
2600
2860
|
return results;
|
|
2601
2861
|
}
|
|
2602
|
-
return await this.default.lsInfo(path
|
|
2862
|
+
return await this.default.lsInfo(path);
|
|
2603
2863
|
}
|
|
2604
2864
|
/**
|
|
2605
2865
|
* Read file content, routing to appropriate backend.
|
|
@@ -2626,9 +2886,9 @@ var CompositeBackend = class {
|
|
|
2626
2886
|
/**
|
|
2627
2887
|
* Structured search results or error string for invalid input.
|
|
2628
2888
|
*/
|
|
2629
|
-
async grepRaw(pattern, path
|
|
2630
|
-
for (const [routePrefix, backend] of this.sortedRoutes) if (path
|
|
2631
|
-
const searchPath = path
|
|
2889
|
+
async grepRaw(pattern, path = "/", glob = null) {
|
|
2890
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (path.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
2891
|
+
const searchPath = path.substring(routePrefix.length - 1);
|
|
2632
2892
|
const raw = await backend.grepRaw(pattern, searchPath || "/", glob);
|
|
2633
2893
|
if (typeof raw === "string") return raw;
|
|
2634
2894
|
return raw.map((m) => ({
|
|
@@ -2637,7 +2897,7 @@ var CompositeBackend = class {
|
|
|
2637
2897
|
}));
|
|
2638
2898
|
}
|
|
2639
2899
|
const allMatches = [];
|
|
2640
|
-
const rawDefault = await this.default.grepRaw(pattern, path
|
|
2900
|
+
const rawDefault = await this.default.grepRaw(pattern, path, glob);
|
|
2641
2901
|
if (typeof rawDefault === "string") return rawDefault;
|
|
2642
2902
|
allMatches.push(...rawDefault);
|
|
2643
2903
|
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
@@ -2653,16 +2913,16 @@ var CompositeBackend = class {
|
|
|
2653
2913
|
/**
|
|
2654
2914
|
* Structured glob matching returning FileInfo objects.
|
|
2655
2915
|
*/
|
|
2656
|
-
async globInfo(pattern, path
|
|
2916
|
+
async globInfo(pattern, path = "/") {
|
|
2657
2917
|
const results = [];
|
|
2658
|
-
for (const [routePrefix, backend] of this.sortedRoutes) if (path
|
|
2659
|
-
const searchPath = path
|
|
2918
|
+
for (const [routePrefix, backend] of this.sortedRoutes) if (path.startsWith(routePrefix.replace(/\/$/, ""))) {
|
|
2919
|
+
const searchPath = path.substring(routePrefix.length - 1);
|
|
2660
2920
|
return (await backend.globInfo(pattern, searchPath || "/")).map((fi) => ({
|
|
2661
2921
|
...fi,
|
|
2662
2922
|
path: routePrefix.slice(0, -1) + fi.path
|
|
2663
2923
|
}));
|
|
2664
2924
|
}
|
|
2665
|
-
const defaultInfos = await this.default.globInfo(pattern, path
|
|
2925
|
+
const defaultInfos = await this.default.globInfo(pattern, path);
|
|
2666
2926
|
results.push(...defaultInfos);
|
|
2667
2927
|
for (const [routePrefix, backend] of Object.entries(this.routes)) {
|
|
2668
2928
|
const infos = await backend.globInfo(pattern, "/");
|
|
@@ -2717,11 +2977,11 @@ var CompositeBackend = class {
|
|
|
2717
2977
|
* @returns List of FileUploadResponse objects, one per input file
|
|
2718
2978
|
*/
|
|
2719
2979
|
async uploadFiles(files) {
|
|
2720
|
-
const results =
|
|
2980
|
+
const results = Array.from({ length: files.length }, () => null);
|
|
2721
2981
|
const batchesByBackend = /* @__PURE__ */ new Map();
|
|
2722
2982
|
for (let idx = 0; idx < files.length; idx++) {
|
|
2723
|
-
const [path
|
|
2724
|
-
const [backend, strippedPath] = this.getBackendAndKey(path
|
|
2983
|
+
const [path, content] = files[idx];
|
|
2984
|
+
const [backend, strippedPath] = this.getBackendAndKey(path);
|
|
2725
2985
|
if (!batchesByBackend.has(backend)) batchesByBackend.set(backend, []);
|
|
2726
2986
|
batchesByBackend.get(backend).push({
|
|
2727
2987
|
idx,
|
|
@@ -2750,11 +3010,11 @@ var CompositeBackend = class {
|
|
|
2750
3010
|
* @returns List of FileDownloadResponse objects, one per input path
|
|
2751
3011
|
*/
|
|
2752
3012
|
async downloadFiles(paths) {
|
|
2753
|
-
const results =
|
|
3013
|
+
const results = Array.from({ length: paths.length }, () => null);
|
|
2754
3014
|
const batchesByBackend = /* @__PURE__ */ new Map();
|
|
2755
3015
|
for (let idx = 0; idx < paths.length; idx++) {
|
|
2756
|
-
const path
|
|
2757
|
-
const [backend, strippedPath] = this.getBackendAndKey(path
|
|
3016
|
+
const path = paths[idx];
|
|
3017
|
+
const [backend, strippedPath] = this.getBackendAndKey(path);
|
|
2758
3018
|
if (!batchesByBackend.has(backend)) batchesByBackend.set(backend, []);
|
|
2759
3019
|
batchesByBackend.get(backend).push({
|
|
2760
3020
|
idx,
|
|
@@ -3037,8 +3297,8 @@ var BaseSandbox = class {
|
|
|
3037
3297
|
* @param path - Absolute path to directory
|
|
3038
3298
|
* @returns List of FileInfo objects for files and directories directly in the directory.
|
|
3039
3299
|
*/
|
|
3040
|
-
async lsInfo(path
|
|
3041
|
-
const command = buildLsCommand(path
|
|
3300
|
+
async lsInfo(path) {
|
|
3301
|
+
const command = buildLsCommand(path);
|
|
3042
3302
|
const result = await this.execute(command);
|
|
3043
3303
|
if (result.exitCode !== 0) return [];
|
|
3044
3304
|
const infos = [];
|
|
@@ -3093,8 +3353,8 @@ var BaseSandbox = class {
|
|
|
3093
3353
|
/**
|
|
3094
3354
|
* Structured search results or error string for invalid input.
|
|
3095
3355
|
*/
|
|
3096
|
-
async grepRaw(pattern, path
|
|
3097
|
-
const command = buildGrepCommand(pattern, path
|
|
3356
|
+
async grepRaw(pattern, path = "/", glob = null) {
|
|
3357
|
+
const command = buildGrepCommand(pattern, path, glob);
|
|
3098
3358
|
const result = await this.execute(command);
|
|
3099
3359
|
if (result.exitCode === 1) {
|
|
3100
3360
|
if (result.output.includes("Invalid regex:")) return result.output.trim();
|
|
@@ -3114,8 +3374,8 @@ var BaseSandbox = class {
|
|
|
3114
3374
|
/**
|
|
3115
3375
|
* Structured glob matching returning FileInfo objects.
|
|
3116
3376
|
*/
|
|
3117
|
-
async globInfo(pattern, path
|
|
3118
|
-
const command = buildGlobCommand(path
|
|
3377
|
+
async globInfo(pattern, path = "/") {
|
|
3378
|
+
const command = buildGlobCommand(path, pattern);
|
|
3119
3379
|
const result = await this.execute(command);
|
|
3120
3380
|
const infos = [];
|
|
3121
3381
|
const lines = result.output.trim().split("\n").filter(Boolean);
|
|
@@ -3361,6 +3621,30 @@ function createSettings(options = {}) {
|
|
|
3361
3621
|
* and injects it into the system prompt. Memory is loaded from:
|
|
3362
3622
|
* - User memory: ~/.deepagents/{agent_name}/agent.md
|
|
3363
3623
|
* - Project memory: {project_root}/.deepagents/agent.md
|
|
3624
|
+
*
|
|
3625
|
+
* @deprecated Use `createMemoryMiddleware` from `./memory.js` instead.
|
|
3626
|
+
* This middleware uses direct filesystem access (Node.js fs module) which is not
|
|
3627
|
+
* portable across backends. The `createMemoryMiddleware` function uses the
|
|
3628
|
+
* `BackendProtocol` abstraction and follows the AGENTS.md specification.
|
|
3629
|
+
*
|
|
3630
|
+
* Migration example:
|
|
3631
|
+
* ```typescript
|
|
3632
|
+
* // Before (deprecated):
|
|
3633
|
+
* import { createAgentMemoryMiddleware } from "./agent-memory.js";
|
|
3634
|
+
* const middleware = createAgentMemoryMiddleware({ settings, assistantId });
|
|
3635
|
+
*
|
|
3636
|
+
* // After (recommended):
|
|
3637
|
+
* import { createMemoryMiddleware } from "./memory.js";
|
|
3638
|
+
* import { FilesystemBackend } from "../backends/filesystem.js";
|
|
3639
|
+
*
|
|
3640
|
+
* const middleware = createMemoryMiddleware({
|
|
3641
|
+
* backend: new FilesystemBackend({ rootDir: "/" }),
|
|
3642
|
+
* sources: [
|
|
3643
|
+
* `~/.deepagents/${assistantId}/AGENTS.md`,
|
|
3644
|
+
* `${projectRoot}/.deepagents/AGENTS.md`,
|
|
3645
|
+
* ],
|
|
3646
|
+
* });
|
|
3647
|
+
* ```
|
|
3364
3648
|
*/
|
|
3365
3649
|
/**
|
|
3366
3650
|
* State schema for agent memory middleware.
|
|
@@ -3506,6 +3790,9 @@ write_file '{project_deepagents_dir}/agent.md' ... # Create project memory file
|
|
|
3506
3790
|
*
|
|
3507
3791
|
* @param options - Configuration options
|
|
3508
3792
|
* @returns AgentMiddleware for memory loading and injection
|
|
3793
|
+
*
|
|
3794
|
+
* @deprecated Use `createMemoryMiddleware` from `./memory.js` instead.
|
|
3795
|
+
* This function uses direct filesystem access which limits portability.
|
|
3509
3796
|
*/
|
|
3510
3797
|
function createAgentMemoryMiddleware(options) {
|
|
3511
3798
|
const { settings, assistantId, systemPromptTemplate } = options;
|