@librechat/agents 3.3.3 → 3.3.5

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 (274) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +2 -1
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/constants.cjs +21 -0
  4. package/dist/cjs/common/constants.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +10 -1
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +793 -111
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  11. package/dist/cjs/hooks/HookRegistry.cjs +25 -0
  12. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  13. package/dist/cjs/hooks/index.cjs +12 -0
  14. package/dist/cjs/hooks/index.cjs.map +1 -1
  15. package/dist/cjs/hooks/types.cjs +1 -0
  16. package/dist/cjs/hooks/types.cjs.map +1 -1
  17. package/dist/cjs/langfuse.cjs +8 -0
  18. package/dist/cjs/langfuse.cjs.map +1 -1
  19. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
  20. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  21. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +38 -13
  22. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  23. package/dist/cjs/llm/google/utils/common.cjs +19 -7
  24. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  25. package/dist/cjs/llm/invoke.cjs +331 -5
  26. package/dist/cjs/llm/invoke.cjs.map +1 -1
  27. package/dist/cjs/llm/openai/index.cjs +41 -5
  28. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  29. package/dist/cjs/llm/openai/utils/index.cjs +25 -4
  30. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  31. package/dist/cjs/llm/openrouter/index.cjs +4 -1
  32. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  33. package/dist/cjs/llm/preempt.cjs +132 -0
  34. package/dist/cjs/llm/preempt.cjs.map +1 -0
  35. package/dist/cjs/main.cjs +36 -5
  36. package/dist/cjs/messages/alternation.cjs +92 -0
  37. package/dist/cjs/messages/alternation.cjs.map +1 -0
  38. package/dist/cjs/messages/cache.cjs +54 -33
  39. package/dist/cjs/messages/cache.cjs.map +1 -1
  40. package/dist/cjs/messages/contextPruning.cjs +17 -43
  41. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  42. package/dist/cjs/messages/core.cjs +315 -23
  43. package/dist/cjs/messages/core.cjs.map +1 -1
  44. package/dist/cjs/messages/format.cjs +290 -60
  45. package/dist/cjs/messages/format.cjs.map +1 -1
  46. package/dist/cjs/messages/handoffCue.cjs +63 -0
  47. package/dist/cjs/messages/handoffCue.cjs.map +1 -0
  48. package/dist/cjs/messages/index.cjs +4 -1
  49. package/dist/cjs/messages/injected.cjs +60 -0
  50. package/dist/cjs/messages/injected.cjs.map +1 -0
  51. package/dist/cjs/messages/prune.cjs +503 -127
  52. package/dist/cjs/messages/prune.cjs.map +1 -1
  53. package/dist/cjs/run.cjs +80 -7
  54. package/dist/cjs/run.cjs.map +1 -1
  55. package/dist/cjs/session/handlers.cjs +18 -9
  56. package/dist/cjs/session/handlers.cjs.map +1 -1
  57. package/dist/cjs/stream.cjs +35 -10
  58. package/dist/cjs/stream.cjs.map +1 -1
  59. package/dist/cjs/summarization/node.cjs +28 -14
  60. package/dist/cjs/summarization/node.cjs.map +1 -1
  61. package/dist/cjs/tools/ToolNode.cjs +56 -63
  62. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  63. package/dist/cjs/tools/search/keenable-scraper.cjs +90 -0
  64. package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -0
  65. package/dist/cjs/tools/search/tool.cjs +9 -1
  66. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  67. package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
  68. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  69. package/dist/cjs/utils/index.cjs +1 -1
  70. package/dist/cjs/utils/llm.cjs +1 -1
  71. package/dist/cjs/utils/llm.cjs.map +1 -1
  72. package/dist/cjs/utils/tokens.cjs +307 -20
  73. package/dist/cjs/utils/tokens.cjs.map +1 -1
  74. package/dist/cjs/utils/toolContent.cjs +1514 -0
  75. package/dist/cjs/utils/toolContent.cjs.map +1 -0
  76. package/dist/esm/agents/AgentContext.mjs +2 -1
  77. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  78. package/dist/esm/common/constants.mjs +19 -1
  79. package/dist/esm/common/constants.mjs.map +1 -1
  80. package/dist/esm/events.mjs +10 -1
  81. package/dist/esm/events.mjs.map +1 -1
  82. package/dist/esm/graphs/Graph.mjs +798 -116
  83. package/dist/esm/graphs/Graph.mjs.map +1 -1
  84. package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
  85. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  86. package/dist/esm/hooks/HookRegistry.mjs +25 -0
  87. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  88. package/dist/esm/hooks/index.mjs +12 -1
  89. package/dist/esm/hooks/index.mjs.map +1 -1
  90. package/dist/esm/hooks/types.mjs +1 -0
  91. package/dist/esm/hooks/types.mjs.map +1 -1
  92. package/dist/esm/langfuse.mjs +9 -1
  93. package/dist/esm/langfuse.mjs.map +1 -1
  94. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
  95. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  96. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +38 -13
  97. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  98. package/dist/esm/llm/google/utils/common.mjs +19 -7
  99. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  100. package/dist/esm/llm/invoke.mjs +332 -8
  101. package/dist/esm/llm/invoke.mjs.map +1 -1
  102. package/dist/esm/llm/openai/index.mjs +41 -5
  103. package/dist/esm/llm/openai/index.mjs.map +1 -1
  104. package/dist/esm/llm/openai/utils/index.mjs +25 -4
  105. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  106. package/dist/esm/llm/openrouter/index.mjs +4 -1
  107. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  108. package/dist/esm/llm/preempt.mjs +131 -0
  109. package/dist/esm/llm/preempt.mjs.map +1 -0
  110. package/dist/esm/main.mjs +14 -11
  111. package/dist/esm/messages/alternation.mjs +91 -0
  112. package/dist/esm/messages/alternation.mjs.map +1 -0
  113. package/dist/esm/messages/cache.mjs +55 -34
  114. package/dist/esm/messages/cache.mjs.map +1 -1
  115. package/dist/esm/messages/contextPruning.mjs +17 -43
  116. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  117. package/dist/esm/messages/core.mjs +305 -24
  118. package/dist/esm/messages/core.mjs.map +1 -1
  119. package/dist/esm/messages/format.mjs +290 -61
  120. package/dist/esm/messages/format.mjs.map +1 -1
  121. package/dist/esm/messages/handoffCue.mjs +61 -0
  122. package/dist/esm/messages/handoffCue.mjs.map +1 -0
  123. package/dist/esm/messages/index.mjs +4 -1
  124. package/dist/esm/messages/injected.mjs +60 -0
  125. package/dist/esm/messages/injected.mjs.map +1 -0
  126. package/dist/esm/messages/prune.mjs +500 -128
  127. package/dist/esm/messages/prune.mjs.map +1 -1
  128. package/dist/esm/run.mjs +80 -7
  129. package/dist/esm/run.mjs.map +1 -1
  130. package/dist/esm/session/handlers.mjs +19 -10
  131. package/dist/esm/session/handlers.mjs.map +1 -1
  132. package/dist/esm/stream.mjs +34 -11
  133. package/dist/esm/stream.mjs.map +1 -1
  134. package/dist/esm/summarization/node.mjs +28 -14
  135. package/dist/esm/summarization/node.mjs.map +1 -1
  136. package/dist/esm/tools/ToolNode.mjs +56 -63
  137. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  138. package/dist/esm/tools/search/keenable-scraper.mjs +88 -0
  139. package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -0
  140. package/dist/esm/tools/search/tool.mjs +9 -1
  141. package/dist/esm/tools/search/tool.mjs.map +1 -1
  142. package/dist/esm/tools/toolOutputReferences.mjs +6 -0
  143. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  144. package/dist/esm/utils/index.mjs +1 -1
  145. package/dist/esm/utils/llm.mjs +1 -1
  146. package/dist/esm/utils/llm.mjs.map +1 -1
  147. package/dist/esm/utils/tokens.mjs +307 -21
  148. package/dist/esm/utils/tokens.mjs.map +1 -1
  149. package/dist/esm/utils/toolContent.mjs +1503 -0
  150. package/dist/esm/utils/toolContent.mjs.map +1 -0
  151. package/dist/types/common/constants.d.ts +18 -0
  152. package/dist/types/graphs/Graph.d.ts +139 -2
  153. package/dist/types/hooks/HookRegistry.d.ts +15 -0
  154. package/dist/types/hooks/index.d.ts +12 -1
  155. package/dist/types/hooks/types.d.ts +45 -6
  156. package/dist/types/index.d.ts +1 -1
  157. package/dist/types/llm/invoke.d.ts +34 -1
  158. package/dist/types/llm/openai/index.d.ts +4 -0
  159. package/dist/types/llm/openai/utils/index.d.ts +2 -0
  160. package/dist/types/llm/openrouter/index.d.ts +5 -0
  161. package/dist/types/llm/preempt.d.ts +40 -0
  162. package/dist/types/messages/alternation.d.ts +23 -0
  163. package/dist/types/messages/contextPruning.d.ts +2 -1
  164. package/dist/types/messages/core.d.ts +36 -0
  165. package/dist/types/messages/format.d.ts +6 -0
  166. package/dist/types/messages/handoffCue.d.ts +40 -0
  167. package/dist/types/messages/index.d.ts +3 -0
  168. package/dist/types/messages/injected.d.ts +3 -0
  169. package/dist/types/messages/prune.d.ts +16 -12
  170. package/dist/types/run.d.ts +7 -0
  171. package/dist/types/stream.d.ts +14 -0
  172. package/dist/types/tools/ToolNode.d.ts +0 -7
  173. package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
  174. package/dist/types/tools/search/types.d.ts +31 -2
  175. package/dist/types/types/graph.d.ts +8 -1
  176. package/dist/types/types/run.d.ts +65 -0
  177. package/dist/types/types/stream.d.ts +1 -26
  178. package/dist/types/utils/tokens.d.ts +7 -0
  179. package/dist/types/utils/toolContent.d.ts +107 -0
  180. package/package.json +1 -1
  181. package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
  182. package/src/agents/AgentContext.ts +1 -0
  183. package/src/{splitStream.test.ts → aggregator.test.ts} +59 -666
  184. package/src/common/constants.ts +21 -0
  185. package/src/events.ts +15 -1
  186. package/src/graphs/Graph.ts +1261 -198
  187. package/src/graphs/MultiAgentGraph.ts +6 -1
  188. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1139 -4
  189. package/src/graphs/__tests__/Graph.preemptSignal.test.ts +126 -0
  190. package/src/hooks/HookRegistry.ts +40 -0
  191. package/src/hooks/__tests__/preemptBoundary.test.ts +152 -0
  192. package/src/hooks/index.ts +16 -2
  193. package/src/hooks/types.ts +47 -3
  194. package/src/index.ts +1 -1
  195. package/src/langfuse.ts +26 -1
  196. package/src/llm/anthropic/utils/message_inputs.ts +158 -12
  197. package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
  198. package/src/llm/bedrock/utils/message_inputs.test.ts +131 -1
  199. package/src/llm/bedrock/utils/message_inputs.ts +100 -24
  200. package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
  201. package/src/llm/google/utils/common.test.ts +75 -9
  202. package/src/llm/google/utils/common.ts +34 -3
  203. package/src/llm/invoke.alternation.test.ts +87 -0
  204. package/src/llm/invoke.handoffCue.test.ts +112 -0
  205. package/src/llm/invoke.test.ts +488 -0
  206. package/src/llm/invoke.ts +611 -8
  207. package/src/llm/openai/index.ts +106 -6
  208. package/src/llm/openai/utils/index.ts +109 -53
  209. package/src/llm/openai/utils/messages.test.ts +330 -1
  210. package/src/llm/openrouter/index.ts +12 -2
  211. package/src/llm/openrouter/reasoning.test.ts +314 -0
  212. package/src/llm/preempt.test.ts +323 -0
  213. package/src/llm/preempt.ts +178 -0
  214. package/src/messages/__tests__/observationMasking.test.ts +93 -2
  215. package/src/messages/alternation.test.ts +212 -0
  216. package/src/messages/alternation.ts +112 -0
  217. package/src/messages/cache.tail.test.ts +193 -0
  218. package/src/messages/cache.test.ts +113 -0
  219. package/src/messages/cache.ts +92 -60
  220. package/src/messages/contextPruning.test.ts +184 -0
  221. package/src/messages/contextPruning.ts +49 -42
  222. package/src/messages/core.ts +653 -40
  223. package/src/messages/ensureThinkingBlock.test.ts +49 -1
  224. package/src/messages/foldToollessToolBlocks.test.ts +163 -5
  225. package/src/messages/format.ts +454 -91
  226. package/src/messages/formatAgentMessages.steer.test.ts +267 -0
  227. package/src/messages/formatAgentMessages.test.ts +604 -14
  228. package/src/messages/formatAgentMessages.tools.test.ts +103 -0
  229. package/src/messages/handoffCue.test.ts +96 -0
  230. package/src/messages/handoffCue.ts +78 -0
  231. package/src/messages/index.ts +3 -0
  232. package/src/messages/injected.test.ts +90 -0
  233. package/src/messages/injected.ts +74 -0
  234. package/src/messages/prune.ts +996 -183
  235. package/src/run.ts +91 -6
  236. package/src/scripts/preempt-probe.ts +330 -0
  237. package/src/scripts/preempt-scenarios.ts +388 -0
  238. package/src/session/handlers.ts +32 -12
  239. package/src/specs/handoffCue.test.ts +165 -0
  240. package/src/specs/langfuse-callbacks.test.ts +352 -2
  241. package/src/specs/preemptSeal.test.ts +309 -0
  242. package/src/specs/prune.test.ts +1083 -6
  243. package/src/specs/summarization-unit.test.ts +105 -0
  244. package/src/specs/tokens.test.ts +609 -32
  245. package/src/stream.dispatch.test.ts +63 -0
  246. package/src/stream.ts +59 -23
  247. package/src/summarization/__tests__/node.test.ts +77 -0
  248. package/src/summarization/node.ts +46 -18
  249. package/src/tools/ToolNode.ts +150 -99
  250. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
  251. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
  252. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
  253. package/src/tools/__tests__/directToolHooks.test.ts +255 -7
  254. package/src/tools/search/keenable-scraper.test.ts +153 -0
  255. package/src/tools/search/keenable-scraper.ts +137 -0
  256. package/src/tools/search/tool.ts +13 -2
  257. package/src/tools/search/types.ts +50 -3
  258. package/src/tools/toolOutputReferences.ts +6 -0
  259. package/src/types/graph.ts +12 -1
  260. package/src/types/run.ts +67 -0
  261. package/src/types/stream.ts +1 -42
  262. package/src/utils/llm.test.ts +18 -0
  263. package/src/utils/llm.ts +4 -1
  264. package/src/utils/tokens.ts +586 -31
  265. package/src/utils/toolContent.test.ts +1432 -0
  266. package/src/utils/toolContent.ts +2707 -0
  267. package/dist/cjs/splitStream.cjs +0 -151
  268. package/dist/cjs/splitStream.cjs.map +0 -1
  269. package/dist/esm/splitStream.mjs +0 -150
  270. package/dist/esm/splitStream.mjs.map +0 -1
  271. package/dist/types/mockStream.d.ts +0 -32
  272. package/dist/types/splitStream.d.ts +0 -37
  273. package/src/mockStream.ts +0 -99
  274. package/src/splitStream.ts +0 -234
