deepagents 1.8.5 → 1.8.7

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.js CHANGED
@@ -75,6 +75,17 @@ var SandboxError = class SandboxError extends Error {
75
75
  return typeof error === "object" && error !== null && error[SANDBOX_ERROR_SYMBOL] === true;
76
76
  }
77
77
  };
78
+ /**
79
+ * Resolve a backend instance or await a {@link BackendFactory}.
80
+ *
81
+ * Accepts {@link BackendRuntime} or {@link ToolRuntime} — store typing differs
82
+ * between LangGraph checkpoint stores and core `ToolRuntime`; factories receive
83
+ * a value that is structurally compatible at runtime.
84
+ */
85
+ async function resolveBackend(backend, runtime) {
86
+ if (typeof backend === "function") return await backend(runtime);
87
+ return backend;
88
+ }
78
89
  //#endregion
79
90
  //#region src/backends/utils.ts
80
91
  /**
@@ -356,15 +367,15 @@ function grepMatchesFromFiles(files, pattern, path = null, glob = null) {
356
367
  * for the middleware to apply via Command.
357
368
  */
358
369
  var StateBackend = class {
359
- stateAndStore;
360
- constructor(stateAndStore) {
361
- this.stateAndStore = stateAndStore;
370
+ runtime;
371
+ constructor(runtime) {
372
+ this.runtime = runtime;
362
373
  }
363
374
  /**
364
375
  * Get files from current state.
365
376
  */
366
377
  getFiles() {
367
- return this.stateAndStore.state.files || {};
378
+ return this.runtime.state.files || {};
368
379
  }
369
380
  /**
370
381
  * List files and directories in the specified directory (non-recursive).
@@ -616,6 +627,62 @@ indicate omitted lines in the middle of the content):
616
627
 
617
628
  {content_sample}`;
618
629
  /**
630
+ * Message template for evicted HumanMessages.
631
+ */
632
+ const TOO_LARGE_HUMAN_MSG = `Message content too large and was saved to the filesystem at: {file_path}
633
+
634
+ You can read the full content using the read_file tool with pagination (offset and limit parameters).
635
+
636
+ Here is a preview showing the head and tail of the content:
637
+
638
+ {content_sample}`;
639
+ /**
640
+ * Extract text content from a message.
641
+ *
642
+ * For string content, returns it directly. For array content (mixed block types
643
+ * like text + image), joins all text blocks. Returns empty string if no text found.
644
+ */
645
+ function extractTextFromMessage(message) {
646
+ if (typeof message.content === "string") return message.content;
647
+ if (Array.isArray(message.content)) return message.content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
648
+ return String(message.content);
649
+ }
650
+ /**
651
+ * Build replacement content for an evicted HumanMessage, preserving non-text blocks.
652
+ *
653
+ * For plain string content, returns the replacement text directly. For list content
654
+ * with mixed block types (e.g., text + image), replaces all text blocks with a single
655
+ * text block containing the replacement text while keeping non-text blocks intact.
656
+ */
657
+ function buildEvictedHumanContent(message, replacementText) {
658
+ if (typeof message.content === "string") return replacementText;
659
+ if (Array.isArray(message.content)) {
660
+ const mediaBlocks = message.content.filter((block) => typeof block === "object" && block !== null && block.type !== "text");
661
+ if (mediaBlocks.length === 0) return replacementText;
662
+ return [{
663
+ type: "text",
664
+ text: replacementText
665
+ }, ...mediaBlocks];
666
+ }
667
+ return replacementText;
668
+ }
669
+ /**
670
+ * Build a truncated HumanMessage for the model request.
671
+ *
672
+ * Computes a preview from the full content still in state and returns a
673
+ * lightweight replacement the model will see. Pure string computation — no
674
+ * backend I/O.
675
+ */
676
+ function buildTruncatedHumanMessage(message, filePath) {
677
+ const contentSample = createContentPreview(extractTextFromMessage(message));
678
+ return new HumanMessage({
679
+ content: buildEvictedHumanContent(message, TOO_LARGE_HUMAN_MSG.replace("{file_path}", filePath).replace("{content_sample}", contentSample)),
680
+ id: message.id,
681
+ additional_kwargs: { ...message.additional_kwargs },
682
+ response_metadata: { ...message.response_metadata }
683
+ });
684
+ }
685
+ /**
619
686
  * Create a preview of content showing head and tail with truncation marker.
620
687
  *
621
688
  * @param contentStr - The full content string to preview.
@@ -677,16 +744,6 @@ const FilesystemStateSchema = new StateSchema({ files: new ReducedValue(z.record
677
744
  inputSchema: z.record(z.string(), FileDataSchema.nullable()).optional(),
678
745
  reducer: fileDataReducer
679
746
  }) });
680
- /**
681
- * Resolve backend from factory or instance.
682
- *
683
- * @param backend - Backend instance or factory function
684
- * @param stateAndStore - State and store container for backend initialization
685
- */
686
- function getBackend(backend, stateAndStore) {
687
- if (typeof backend === "function") return backend(stateAndStore);
688
- return backend;
689
- }
690
747
  const FILESYSTEM_SYSTEM_PROMPT = `## Filesystem Tools \`ls\`, \`read_file\`, \`write_file\`, \`edit_file\`, \`glob\`, \`grep\`
691
748
 
692
749
  You have access to a filesystem which you can interact with using these tools.
@@ -804,11 +861,8 @@ Use this tool to run commands, scripts, tests, builds, and other shell operation
804
861
  */
805
862
  function createLsTool(backend, options) {
806
863
  const { customDescription } = options;
807
- return tool(async (input, config) => {
808
- const resolvedBackend = getBackend(backend, {
809
- state: getCurrentTaskInput(config),
810
- store: config.store
811
- });
864
+ return tool(async (input, runtime) => {
865
+ const resolvedBackend = await resolveBackend(backend, runtime);
812
866
  const path = input.path || "/";
813
867
  const infos = await resolvedBackend.lsInfo(path);
814
868
  if (infos.length === 0) return `No files found in ${path}`;
@@ -832,11 +886,8 @@ function createLsTool(backend, options) {
832
886
  */
833
887
  function createReadFileTool(backend, options) {
834
888
  const { customDescription, toolTokenLimitBeforeEvict } = options;
835
- return tool(async (input, config) => {
836
- const resolvedBackend = getBackend(backend, {
837
- state: getCurrentTaskInput(config),
838
- store: config.store
839
- });
889
+ return tool(async (input, runtime) => {
890
+ const resolvedBackend = await resolveBackend(backend, runtime);
840
891
  const { file_path, offset = 0, limit = 100 } = input;
841
892
  let result = await resolvedBackend.read(file_path, offset, limit);
842
893
  const lines = result.split("\n");
@@ -862,17 +913,14 @@ function createReadFileTool(backend, options) {
862
913
  */
863
914
  function createWriteFileTool(backend, options) {
864
915
  const { customDescription } = options;
865
- return tool(async (input, config) => {
866
- const resolvedBackend = getBackend(backend, {
867
- state: getCurrentTaskInput(config),
868
- store: config.store
869
- });
916
+ return tool(async (input, runtime) => {
917
+ const resolvedBackend = await resolveBackend(backend, runtime);
870
918
  const { file_path, content } = input;
871
919
  const result = await resolvedBackend.write(file_path, content);
872
920
  if (result.error) return result.error;
873
921
  const message = new ToolMessage({
874
922
  content: `Successfully wrote to '${file_path}'`,
875
- tool_call_id: config.toolCall?.id,
923
+ tool_call_id: runtime.toolCall?.id,
876
924
  name: "write_file",
877
925
  metadata: result.metadata
878
926
  });
@@ -895,17 +943,14 @@ function createWriteFileTool(backend, options) {
895
943
  */
896
944
  function createEditFileTool(backend, options) {
897
945
  const { customDescription } = options;
898
- return tool(async (input, config) => {
899
- const resolvedBackend = getBackend(backend, {
900
- state: getCurrentTaskInput(config),
901
- store: config.store
902
- });
946
+ return tool(async (input, runtime) => {
947
+ const resolvedBackend = await resolveBackend(backend, runtime);
903
948
  const { file_path, old_string, new_string, replace_all = false } = input;
904
949
  const result = await resolvedBackend.edit(file_path, old_string, new_string, replace_all);
905
950
  if (result.error) return result.error;
906
951
  const message = new ToolMessage({
907
952
  content: `Successfully replaced ${result.occurrences} occurrence(s) in '${file_path}'`,
908
- tool_call_id: config.toolCall?.id,
953
+ tool_call_id: runtime.toolCall?.id,
909
954
  name: "edit_file",
910
955
  metadata: result.metadata
911
956
  });
@@ -930,11 +975,8 @@ function createEditFileTool(backend, options) {
930
975
  */
931
976
  function createGlobTool(backend, options) {
932
977
  const { customDescription } = options;
933
- return tool(async (input, config) => {
934
- const resolvedBackend = getBackend(backend, {
935
- state: getCurrentTaskInput(config),
936
- store: config.store
937
- });
978
+ return tool(async (input, runtime) => {
979
+ const resolvedBackend = await resolveBackend(backend, runtime);
938
980
  const { pattern, path = "/" } = input;
939
981
  const infos = await resolvedBackend.globInfo(pattern, path);
940
982
  if (infos.length === 0) return `No files found matching pattern '${pattern}'`;
@@ -955,11 +997,8 @@ function createGlobTool(backend, options) {
955
997
  */
956
998
  function createGrepTool(backend, options) {
957
999
  const { customDescription } = options;
958
- return tool(async (input, config) => {
959
- const resolvedBackend = getBackend(backend, {
960
- state: getCurrentTaskInput(config),
961
- store: config.store
962
- });
1000
+ return tool(async (input, runtime) => {
1001
+ const resolvedBackend = await resolveBackend(backend, runtime);
963
1002
  const { pattern, path = "/", glob = null } = input;
964
1003
  const result = await resolvedBackend.grepRaw(pattern, path, glob);
965
1004
  if (typeof result === "string") return result;
@@ -991,11 +1030,8 @@ function createGrepTool(backend, options) {
991
1030
  */
992
1031
  function createExecuteTool(backend, options) {
993
1032
  const { customDescription } = options;
994
- return tool(async (input, config) => {
995
- const resolvedBackend = getBackend(backend, {
996
- state: getCurrentTaskInput(config),
997
- store: config.store
998
- });
1033
+ return tool(async (input, runtime) => {
1034
+ const resolvedBackend = await resolveBackend(backend, runtime);
999
1035
  if (!isSandboxBackend(resolvedBackend)) return "Error: Execution not available. This agent's backend does not support command execution (SandboxBackendProtocol). To use the execute tool, provide a backend that implements SandboxBackendProtocol.";
1000
1036
  const result = await resolvedBackend.execute(input.command);
1001
1037
  const parts = [result.output];
@@ -1015,7 +1051,7 @@ function createExecuteTool(backend, options) {
1015
1051
  * Create filesystem middleware with all tools and features.
1016
1052
  */
1017
1053
  function createFilesystemMiddleware(options = {}) {
1018
- const { backend = (stateAndStore) => new StateBackend(stateAndStore), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4 } = options;
1054
+ const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4 } = options;
1019
1055
  const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;
1020
1056
  const allToolsByName = {
1021
1057
  ls: createLsTool(backend, { customDescription: customToolDescriptions?.ls }),
@@ -1033,19 +1069,53 @@ function createFilesystemMiddleware(options = {}) {
1033
1069
  name: "FilesystemMiddleware",
1034
1070
  stateSchema: FilesystemStateSchema,
1035
1071
  tools: Object.values(allToolsByName),
1072
+ async beforeAgent(state) {
1073
+ if (!humanMessageTokenLimitBeforeEvict) return;
1074
+ const messages = state.messages;
1075
+ if (!messages || messages.length === 0) return;
1076
+ const last = messages[messages.length - 1];
1077
+ if (!HumanMessage.isInstance(last)) return;
1078
+ if (last.additional_kwargs?.lc_evicted_to) return;
1079
+ const contentStr = extractTextFromMessage(last);
1080
+ const threshold = 4 * humanMessageTokenLimitBeforeEvict;
1081
+ if (contentStr.length <= threshold) return;
1082
+ const resolvedBackend = await resolveBackend(backend, { state: state || {} });
1083
+ const filePath = `/conversation_history/${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
1084
+ const writeResult = await resolvedBackend.write(filePath, contentStr);
1085
+ if (writeResult.error) return;
1086
+ const result = { messages: [new HumanMessage({
1087
+ content: last.content,
1088
+ id: last.id,
1089
+ additional_kwargs: {
1090
+ ...last.additional_kwargs,
1091
+ lc_evicted_to: filePath
1092
+ },
1093
+ response_metadata: { ...last.response_metadata }
1094
+ })] };
1095
+ if (writeResult.filesUpdate) result.files = writeResult.filesUpdate;
1096
+ return result;
1097
+ },
1036
1098
  wrapModelCall: async (request, handler) => {
1037
- const supportsExecution = isSandboxBackend(getBackend(backend, {
1038
- state: request.state || {},
1039
- store: request.runtime?.store
1099
+ const supportsExecution = isSandboxBackend(await resolveBackend(backend, {
1100
+ ...request.runtime,
1101
+ state: request.state
1040
1102
  }));
1041
1103
  let tools = request.tools;
1042
1104
  if (!supportsExecution) tools = tools.filter((t) => t.name !== "execute");
1043
1105
  let filesystemPrompt = baseSystemPrompt;
1044
1106
  if (supportsExecution) filesystemPrompt = `${filesystemPrompt}\n\n${EXECUTION_SYSTEM_PROMPT}`;
1045
1107
  const newSystemMessage = request.systemMessage.concat(filesystemPrompt);
1108
+ let messages = request.messages;
1109
+ if (humanMessageTokenLimitBeforeEvict && messages) {
1110
+ if (messages.some((msg) => HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to)) messages = messages.map((msg) => {
1111
+ if (HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to) return buildTruncatedHumanMessage(msg, msg.additional_kwargs.lc_evicted_to);
1112
+ return msg;
1113
+ });
1114
+ }
1046
1115
  return handler({
1047
1116
  ...request,
1048
1117
  tools,
1118
+ messages,
1049
1119
  systemMessage: newSystemMessage
1050
1120
  });
1051
1121
  },
@@ -1056,9 +1126,9 @@ function createFilesystemMiddleware(options = {}) {
1056
1126
  const result = await handler(request);
1057
1127
  async function processToolMessage(msg, toolTokenLimitBeforeEvict) {
1058
1128
  if (typeof msg.content === "string" && msg.content.length > toolTokenLimitBeforeEvict * 4) {
1059
- const resolvedBackend = getBackend(backend, {
1060
- state: request.state || {},
1061
- store: request.runtime?.store
1129
+ const resolvedBackend = await resolveBackend(backend, {
1130
+ ...request.runtime,
1131
+ state: request.state
1062
1132
  });
1063
1133
  const evictPath = `/large_tool_results/${sanitizeToolCallId(request.toolCall?.id || msg.tool_call_id)}`;
1064
1134
  const writeResult = await resolvedBackend.write(evictPath, msg.content);
@@ -1826,19 +1896,12 @@ async function loadMemoryFromBackend(backend, path) {
1826
1896
  */
1827
1897
  function createMemoryMiddleware(options) {
1828
1898
  const { backend, sources, addCacheControl = false } = options;
1829
- /**
1830
- * Resolve backend from instance or factory.
1831
- */
1832
- function getBackend(state) {
1833
- if (typeof backend === "function") return backend({ state });
1834
- return backend;
1835
- }
1836
1899
  return createMiddleware({
1837
1900
  name: "MemoryMiddleware",
1838
1901
  stateSchema: MemoryStateSchema,
1839
1902
  async beforeAgent(state) {
1840
1903
  if ("memoryContents" in state && state.memoryContents != null) return;
1841
- const resolvedBackend = getBackend(state);
1904
+ const resolvedBackend = await resolveBackend(backend, { state });
1842
1905
  const contents = {};
1843
1906
  for (const path of sources) try {
1844
1907
  const content = await loadMemoryFromBackend(resolvedBackend, path);
@@ -2240,13 +2303,6 @@ function formatSkillsList(skills, sources) {
2240
2303
  function createSkillsMiddleware(options) {
2241
2304
  const { backend, sources } = options;
2242
2305
  let loadedSkills = [];
2243
- /**
2244
- * Resolve backend from instance or factory.
2245
- */
2246
- function getBackend(state) {
2247
- if (typeof backend === "function") return backend({ state });
2248
- return backend;
2249
- }
2250
2306
  return createMiddleware({
2251
2307
  name: "SkillsMiddleware",
2252
2308
  stateSchema: SkillsStateSchema,
@@ -2256,7 +2312,7 @@ function createSkillsMiddleware(options) {
2256
2312
  loadedSkills = state.skillsMetadata;
2257
2313
  return;
2258
2314
  }
2259
- const resolvedBackend = getBackend(state);
2315
+ const resolvedBackend = await resolveBackend(backend, { state });
2260
2316
  const allSkills = /* @__PURE__ */ new Map();
2261
2317
  for (const sourcePath of sources) try {
2262
2318
  const skills = await listSkillsFromBackend(resolvedBackend, sourcePath);
@@ -2473,13 +2529,6 @@ function createSummarizationMiddleware(options) {
2473
2529
  let sessionId = null;
2474
2530
  let tokenEstimationMultiplier = 1;
2475
2531
  /**
2476
- * Resolve backend from instance or factory.
2477
- */
2478
- function getBackend(state) {
2479
- if (typeof backend === "function") return backend({ state });
2480
- return backend;
2481
- }
2482
- /**
2483
2532
  * Get or create session ID for history file naming.
2484
2533
  */
2485
2534
  function getSessionId(state) {
@@ -2845,7 +2894,7 @@ ${summary}
2845
2894
  * the file path, and the state cutoff index.
2846
2895
  */
2847
2896
  async function summarizeMessages(messagesToSummarize, resolvedModel, state, previousCutoffIndex, cutoffIndex) {
2848
- const filePath = await offloadToBackend(getBackend(state), messagesToSummarize, state);
2897
+ const filePath = await offloadToBackend(await resolveBackend(backend, { state }), messagesToSummarize, state);
2849
2898
  if (filePath === null) console.warn(`[SummarizationMiddleware] Backend offload failed during summarization. Proceeding with summary generation.`);
2850
2899
  return {
2851
2900
  summaryMessage: buildSummaryMessage(await createSummary(messagesToSummarize, resolvedModel), filePath),
@@ -4926,9 +4975,9 @@ var LangSmithSandbox = class LangSmithSandbox extends BaseSandbox {
4926
4975
  * ```
4927
4976
  */
4928
4977
  static async create(options = {}) {
4929
- const { templateName = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout } = options;
4978
+ const { templateName = "deepagents", apiKey = process.env.LANGSMITH_API_KEY, defaultTimeout, ...createSandboxOptions } = options;
4930
4979
  return new LangSmithSandbox({
4931
- sandbox: await new SandboxClient({ apiKey }).createSandbox(templateName),
4980
+ sandbox: await new SandboxClient({ apiKey }).createSandbox(templateName, createSandboxOptions),
4932
4981
  defaultTimeout
4933
4982
  });
4934
4983
  }
@@ -5087,7 +5136,7 @@ function createDeepAgent(params = {}) {
5087
5136
  * Create backend configuration for filesystem middleware
5088
5137
  * If no backend is provided, use a factory that creates a StateBackend
5089
5138
  */
5090
- const filesystemBackend = backend ? backend : (config) => new StateBackend(config);
5139
+ const filesystemBackend = backend ? backend : (runtime) => new StateBackend(runtime);
5091
5140
  /**
5092
5141
  * Skills middleware (created conditionally for runtime use)
5093
5142
  */
@@ -5724,6 +5773,6 @@ function listSkills(options) {
5724
5773
  return Array.from(allSkills.values());
5725
5774
  }
5726
5775
  //#endregion
5727
- export { BaseSandbox, CompositeBackend, ConfigurationError, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, LangSmithSandbox, LocalShellBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, computeSummarizationDefaults, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata };
5776
+ export { BaseSandbox, CompositeBackend, ConfigurationError, DEFAULT_GENERAL_PURPOSE_DESCRIPTION, DEFAULT_SUBAGENT_PROMPT, FilesystemBackend, GENERAL_PURPOSE_SUBAGENT, LangSmithSandbox, LocalShellBackend, MAX_SKILL_DESCRIPTION_LENGTH, MAX_SKILL_FILE_SIZE, MAX_SKILL_NAME_LENGTH, SandboxError, StateBackend, StoreBackend, TASK_SYSTEM_PROMPT, computeSummarizationDefaults, createAgentMemoryMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSettings, createSkillsMiddleware, createSubAgentMiddleware, createSummarizationMiddleware, filesValue, findProjectRoot, isSandboxBackend, listSkills, parseSkillMetadata, resolveBackend };
5728
5777
 
5729
5778
  //# sourceMappingURL=index.js.map