@librechat/agents 3.8.0 → 3.8.2

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 (248) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +13 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +176 -55
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +16 -6
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/hitl/approvalReview.cjs +89 -0
  8. package/dist/cjs/hitl/approvalReview.cjs.map +1 -0
  9. package/dist/cjs/langfuse.cjs +42 -0
  10. package/dist/cjs/langfuse.cjs.map +1 -1
  11. package/dist/cjs/langfuseToolOutputTracing.cjs +42 -0
  12. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  13. package/dist/cjs/langfuseTraceShaping.cjs +7 -1
  14. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  15. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +6 -10
  16. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  17. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +2 -7
  18. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  19. package/dist/cjs/llm/contextPressureMeter.cjs +51 -10
  20. package/dist/cjs/llm/contextPressureMeter.cjs.map +1 -1
  21. package/dist/cjs/llm/init.cjs +1 -1
  22. package/dist/cjs/llm/invoke.cjs +2 -2
  23. package/dist/cjs/llm/openai/index.cjs +4 -3
  24. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  25. package/dist/cjs/llm/openai/utils/index.cjs +5 -4
  26. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  27. package/dist/cjs/llm/preempt.cjs +3 -2
  28. package/dist/cjs/llm/preempt.cjs.map +1 -1
  29. package/dist/cjs/llm/prepareProviderRequest.cjs +9 -7
  30. package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -1
  31. package/dist/cjs/llm/providers.cjs +1 -1
  32. package/dist/cjs/main.cjs +14 -8
  33. package/dist/cjs/messages/alternation.cjs +1 -5
  34. package/dist/cjs/messages/alternation.cjs.map +1 -1
  35. package/dist/cjs/messages/budget.cjs +206 -7
  36. package/dist/cjs/messages/budget.cjs.map +1 -1
  37. package/dist/cjs/messages/cache.cjs +3 -9
  38. package/dist/cjs/messages/cache.cjs.map +1 -1
  39. package/dist/cjs/messages/core.cjs +26 -38
  40. package/dist/cjs/messages/core.cjs.map +1 -1
  41. package/dist/cjs/messages/format.cjs +132 -43
  42. package/dist/cjs/messages/format.cjs.map +1 -1
  43. package/dist/cjs/messages/index.cjs +2 -0
  44. package/dist/cjs/messages/prune.cjs +14 -9
  45. package/dist/cjs/messages/prune.cjs.map +1 -1
  46. package/dist/cjs/messages/reasoningTypes.cjs +21 -0
  47. package/dist/cjs/messages/reasoningTypes.cjs.map +1 -0
  48. package/dist/cjs/messages/recency.cjs +15 -94
  49. package/dist/cjs/messages/recency.cjs.map +1 -1
  50. package/dist/cjs/messages/toolHistoryProjection.cjs +243 -0
  51. package/dist/cjs/messages/toolHistoryProjection.cjs.map +1 -0
  52. package/dist/cjs/messages/toolResultTypes.cjs +145 -4
  53. package/dist/cjs/messages/toolResultTypes.cjs.map +1 -1
  54. package/dist/cjs/run.cjs +22 -10
  55. package/dist/cjs/run.cjs.map +1 -1
  56. package/dist/cjs/session/AgentSession.cjs +7 -5
  57. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  58. package/dist/cjs/session/JsonlSessionStore.cjs +3 -0
  59. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  60. package/dist/cjs/session/index.cjs +1 -1
  61. package/dist/cjs/session/sessionProjection.cjs +75 -0
  62. package/dist/cjs/session/sessionProjection.cjs.map +1 -0
  63. package/dist/cjs/stream.cjs +20 -19
  64. package/dist/cjs/stream.cjs.map +1 -1
  65. package/dist/cjs/summarization/index.cjs.map +1 -1
  66. package/dist/cjs/summarization/node.cjs +19 -9
  67. package/dist/cjs/summarization/node.cjs.map +1 -1
  68. package/dist/cjs/summarization/shared.cjs +9 -0
  69. package/dist/cjs/summarization/shared.cjs.map +1 -1
  70. package/dist/cjs/tools/ToolNode.cjs +256 -84
  71. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  72. package/dist/cjs/tools/local/CompileCheckTool.cjs +1 -1
  73. package/dist/cjs/tools/local/LocalCodingTools.cjs +1 -1
  74. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +24 -9
  75. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  76. package/dist/cjs/tools/subagent/SubagentReplay.cjs +2 -0
  77. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  78. package/dist/cjs/tools/toolBatchReplay.cjs +187 -0
  79. package/dist/cjs/tools/toolBatchReplay.cjs.map +1 -0
  80. package/dist/cjs/tools/toolOutputReferences.cjs +12 -0
  81. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  82. package/dist/cjs/types/hitl.cjs +4 -0
  83. package/dist/cjs/types/hitl.cjs.map +1 -1
  84. package/dist/cjs/utils/events.cjs +13 -0
  85. package/dist/cjs/utils/events.cjs.map +1 -1
  86. package/dist/cjs/utils/tokens.cjs +105 -0
  87. package/dist/cjs/utils/tokens.cjs.map +1 -1
  88. package/dist/esm/agents/AgentContext.mjs +13 -3
  89. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  90. package/dist/esm/graphs/Graph.mjs +178 -57
  91. package/dist/esm/graphs/Graph.mjs.map +1 -1
  92. package/dist/esm/graphs/MultiAgentGraph.mjs +16 -6
  93. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  94. package/dist/esm/hitl/approvalReview.mjs +83 -0
  95. package/dist/esm/hitl/approvalReview.mjs.map +1 -0
  96. package/dist/esm/langfuse.mjs +43 -2
  97. package/dist/esm/langfuse.mjs.map +1 -1
  98. package/dist/esm/langfuseToolOutputTracing.mjs +41 -1
  99. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  100. package/dist/esm/langfuseTraceShaping.mjs +7 -1
  101. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  102. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +6 -10
  103. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  104. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +2 -7
  105. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  106. package/dist/esm/llm/contextPressureMeter.mjs +52 -11
  107. package/dist/esm/llm/contextPressureMeter.mjs.map +1 -1
  108. package/dist/esm/llm/init.mjs +1 -1
  109. package/dist/esm/llm/invoke.mjs +2 -2
  110. package/dist/esm/llm/openai/index.mjs +2 -1
  111. package/dist/esm/llm/openai/index.mjs.map +1 -1
  112. package/dist/esm/llm/openai/utils/index.mjs +5 -4
  113. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  114. package/dist/esm/llm/preempt.mjs +2 -1
  115. package/dist/esm/llm/preempt.mjs.map +1 -1
  116. package/dist/esm/llm/prepareProviderRequest.mjs +9 -7
  117. package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -1
  118. package/dist/esm/llm/providers.mjs +1 -1
  119. package/dist/esm/main.mjs +13 -10
  120. package/dist/esm/messages/alternation.mjs +2 -6
  121. package/dist/esm/messages/alternation.mjs.map +1 -1
  122. package/dist/esm/messages/budget.mjs +206 -8
  123. package/dist/esm/messages/budget.mjs.map +1 -1
  124. package/dist/esm/messages/cache.mjs +3 -9
  125. package/dist/esm/messages/cache.mjs.map +1 -1
  126. package/dist/esm/messages/core.mjs +24 -34
  127. package/dist/esm/messages/core.mjs.map +1 -1
  128. package/dist/esm/messages/format.mjs +133 -44
  129. package/dist/esm/messages/format.mjs.map +1 -1
  130. package/dist/esm/messages/index.mjs +2 -0
  131. package/dist/esm/messages/prune.mjs +14 -9
  132. package/dist/esm/messages/prune.mjs.map +1 -1
  133. package/dist/esm/messages/reasoningTypes.mjs +20 -0
  134. package/dist/esm/messages/reasoningTypes.mjs.map +1 -0
  135. package/dist/esm/messages/recency.mjs +16 -95
  136. package/dist/esm/messages/recency.mjs.map +1 -1
  137. package/dist/esm/messages/toolHistoryProjection.mjs +237 -0
  138. package/dist/esm/messages/toolHistoryProjection.mjs.map +1 -0
  139. package/dist/esm/messages/toolResultTypes.mjs +141 -4
  140. package/dist/esm/messages/toolResultTypes.mjs.map +1 -1
  141. package/dist/esm/run.mjs +23 -11
  142. package/dist/esm/run.mjs.map +1 -1
  143. package/dist/esm/session/AgentSession.mjs +7 -5
  144. package/dist/esm/session/AgentSession.mjs.map +1 -1
  145. package/dist/esm/session/JsonlSessionStore.mjs +3 -0
  146. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  147. package/dist/esm/session/index.mjs +1 -1
  148. package/dist/esm/session/sessionProjection.mjs +72 -0
  149. package/dist/esm/session/sessionProjection.mjs.map +1 -0
  150. package/dist/esm/stream.mjs +17 -16
  151. package/dist/esm/stream.mjs.map +1 -1
  152. package/dist/esm/summarization/index.mjs.map +1 -1
  153. package/dist/esm/summarization/node.mjs +20 -10
  154. package/dist/esm/summarization/node.mjs.map +1 -1
  155. package/dist/esm/summarization/shared.mjs +9 -1
  156. package/dist/esm/summarization/shared.mjs.map +1 -1
  157. package/dist/esm/tools/ToolNode.mjs +256 -84
  158. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  159. package/dist/esm/tools/local/CompileCheckTool.mjs +1 -1
  160. package/dist/esm/tools/local/LocalCodingTools.mjs +1 -1
  161. package/dist/esm/tools/subagent/SubagentExecutor.mjs +24 -9
  162. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  163. package/dist/esm/tools/subagent/SubagentReplay.mjs +1 -1
  164. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  165. package/dist/esm/tools/toolBatchReplay.mjs +176 -0
  166. package/dist/esm/tools/toolBatchReplay.mjs.map +1 -0
  167. package/dist/esm/tools/toolOutputReferences.mjs +12 -0
  168. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  169. package/dist/esm/types/hitl.mjs +4 -1
  170. package/dist/esm/types/hitl.mjs.map +1 -1
  171. package/dist/esm/utils/events.mjs +13 -0
  172. package/dist/esm/utils/events.mjs.map +1 -1
  173. package/dist/esm/utils/tokens.mjs +105 -1
  174. package/dist/esm/utils/tokens.mjs.map +1 -1
  175. package/dist/types/agents/AgentContext.d.ts +13 -1
  176. package/dist/types/graphs/Graph.d.ts +27 -0
  177. package/dist/types/hitl/approvalReview.d.ts +32 -0
  178. package/dist/types/hooks/types.d.ts +4 -2
  179. package/dist/types/index.d.ts +1 -0
  180. package/dist/types/langfuse.d.ts +5 -0
  181. package/dist/types/langfuseToolOutputTracing.d.ts +2 -0
  182. package/dist/types/llm/contextPressureMeter.d.ts +2 -1
  183. package/dist/types/llm/prepareProviderRequest.d.ts +7 -1
  184. package/dist/types/messages/budget.d.ts +15 -10
  185. package/dist/types/messages/core.d.ts +8 -17
  186. package/dist/types/messages/format.d.ts +3 -2
  187. package/dist/types/messages/index.d.ts +1 -1
  188. package/dist/types/messages/prune.d.ts +1 -1
  189. package/dist/types/messages/reasoningTypes.d.ts +7 -0
  190. package/dist/types/messages/recency.d.ts +3 -0
  191. package/dist/types/messages/toolHistoryProjection.d.ts +65 -0
  192. package/dist/types/messages/toolResultTypes.d.ts +31 -1
  193. package/dist/types/session/sessionProjection.d.ts +10 -0
  194. package/dist/types/summarization/index.d.ts +2 -1
  195. package/dist/types/summarization/node.d.ts +1 -0
  196. package/dist/types/summarization/shared.d.ts +12 -0
  197. package/dist/types/tools/ToolNode.d.ts +12 -6
  198. package/dist/types/tools/subagent/SubagentReplay.d.ts +2 -0
  199. package/dist/types/tools/toolBatchReplay.d.ts +54 -0
  200. package/dist/types/tools/toolOutputReferences.d.ts +2 -0
  201. package/dist/types/types/graph.d.ts +20 -0
  202. package/dist/types/types/run.d.ts +4 -0
  203. package/dist/types/types/summarize.d.ts +4 -1
  204. package/dist/types/utils/tokens.d.ts +2 -1
  205. package/package.json +1 -1
  206. package/src/agents/AgentContext.ts +34 -3
  207. package/src/graphs/Graph.ts +465 -147
  208. package/src/graphs/MultiAgentGraph.ts +28 -6
  209. package/src/hitl/approvalReview.ts +209 -0
  210. package/src/hooks/types.ts +4 -1
  211. package/src/index.ts +1 -0
  212. package/src/langfuse.ts +67 -9
  213. package/src/langfuseToolOutputTracing.ts +91 -0
  214. package/src/langfuseTraceShaping.ts +17 -4
  215. package/src/llm/anthropic/utils/message_inputs.ts +5 -10
  216. package/src/llm/bedrock/utils/message_inputs.ts +2 -8
  217. package/src/llm/contextPressureMeter.ts +80 -12
  218. package/src/llm/openai/utils/index.ts +5 -4
  219. package/src/llm/prepareProviderRequest.ts +21 -16
  220. package/src/messages/alternation.ts +2 -15
  221. package/src/messages/budget.ts +439 -23
  222. package/src/messages/cache.ts +8 -9
  223. package/src/messages/core.ts +65 -95
  224. package/src/messages/format.ts +262 -78
  225. package/src/messages/index.ts +1 -1
  226. package/src/messages/prune.ts +31 -12
  227. package/src/messages/reasoningTypes.ts +23 -0
  228. package/src/messages/recency.ts +35 -179
  229. package/src/messages/toolHistoryProjection.ts +460 -0
  230. package/src/messages/toolResultTypes.ts +331 -100
  231. package/src/run.ts +44 -22
  232. package/src/session/AgentSession.ts +33 -16
  233. package/src/session/JsonlSessionStore.ts +6 -0
  234. package/src/session/sessionProjection.ts +126 -0
  235. package/src/stream.ts +14 -19
  236. package/src/summarization/index.ts +2 -0
  237. package/src/summarization/node.ts +62 -13
  238. package/src/summarization/shared.ts +23 -0
  239. package/src/tools/ToolNode.ts +518 -184
  240. package/src/tools/subagent/SubagentExecutor.ts +35 -6
  241. package/src/tools/subagent/SubagentReplay.ts +2 -2
  242. package/src/tools/toolBatchReplay.ts +395 -0
  243. package/src/tools/toolOutputReferences.ts +16 -0
  244. package/src/types/graph.ts +20 -0
  245. package/src/types/run.ts +4 -0
  246. package/src/types/summarize.ts +4 -1
  247. package/src/utils/events.ts +19 -0
  248. package/src/utils/tokens.ts +183 -0