@@ -1,5 +1,7 @@
1
1
  import "../common/enum.mjs";
2
2
  import "../common/index.mjs";
3
+ import { HARD_MAX_TOOL_RESULT_CHARS } from "../utils/truncation.mjs";
4
+ import { compactToolContent, getToolContentCharLength, isAtomicToolContentBlock, serializeStructuredValueBounded } from "../utils/toolContent.mjs";
3
5
  import { toLangChainContent, toLangChainMessageFields } from "./langchain.mjs";
4
6
  import { normalizeAnthropicToolCallId } from "../llm/anthropic/utils/message_inputs.mjs";
5
7
  import { emitAgentLog } from "../utils/events.mjs";
@@ -175,6 +177,23 @@ function hasToolCallOutput(part) {
175
177
  const output = part.tool_call?.output;
176
178
  return output != null && output !== "";
177
179
  }
180
+ function formatToolCallOutput(output) {
181
+ if (output == null) return "";
182
+ return compactToolContent(output, HARD_MAX_TOOL_RESULT_CHARS).content;
183
+ }
184
+ /**
185
+ * Content for the synthetic assistant turn that separates a trailing steer
186
+ * from the next user turn. Non-empty by necessity — see the push site.
187
+ */
188
+ const STEER_ANCHOR_PLACEHOLDER = "_";
189
+ /**
190
+ * True when an assistant message replayed as a steer and nothing followed it,
191
+ * so the emitted run ends on the steer's `HumanMessage`.
192
+ */
193
+ function endsWithSteerMessage(formatted) {
194
+ if (formatted.length === 0) return false;
195
+ return formatted[formatted.length - 1].additional_kwargs.source === "steer";
196
+ }
178
197
  /**
179
198
  * Helper function to format an assistant message
180
199
  * @param message The message to format
@@ -312,7 +331,7 @@ function formatAssistantMessage(message, options) {
312
331
  formattedMessages.push(withMessageRole(new ToolMessage({
313
332
  tool_call_id: tool_call.id ?? "",
314
333
  name: tool_call.name,
315
- content: output != null ? output : ""
334
+ content: formatToolCallOutput(output)
316
335
  }), "tool"));
317
336
  } else if (part.type === "think" || part.type === "thinking" || part.type === "reasoning" || part.type === "reasoning_content" || part.type === "redacted_thinking") {
318
337
  hasReasoning = true;
@@ -583,7 +602,10 @@ function contentPartCharLength(part) {
583
602
  if (typeof record.thinking === "string") len += record.thinking.length;
584
603
  const { input } = record;
585
604
  if (typeof input === "string") len += input.length;
586
- else if (input != null && typeof input === "object") len += JSON.stringify(input).length;
605
+ else if (input != null && typeof input === "object") {
606
+ const measured = serializeStructuredValueBounded(input, 0).originalChars;
607
+ len += measured === Number.MAX_SAFE_INTEGER ? HARD_MAX_TOOL_RESULT_CHARS : Math.min(measured, HARD_MAX_TOOL_RESULT_CHARS);
608
+ }
587
609
  return len;
588
610
  }
589
611
  /** Extracts the skillName from a skill tool_call's args (string or object). */
