deepagents 1.8.6 → 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.d.cts CHANGED
@@ -475,6 +475,8 @@ interface FilesystemMiddlewareOptions {
475
475
  customToolDescriptions?: Record<string, string> | null;
476
476
  /** Optional token limit before evicting a tool result to the filesystem (default: 20000 tokens, ~80KB) */
477
477
  toolTokenLimitBeforeEvict?: number | null;
478
+ /** Optional token limit before evicting a HumanMessage to the filesystem (default: 50000 tokens, ~200KB) */
479
+ humanMessageTokenLimitBeforeEvict?: number | null;
478
480
  }
479
481
  /**
480
482
  * Create filesystem middleware with all tools and features.
package/dist/index.d.ts CHANGED
@@ -475,6 +475,8 @@ interface FilesystemMiddlewareOptions {
475
475
  customToolDescriptions?: Record<string, string> | null;
476
476
  /** Optional token limit before evicting a tool result to the filesystem (default: 20000 tokens, ~80KB) */
477
477
  toolTokenLimitBeforeEvict?: number | null;
478
+ /** Optional token limit before evicting a HumanMessage to the filesystem (default: 50000 tokens, ~200KB) */
479
+ humanMessageTokenLimitBeforeEvict?: number | null;
478
480
  }
479
481
  /**
480
482
  * Create filesystem middleware with all tools and features.
package/dist/index.js CHANGED
@@ -627,6 +627,62 @@ indicate omitted lines in the middle of the content):
627
627
 
628
628
  {content_sample}`;
629
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
+ /**
630
686
  * Create a preview of content showing head and tail with truncation marker.
631
687
  *
632
688
  * @param contentStr - The full content string to preview.
@@ -995,7 +1051,7 @@ function createExecuteTool(backend, options) {
995
1051
  * Create filesystem middleware with all tools and features.
996
1052
  */
997
1053
  function createFilesystemMiddleware(options = {}) {
998
- const { backend = (runtime) => new StateBackend(runtime), 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;
999
1055
  const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;
1000
1056
  const allToolsByName = {
1001
1057
  ls: createLsTool(backend, { customDescription: customToolDescriptions?.ls }),
@@ -1013,6 +1069,32 @@ function createFilesystemMiddleware(options = {}) {
1013
1069
  name: "FilesystemMiddleware",
1014
1070
  stateSchema: FilesystemStateSchema,
1015
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
+ },
1016
1098
  wrapModelCall: async (request, handler) => {
1017
1099
  const supportsExecution = isSandboxBackend(await resolveBackend(backend, {
1018
1100
  ...request.runtime,
@@ -1023,9 +1105,17 @@ function createFilesystemMiddleware(options = {}) {
1023
1105
  let filesystemPrompt = baseSystemPrompt;
1024
1106
  if (supportsExecution) filesystemPrompt = `${filesystemPrompt}\n\n${EXECUTION_SYSTEM_PROMPT}`;
1025
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
+ }
1026
1115
  return handler({
1027
1116
  ...request,
1028
1117
  tools,
1118
+ messages,
1029
1119
  systemMessage: newSystemMessage
1030
1120
  });
1031
1121
  },