@@ -13,6 +13,11 @@ import type {
13
13
  } from '@langchain/core/messages';
14
14
  import type { RunnableConfig } from '@langchain/core/runnables';
15
15
  import type { ToolCall } from '@langchain/core/messages/tool';
16
+ import type {
17
+ OrderedToolHistoryProjection,
18
+ ToolHistoryPreparation,
19
+ ToolHistoryCallMirrors,
20
+ } from './toolHistoryProjection';
16
21
  import type {
17
22
  BedrockReasoningContentText,
18
23
  ExtendedMessageContent,
@@ -44,6 +49,7 @@ import {
44
49
  getProviderToolCallPartDescriptor,
45
50
  getProviderToolResultPartDescriptor,
46
51
  hasStructurallyValidAnthropicWebSearchResultContent,
52
+ isProviderToolContentPart,
47
53
  } from './toolResultTypes';
48
54
  import {
49
55
  hasBijectiveProviderContentPartMapping,
@@ -72,9 +78,15 @@ import {
72
78
  } from '@/common';
73
79
  import { normalizeAnthropicToolCallId } from '@/llm/anthropic/utils/message_inputs';
74
80
  import { toLangChainContent, toLangChainMessageFields } from './langchain';
81
+ import { isReasoningContentBlock } from './reasoningTypes';
75
82
  import { flattenLegacyContent, isLegacyConvertible } from './content';
76
83
  import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
77
84
  import { emitAgentLog } from '@/utils/events';
85
+ import {
86
+ createToolHistoryPreparation,
87
+ isToolHistoryCallMirror,
88
+ recordToolHistoryCallMirror,
89
+ } from './toolHistoryProjection';
78
90
 
79
91
  interface MediaMessageParams {
80
92
  message: {
@@ -437,10 +449,18 @@ function extractReasoningContent(
437
449
  const thinking = (part as ThinkingContentText).thinking;
438
450
  return typeof thinking === 'string' ? thinking : '';
439
451
  }
452
+ if (part.type === 'thinking_delta') {
453
+ const thinking = readFoldedDataProperty(part, 'thinking');
454
+ return typeof thinking === 'string' ? thinking : '';
455
+ }
440
456
  if (part.type === ContentTypes.REASONING) {
441
457
  const reasoning = (part as GoogleReasoningContentText).reasoning;
442
458
  return typeof reasoning === 'string' ? reasoning : '';
443
459
  }
460
+ if (part.type === 'reasoning-delta') {
461
+ const reasoning = readFoldedDataProperty(part, 'reasoning');
462
+ return typeof reasoning === 'string' ? reasoning : '';
463
+ }
444
464
  if (part.type === ContentTypes.REASONING_CONTENT) {
445
465
  const reasoningText = (part as BedrockReasoningContentText).reasoningText;
446
466
  return typeof reasoningText.text === 'string' ? reasoningText.text : '';
@@ -484,13 +504,7 @@ function hasMeaningfulAssistantContent(part: MessageContentComplex): boolean {
484
504
  ) {
485
505
  return false;
486
506
  }
487
- if (
488
- part.type === ContentTypes.THINK ||
489
- part.type === ContentTypes.THINKING ||
490
- part.type === ContentTypes.REASONING ||
491
- part.type === ContentTypes.REASONING_CONTENT ||
492
- part.type === 'redacted_thinking'
493
- ) {
507
+ if (isReasoningContentBlock(part)) {
494
508
  return extractReasoningContent(part).trim().length > 0;
495
509
  }
496
510
  return part.type != null && part.type !== '';
@@ -599,8 +613,7 @@ function createDerivedCompactionSemanticIndexCollector(
599
613
  Number.isSafeInteger(serializedProvidedEntryCount) &&
600
614
  serializedProvidedEntryCount >= snapshotProvidedEntryCount;
601
615
  const providedEntryCount =
602
- validSerializedProvidedEntryCount &&
603
- serializedProvidedEntryCount != null
616
+ validSerializedProvidedEntryCount && serializedProvidedEntryCount != null
604
617
  ? serializedProvidedEntryCount
605
618
  : snapshotProvidedEntryCount;
606
619
  collector.omittedBaseEntryCount = Math.max(
@@ -1983,13 +1996,7 @@ function formatAssistantMessage(
1983
1996
  'tool',
1984
1997
  sourcePartIndices
1985
1998
  );
1986
- } else if (
1987
- part.type === ContentTypes.THINK ||
1988
- part.type === ContentTypes.THINKING ||
1989
- part.type === ContentTypes.REASONING ||
1990
- part.type === ContentTypes.REASONING_CONTENT ||
1991
- part.type === 'redacted_thinking'
1992
- ) {
1999
+ } else if (isReasoningContentBlock(part)) {
1993
2000
  if (
1994
2001
  part.type === ContentTypes.THINK &&
1995
2002
  compactionSemanticIndex != null &&
@@ -3037,10 +3044,7 @@ export const formatAgentMessages = (
3037
3044
  }
3038
3045
  }
3039
3046
 
3040
- if (
3041
- discoveredTools.has(toolName) ||
3042
- isSdkManagedToolName(toolName)
3043
- ) {
3047
+ if (discoveredTools.has(toolName) || isSdkManagedToolName(toolName)) {
3044
3048
  filteredContent.push(part);
3045
3049
  filteredSourceContentPartIndices.push(partSourceContentIndices);
3046
3050
  if (
@@ -3547,6 +3551,7 @@ interface FoldedSourceMessage {
3547
3551
  * string content or tool-call metadata contributed as a whole. */
3548
3552
  readonly retainedContentPartIndices?: ReadonlySet<number>;
3549
3553
  readonly mappingAmbiguous?: boolean;
3554
+ readonly additionalAttributions?: readonly ProviderMessageAttribution[];
3550
3555
  }
3551
3556
 
3552
3557
  function getFoldedSourceAttribution(
@@ -3668,6 +3673,22 @@ function getSyntheticProviderContextProvenanceParts(
3668
3673
  if (parts.length === sourcePartStart) {
3669
3674
  parts.push({ attribution: fallbackAttribution });
3670
3675
  }
3676
+ for (const attribution of source.additionalAttributions ?? []) {
3677
+ const sourceMessageId =
3678
+ sourceMessageIds.length === 1
3679
+ ? sourceMessageIds[0]
3680
+ : undefined;
3681
+ const retainedSourceId =
3682
+ retainedSourceIds.size === 1
3683
+ ? retainedSourceIds.values().next().value
3684
+ : undefined;
3685
+ parts.push({
3686
+ attribution,
3687
+ ...((sourceMessageId ?? retainedSourceId) != null && {
3688
+ sourceMessageId: sourceMessageId ?? retainedSourceId,
3689
+ }),
3690
+ });
3691
+ }
3671
3692
  }
3672
3693
  return mergeAdjacentProviderProvenanceParts(parts);
3673
3694
  }
@@ -3763,6 +3784,78 @@ function flushTextChunks(
3763
3784
  textChunks.length = 0;
3764
3785
  }
3765
3786
 
3787
+ function getAuthoritativeResponsesToolOutput(
3788
+ message: BaseMessage,
3789
+ preparation: ToolHistoryPreparation
3790
+ ): OrderedToolHistoryProjection | undefined {
3791
+ const projection = preparation.get(message);
3792
+ return projection?.hasToolContent === true ? projection : undefined;
3793
+ }
3794
+
3795
+ function appendOrderedResponsesOutput(
3796
+ projection: OrderedToolHistoryProjection,
3797
+ textChunks: string[],
3798
+ parts: MessageContentComplex[],
3799
+ budget: FoldContextBudget,
3800
+ omitText = false
3801
+ ): { contributed: boolean; complete: boolean; hasTool: boolean } {
3802
+ let contributed = false;
3803
+ let hasTool = false;
3804
+ for (const contribution of projection.contributions) {
3805
+ if (!consumeFoldedWork(textChunks, budget)) {
3806
+ break;
3807
+ }
3808
+ const remainingCharsBefore = budget.remainingChars;
3809
+ if (contribution.kind === 'image') {
3810
+ const chars = getToolContentCharLength([contribution.image]);
3811
+ if (chars > budget.remainingChars) {
3812
+ appendFoldedLine(textChunks, budget, [
3813
+ 'Tool: [image omitted: folded context limit]',
3814
+ ]);
3815
+ contributed = true;
3816
+ hasTool = true;
3817
+ continue;
3818
+ }
3819
+ flushTextChunks(textChunks, parts);
3820
+ parts.push(contribution.image as MessageContentComplex);
3821
+ budget.remainingChars -= chars;
3822
+ contributed = true;
3823
+ hasTool = true;
3824
+ continue;
3825
+ }
3826
+ if (contribution.kind === 'text') {
3827
+ if (omitText) {
3828
+ continue;
3829
+ }
3830
+ appendFoldedLine(textChunks, budget, ['AI: ', contribution.text]);
3831
+ } else if (contribution.kind === 'call') {
3832
+ appendFoldedLine(textChunks, budget, [
3833
+ `AI: [tool_call] ${contribution.name}(`,
3834
+ serializeFoldedValue(contribution.arguments),
3835
+ ')',
3836
+ ]);
3837
+ } else if (contribution.providerType !== 'reasoning') {
3838
+ appendFoldedLine(textChunks, budget, [
3839
+ 'AI: [server_tool_output] ',
3840
+ serializeFoldedValue(contribution.value),
3841
+ ]);
3842
+ }
3843
+ const retained = budget.remainingChars < remainingCharsBefore;
3844
+ contributed ||= retained;
3845
+ hasTool ||= retained && contribution.actor === 'tool';
3846
+ }
3847
+ if (projection.truncated) {
3848
+ const before = budget.remainingChars;
3849
+ appendFoldedLine(textChunks, budget, [FOLDED_CONTEXT_TRUNCATION_NOTICE]);
3850
+ contributed ||= budget.remainingChars < before;
3851
+ }
3852
+ return {
3853
+ contributed,
3854
+ complete: !budget.truncated && !projection.truncated,
3855
+ hasTool,
3856
+ };
3857
+ }
3858
+
3766
3859
  /**
3767
3860
  * Appends a single message's content to the running `textChunks` / `parts`
3768
3861
  * accumulators. Portable image blocks are shallow-copied into `parts` so
@@ -3770,18 +3863,49 @@ function flushTextChunks(
3770
3863
  * blocks are retained as bounded text so a folded cross-provider history
3771
3864
  * cannot contain an unsupported native block or JSON-expand without limit.
3772
3865
  *
3773
- * When `content` is an array containing tool_use blocks, `tool_calls` is NOT
3774
- * additionally serialized (avoiding double output). `tool_calls` is used as
3775
- * a fallback when `content` is a plain string or an array with no tool_use.
3866
+ * Parsed calls are omitted only when identity, name, and complete arguments
3867
+ * establish an equivalent content representation.
3776
3868
  */
3777
3869
  function appendMessageContent(
3778
3870
  msg: BaseMessage,
3779
3871
  role: string,
3780
3872
  textChunks: string[],
3781
3873
  parts: MessageContentComplex[],
3782
- budget: FoldContextBudget
3874
+ budget: FoldContextBudget,
3875
+ preparation: ToolHistoryPreparation = createToolHistoryPreparation()
3783
3876
  ): FoldedSourceMessage | undefined {
3784
3877
  const { content } = msg;
3878
+ const responsesOutput = getAuthoritativeResponsesToolOutput(msg, preparation);
3879
+ if (
3880
+ responsesOutput != null &&
3881
+ (responsesOutput.source.coverage === 'complete-output' ||
3882
+ typeof content === 'string' ||
3883
+ (Array.isArray(content) &&
3884
+ content.length <= MAX_FOLDED_CONTEXT_WORK &&
3885
+ content.every(
3886
+ (block) => readFoldedDataProperty(block, 'type') === 'text'
3887
+ )))
3888
+ ) {
3889
+ const orderedOutput = appendOrderedResponsesOutput(
3890
+ responsesOutput,
3891
+ textChunks,
3892
+ parts,
3893
+ budget
3894
+ );
3895
+ const toolCalls =
3896
+ responsesOutput.source.coverage === 'tool-sidecar'
3897
+ ? appendToolCalls(msg, role, textChunks, budget)
3898
+ : { contributed: false, complete: true };
3899
+ return orderedOutput.contributed || toolCalls.contributed
3900
+ ? {
3901
+ message: msg,
3902
+ mappingAmbiguous: true,
3903
+ ...(orderedOutput.hasTool && {
3904
+ additionalAttributions: ['tool'] as const,
3905
+ }),
3906
+ }
3907
+ : undefined;
3908
+ }
3785
3909
 
3786
3910
  if (typeof content === 'string') {
3787
3911
  const remainingCharsBefore = budget.remainingChars;
@@ -3812,13 +3936,12 @@ function appendMessageContent(
3812
3936
  : undefined;
3813
3937
  }
3814
3938
 
3815
- let hasToolUseBlock = false;
3939
+ const representedToolCalls: ToolHistoryCallMirrors = new Map();
3816
3940
  const retainedContentPartIndices = new Set<number>();
3817
3941
 
3818
3942
  for (let blockIndex = 0; blockIndex < content.length; blockIndex++) {
3819
3943
  const block = content[blockIndex] as ExtendedMessageContent;
3820
3944
  if (!consumeFoldedWork(textChunks, budget)) {
3821
- hasToolUseBlock = true;
3822
3945
  break;
3823
3946
  }
3824
3947
  const remainingCharsBefore = budget.remainingChars;
@@ -3830,7 +3953,12 @@ function appendMessageContent(
3830
3953
  const blockTypeValue = readFoldedDataProperty(block, 'type');
3831
3954
  const blockType =
3832
3955
  typeof blockTypeValue === 'string' ? blockTypeValue : undefined;
3833
-
3956
+ const blockRole =
3957
+ getProviderToolResultPartDescriptor(block) != null ? 'Tool' : role;
3958
+ const callDescriptor = getProviderToolCallPartDescriptor(block);
3959
+ if (callDescriptor != null) {
3960
+ recordToolHistoryCallMirror(representedToolCalls, block);
3961
+ }
3834
3962
  if (
3835
3963
  blockType !== 'tool_use' &&
3836
3964
  blockType !== 'tool_call' &&
@@ -3841,7 +3969,7 @@ function appendMessageContent(
3841
3969
  const blockChars = getToolContentCharLength([block]);
3842
3970
  if (blockChars > budget.remainingChars) {
3843
3971
  appendFoldedLine(textChunks, budget, [
3844
- `${role}: [${blockType ?? 'media'} omitted: folded context limit]`,
3972
+ `${blockRole}: [${blockType ?? 'media'} omitted: folded context limit]`,
3845
3973
  ]);
3846
3974
  markBlockRetained();
3847
3975
  continue;
@@ -3851,7 +3979,7 @@ function appendMessageContent(
3851
3979
  budget.remainingChars -= blockChars;
3852
3980
  } else {
3853
3981
  appendFoldedLine(textChunks, budget, [
3854
- `${role}: [${blockType ?? 'media'}] `,
3982
+ `${blockRole}: [${blockType ?? 'media'}] `,
3855
3983
  serializeFoldedValue(block),
3856
3984
  ]);
3857
3985
  }
@@ -3860,7 +3988,6 @@ function appendMessageContent(
3860
3988
  }
3861
3989
 
3862
3990
  if (blockType === 'tool_use') {
3863
- hasToolUseBlock = true;
3864
3991
  const name = readFoldedDataProperty(block, 'name');
3865
3992
  const input = readFoldedDataProperty(block, 'input');
3866
3993
  appendFoldedLine(textChunks, budget, [
@@ -3877,7 +4004,6 @@ function appendMessageContent(
3877
4004
  // output } }`, from `convertMessagesToContent` / persisted history). Handle
3878
4005
  // both, and emit any embedded output, so the name/args/result survive.
3879
4006
  if (blockType === 'tool_call') {
3880
- hasToolUseBlock = true;
3881
4007
  const nested = readFoldedDataProperty(block, 'tool_call');
3882
4008
  const nestedName = readFoldedDataProperty(nested, 'name');
3883
4009
  const topLevelName = readFoldedDataProperty(block, 'name');
@@ -3911,16 +4037,12 @@ function appendMessageContent(
3911
4037
  // user message carrying the result). Preserve nested image blocks as-is
3912
4038
  // instead of JSON-stringifying them through the generic fallback.
3913
4039
  if (blockType === 'tool_result') {
3914
- hasToolUseBlock = true;
3915
4040
  const inner = readFoldedDataProperty(block, 'content') as
3916
4041
  | ToolResultContent['content']
3917
4042
  | undefined;
3918
4043
  if (typeof inner === 'string') {
3919
4044
  if (inner) {
3920
- appendFoldedLine(textChunks, budget, [
3921
- `${role}: [tool_result] `,
3922
- inner,
3923
- ]);
4045
+ appendFoldedLine(textChunks, budget, ['Tool: [tool_result] ', inner]);
3924
4046
  }
3925
4047
  } else if (Array.isArray(inner)) {
3926
4048
  for (const innerBlock of inner as Array<
@@ -3932,7 +4054,7 @@ function appendMessageContent(
3932
4054
  if (typeof innerBlock === 'string') {
3933
4055
  if (innerBlock) {
3934
4056
  appendFoldedLine(textChunks, budget, [
3935
- `${role}: [tool_result] `,
4057
+ 'Tool: [tool_result] ',
3936
4058
  innerBlock,
3937
4059
  ]);
3938
4060
  }
@@ -3951,7 +4073,7 @@ function appendMessageContent(
3951
4073
  budget.remainingChars -= blockChars;
3952
4074
  } else {
3953
4075
  appendFoldedLine(textChunks, budget, [
3954
- `${role}: [${innerType ?? 'media'} omitted: folded context limit]`,
4076
+ `Tool: [${innerType ?? 'media'} omitted: folded context limit]`,
3955
4077
  ]);
3956
4078
  }
3957
4079
  } else {
@@ -3959,7 +4081,7 @@ function appendMessageContent(
3959
4081
  const inputValue = readFoldedDataProperty(innerBlock, 'input');
3960
4082
  const innerText = textValue ?? inputValue;
3961
4083
  appendFoldedLine(textChunks, budget, [
3962
- `${role}: [tool_result] `,
4084
+ 'Tool: [tool_result] ',
3963
4085
  typeof innerText === 'string' && innerText
3964
4086
  ? innerText
3965
4087
  : serializeFoldedValue(innerBlock),
@@ -3969,7 +4091,7 @@ function appendMessageContent(
3969
4091
  }
3970
4092
  } else if (inner != null) {
3971
4093
  appendFoldedLine(textChunks, budget, [
3972
- `${role}: [tool_result] `,
4094
+ 'Tool: [tool_result] ',
3973
4095
  serializeFoldedValue(inner),
3974
4096
  ]);
3975
4097
  }
@@ -3989,22 +4111,39 @@ function appendMessageContent(
3989
4111
  // Fallback: serialize unrecognized block types to preserve context
3990
4112
  if (blockType != null && blockType !== '') {
3991
4113
  appendFoldedLine(textChunks, budget, [
3992
- `${role}: [${blockType}] `,
4114
+ `${blockRole}: [${blockType}] `,
3993
4115
  serializeFoldedValue(block),
3994
4116
  ]);
3995
4117
  }
3996
4118
  markBlockRetained();
3997
4119
  }
3998
4120
 
3999
- // If content array had no tool_use blocks, fall back to tool_calls metadata
4000
- // (handles edge case: empty content array with tool_calls populated)
4001
- const toolCalls = !hasToolUseBlock
4002
- ? appendToolCalls(msg, role, textChunks, budget)
4003
- : { contributed: false, complete: true };
4004
- if (toolCalls.contributed) {
4121
+ const responsesOutputContributed =
4122
+ responsesOutput != null &&
4123
+ appendOrderedResponsesOutput(
4124
+ responsesOutput,
4125
+ textChunks,
4126
+ parts,
4127
+ budget,
4128
+ true
4129
+ ).contributed;
4130
+
4131
+ const toolCalls = appendToolCalls(
4132
+ msg,
4133
+ role,
4134
+ textChunks,
4135
+ budget,
4136
+ representedToolCalls
4137
+ );
4138
+ if (toolCalls.contributed || responsesOutputContributed) {
4005
4139
  return {
4006
4140
  message: msg,
4007
- ...(!toolCalls.complete && { mappingAmbiguous: true }),
4141
+ ...((responsesOutputContributed || !toolCalls.complete) && {
4142
+ mappingAmbiguous: true,
4143
+ }),
4144
+ ...(responsesOutputContributed && {
4145
+ additionalAttributions: ['tool'] as const,
4146
+ }),
4008
4147
  };
4009
4148
  }
4010
4149
  return retainedContentPartIndices.size > 0
@@ -4016,7 +4155,8 @@ function appendToolCalls(
4016
4155
  msg: BaseMessage,
4017
4156
  role: string,
4018
4157
  textChunks: string[],
4019
- budget: FoldContextBudget
4158
+ budget: FoldContextBudget,
4159
+ representedToolCalls?: ToolHistoryCallMirrors
4020
4160
  ): { contributed: boolean; complete: boolean } {
4021
4161
  if (role !== 'AI') {
4022
4162
  return { contributed: false, complete: true };
@@ -4052,6 +4192,16 @@ function appendToolCalls(
4052
4192
  }
4053
4193
  const name = readFoldedDataProperty(tc, 'name');
4054
4194
  const args = readFoldedDataProperty(tc, 'args');
4195
+ if (
4196
+ isToolHistoryCallMirror(
4197
+ representedToolCalls,
4198
+ readFoldedDataProperty(tc, 'id'),
4199
+ name,
4200
+ args
4201
+ )
4202
+ ) {
4203
+ continue;
4204
+ }
4055
4205
  appendFoldedLine(textChunks, budget, [
4056
4206
  `AI: [tool_call] ${typeof name === 'string' ? name : ''}(`,
4057
4207
  serializeFoldedValue(args),
@@ -4082,7 +4232,7 @@ function appendToolCalls(
4082
4232
  const args = readFoldedDataProperty(fn, 'arguments');
4083
4233
  appendFoldedLine(textChunks, budget, [
4084
4234
  `AI: [tool_call] ${typeof name === 'string' ? name : ''}(`,
4085
- typeof args === 'string' ? args : '',
4235
+ serializeFoldedValue(typeof args === 'string' ? args : ''),
4086
4236
  ')',
4087
4237
  ]);
4088
4238
  }
@@ -4122,7 +4272,8 @@ export function ensureThinkingBlockInMessages(
4122
4272
  messages: BaseMessage[],
4123
4273
  _provider: ProviderName,
4124
4274
  config?: RunnableConfig,
4125
- runStartIndex?: number
4275
+ runStartIndex?: number,
4276
+ preparation: ToolHistoryPreparation = createToolHistoryPreparation()
4126
4277
  ): BaseMessage[] {
4127
4278
  if (messages.length === 0) {
4128
4279
  return messages;
@@ -4182,12 +4333,7 @@ export function ensureThinkingBlockInMessages(
4182
4333
  const type = readFoldedDataProperty(c, 'type');
4183
4334
  if (type === 'tool_use') {
4184
4335
  hasToolUse = true;
4185
- } else if (
4186
- type === ContentTypes.THINKING ||
4187
- type === ContentTypes.REASONING_CONTENT ||
4188
- type === ContentTypes.REASONING ||
4189
- type === 'redacted_thinking'
4190
- ) {
4336
+ } else if (isReasoningContentBlock({ type })) {
4191
4337
  hasThinkingBlock = true;
4192
4338
  }
4193
4339
  if (hasToolUse && hasThinkingBlock) {
@@ -4250,7 +4396,8 @@ export function ensureThinkingBlockInMessages(
4250
4396
  'AI',
4251
4397
  textChunks,
4252
4398
  parts,
4253
- foldBudget
4399
+ foldBudget,
4400
+ preparation
4254
4401
  );
4255
4402
  if (aiSource != null) {
4256
4403
  foldedSourceMessages.push(aiSource);
@@ -4263,7 +4410,8 @@ export function ensureThinkingBlockInMessages(
4263
4410
  'Tool',
4264
4411
  textChunks,
4265
4412
  parts,
4266
- foldBudget
4413
+ foldBudget,
4414
+ preparation
4267
4415
  );
4268
4416
  if (toolSource != null) {
4269
4417
  foldedSourceMessages.push(toolSource);
@@ -4307,7 +4455,11 @@ export function ensureThinkingBlockInMessages(
4307
4455
  * `toolUse` / `toolResult`). Missing the parent AI message is not just a
4308
4456
  * passthrough: folding its ToolMessage alone would leave an orphan
4309
4457
  * `assistant(tool_calls) -> user(...)` sequence. */
4310
- function messageHasToolContent(msg: BaseMessage): boolean {
4458
+ function messageHasToolContent(
4459
+ msg: BaseMessage,
4460
+ preserveNativeOpenAIResponses = false,
4461
+ preparation: ToolHistoryPreparation = createToolHistoryPreparation()
4462
+ ): boolean {
4311
4463
  if (isToolMessage(msg)) {
4312
4464
  return true;
4313
4465
  }
@@ -4320,15 +4472,33 @@ function messageHasToolContent(msg: BaseMessage): boolean {
4320
4472
  return true;
4321
4473
  }
4322
4474
  if (Array.isArray(msg.content)) {
4475
+ let hasOpenAINativeServerContent = false;
4323
4476
  for (const block of msg.content as ExtendedMessageContent[]) {
4324
4477
  const type = readFoldedDataProperty(block, 'type');
4478
+ const isNativeOpenAIServerContent =
4479
+ preserveNativeOpenAIResponses &&
4480
+ (type === 'server_tool_call' ||
4481
+ type === 'server_tool_call_result' ||
4482
+ type === 'server_tool_result');
4483
+ hasOpenAINativeServerContent ||= isNativeOpenAIServerContent;
4325
4484
  if (
4326
4485
  typeof block === 'object' &&
4327
- (type === 'tool_use' || type === 'tool_call' || type === 'tool_result')
4486
+ !isNativeOpenAIServerContent &&
4487
+ (isProviderToolContentPart(block) ||
4488
+ readFoldedDataProperty(block, 'tool_call') != null)
4328
4489
  ) {
4329
4490
  return true;
4330
4491
  }
4331
4492
  }
4493
+ if (hasOpenAINativeServerContent) {
4494
+ return false;
4495
+ }
4496
+ }
4497
+ if (
4498
+ !preserveNativeOpenAIResponses &&
4499
+ getAuthoritativeResponsesToolOutput(msg, preparation) != null
4500
+ ) {
4501
+ return true;
4332
4502
  }
4333
4503
  return false;
4334
4504
  }
@@ -4346,12 +4516,29 @@ function isToolResultMessage(msg: BaseMessage): boolean {
4346
4516
  return (msg.content as ExtendedMessageContent[]).some(
4347
4517
  (block) =>
4348
4518
  typeof block === 'object' &&
4349
- readFoldedDataProperty(block, 'type') === 'tool_result'
4519
+ getProviderToolResultPartDescriptor(block) != null
4350
4520
  );
4351
4521
  }
4352
4522
  return false;
4353
4523
  }
4354
4524
 
4525
+ function getFoldedMessageRole(msg: BaseMessage): 'AI' | 'Tool' | 'User' {
4526
+ if (isToolMessage(msg)) {
4527
+ return 'Tool';
4528
+ }
4529
+ if (
4530
+ msg instanceof AIMessage ||
4531
+ msg instanceof AIMessageChunk ||
4532
+ ('role' in msg && msg.role === 'assistant')
4533
+ ) {
4534
+ return 'AI';
4535
+ }
4536
+ if (msg instanceof HumanMessage || ('role' in msg && msg.role === 'user')) {
4537
+ return 'User';
4538
+ }
4539
+ return isToolResultMessage(msg) ? 'Tool' : 'AI';
4540
+ }
4541
+
4355
4542
  /**
4356
4543
  * Folds tool_use / tool_result content into plain text for an agent that binds
4357
4544
  * no tools.
@@ -4375,7 +4562,9 @@ function isToolResultMessage(msg: BaseMessage): boolean {
4375
4562
  */
4376
4563
  export function foldToolBlocksForToollessAgent(
4377
4564
  messages: BaseMessage[],
4378
- config?: RunnableConfig
4565
+ config?: RunnableConfig,
4566
+ preserveNativeOpenAIResponses = false,
4567
+ preparation: ToolHistoryPreparation = createToolHistoryPreparation()
4379
4568
  ): BaseMessage[] {
4380
4569
  let result: BaseMessage[] | null = null;
4381
4570
  let foldedCount = 0;
@@ -4383,7 +4572,9 @@ export function foldToolBlocksForToollessAgent(
4383
4572
  let i = 0;
4384
4573
  while (i < messages.length) {
4385
4574
  const msg = messages[i];
4386
- if (!messageHasToolContent(msg)) {
4575
+ if (
4576
+ !messageHasToolContent(msg, preserveNativeOpenAIResponses, preparation)
4577
+ ) {
4387
4578
  result?.push(msg);
4388
4579
  i++;
4389
4580
  continue;
@@ -4400,10 +4591,11 @@ export function foldToolBlocksForToollessAgent(
4400
4591
  appendFoldedLine(textChunks, foldBudget, ['[Previous tool interaction]']);
4401
4592
  const initialSource = appendMessageContent(
4402
4593
  msg,
4403
- isToolResultMessage(msg) ? 'Tool' : 'AI',
4594
+ getFoldedMessageRole(msg),
4404
4595
  textChunks,
4405
4596
  parts,
4406
- foldBudget
4597
+ foldBudget,
4598
+ preparation
4407
4599
  );
4408
4600
  if (initialSource != null) {
4409
4601
  foldedSourceMessages.push(initialSource);
@@ -4414,10 +4606,11 @@ export function foldToolBlocksForToollessAgent(
4414
4606
  while (j < messages.length && isToolResultMessage(messages[j])) {
4415
4607
  const toolSource = appendMessageContent(
4416
4608
  messages[j],
4417
- 'Tool',
4609
+ getFoldedMessageRole(messages[j]),
4418
4610
  textChunks,
4419
4611
  parts,
4420
- foldBudget
4612
+ foldBudget,
4613
+ preparation
4421
4614
  );
4422
4615
  if (toolSource != null) {
4423
4616
  foldedSourceMessages.push(toolSource);
@@ -4482,16 +4675,7 @@ function chainHasThinkingBlock(
4482
4675
 
4483
4676
  if (Array.isArray(prevAiMsg.content) && prevAiMsg.content.length > 0) {
4484
4677
  const content = prevAiMsg.content as ExtendedMessageContent[];
4485
- if (
4486
- content.some(
4487
- (c) =>
4488
- typeof c === 'object' &&
4489
- (c.type === ContentTypes.THINKING ||
4490
- c.type === ContentTypes.REASONING_CONTENT ||
4491
- c.type === ContentTypes.REASONING ||
4492
- c.type === 'redacted_thinking')
4493
- )
4494
- ) {
4678
+ if (content.some((c) => isReasoningContentBlock(c))) {
4495
4679
  return true;
4496
4680
  }
4497
4681
  }
@@ -1,7 +1,7 @@
1
1
  export * from './core';
2
2
  export * from './ids';
3
3
  export * from './prune';
4
- export * from './budget';
4
+ export { syncBudgetDerivedFields } from './budget';
5
5
  export * from './format';
6
6
  export * from './cache';
7
7
  export * from './anthropicToolCache';