@@ -608,6 +630,27 @@ function extractSkillName(args) {
608
630
  */
609
631
  const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options) => {
610
632
  const messages = [];
633
+ /**
634
+ * A steer ended the previous payload entry, so the next message emitted —
635
+ * whichever entry finally produces one — must be separated from it by an
636
+ * assistant turn. Held rather than emitted so an entry that produces
637
+ * nothing cannot leave the anchor stranded as the final turn.
638
+ */
639
+ let pendingSteerAnchor = false;
640
+ /**
641
+ * Emits the deferred anchor ahead of `next` — the message about to be
642
+ * pushed. When that message is itself an assistant turn, it already IS the
643
+ * separation the anchor exists to synthesize, so the intent is simply
644
+ * discharged: emitting the placeholder anyway would put two assistant turns
645
+ * back to back, which strict-alternation providers can reject and nothing downstream
646
+ * repairs (`coalesceAdjacentUserTurns` merges user turns only).
647
+ */
648
+ const flushSteerAnchor = (next) => {
649
+ if (!pendingSteerAnchor) return;
650
+ pendingSteerAnchor = false;
651
+ if (next.role === "assistant") return;
652
+ messages.push(withMessageRole(new AIMessage({ content: STEER_ANCHOR_PLACEHOLDER }), "assistant"));
653
+ };
611
654
  const updatedIndexTokenCountMap = {};
612
655
  let boundaryTokenAdjustment;
613
656
  const indexMapping = {};
@@ -643,6 +686,7 @@ const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options
643
686
  langChain: true
644
687
  });
