@librechat/agents 3.1.56 → 3.1.60

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.
Files changed (214) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +326 -62
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +13 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +7 -27
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +303 -222
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +4 -4
  10. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +6 -2
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/init.cjs +60 -0
  14. package/dist/cjs/llm/init.cjs.map +1 -0
  15. package/dist/cjs/llm/invoke.cjs +90 -0
  16. package/dist/cjs/llm/invoke.cjs.map +1 -0
  17. package/dist/cjs/llm/openai/index.cjs +2 -0
  18. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  19. package/dist/cjs/llm/request.cjs +41 -0
  20. package/dist/cjs/llm/request.cjs.map +1 -0
  21. package/dist/cjs/main.cjs +40 -0
  22. package/dist/cjs/main.cjs.map +1 -1
  23. package/dist/cjs/messages/cache.cjs +76 -89
  24. package/dist/cjs/messages/cache.cjs.map +1 -1
  25. package/dist/cjs/messages/contextPruning.cjs +156 -0
  26. package/dist/cjs/messages/contextPruning.cjs.map +1 -0
  27. package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
  28. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
  29. package/dist/cjs/messages/core.cjs +23 -37
  30. package/dist/cjs/messages/core.cjs.map +1 -1
  31. package/dist/cjs/messages/format.cjs +156 -11
  32. package/dist/cjs/messages/format.cjs.map +1 -1
  33. package/dist/cjs/messages/prune.cjs +1161 -49
  34. package/dist/cjs/messages/prune.cjs.map +1 -1
  35. package/dist/cjs/messages/reducer.cjs +87 -0
  36. package/dist/cjs/messages/reducer.cjs.map +1 -0
  37. package/dist/cjs/run.cjs +81 -42
  38. package/dist/cjs/run.cjs.map +1 -1
  39. package/dist/cjs/stream.cjs +54 -7
  40. package/dist/cjs/stream.cjs.map +1 -1
  41. package/dist/cjs/summarization/index.cjs +75 -0
  42. package/dist/cjs/summarization/index.cjs.map +1 -0
  43. package/dist/cjs/summarization/node.cjs +663 -0
  44. package/dist/cjs/summarization/node.cjs.map +1 -0
  45. package/dist/cjs/tools/ToolNode.cjs +16 -8
  46. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  47. package/dist/cjs/tools/handlers.cjs +2 -0
  48. package/dist/cjs/tools/handlers.cjs.map +1 -1
  49. package/dist/cjs/utils/errors.cjs +115 -0
  50. package/dist/cjs/utils/errors.cjs.map +1 -0
  51. package/dist/cjs/utils/events.cjs +17 -0
  52. package/dist/cjs/utils/events.cjs.map +1 -1
  53. package/dist/cjs/utils/handlers.cjs +16 -0
  54. package/dist/cjs/utils/handlers.cjs.map +1 -1
  55. package/dist/cjs/utils/llm.cjs +10 -0
  56. package/dist/cjs/utils/llm.cjs.map +1 -1
  57. package/dist/cjs/utils/tokens.cjs +247 -14
  58. package/dist/cjs/utils/tokens.cjs.map +1 -1
  59. package/dist/cjs/utils/truncation.cjs +107 -0
  60. package/dist/cjs/utils/truncation.cjs.map +1 -0
  61. package/dist/esm/agents/AgentContext.mjs +325 -61
  62. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  63. package/dist/esm/common/enum.mjs +13 -0
  64. package/dist/esm/common/enum.mjs.map +1 -1
  65. package/dist/esm/events.mjs +8 -28
  66. package/dist/esm/events.mjs.map +1 -1
  67. package/dist/esm/graphs/Graph.mjs +307 -226
  68. package/dist/esm/graphs/Graph.mjs.map +1 -1
  69. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +4 -4
  70. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  71. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +6 -2
  72. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  73. package/dist/esm/llm/init.mjs +58 -0
  74. package/dist/esm/llm/init.mjs.map +1 -0
  75. package/dist/esm/llm/invoke.mjs +87 -0
  76. package/dist/esm/llm/invoke.mjs.map +1 -0
  77. package/dist/esm/llm/openai/index.mjs +2 -0
  78. package/dist/esm/llm/openai/index.mjs.map +1 -1
  79. package/dist/esm/llm/request.mjs +38 -0
  80. package/dist/esm/llm/request.mjs.map +1 -0
  81. package/dist/esm/main.mjs +13 -3
  82. package/dist/esm/main.mjs.map +1 -1
  83. package/dist/esm/messages/cache.mjs +76 -89
  84. package/dist/esm/messages/cache.mjs.map +1 -1
  85. package/dist/esm/messages/contextPruning.mjs +154 -0
  86. package/dist/esm/messages/contextPruning.mjs.map +1 -0
  87. package/dist/esm/messages/contextPruningSettings.mjs +50 -0
  88. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
  89. package/dist/esm/messages/core.mjs +23 -37
  90. package/dist/esm/messages/core.mjs.map +1 -1
  91. package/dist/esm/messages/format.mjs +156 -11
  92. package/dist/esm/messages/format.mjs.map +1 -1
  93. package/dist/esm/messages/prune.mjs +1158 -52
  94. package/dist/esm/messages/prune.mjs.map +1 -1
  95. package/dist/esm/messages/reducer.mjs +83 -0
  96. package/dist/esm/messages/reducer.mjs.map +1 -0
  97. package/dist/esm/run.mjs +82 -43
  98. package/dist/esm/run.mjs.map +1 -1
  99. package/dist/esm/stream.mjs +54 -7
  100. package/dist/esm/stream.mjs.map +1 -1
  101. package/dist/esm/summarization/index.mjs +73 -0
  102. package/dist/esm/summarization/index.mjs.map +1 -0
  103. package/dist/esm/summarization/node.mjs +659 -0
  104. package/dist/esm/summarization/node.mjs.map +1 -0
  105. package/dist/esm/tools/ToolNode.mjs +16 -8
  106. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  107. package/dist/esm/tools/handlers.mjs +2 -0
  108. package/dist/esm/tools/handlers.mjs.map +1 -1
  109. package/dist/esm/utils/errors.mjs +111 -0
  110. package/dist/esm/utils/errors.mjs.map +1 -0
  111. package/dist/esm/utils/events.mjs +17 -1
  112. package/dist/esm/utils/events.mjs.map +1 -1
  113. package/dist/esm/utils/handlers.mjs +16 -0
  114. package/dist/esm/utils/handlers.mjs.map +1 -1
  115. package/dist/esm/utils/llm.mjs +10 -1
  116. package/dist/esm/utils/llm.mjs.map +1 -1
  117. package/dist/esm/utils/tokens.mjs +245 -15
  118. package/dist/esm/utils/tokens.mjs.map +1 -1
  119. package/dist/esm/utils/truncation.mjs +102 -0
  120. package/dist/esm/utils/truncation.mjs.map +1 -0
  121. package/dist/types/agents/AgentContext.d.ts +124 -6
  122. package/dist/types/common/enum.d.ts +14 -1
  123. package/dist/types/graphs/Graph.d.ts +22 -27
  124. package/dist/types/index.d.ts +5 -0
  125. package/dist/types/llm/init.d.ts +18 -0
  126. package/dist/types/llm/invoke.d.ts +48 -0
  127. package/dist/types/llm/request.d.ts +14 -0
  128. package/dist/types/messages/contextPruning.d.ts +42 -0
  129. package/dist/types/messages/contextPruningSettings.d.ts +44 -0
  130. package/dist/types/messages/core.d.ts +1 -1
  131. package/dist/types/messages/format.d.ts +17 -1
  132. package/dist/types/messages/index.d.ts +3 -0
  133. package/dist/types/messages/prune.d.ts +162 -1
  134. package/dist/types/messages/reducer.d.ts +18 -0
  135. package/dist/types/run.d.ts +12 -1
  136. package/dist/types/summarization/index.d.ts +20 -0
  137. package/dist/types/summarization/node.d.ts +29 -0
  138. package/dist/types/tools/ToolNode.d.ts +3 -1
  139. package/dist/types/types/graph.d.ts +44 -6
  140. package/dist/types/types/index.d.ts +1 -0
  141. package/dist/types/types/run.d.ts +30 -0
  142. package/dist/types/types/stream.d.ts +31 -4
  143. package/dist/types/types/summarize.d.ts +47 -0
  144. package/dist/types/types/tools.d.ts +7 -0
  145. package/dist/types/utils/errors.d.ts +28 -0
  146. package/dist/types/utils/events.d.ts +13 -0
  147. package/dist/types/utils/index.d.ts +2 -0
  148. package/dist/types/utils/llm.d.ts +4 -0
  149. package/dist/types/utils/tokens.d.ts +14 -1
  150. package/dist/types/utils/truncation.d.ts +49 -0
  151. package/package.json +2 -2
  152. package/src/agents/AgentContext.ts +388 -58
  153. package/src/agents/__tests__/AgentContext.test.ts +265 -5
  154. package/src/common/enum.ts +13 -0
  155. package/src/events.ts +9 -39
  156. package/src/graphs/Graph.ts +468 -331
  157. package/src/index.ts +7 -0
  158. package/src/llm/anthropic/llm.spec.ts +3 -3
  159. package/src/llm/anthropic/utils/message_inputs.ts +6 -4
  160. package/src/llm/bedrock/llm.spec.ts +1 -1
  161. package/src/llm/bedrock/utils/message_inputs.ts +6 -2
  162. package/src/llm/init.ts +63 -0
  163. package/src/llm/invoke.ts +144 -0
  164. package/src/llm/request.ts +55 -0
  165. package/src/messages/__tests__/observationMasking.test.ts +221 -0
  166. package/src/messages/cache.ts +77 -102
  167. package/src/messages/contextPruning.ts +191 -0
  168. package/src/messages/contextPruningSettings.ts +90 -0
  169. package/src/messages/core.ts +32 -53
  170. package/src/messages/ensureThinkingBlock.test.ts +39 -39
  171. package/src/messages/format.ts +227 -15
  172. package/src/messages/formatAgentMessages.test.ts +511 -1
  173. package/src/messages/index.ts +3 -0
  174. package/src/messages/prune.ts +1548 -62
  175. package/src/messages/reducer.ts +22 -0
  176. package/src/run.ts +104 -51
  177. package/src/scripts/bedrock-merge-test.ts +1 -1
  178. package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
  179. package/src/scripts/test-thinking-handoff.ts +1 -1
  180. package/src/scripts/thinking-bedrock.ts +1 -1
  181. package/src/scripts/thinking.ts +1 -1
  182. package/src/specs/anthropic.simple.test.ts +1 -1
  183. package/src/specs/multi-agent-summarization.test.ts +396 -0
  184. package/src/specs/prune.test.ts +1196 -23
  185. package/src/specs/summarization-unit.test.ts +868 -0
  186. package/src/specs/summarization.test.ts +3810 -0
  187. package/src/specs/summarize-prune.test.ts +376 -0
  188. package/src/specs/thinking-handoff.test.ts +10 -10
  189. package/src/specs/thinking-prune.test.ts +7 -4
  190. package/src/specs/token-accounting-e2e.test.ts +1034 -0
  191. package/src/specs/token-accounting-pipeline.test.ts +882 -0
  192. package/src/specs/token-distribution-edge-case.test.ts +25 -26
  193. package/src/splitStream.test.ts +42 -33
  194. package/src/stream.ts +64 -11
  195. package/src/summarization/__tests__/aggregator.test.ts +153 -0
  196. package/src/summarization/__tests__/node.test.ts +708 -0
  197. package/src/summarization/__tests__/trigger.test.ts +50 -0
  198. package/src/summarization/index.ts +102 -0
  199. package/src/summarization/node.ts +982 -0
  200. package/src/tools/ToolNode.ts +25 -3
  201. package/src/types/graph.ts +62 -7
  202. package/src/types/index.ts +1 -0
  203. package/src/types/run.ts +32 -0
  204. package/src/types/stream.ts +45 -5
  205. package/src/types/summarize.ts +58 -0
  206. package/src/types/tools.ts +7 -0
  207. package/src/utils/errors.ts +117 -0
  208. package/src/utils/events.ts +31 -0
  209. package/src/utils/handlers.ts +18 -0
  210. package/src/utils/index.ts +2 -0
  211. package/src/utils/llm.ts +12 -0
  212. package/src/utils/tokens.ts +336 -18
  213. package/src/utils/truncation.ts +124 -0
  214. package/src/scripts/image.ts +0 -180
