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.cjs CHANGED
@@ -658,6 +658,62 @@ indicate omitted lines in the middle of the content):
658
658
 
659
659
  {content_sample}`;
660
660
  /**
661
+ * Message template for evicted HumanMessages.
662
+ */
663
+ const TOO_LARGE_HUMAN_MSG = `Message content too large and was saved to the filesystem at: {file_path}
664
+
665
+ You can read the full content using the read_file tool with pagination (offset and limit parameters).
666
+
667
+ Here is a preview showing the head and tail of the content:
668
+
669
+ {content_sample}`;
670
+ /**
671
+ * Extract text content from a message.
672
+ *
673
+ * For string content, returns it directly. For array content (mixed block types
674
+ * like text + image), joins all text blocks. Returns empty string if no text found.
675
+ */
676
+ function extractTextFromMessage(message) {
677
+ if (typeof message.content === "string") return message.content;
678
+ if (Array.isArray(message.content)) return message.content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
679
+ return String(message.content);
680
+ }
681
+ /**
682
+ * Build replacement content for an evicted HumanMessage, preserving non-text blocks.
683
+ *
684
+ * For plain string content, returns the replacement text directly. For list content
685
+ * with mixed block types (e.g., text + image), replaces all text blocks with a single
686
+ * text block containing the replacement text while keeping non-text blocks intact.
687
+ */
688
+ function buildEvictedHumanContent(message, replacementText) {
689
+ if (typeof message.content === "string") return replacementText;
690
+ if (Array.isArray(message.content)) {
691
+ const mediaBlocks = message.content.filter((block) => typeof block === "object" && block !== null && block.type !== "text");
692
+ if (mediaBlocks.length === 0) return replacementText;
693
+ return [{
694
+ type: "text",
695
+ text: replacementText
696
+ }, ...mediaBlocks];
697
+ }
698
+ return replacementText;
699
+ }
700
+ /**
701
+ * Build a truncated HumanMessage for the model request.
702
+ *
703
+ * Computes a preview from the full content still in state and returns a
704
+ * lightweight replacement the model will see. Pure string computation — no
705
+ * backend I/O.
706
+ */
707
+ function buildTruncatedHumanMessage(message, filePath) {
708
+ const contentSample = createContentPreview(extractTextFromMessage(message));
709
+ return new langchain.HumanMessage({
710
+ content: buildEvictedHumanContent(message, TOO_LARGE_HUMAN_MSG.replace("{file_path}", filePath).replace("{content_sample}", contentSample)),
711
+ id: message.id,
712
+ additional_kwargs: { ...message.additional_kwargs },
713
+ response_metadata: { ...message.response_metadata }
714
+ });
715
+ }
716
+ /**
661
717
  * Create a preview of content showing head and tail with truncation marker.
662
718
  *
663
719
  * @param contentStr - The full content string to preview.
@@ -1026,7 +1082,7 @@ function createExecuteTool(backend, options) {
1026
1082
  * Create filesystem middleware with all tools and features.
1027
1083
  */
1028
1084
  function createFilesystemMiddleware(options = {}) {
1029
- const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4 } = options;
1085
+ const { backend = (runtime) => new StateBackend(runtime), systemPrompt: customSystemPrompt = null, customToolDescriptions = null, toolTokenLimitBeforeEvict = 2e4, humanMessageTokenLimitBeforeEvict = 5e4 } = options;
1030
1086
  const baseSystemPrompt = customSystemPrompt || FILESYSTEM_SYSTEM_PROMPT;
1031
1087
  const allToolsByName = {
1032
1088
  ls: createLsTool(backend, { customDescription: customToolDescriptions?.ls }),
@@ -1044,6 +1100,32 @@ function createFilesystemMiddleware(options = {}) {
1044
1100
  name: "FilesystemMiddleware",
1045
1101
  stateSchema: FilesystemStateSchema,
1046
1102
  tools: Object.values(allToolsByName),
1103
+ async beforeAgent(state) {
1104
+ if (!humanMessageTokenLimitBeforeEvict) return;
1105
+ const messages = state.messages;
1106
+ if (!messages || messages.length === 0) return;
1107
+ const last = messages[messages.length - 1];
1108
+ if (!langchain.HumanMessage.isInstance(last)) return;
1109
+ if (last.additional_kwargs?.lc_evicted_to) return;
1110
+ const contentStr = extractTextFromMessage(last);
1111
+ const threshold = 4 * humanMessageTokenLimitBeforeEvict;
1112
+ if (contentStr.length <= threshold) return;
1113
+ const resolvedBackend = await resolveBackend(backend, { state: state || {} });
1114
+ const filePath = `/conversation_history/${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
1115
+ const writeResult = await resolvedBackend.write(filePath, contentStr);
1116
+ if (writeResult.error) return;
1117
+ const result = { messages: [new langchain.HumanMessage({
1118
+ content: last.content,
1119
+ id: last.id,
1120
+ additional_kwargs: {
1121
+ ...last.additional_kwargs,
1122
+ lc_evicted_to: filePath
1123
+ },
1124
+ response_metadata: { ...last.response_metadata }
1125
+ })] };
1126
+ if (writeResult.filesUpdate) result.files = writeResult.filesUpdate;
1127
+ return result;
1128
+ },
1047
1129
  wrapModelCall: async (request, handler) => {
1048
1130
  const supportsExecution = isSandboxBackend(await resolveBackend(backend, {
1049
1131
  ...request.runtime,
@@ -1054,9 +1136,17 @@ function createFilesystemMiddleware(options = {}) {
1054
1136
  let filesystemPrompt = baseSystemPrompt;
1055
1137
  if (supportsExecution) filesystemPrompt = `${filesystemPrompt}\n\n${EXECUTION_SYSTEM_PROMPT}`;
1056
1138
  const newSystemMessage = request.systemMessage.concat(filesystemPrompt);
1139
+ let messages = request.messages;
1140
+ if (humanMessageTokenLimitBeforeEvict && messages) {
1141
+ if (messages.some((msg) => langchain.HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to)) messages = messages.map((msg) => {
1142
+ if (langchain.HumanMessage.isInstance(msg) && msg.additional_kwargs?.lc_evicted_to) return buildTruncatedHumanMessage(msg, msg.additional_kwargs.lc_evicted_to);
1143
+ return msg;
1144
+ });
1145
+ }
1057
1146
  return handler({
1058
1147
  ...request,
1059
1148
  tools,
1149
+ messages,
1060
1150
  systemMessage: newSystemMessage
1061
1151
  });
1062
1152
  },