645
688
  if (sourceMessageId != null && sourceMessageId !== "") formattedMessage.id = sourceMessageId;
689
+ flushSteerAnchor(formattedMessage);
646
690
  messages.push(formattedMessage);
647
691
  indexMapping[i] = [messages.length - 1];
648
692
  continue;
@@ -746,7 +790,44 @@ const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options
746
790
  provider: options?.provider
747
791
  });
748
792
  if (sourceMessageId != null && sourceMessageId !== "") for (const formattedMessage of formattedMessages) formattedMessage.id = sourceMessageId;
793
+ /**
794
+ * A steer that ends an assistant message leaves the replay on a
795
+ * `HumanMessage`. The next payload message is itself a user turn, so the
796
+ * sequence would reach the provider as two adjacent user turns — rejected
797
+ * by strict-alternation providers. Anchor it with a placeholder assistant
798
+ * turn.
799
+ *
800
+ * The placeholder must be NON-EMPTY. A string-content assistant message
801
+ * with no tool calls passes through `_convertMessagesToAnthropicPayload`
802
+ * verbatim — the empty-text repair there only covers array content and
803
+ * tool-call turns — so an empty anchor would reach Anthropic as
804
+ * `{role: 'assistant', content: ''}` and trade one invalid sequence for
805
+ * another. Same single-underscore convention the Anthropic converter
806
+ * already uses when it has to synthesize a non-empty block.
807
+ *
808
+ * Deferred rather than decided by lookahead. `i < payload.length - 1` only
809
+ * proves a later ENTRY exists, not that it EMITS: entries with empty
810
+ * content, and entries dropped by `applySummaryBoundary`, are skipped
811
+ * silently. A trailing steer followed only by those would get the anchor
812
+ * as the FINAL turn — an assistant prefill with no request after it, which
813
+ * the model may simply never answer. So the intent is recorded and flushed
814
+ * only when a message actually follows.
815
+ *
816
+ * Pushed AFTER the id stamping above, deliberately. `messagesStateReducer`
817
+ * treats a repeated id as replace-in-place, so an anchor carrying the
818
+ * shared `sourceMessageId` would overwrite the steer it exists to protect.
819
+ * Left unstamped, it reaches the reducer with a null id and is assigned a
820
+ * fresh one. `endsWithSteerMessage` reads only `additional_kwargs.source`,
821
+ * so the deferral cannot change which messages get anchored.
822
+ */
823
+ /**
824
+ * Guarded on emission: an assistant entry whose blocks all filtered away
825
+ * emits nothing, and flushing for it would strand the anchor as the final
826
+ * turn — the pending flag stays set for whichever entry emits next.
827
+ */
828
+ if (formattedMessages.length > 0) flushSteerAnchor(formattedMessages[0]);
749
829
  messages.push(...formattedMessages);
830
+ if (endsWithSteerMessage(formattedMessages)) pendingSteerAnchor = true;
750
831
  const endMessageIndex = messages.length;
751
832
  if (pendingSkillNames?.size != null && pendingSkillNames.size > 0) {
752
833
  const skipSkillBodyNames = options?.skipSkillBodyNames;
@@ -821,7 +902,10 @@ const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options
821
902
  if (typeof tc.name === "string") len += tc.name.length;
822
903
  const { args } = tc;
823
904
  if (typeof args === "string") len += args.length;
824
- else if (args != null) len += JSON.stringify(args).length;
905
+ else if (args != null) {
906
+ const measured = serializeStructuredValueBounded(args, 0).originalChars;
907
+ len += measured === Number.MAX_SAFE_INTEGER ? HARD_MAX_TOOL_RESULT_CHARS : Math.min(measured, HARD_MAX_TOOL_RESULT_CHARS);
908
+ }
825
909
  }
826
910
  lengths[k] = len;
827
911
  totalLength += len;
@@ -867,10 +951,99 @@ function shiftIndexTokenCountMap(indexTokenCountMap, instructionsTokenCount) {
867
951
  }