@@ -105,45 +105,6 @@ describe('ensureThinkingBlockInMessages', () => {
105
105
  );
106
106
  });
107
107
 
108
- test('should not modify AI message when reasoning_content is not the first block (Bedrock whitespace artifact)', () => {
109
- // Bedrock emits a "\n\n" text chunk before the thinking block,
110
- // pushing reasoning_content to content[1] instead of content[0].
111
- const messages = [
112
- new HumanMessage({ content: 'Do something' }),
113
- new AIMessage({
114
- content: [
115
- { type: 'text', text: '\n\n' },
116
- {
117
- type: ContentTypes.REASONING_CONTENT,
118
- reasoningText: { text: 'Let me think about this' },
119
- },
120
- { type: 'text', text: 'Let me help!' },
121
- ],
122
- tool_calls: [
123
- {
124
- id: 'call_bedrock',
125
- name: 'some_tool',
126
- args: { x: 1 },
127
- type: 'tool_call' as const,
128
- },
129
- ],
130
- }),
131
- new ToolMessage({
132
- content: 'tool result',
133
- tool_call_id: 'call_bedrock',
134
- }),
135
- ];
136
-
137
- const result = ensureThinkingBlockInMessages(messages, Providers.BEDROCK);
138
-
139
- expect(result).toHaveLength(3);
140
- expect(result[0]).toBeInstanceOf(HumanMessage);
141
- expect(result[1]).toBeInstanceOf(AIMessage);
142
- expect(result[2]).toBeInstanceOf(ToolMessage);
143
- // The AI message should be preserved, not converted to a HumanMessage
144
- expect(result[1].content).toEqual(messages[1].content);
145
- });
146
-
147
108
  test('should not convert follow-up tool calls in a thinking-enabled chain (Bedrock multi-step)', () => {
148
109
  // Bedrock reasoning models produce reasoning on the first AI response,
149
110
  // then subsequent tool calls in the same chain have content: "" with no
@@ -312,6 +273,45 @@ describe('ensureThinkingBlockInMessages', () => {
312
273
  expect(result[3]).toBeInstanceOf(AIMessage);
313
274
  });
314
275
 
276
+ test('should not modify AI message when reasoning_content is not the first block (Bedrock whitespace artifact)', () => {
277
+ // Bedrock emits a "\n\n" text chunk before the thinking block,
278
+ // pushing reasoning_content to content[1] instead of content[0].
279
+ const messages = [
280
+ new HumanMessage({ content: 'Do something' }),
281
+ new AIMessage({
282
+ content: [
283
+ { type: 'text', text: '\n\n' },
284
+ {
285
+ type: ContentTypes.REASONING_CONTENT,
286
+ reasoningText: { text: 'Let me think about this' },
287
+ },
288
+ { type: 'text', text: 'Let me help!' },
289
+ ],
290
+ tool_calls: [
291
+ {
292
+ id: 'call_bedrock',
293
+ name: 'some_tool',
294
+ args: { x: 1 },
295
+ type: 'tool_call' as const,
296
+ },
297
+ ],
298
+ }),
299
+ new ToolMessage({
300
+ content: 'tool result',
301
+ tool_call_id: 'call_bedrock',
302
+ }),
303
+ ];
304
+
305
+ const result = ensureThinkingBlockInMessages(messages, Providers.BEDROCK);
306
+
307
+ expect(result).toHaveLength(3);
308
+ expect(result[0]).toBeInstanceOf(HumanMessage);
309
+ expect(result[1]).toBeInstanceOf(AIMessage);
310
+ expect(result[2]).toBeInstanceOf(ToolMessage);
311
+ // The AI message should be preserved, not converted to a HumanMessage
312
+ expect(result[1].content).toEqual(messages[1].content);
313
+ });
314
+
315
315
  test('should not modify AI message with reasoning block and tool calls', () => {
316
316
  const messages = [
317
317
  new HumanMessage({ content: 'Calculate something' }),
@@ -13,11 +13,14 @@ import type {
13
13
  ExtendedMessageContent,
14
14
  MessageContentComplex,
15
15
  ReasoningContentText,
16
+ SummaryContentBlock,
16
17
  ToolCallContent,
17
18
  ToolCallPart,
18
19
  TPayload,
19
20
  TMessage,
20
21
  } from '@/types';
22
+ import type { RunnableConfig } from '@langchain/core/runnables';
23
+ import { emitAgentLog } from '@/utils/events';
21
24
  import { Providers, ContentTypes, Constants } from '@/common';
22
25
 
23
26
  interface MediaMessageParams {
@@ -380,7 +383,8 @@ function formatAssistantMessage(
380
383
  continue;
381
384
  } else if (
382
385
  part.type === ContentTypes.ERROR ||
383
- part.type === ContentTypes.AGENT_UPDATE
386
+ part.type === ContentTypes.AGENT_UPDATE ||
387
+ part.type === ContentTypes.SUMMARY
384
388
  ) {
385
389
  continue;
386
390
  } else {
@@ -415,6 +419,17 @@ function formatAssistantMessage(
415
419
  return formattedMessages;
416
420
  }
417
421
 
422
+ function getSourceMessageId(message: Partial<TMessage>): string | undefined {
423
+ const candidate =
424
+ (message as { messageId?: string }).messageId ??
425
+ (message as { id?: string }).id;
426
+ if (typeof candidate !== 'string') {
427
+ return undefined;
428
+ }
429
+ const normalized = candidate.trim();
430
+ return normalized.length > 0 ? normalized : undefined;
431
+ }
432
+
418
433
  /**
419
434
  * Labels all agent content for parallel patterns (fan-out/fan-in)
420
435
  * Groups consecutive content by agent and wraps with clear labels
@@ -677,6 +692,111 @@ function extractToolNamesFromSearchOutput(output: string): string[] {
677
692
  return [];
678
693
  }
679
694
 
695
+ type SummaryBoundary = {
696
+ messageIndex: number;
697
+ contentIndex: number;
698
+ text: string;
699
+ tokenCount: number;
700
+ };
701
+
702
+ function getLatestSummaryBoundary(
703
+ payload: TPayload
704
+ ): SummaryBoundary | undefined {
705
+ let summaryBoundary: SummaryBoundary | undefined;
706
+
707
+ for (let i = 0; i < payload.length; i++) {
708
+ const message = payload[i];
709
+ if (!Array.isArray(message.content)) {
710
+ continue;
711
+ }
712
+
713
+ for (let j = 0; j < message.content.length; j++) {
714
+ const part = message.content[j] as MessageContentComplex | undefined;
715
+ if (part == null || part.type !== ContentTypes.SUMMARY) {
716
+ continue;
717
+ }
718
+
719
+ const summaryPart = part as Partial<SummaryContentBlock> & {
720
+ text?: string;
721
+ };
722
+
723
+ // Try content array first (new format), then direct text (legacy format)
724
+ let summaryText = (summaryPart.content ?? [])
725
+ .map((block) =>
726
+ 'text' in block ? (block as { text: string }).text : ''
727
+ )
728
+ .join('')
729
+ .trim();
730
+
731
+ // Fallback: legacy format where text was a direct field on the block
732
+ if (summaryText.length === 0 && typeof summaryPart.text === 'string') {
733
+ summaryText = summaryPart.text.trim();
734
+ }
735
+
736
+ if (summaryText.length === 0) {
737
+ continue;
738
+ }
739
+
740
+ summaryBoundary = {
741
+ messageIndex: i,
742
+ contentIndex: j,
743
+ text: summaryText,
744
+ tokenCount:
745
+ typeof summaryPart.tokenCount === 'number' &&
746
+ Number.isFinite(summaryPart.tokenCount)
747
+ ? summaryPart.tokenCount
748
+ : 0,
749
+ };
750
+ }
751
+ }
752
+
753
+ return summaryBoundary;
754
+ }
755
+
756
+ function applySummaryBoundary(
757
+ message: Partial<TMessage>,
758
+ messageIndex: number,
759
+ summaryBoundary?: SummaryBoundary
760
+ ): Partial<TMessage> | null {
761
+ if (!summaryBoundary) {
762
+ return message;
763
+ }
764
+
765
+ if (messageIndex < summaryBoundary.messageIndex) {
766
+ return null;
767
+ }
768
+
769
+ if (
770
+ messageIndex !== summaryBoundary.messageIndex ||
771
+ !Array.isArray(message.content)
772
+ ) {
773
+ return message;
774
+ }
775
+
776
+ return {
777
+ ...message,
778
+ content: message.content.slice(summaryBoundary.contentIndex + 1),
779
+ };
780
+ }
781
+
782
+ function contentPartCharLength(part: MessageContentComplex): number {
783
+ const record = part as Record<string, unknown>;
784
+ let len = 0;
785
+ if (typeof record.text === 'string') {
786
+ len += record.text.length;
787
+ }
788
+ if (typeof record.thinking === 'string') {
789
+ len += record.thinking.length;
790
+ }
791
+ const { input } = record;
792
+ if (typeof input === 'string') {
793
+ len += input.length;
794
+ } else if (input != null && typeof input === 'object') {
795
+ len += JSON.stringify(input).length;
796
+ }
797
+ return len;
798
+ }
799
+
680
800
  /**
681
801
  * Formats an array of messages for LangChain, handling tool calls and creating ToolMessage instances.
682
802
  *
@@ -692,14 +812,39 @@ export const formatAgentMessages = (
692
812
  ): {
693
813
  messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>;
694
814
  indexTokenCountMap?: Record<number, number>;
815
+ /** Cross-run summary extracted from the payload. Should be forwarded to the
816
+ * agent run so it can be included in the system message via AgentContext. */
817
+ summary?: { text: string; tokenCount: number };
818
+ /** When a summary boundary sliced content from a message, the token count
819
+ * was proportionally reduced. Returned so the caller can log it. */
820
+ boundaryTokenAdjustment?: {
821
+ original: number;
822
+ adjusted: number;
823
+ remainingChars: number;
824
+ totalChars: number;
825
+ };
695
826
  } => {
696
827
  const messages: Array<
697
828
  HumanMessage | AIMessage | SystemMessage | ToolMessage
698
829
  > = [];
699
830
  // If indexTokenCountMap is provided, create a new map to track the updated indices
700
831
  const updatedIndexTokenCountMap: Record<number, number> = {};
832
+ let boundaryTokenAdjustment:
833
+ | {
834
+ original: number;
835
+ adjusted: number;
836
+ remainingChars: number;
837
+ totalChars: number;
838
+ }
839
+ | undefined;
701
840
  // Keep track of the mapping from original payload indices to result indices
702
841
  const indexMapping: Record<number, number[] | undefined> = {};
842
+ const summaryBoundary = getLatestSummaryBoundary(payload);
843
+
844
+ // Summary metadata is returned to the caller so it can be forwarded to the
845
+ // agent run and included in the single system message via AgentContext.
846
+ // We intentionally do NOT create a SystemMessage here — that would conflict
847
+ // with the agent's own system message (instructions + summary combined).
703
848
 
704
849
  /**
705
850
  * Create a mutable copy of the tools set that can be expanded dynamically.
@@ -710,21 +855,37 @@ export const formatAgentMessages = (
710
855
 
711
856
  // Process messages with tool conversion if tools set is provided
712
857
  for (let i = 0; i < payload.length; i++) {
713
- const message = payload[i];
858
+ const rawMessage = payload[i];
859
+ const sourceMessageId = getSourceMessageId(rawMessage);
860
+ let message = applySummaryBoundary(rawMessage, i, summaryBoundary);
861
+ if (!message) {
862
+ indexMapping[i] = [];
863
+ continue;
864
+ }
865
+
714
866
  // Q: Store the current length of messages to track where this payload message starts in the result?
715
867
  // const startIndex = messages.length;
716
868
  if (typeof message.content === 'string') {
717
- message.content = [
718
- { type: ContentTypes.TEXT, [ContentTypes.TEXT]: message.content },
719
- ];
869
+ message = {
870
+ ...message,
871
+ content: [
872
+ { type: ContentTypes.TEXT, [ContentTypes.TEXT]: message.content },
873
+ ],
874
+ };
875
+ } else if (Array.isArray(message.content) && message.content.length === 0) {
876
+ indexMapping[i] = [];
877
+ continue;
720
878
  }
879
+
721
880
  if (message.role !== 'assistant') {
722
- messages.push(
723
- formatMessage({
724
- message: message as MessageInput,
725
- langChain: true,
726
- }) as HumanMessage | AIMessage | SystemMessage
727
- );
881
+ const formattedMessage = formatMessage({
882
+ message: message as MessageInput,
883
+ langChain: true,
884
+ }) as HumanMessage | AIMessage | SystemMessage;
885
+ if (sourceMessageId != null && sourceMessageId !== '') {
886
+ formattedMessage.id = sourceMessageId;
887
+ }
888
+ messages.push(formattedMessage);
728
889
 
729
890
  // Update the index mapping for this message
730
891
  indexMapping[i] = [messages.length - 1];
@@ -743,7 +904,7 @@ export const formatAgentMessages = (
743
904
  let processedMessage = message;
744
905
  if (discoveredTools) {
745
906
  const content = message.content;
746
- if (content && Array.isArray(content)) {
907
+ if (content != null && Array.isArray(content)) {
747
908
  const filteredContent: typeof content = [];
748
909
  const invalidToolCallIds = new Set<string>();
749
910
  const invalidToolStrings: string[] = [];
@@ -866,8 +1027,12 @@ export const formatAgentMessages = (
866
1027
  }
867
1028
  }
868
1029
 
869
- // Process the assistant message using the helper function
870
1030
  const formattedMessages = formatAssistantMessage(processedMessage);
1031
+ if (sourceMessageId != null && sourceMessageId !== '') {
1032
+ for (const formattedMessage of formattedMessages) {
1033
+ formattedMessage.id = sourceMessageId;
1034
+ }
1035
+ }
871
1036
  messages.push(...formattedMessages);
872
1037
 
873
1038
  // Update the index mapping for this assistant message
@@ -887,12 +1052,46 @@ export const formatAgentMessages = (
887
1052
  originalIndex++
888
1053
  ) {
889
1054
  const resultIndices = indexMapping[originalIndex] || [];
890
- const tokenCount = indexTokenCountMap[originalIndex];
1055
+ let tokenCount = indexTokenCountMap[originalIndex];
891
1056
 
892
1057
  if (tokenCount === undefined) {
893
1058
  continue;
894
1059
  }
895
1060
 
1061
+ if (
1062
+ summaryBoundary &&
1063
+ originalIndex === summaryBoundary.messageIndex &&
1064
+ Array.isArray(payload[originalIndex].content)
1065
+ ) {
1066
+ const content = payload[originalIndex]
1067
+ .content as MessageContentComplex[];
1068
+ const { contentIndex } = summaryBoundary;
1069
+ if (contentIndex >= 0 && contentIndex < content.length - 1) {
1070
+ let totalCharLen = 0;
1071
+ let remainingCharLen = 0;
1072
+ for (let p = 0; p < content.length; p++) {
1073
+ const charLen = contentPartCharLength(content[p]);
1074
+ totalCharLen += charLen;
1075
+ if (p > contentIndex) {
1076
+ remainingCharLen += charLen;
1077
+ }
1078
+ }
1079
+ if (totalCharLen > 0) {
1080
+ const original = tokenCount;
1081
+ tokenCount = Math.max(
1082
+ 1,
1083
+ Math.round(tokenCount * (remainingCharLen / totalCharLen))
1084
+ );
1085
+ boundaryTokenAdjustment = {
1086
+ original,
1087
+ adjusted: tokenCount,
1088
+ remainingChars: remainingCharLen,
1089
+ totalChars: totalCharLen,
1090
+ };
1091
+ }
1092
+ }
1093
+ }
1094
+
896
1095
  const msgCount = resultIndices.length;
897
1096
  if (msgCount === 1) {
898
1097
  updatedIndexTokenCountMap[resultIndices[0]] = tokenCount;
@@ -969,6 +1168,10 @@ export const formatAgentMessages = (
969
1168
  indexTokenCountMap: indexTokenCountMap
970
1169
  ? updatedIndexTokenCountMap
971
1170
  : undefined,
1171
+ summary: summaryBoundary
1172
+ ? { text: summaryBoundary.text, tokenCount: summaryBoundary.tokenCount }
1173
+ : undefined,
1174
+ boundaryTokenAdjustment,
972
1175
  };
973
1176
  };
974
1177
 
@@ -1117,11 +1320,13 @@ function appendToolCalls(
1117
1320
  *
1118
1321
  * @param messages - Array of messages to process
1119
1322
  * @param provider - The provider being used (unused but kept for future compatibility)
1323
+ * @param config - Optional RunnableConfig for structured agent logging
1120
1324
  * @returns The messages array with tool sequences converted to buffer strings if necessary
1121
1325
  */
1122
1326
  export function ensureThinkingBlockInMessages(
1123
1327
  messages: BaseMessage[],
1124
- _provider: Providers
1328
+ _provider: Providers,
1329
+ config?: RunnableConfig
1125
1330
  ): BaseMessage[] {
1126
1331
  if (messages.length === 0) {
1127
1332
  return messages;
@@ -1232,6 +1437,13 @@ export function ensureThinkingBlockInMessages(
1232
1437
  }
1233
1438
 
1234
1439
  flushTextChunks(textChunks, parts);
1440
+ emitAgentLog(
1441
+ config,
1442
+ 'warn',
1443
+ 'format',
1444
+ 'ensureThinkingBlockInMessages: injecting [Previous agent context] HumanMessage' +
1445
+ ` (${parts.length} msgs at index ${i}, no thinking block in chain)`
1446
+ );
1235
1447
  result.push(new HumanMessage({ content: parts }));
1236
1448
  i = j;
1237
1449
  } else {