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 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$4) {
209
- const pathStr = path$4 || "/";
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$4 = "/") {
249
+ function globSearchFiles(files, pattern, path = "/") {
232
250
  let normalizedPath;
233
251
  try {
234
- normalizedPath = validatePath(path$4);
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$4 = null, glob = null) {
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$4);
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$4) {
344
+ lsInfo(path) {
327
345
  const files = this.getFiles();
328
346
  const infos = [];
329
347
  const subdirs = /* @__PURE__ */ new Set();
330
- const normalizedPath = path$4.endsWith("/") ? path$4 : path$4 + "/";
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$4 = "/", glob = null) {
413
- return grepMatchesFromFiles(this.getFiles(), pattern, path$4, glob);
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$4 = "/") {
436
+ globInfo(pattern, path = "/") {
419
437
  const files = this.getFiles();
420
- const result = globSearchFiles(files, pattern, path$4);
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$4, content] of files) try {
449
- updates[path$4] = createFileData(new TextDecoder().decode(content));
466
+ for (const [path, content] of files) try {
467
+ updates[path] = createFileData(new TextDecoder().decode(content));
450
468
  responses.push({
451
- path: path$4,
469
+ path,
452
470
  error: null
453
471
  });
454
472
  } catch {
455
473
  responses.push({
456
- path: path$4,
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$4 of paths) {
474
- const fileData = files[path$4];
491
+ for (const path of paths) {
492
+ const fileData = files[path];
475
493
  if (!fileData) {
476
494
  responses.push({
477
- path: path$4,
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: path$4,
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
- * Merge file updates with support for deletions.
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(left, right) {
516
- if (left === void 0) {
517
- const result$1 = {};
518
- for (const [key, value] of Object.entries(right)) if (value !== null) result$1[key] = value;
519
- return result$1;
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 = { ...left };
522
- for (const [key, value] of Object.entries(right)) if (value === null) delete result[key];
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 = zod_v4.z.object({ files: zod_v4.z.record(zod_v4.z.string(), FileDataSchema).default({}).meta({ reducer: {
533
- fn: fileDataReducer,
534
- schema: zod_v4.z.record(zod_v4.z.string(), FileDataSchema.nullable())
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 = `You have access to a virtual filesystem. All file paths must start with a /.
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 = "List files and directories in a directory";
555
- const READ_FILE_TOOL_DESCRIPTION = "Read the contents of a file";
556
- const WRITE_FILE_TOOL_DESCRIPTION = "Write content to a new file. Returns an error if the file already exists";
557
- const EDIT_FILE_TOOL_DESCRIPTION = "Edit a file by replacing a specific string with a new string";
558
- const GLOB_TOOL_DESCRIPTION = "Find files matching a glob pattern (e.g., '**/*.py' for all Python files)";
559
- const GREP_TOOL_DESCRIPTION = "Search for a regex pattern in files. Returns matching files and line numbers";
560
- const EXECUTE_TOOL_DESCRIPTION = `Executes a given command in the sandbox environment with proper handling and security measures.
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
- - Commands run in an isolated sandbox environment
570
- - Returns combined stdout/stderr output with exit code
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
- - The command parameter is required
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: Avoid using search commands like find and grep. Use the grep, glob tools instead.
576
- - Avoid read tools like cat, head, tail - use read_file instead.
577
- - Use '&&' to chain dependent commands, ';' for independent commands
578
- - Try to use absolute paths to avoid cd`;
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$4 = input.path || "/";
596
- const infos = await resolvedBackend.lsInfo(path$4);
597
- if (infos.length === 0) return `No files found in ${path$4}`;
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: path$4 = "/" } = input;
712
- const infos = await resolvedBackend.globInfo(pattern, path$4);
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: path$4 = "/", glob = null } = input;
735
- const result = await resolvedBackend.grepRaw(pattern, path$4, glob);
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: toolTokenLimitBeforeEvict ? async (request, handler) => {
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 * 4) {
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: `Tool result too large (${Math.round(msg.content.length / 4)} tokens). Content saved to ${evictPath}`,
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
- } : void 0
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$4 of sources) if (contents[path$4]) sections.push(`${path$4}\n${contents[path$4]}`);
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$4) {
1508
+ async function loadMemoryFromBackend(backend, path) {
1337
1509
  if (!backend.downloadFiles) {
1338
- const content = await backend.read(path$4);
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$4]);
1343
- if (results.length !== 1) throw new Error(`Expected 1 response for path ${path$4}, got ${results.length}`);
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$4}: ${response.error}`);
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$1(state) {
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$1(state);
1558
+ const resolvedBackend = getBackend(state);
1387
1559
  const contents = {};
1388
- for (const path$4 of sources) try {
1389
- const content = await loadMemoryFromBackend(resolvedBackend, path$4);
1390
- if (content) contents[path$4] = content;
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$4}:`, error);
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
- * State schema for skills middleware.
1627
+ * Zod schema for a single skill metadata entry.
1456
1628
  */
1457
- const SkillsStateSchema = zod.z.object({ skillsMetadata: zod.z.array(zod.z.object({
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
- })).optional() });
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(/\/$/, "").split("/").pop() || "",
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 = ["**Skills Sources:**"];
1629
- for (const source of sources) lines.push(`- \`${source}\``);
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. Skills can be created in ${sources.join(" or ")})`;
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$1(state) {
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$1(state);
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$4) {
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$4.endsWith("/") ? path$4 : path$4 + "/";
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$4 = "/", glob = null) {
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$4, glob);
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$4 = "/") {
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$4);
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$4, content] of files) try {
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$4, storeValue);
2234
+ await store.put(namespace, path, storeValue);
1975
2235
  responses.push({
1976
- path: path$4,
2236
+ path,
1977
2237
  error: null
1978
2238
  });
1979
2239
  } catch {
1980
2240
  responses.push({
1981
- path: path$4,
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$4 of paths) try {
1998
- const item = await store.get(namespace, path$4);
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: path$4,
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: path$4,
2270
+ path,
2011
2271
  content,
2012
2272
  error: null
2013
2273
  });
2014
2274
  } catch {
2015
2275
  responses.push({
2016
- path: path$4,
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$4) {
2578
- for (const [routePrefix, backend] of this.sortedRoutes) if (path$4.startsWith(routePrefix.replace(/\/$/, ""))) {
2579
- const suffix = path$4.substring(routePrefix.length);
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$4 === "/") {
2849
+ if (path === "/") {
2590
2850
  const results = [];
2591
- const defaultInfos = await this.default.lsInfo(path$4);
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$4);
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$4 = "/", glob = null) {
2630
- for (const [routePrefix, backend] of this.sortedRoutes) if (path$4.startsWith(routePrefix.replace(/\/$/, ""))) {
2631
- const searchPath = path$4.substring(routePrefix.length - 1);
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$4, glob);
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$4 = "/") {
2916
+ async globInfo(pattern, path = "/") {
2657
2917
  const results = [];
2658
- for (const [routePrefix, backend] of this.sortedRoutes) if (path$4.startsWith(routePrefix.replace(/\/$/, ""))) {
2659
- const searchPath = path$4.substring(routePrefix.length - 1);
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$4);
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 = new Array(files.length).fill(null);
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$4, content] = files[idx];
2724
- const [backend, strippedPath] = this.getBackendAndKey(path$4);
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 = new Array(paths.length).fill(null);
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$4 = paths[idx];
2757
- const [backend, strippedPath] = this.getBackendAndKey(path$4);
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$4) {
3041
- const command = buildLsCommand(path$4);
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$4 = "/", glob = null) {
3097
- const command = buildGrepCommand(pattern, path$4, glob);
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$4 = "/") {
3118
- const command = buildGlobCommand(path$4, pattern);
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;