868
952
  return shiftedMap;
869
953
  }
870
- /** Block types that contain binary image data and must be preserved structurally. */
871
- const IMAGE_BLOCK_TYPES = new Set(["image_url", "image"]);
872
954
  /** Checks whether a BaseMessage is a tool-role message. */
873
955
  const isToolMessage$1 = (m) => m instanceof ToolMessage || "role" in m && m.role === "tool";
956
+ const PORTABLE_FOLDED_MEDIA_TYPES = new Set(["image", "image_url"]);
957
+ const MAX_FOLDED_BLOCK_CHARS = 8e3;
958
+ const MAX_FOLDED_CONTEXT_CHARS = HARD_MAX_TOOL_RESULT_CHARS;
959
+ const MAX_FOLDED_CONTEXT_WORK = 1e5;
960
+ const FOLDED_CONTEXT_TRUNCATION_NOTICE = "… [additional folded context omitted]";
961
+ const syntheticProviderContextMessages = /* @__PURE__ */ new WeakSet();
962
+ function createFoldContextBudget() {
963
+ return {
964
+ remainingChars: MAX_FOLDED_CONTEXT_CHARS,
965
+ remainingWork: MAX_FOLDED_CONTEXT_WORK,
966
+ truncated: false
967
+ };
968
+ }
969
+ function readFoldedDataProperty(value, key) {
970
+ if (value == null || typeof value !== "object") return;
971
+ try {
972
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
973
+ return descriptor != null && "value" in descriptor ? descriptor.value : void 0;
974
+ } catch {
975
+ return;
976
+ }
977
+ }
978
+ /**
979
+ * Adds one logical line without first concatenating caller-controlled strings.
980
+ * The shared budget covers every synthetic message emitted by one transform,
981
+ * so many individually bounded blocks cannot build an unbounded aggregate.
982
+ */
983
+ function appendFoldedLine(textChunks, budget, pieces) {
984
+ if (budget.remainingChars <= 0) {
985
+ budget.truncated = true;
986
+ return false;
987
+ }
988
+ const separatorChars = textChunks.length > 0 ? 1 : 0;
989
+ const available = budget.remainingChars - separatorChars;
990
+ if (available <= 0) {
991
+ budget.remainingChars = 0;
992
+ budget.truncated = true;
993
+ return false;
994
+ }
995
+ let totalChars = 0;
996
+ for (const piece of pieces) totalChars = Math.min(Number.MAX_SAFE_INTEGER, totalChars + piece.length);
997
+ if (totalChars <= available) {
998
+ const line = pieces.join("");
999
+ textChunks.push(line);
1000
+ budget.remainingChars -= separatorChars + line.length;
1001
+ return true;
1002
+ }
1003
+ const notice = FOLDED_CONTEXT_TRUNCATION_NOTICE.slice(0, available);
1004
+ let remainingHeadChars = Math.max(0, available - notice.length);
1005
+ const boundedPieces = [];
1006
+ for (const piece of pieces) {
1007
+ if (remainingHeadChars <= 0) break;
1008
+ const retained = piece.slice(0, remainingHeadChars);
1009
+ boundedPieces.push(retained);
1010
+ remainingHeadChars -= retained.length;
1011
+ }
1012
+ boundedPieces.push(notice);
1013
+ textChunks.push(boundedPieces.join(""));
1014
+ budget.remainingChars = 0;
1015
+ budget.truncated = true;
1016
+ return false;
1017
+ }
1018
+ function consumeFoldedWork(textChunks, budget) {
1019
+ if (budget.remainingChars <= 0) return false;
1020
+ if (budget.remainingWork-- > 0) return true;
1021
+ appendFoldedLine(textChunks, budget, [FOLDED_CONTEXT_TRUNCATION_NOTICE]);
1022
+ budget.remainingChars = 0;
1023
+ budget.truncated = true;
1024
+ return false;
1025
+ }
1026
+ function serializeFoldedValue(value) {
1027
+ const compacted = compactToolContent(value, MAX_FOLDED_BLOCK_CHARS).content;
1028
+ return typeof compacted === "string" ? compacted : serializeStructuredValueBounded(compacted, MAX_FOLDED_BLOCK_CHARS).content;
1029
+ }
1030
+ function markSyntheticProviderContext(message) {
1031
+ syntheticProviderContextMessages.add(message);
1032
+ return message;
1033
+ }
1034
+ function appendSyntheticProviderContextMessage(result, parts) {
1035
+ if (parts.length === 0) return false;
1036
+ result.push(markSyntheticProviderContext(withMessageRole(new HumanMessage({ content: toLangChainContent(parts) }), "user")));
1037
+ return true;
1038
+ }
1039
+ /**
1040
+ * Identifies provider-context placeholders created by this module without
1041
+ * trusting user-controlled content prefixes or leaking marker metadata onto
1042
+ * the provider wire.
1043
+ */
1044
+ function isSyntheticProviderContextMessage(message) {
1045
+ return syntheticProviderContextMessages.has(message);
1046
+ }
874
1047
  /** Flushes accumulated text chunks into `parts` as a single text block. */
875
1048
  function flushTextChunks(textChunks, parts) {
876
1049
  if (textChunks.length === 0) return;
@@ -882,88 +1055,139 @@ function flushTextChunks(textChunks, parts) {
882
1055
  }
883
1056
  /**
884
1057
  * Appends a single message's content to the running `textChunks` / `parts`
885
- * accumulators. Image blocks are shallow-copied into `parts` as-is so that
886
- * binary data (base64 images) never becomes text tokens. All other block
887
- * types are serialized to text unrecognized types are JSON-serialized
888
- * rather than silently dropped.
1058
+ * accumulators. Portable image blocks are shallow-copied into `parts` so
1059
+ * binary data never becomes text tokens. Provider-specific media/resource
1060
+ * blocks are retained as bounded text so a folded cross-provider history
1061
+ * cannot contain an unsupported native block or JSON-expand without limit.
889
1062
  *
890
1063
  * When `content` is an array containing tool_use blocks, `tool_calls` is NOT
891
1064
  * additionally serialized (avoiding double output). `tool_calls` is used as
892
1065
  * a fallback when `content` is a plain string or an array with no tool_use.
893
1066
  */
894
- function appendMessageContent(msg, role, textChunks, parts) {
1067
+ function appendMessageContent(msg, role, textChunks, parts, budget) {
895
1068
  const { content } = msg;
896
1069
  if (typeof content === "string") {
897
- if (content) textChunks.push(`${role}: ${content}`);
898
- appendToolCalls(msg, role, textChunks);
1070
+ if (content && consumeFoldedWork(textChunks, budget)) appendFoldedLine(textChunks, budget, [`${role}: `, content]);
1071
+ appendToolCalls(msg, role, textChunks, budget);
899
1072
  return;
900
1073
  }
901
1074
  if (!Array.isArray(content)) {
902
- appendToolCalls(msg, role, textChunks);
1075
+ appendToolCalls(msg, role, textChunks, budget);
903
1076
  return;
904
1077
  }
905
1078
  let hasToolUseBlock = false;
906
1079
  for (const block of content) {
907
- if (IMAGE_BLOCK_TYPES.has(block.type ?? "")) {
908
- flushTextChunks(textChunks, parts);
909
- parts.push({ ...block });
1080
+ if (!consumeFoldedWork(textChunks, budget)) {
1081
+ hasToolUseBlock = true;
1082
+ break;
1083
+ }
1084
+ const blockTypeValue = readFoldedDataProperty(block, "type");
1085
+ const blockType = typeof blockTypeValue === "string" ? blockTypeValue : void 0;
1086
+ if (blockType !== "tool_use" && blockType !== "tool_call" && blockType !== "tool_result" && isAtomicToolContentBlock(block)) {
1087
+ if (PORTABLE_FOLDED_MEDIA_TYPES.has(blockType ?? "")) {
1088
+ const blockChars = getToolContentCharLength([block]);
1089
+ if (blockChars > budget.remainingChars) {
1090
+ appendFoldedLine(textChunks, budget, [`${role}: [${blockType ?? "media"} omitted: folded context limit]`]);
1091
+ continue;
1092
+ }
1093
+ flushTextChunks(textChunks, parts);
1094
+ parts.push({ ...block });
1095
+ budget.remainingChars -= blockChars;
1096
+ } else appendFoldedLine(textChunks, budget, [`${role}: [${blockType ?? "media"}] `, serializeFoldedValue(block)]);
910
1097
  continue;
911
1098
  }
912
- if (block.type === "tool_use") {
1099
+ if (blockType === "tool_use") {
913
1100
  hasToolUseBlock = true;
914
- textChunks.push(`${role}: [tool_use] ${String(block.name ?? "")} ${JSON.stringify(block.input ?? {})}`);
1101
+ const name = readFoldedDataProperty(block, "name");
1102
+ const input = readFoldedDataProperty(block, "input");
1103
+ appendFoldedLine(textChunks, budget, [`${role}: [tool_use] ${typeof name === "string" ? name : ""} `, serializeFoldedValue(input ?? {})]);
915
1104
  continue;
916
1105
  }
917
- if (block.type === "tool_call") {
1106
+ if (blockType === "tool_call") {
918
1107
  hasToolUseBlock = true;
919
- const nested = block.tool_call;
920
- const name = String(nested?.name ?? block.name ?? "");
921
- const rawArgs = nested?.args ?? block.args ?? {};
922
- const argsText = typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs);
923
- textChunks.push(`${role}: [tool_use] ${name} ${argsText}`.trimEnd());
924
- const output = nested?.output;
925
- if (output != null && output !== "") textChunks.push(`Tool: ${String(output)}`);
1108
+ const nested = readFoldedDataProperty(block, "tool_call");
1109
+ const nestedName = readFoldedDataProperty(nested, "name");
1110
+ const topLevelName = readFoldedDataProperty(block, "name");
1111
+ let name = "";
1112
+ if (typeof nestedName === "string") name = nestedName;
1113
+ else if (typeof topLevelName === "string") name = topLevelName;
1114
+ const nestedArgs = readFoldedDataProperty(nested, "args");
1115
+ const topLevelArgs = readFoldedDataProperty(block, "args");
1116
+ const rawArgs = nestedArgs ?? topLevelArgs ?? {};
1117
+ const argsText = typeof rawArgs === "string" ? rawArgs : serializeFoldedValue(rawArgs);
1118
+ appendFoldedLine(textChunks, budget, [`${role}: [tool_use] ${name}${argsText ? " " : ""}`, argsText]);
1119
+ const output = readFoldedDataProperty(nested, "output");
1120
+ if (output != null && output !== "") appendFoldedLine(textChunks, budget, ["Tool: ", typeof output === "string" ? output : serializeFoldedValue(output)]);
926
1121
  continue;
927
1122
  }
928
- if (block.type === "tool_result") {
1123
+ if (blockType === "tool_result") {
929
1124
  hasToolUseBlock = true;
930
- const inner = block.content;
1125
+ const inner = readFoldedDataProperty(block, "content");
931
1126
  if (typeof inner === "string") {
932
- if (inner) textChunks.push(`${role}: [tool_result] ${inner}`);
933
- } else if (Array.isArray(inner)) for (const innerBlock of inner) if (typeof innerBlock === "string") {
934
- if (innerBlock) textChunks.push(`${role}: [tool_result] ${innerBlock}`);
935
- } else if (IMAGE_BLOCK_TYPES.has(innerBlock.type ?? "")) {
936
- flushTextChunks(textChunks, parts);
937
- parts.push({ ...innerBlock });
938
- } else {
939
- const innerText = innerBlock.text ?? innerBlock.input;
940
- textChunks.push(`${role}: [tool_result] ${typeof innerText === "string" && innerText ? innerText : JSON.stringify(innerBlock)}`);
1127
+ if (inner) appendFoldedLine(textChunks, budget, [`${role}: [tool_result] `, inner]);
1128
+ } else if (Array.isArray(inner)) for (const innerBlock of inner) {
1129
+ if (!consumeFoldedWork(textChunks, budget)) break;
1130
+ if (typeof innerBlock === "string") {
1131
+ if (innerBlock) appendFoldedLine(textChunks, budget, [`${role}: [tool_result] `, innerBlock]);
1132
+ } else {
1133
+ const innerTypeValue = readFoldedDataProperty(innerBlock, "type");
1134
+ const innerType = typeof innerTypeValue === "string" ? innerTypeValue : void 0;
1135
+ if (isAtomicToolContentBlock(innerBlock) && PORTABLE_FOLDED_MEDIA_TYPES.has(innerType ?? "")) {
1136
+ const blockChars = getToolContentCharLength([innerBlock]);
1137
+ if (blockChars <= budget.remainingChars) {
1138
+ flushTextChunks(textChunks, parts);
1139
+ parts.push({ ...innerBlock });
1140
+ budget.remainingChars -= blockChars;
1141
+ } else appendFoldedLine(textChunks, budget, [`${role}: [${innerType ?? "media"} omitted: folded context limit]`]);
1142
+ } else {
1143
+ const textValue = readFoldedDataProperty(innerBlock, "text");
1144
+ const inputValue = readFoldedDataProperty(innerBlock, "input");
1145
+ const innerText = textValue ?? inputValue;
1146
+ appendFoldedLine(textChunks, budget, [`${role}: [tool_result] `, typeof innerText === "string" && innerText ? innerText : serializeFoldedValue(innerBlock)]);
1147
+ }
1148
+ }
941
1149
  }
942
- else if (inner != null) textChunks.push(`${role}: [tool_result] ${JSON.stringify(inner)}`);
1150
+ else if (inner != null) appendFoldedLine(textChunks, budget, [`${role}: [tool_result] `, serializeFoldedValue(inner)]);
943
1151
  continue;
944
1152
  }
945
- const text = block.text ?? block.input;
1153
+ const text = readFoldedDataProperty(block, "text") ?? readFoldedDataProperty(block, "input");
946
1154
  if (typeof text === "string" && text) {
947
- textChunks.push(`${role}: ${text}`);
1155
+ appendFoldedLine(textChunks, budget, [`${role}: `, text]);
948
1156
  continue;
949
1157
  }
950
- if (block.type != null && block.type !== "") textChunks.push(`${role}: [${block.type}] ${JSON.stringify(block)}`);
1158
+ if (blockType != null && blockType !== "") appendFoldedLine(textChunks, budget, [`${role}: [${blockType}] `, serializeFoldedValue(block)]);
951
1159
  }
952
- if (!hasToolUseBlock) appendToolCalls(msg, role, textChunks);
1160
+ if (!hasToolUseBlock) appendToolCalls(msg, role, textChunks, budget);
953
1161
  }
954
- function appendToolCalls(msg, role, textChunks) {
1162
+ function appendToolCalls(msg, role, textChunks, budget) {
955
1163
  if (role !== "AI") return;
956
1164
  const aiMsg = msg;
957
1165
  if (aiMsg.tool_calls && aiMsg.tool_calls.length > 0) {
958
- for (const tc of aiMsg.tool_calls) textChunks.push(`AI: [tool_call] ${tc.name}(${JSON.stringify(tc.args)})`);
1166
+ for (const tc of aiMsg.tool_calls) {
1167
+ if (!consumeFoldedWork(textChunks, budget)) break;
1168
+ const name = readFoldedDataProperty(tc, "name");
1169
+ const args = readFoldedDataProperty(tc, "args");
1170
+ appendFoldedLine(textChunks, budget, [
1171
+ `AI: [tool_call] ${typeof name === "string" ? name : ""}(`,
1172
+ serializeFoldedValue(args),
1173
+ ")"
1174
+ ]);
1175
+ }
959
1176
  return;
960
1177
  }
961
1178
  const rawToolCalls = aiMsg.additional_kwargs.tool_calls;
962
1179
  if (!Array.isArray(rawToolCalls)) return;
963
1180
  for (const tc of rawToolCalls) {
964
- const fn = tc.function;
1181
+ if (!consumeFoldedWork(textChunks, budget)) break;
1182
+ const fn = readFoldedDataProperty(tc, "function");
965
1183
  if (fn == null) continue;
966
- textChunks.push(`AI: [tool_call] ${String(fn.name ?? "")}(${String(fn.arguments ?? "")})`);
1184
+ const name = readFoldedDataProperty(fn, "name");
1185
+ const args = readFoldedDataProperty(fn, "arguments");
1186
+ appendFoldedLine(textChunks, budget, [
1187
+ `AI: [tool_call] ${typeof name === "string" ? name : ""}(`,
1188
+ typeof args === "string" ? args : "",
1189
+ ")"
1190
+ ]);
967
1191
  }
968
1192
  }
969
1193
  /**
@@ -1004,6 +1228,7 @@ function ensureThinkingBlockInMessages(messages, _provider, config, runStartInde
1004
1228
  }
1005
1229
  if (lastHumanIndex === messages.length - 1) return messages;
1006
1230
  const result = lastHumanIndex >= 0 ? messages.slice(0, lastHumanIndex + 1) : [];
1231
+ const foldBudget = createFoldContextBudget();
1007
1232
  let i = lastHumanIndex + 1;
1008
1233
  while (i < messages.length) {
1009
1234
  const msg = messages[i];
@@ -1019,8 +1244,9 @@ function ensureThinkingBlockInMessages(messages, _provider, config, runStartInde
1019
1244
  let hasThinkingBlock = false;
1020
1245
  if (contentIsArray && aiMsg.content.length > 0) for (const c of aiMsg.content) {
1021
1246
  if (typeof c !== "object") continue;
1022
- if (c.type === "tool_use") hasToolUse = true;
1023
- else if (c.type === "thinking" || c.type === "reasoning_content" || c.type === "reasoning" || c.type === "redacted_thinking") hasThinkingBlock = true;
1247
+ const type = readFoldedDataProperty(c, "type");
1248
+ if (type === "tool_use") hasToolUse = true;
1249
+ else if (type === "thinking" || type === "reasoning_content" || type === "reasoning" || type === "redacted_thinking") hasThinkingBlock = true;
1024
1250
  if (hasToolUse && hasThinkingBlock) break;
1025
1251
  }
1026
1252
  if (!hasThinkingBlock && aiMsg.additional_kwargs.reasoning_content != null) hasThinkingBlock = true;
@@ -1036,16 +1262,16 @@ function ensureThinkingBlockInMessages(messages, _provider, config, runStartInde
1036
1262
  continue;
1037
1263
  }
1038
1264
  const parts = [];
1039
- const textChunks = ["[Previous agent context]"];
1040
- appendMessageContent(msg, "AI", textChunks, parts);
1265
+ const textChunks = [];
1266
+ appendFoldedLine(textChunks, foldBudget, ["[Previous agent context]"]);
1267
+ appendMessageContent(msg, "AI", textChunks, parts, foldBudget);
1041
1268
  let j = i + 1;
1042
1269
  while (j < messages.length && isToolMessage$1(messages[j])) {
1043
- appendMessageContent(messages[j], "Tool", textChunks, parts);
1270
+ appendMessageContent(messages[j], "Tool", textChunks, parts, foldBudget);
1044
1271
  j++;
1045
1272
  }
1046
1273
  flushTextChunks(textChunks, parts);
1047
- emitAgentLog(config, "warn", "format", `ensureThinkingBlockInMessages: injecting [Previous agent context] HumanMessage (${parts.length} msgs at index ${i}, no thinking block in chain)`);
1048
- result.push(withMessageRole(new HumanMessage({ content: toLangChainContent(parts) }), "user"));
1274
+ if (appendSyntheticProviderContextMessage(result, parts)) emitAgentLog(config, "warn", "format", `ensureThinkingBlockInMessages: injecting [Previous agent context] HumanMessage (${parts.length} msgs at index ${i}, no thinking block in chain)`);
1049
1275
  i = j;
1050
1276
  } else {
1051
1277
  result.push(msg);
@@ -1069,8 +1295,9 @@ function messageHasToolContent(msg) {
1069
1295
  if (aiMsg.tool_calls != null && aiMsg.tool_calls.length > 0) return true;
1070
1296
  const rawToolCalls = aiMsg.additional_kwargs.tool_calls;
1071
1297
  if (Array.isArray(rawToolCalls) && rawToolCalls.length > 0) return true;
1072
- if (Array.isArray(msg.content)) {
1073
- for (const block of msg.content) if (typeof block === "object" && (block.type === "tool_use" || block.type === "tool_call" || block.type === "tool_result")) return true;
1298
+ if (Array.isArray(msg.content)) for (const block of msg.content) {
1299
+ const type = readFoldedDataProperty(block, "type");
1300
+ if (typeof block === "object" && (type === "tool_use" || type === "tool_call" || type === "tool_result")) return true;
1074
1301
  }
1075
1302
  return false;
1076
1303
  }
@@ -1081,7 +1308,7 @@ function messageHasToolContent(msg) {
1081
1308
  * and labelled as tool output. */
1082
1309
  function isToolResultMessage(msg) {
1083
1310
  if (isToolMessage$1(msg)) return true;
1084
- if (Array.isArray(msg.content)) return msg.content.some((block) => typeof block === "object" && block.type === "tool_result");
1311
+ if (Array.isArray(msg.content)) return msg.content.some((block) => typeof block === "object" && readFoldedDataProperty(block, "type") === "tool_result");
1085
1312
  return false;
1086
1313
  }
1087
1314
  /**
@@ -1108,6 +1335,7 @@ function isToolResultMessage(msg) {
1108
1335
  function foldToolBlocksForToollessAgent(messages, config) {
1109
1336
  let result = null;
1110
1337
  let foldedCount = 0;
1338
+ const foldBudget = createFoldContextBudget();
1111
1339
  let i = 0;
1112
1340
  while (i < messages.length) {
1113
1341
  const msg = messages[i];
@@ -1119,17 +1347,18 @@ function foldToolBlocksForToollessAgent(messages, config) {
1119
1347
  /** First fold — copy the untouched prefix once, then append from here. */
1120
1348
  if (result === null) result = messages.slice(0, i);
1121
1349
  const parts = [];
1122
- const textChunks = ["[Previous tool interaction]"];
1123
- appendMessageContent(msg, isToolResultMessage(msg) ? "Tool" : "AI", textChunks, parts);
1350
+ const textChunks = [];
1351
+ appendFoldedLine(textChunks, foldBudget, ["[Previous tool interaction]"]);
1352
+ appendMessageContent(msg, isToolResultMessage(msg) ? "Tool" : "AI", textChunks, parts, foldBudget);
1124
1353
  foldedCount++;
1125
1354
  let j = i + 1;
1126
1355
  while (j < messages.length && isToolResultMessage(messages[j])) {
1127
- appendMessageContent(messages[j], "Tool", textChunks, parts);
1356
+ appendMessageContent(messages[j], "Tool", textChunks, parts, foldBudget);
1128
1357
  foldedCount++;
1129
1358
  j++;
1130
1359
  }
1131
1360
  flushTextChunks(textChunks, parts);
1132
- result.push(withMessageRole(new HumanMessage({ content: toLangChainContent(parts) }), "user"));
1361
+ appendSyntheticProviderContextMessage(result, parts);
1133
1362
  i = j;
1134
1363
  }
1135
1364
  if (result === null) return messages;
@@ -1162,6 +1391,6 @@ function chainHasThinkingBlock(messages, currentIndex) {
1162
1391
  return false;
1163
1392
  }
1164
1393
  //#endregion
1165
- export { ensureThinkingBlockInMessages, foldToolBlocksForToollessAgent, formatAgentMessages, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, labelContentByAgent, shiftIndexTokenCountMap, withMessageRole };
1394
+ export { ensureThinkingBlockInMessages, foldToolBlocksForToollessAgent, formatAgentMessages, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, isSyntheticProviderContextMessage, labelContentByAgent, shiftIndexTokenCountMap, withMessageRole };
1166
1395
 
1167
1396
  //# sourceMappingURL=format.mjs.map