@librechat/agents 3.3.2 → 3.3.4

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 (162) 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/graphs/Graph.cjs +342 -109
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/langfuse.cjs +6 -0
  8. package/dist/cjs/langfuse.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
  10. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +37 -10
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/google/utils/common.cjs +19 -7
  14. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  15. package/dist/cjs/llm/invoke.cjs +64 -3
  16. package/dist/cjs/llm/invoke.cjs.map +1 -1
  17. package/dist/cjs/llm/openai/index.cjs +41 -5
  18. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  19. package/dist/cjs/llm/openai/utils/index.cjs +25 -4
  20. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  21. package/dist/cjs/llm/openrouter/index.cjs +4 -1
  22. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  23. package/dist/cjs/main.cjs +19 -2
  24. package/dist/cjs/messages/cache.cjs +54 -33
  25. package/dist/cjs/messages/cache.cjs.map +1 -1
  26. package/dist/cjs/messages/contextPruning.cjs +17 -43
  27. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  28. package/dist/cjs/messages/core.cjs +315 -23
  29. package/dist/cjs/messages/core.cjs.map +1 -1
  30. package/dist/cjs/messages/format.cjs +218 -60
  31. package/dist/cjs/messages/format.cjs.map +1 -1
  32. package/dist/cjs/messages/index.cjs +1 -1
  33. package/dist/cjs/messages/prune.cjs +503 -127
  34. package/dist/cjs/messages/prune.cjs.map +1 -1
  35. package/dist/cjs/stream.cjs +7 -3
  36. package/dist/cjs/stream.cjs.map +1 -1
  37. package/dist/cjs/summarization/node.cjs +28 -14
  38. package/dist/cjs/summarization/node.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +54 -41
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
  42. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  43. package/dist/cjs/utils/index.cjs +1 -1
  44. package/dist/cjs/utils/llm.cjs +1 -1
  45. package/dist/cjs/utils/llm.cjs.map +1 -1
  46. package/dist/cjs/utils/tokens.cjs +307 -20
  47. package/dist/cjs/utils/tokens.cjs.map +1 -1
  48. package/dist/cjs/utils/toolContent.cjs +1514 -0
  49. package/dist/cjs/utils/toolContent.cjs.map +1 -0
  50. package/dist/esm/agents/AgentContext.mjs +2 -1
  51. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  52. package/dist/esm/graphs/Graph.mjs +349 -116
  53. package/dist/esm/graphs/Graph.mjs.map +1 -1
  54. package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
  55. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  56. package/dist/esm/langfuse.mjs +6 -0
  57. package/dist/esm/langfuse.mjs.map +1 -1
  58. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
  59. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  60. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +37 -10
  61. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  62. package/dist/esm/llm/google/utils/common.mjs +19 -7
  63. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  64. package/dist/esm/llm/invoke.mjs +65 -5
  65. package/dist/esm/llm/invoke.mjs.map +1 -1
  66. package/dist/esm/llm/openai/index.mjs +41 -5
  67. package/dist/esm/llm/openai/index.mjs.map +1 -1
  68. package/dist/esm/llm/openai/utils/index.mjs +25 -4
  69. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  70. package/dist/esm/llm/openrouter/index.mjs +4 -1
  71. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  72. package/dist/esm/main.mjs +7 -7
  73. package/dist/esm/messages/cache.mjs +55 -34
  74. package/dist/esm/messages/cache.mjs.map +1 -1
  75. package/dist/esm/messages/contextPruning.mjs +17 -43
  76. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  77. package/dist/esm/messages/core.mjs +305 -24
  78. package/dist/esm/messages/core.mjs.map +1 -1
  79. package/dist/esm/messages/format.mjs +218 -61
  80. package/dist/esm/messages/format.mjs.map +1 -1
  81. package/dist/esm/messages/index.mjs +1 -1
  82. package/dist/esm/messages/prune.mjs +500 -128
  83. package/dist/esm/messages/prune.mjs.map +1 -1
  84. package/dist/esm/stream.mjs +7 -3
  85. package/dist/esm/stream.mjs.map +1 -1
  86. package/dist/esm/summarization/node.mjs +28 -14
  87. package/dist/esm/summarization/node.mjs.map +1 -1
  88. package/dist/esm/tools/ToolNode.mjs +54 -41
  89. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  90. package/dist/esm/tools/toolOutputReferences.mjs +6 -0
  91. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  92. package/dist/esm/utils/index.mjs +1 -1
  93. package/dist/esm/utils/llm.mjs +1 -1
  94. package/dist/esm/utils/llm.mjs.map +1 -1
  95. package/dist/esm/utils/tokens.mjs +307 -21
  96. package/dist/esm/utils/tokens.mjs.map +1 -1
  97. package/dist/esm/utils/toolContent.mjs +1503 -0
  98. package/dist/esm/utils/toolContent.mjs.map +1 -0
  99. package/dist/types/graphs/Graph.d.ts +1 -1
  100. package/dist/types/llm/invoke.d.ts +27 -1
  101. package/dist/types/llm/openai/index.d.ts +4 -0
  102. package/dist/types/llm/openai/utils/index.d.ts +2 -0
  103. package/dist/types/llm/openrouter/index.d.ts +5 -0
  104. package/dist/types/messages/contextPruning.d.ts +2 -1
  105. package/dist/types/messages/core.d.ts +36 -0
  106. package/dist/types/messages/format.d.ts +6 -0
  107. package/dist/types/messages/prune.d.ts +16 -12
  108. package/dist/types/types/stream.d.ts +1 -1
  109. package/dist/types/utils/tokens.d.ts +7 -0
  110. package/dist/types/utils/toolContent.d.ts +107 -0
  111. package/package.json +13 -9
  112. package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
  113. package/src/agents/AgentContext.ts +1 -0
  114. package/src/graphs/Graph.ts +698 -200
  115. package/src/graphs/MultiAgentGraph.ts +6 -1
  116. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1128 -4
  117. package/src/langfuse.ts +15 -0
  118. package/src/llm/anthropic/utils/message_inputs.ts +158 -12
  119. package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
  120. package/src/llm/bedrock/utils/message_inputs.test.ts +49 -1
  121. package/src/llm/bedrock/utils/message_inputs.ts +86 -13
  122. package/src/llm/google/utils/common.test.ts +75 -9
  123. package/src/llm/google/utils/common.ts +34 -3
  124. package/src/llm/invoke.test.ts +488 -0
  125. package/src/llm/invoke.ts +196 -2
  126. package/src/llm/openai/index.ts +106 -6
  127. package/src/llm/openai/utils/index.ts +109 -53
  128. package/src/llm/openai/utils/messages.test.ts +330 -1
  129. package/src/llm/openrouter/index.ts +12 -2
  130. package/src/llm/openrouter/reasoning.test.ts +314 -0
  131. package/src/messages/__tests__/observationMasking.test.ts +93 -2
  132. package/src/messages/cache.tail.test.ts +193 -0
  133. package/src/messages/cache.test.ts +113 -0
  134. package/src/messages/cache.ts +92 -60
  135. package/src/messages/contextPruning.test.ts +184 -0
  136. package/src/messages/contextPruning.ts +49 -42
  137. package/src/messages/core.ts +653 -40
  138. package/src/messages/ensureThinkingBlock.test.ts +49 -1
  139. package/src/messages/foldToollessToolBlocks.test.ts +163 -5
  140. package/src/messages/format.ts +363 -91
  141. package/src/messages/formatAgentMessages.test.ts +604 -14
  142. package/src/messages/formatAgentMessages.tools.test.ts +103 -0
  143. package/src/messages/prune.ts +996 -183
  144. package/src/specs/langfuse-callbacks.test.ts +203 -1
  145. package/src/specs/prune.test.ts +1083 -6
  146. package/src/specs/summarization-unit.test.ts +105 -0
  147. package/src/specs/tokens.test.ts +609 -32
  148. package/src/stream.ts +23 -15
  149. package/src/summarization/__tests__/node.test.ts +77 -0
  150. package/src/summarization/node.ts +46 -18
  151. package/src/tools/ToolNode.ts +147 -68
  152. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
  153. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
  154. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
  155. package/src/tools/__tests__/directToolHooks.test.ts +255 -7
  156. package/src/tools/toolOutputReferences.ts +6 -0
  157. package/src/types/stream.ts +1 -1
  158. package/src/utils/llm.test.ts +18 -0
  159. package/src/utils/llm.ts +4 -1
  160. package/src/utils/tokens.ts +586 -31
  161. package/src/utils/toolContent.test.ts +1432 -0
  162. package/src/utils/toolContent.ts +2707 -0
@@ -1,10 +1,13 @@
1
1
  import "../common/enum.mjs";
2
2
  import "../common/index.mjs";
3
+ import { calculateMaxToolResultChars, truncateToolResultContent } from "../utils/truncation.mjs";
4
+ import { cloneToolMessageWithContent, compactToolContent, getToolContentCharLength, isComputerCallOutputMessage, serializeStructuredValueBounded, serializeToolContentBounded } from "../utils/toolContent.mjs";
5
+ import { hasUnsafeStructuredSerialization } from "../utils/tokens.mjs";
3
6
  import { toLangChainContent } from "./langchain.mjs";
4
- import { calculateMaxToolResultChars, truncateToolInput, truncateToolResultContent } from "../utils/truncation.mjs";
5
7
  import { resolveContextPruningSettings } from "./contextPruningSettings.mjs";
6
8
  import { applyContextPruning } from "./contextPruning.mjs";
7
9
  import { AIMessage, ToolMessage } from "@langchain/core/messages";
10
+ import { isProxy } from "node:util/types";
8
11
  //#region src/messages/prune.ts
9
12
  function sumTokenCounts(tokenMap, count) {
10
13
  let total = 0;
@@ -13,6 +16,8 @@ function sumTokenCounts(tokenMap, count) {
13
16
  }
14
17
  /** Default fraction of the token budget reserved as headroom (5 %). */
15
18
  const DEFAULT_RESERVE_RATIO = .05;
19
+ /** Provider framing reserved for the assistant reply label. */
20
+ const REPLY_PRIMER_TOKENS = 3;
16
21
  /** Context pressure at which observation masking and context fading activate. */
17
22
  const PRESSURE_THRESHOLD_MASKING = .8;
18
23
  /** Pressure band thresholds paired with budget factors for progressive context fading. */
@@ -55,7 +60,8 @@ function clampCalibrationRatio(ratio) {
55
60
  return Math.max(CALIBRATION_RATIO_MIN, Math.min(5, ratio));
56
61
  }
57
62
  function getToolCallIds(message) {
58
- if (message.getType() !== "ai") return /* @__PURE__ */ new Set();
63
+ const messageRole = message.role;
64
+ if (message.getType() !== "ai" && messageRole !== "assistant") return /* @__PURE__ */ new Set();
59
65
  const ids = /* @__PURE__ */ new Set();
60
66
  const aiMessage = message;
61
67
  for (const toolCall of aiMessage.tool_calls ?? []) if (typeof toolCall.id === "string" && toolCall.id.length > 0) ids.add(toolCall.id);
@@ -64,8 +70,26 @@ function getToolCallIds(message) {
64
70
  const record = part;
65
71
  if ((record.type === "tool_use" || record.type === "tool_call") && typeof record.id === "string" && record.id.length > 0) ids.add(record.id);
66
72
  }
73
+ const toolOutputSource = getResponsesToolOutputSource(message);
74
+ if (toolOutputSource != null) for (const item of toolOutputSource.items) {
75
+ if (item == null || typeof item !== "object") continue;
76
+ const record = item;
77
+ if ((record.type === "function_call" || record.type === "custom_tool_call" || record.type === "computer_call") && typeof record.call_id === "string" && record.call_id !== "") ids.add(record.call_id);
78
+ }
67
79
  return ids;
68
80
  }
81
+ function getResponsesToolOutputSource(message) {
82
+ const responseOutput = readPropertyWithoutAccessors(message.response_metadata, "output");
83
+ if (!responseOutput.accessor && Array.isArray(responseOutput.value) && !isProxy(responseOutput.value) && responseOutput.value.length > 0) return {
84
+ source: "response_metadata",
85
+ items: responseOutput.value
86
+ };
87
+ const fallbackOutput = readPropertyWithoutAccessors(message.additional_kwargs, "tool_outputs");
88
+ if (!fallbackOutput.accessor && Array.isArray(fallbackOutput.value) && !isProxy(fallbackOutput.value)) return {
89
+ source: "tool_outputs",
90
+ items: fallbackOutput.value
91
+ };
92
+ }
69
93
  function getToolResultId(message) {
70
94
  if (message.getType() !== "tool") return null;
71
95
  const toolMessage = message;
@@ -109,7 +133,8 @@ function repairOrphanedToolMessages({ context, allMessages, tokenCounter, indexT
109
133
  repairedContext.push(message);
110
134
  continue;
111
135
  }
112
- if (message.getType() === "ai" && message instanceof AIMessage) {
136
+ const messageRole = message.role;
137
+ if (message.getType() === "ai" || messageRole === "assistant") {
113
138
  const toolCallIds = getToolCallIds(message);
114
139
  if (toolCallIds.size > 0) {
115
140
  let hasOrphanToolCalls = false;
@@ -153,22 +178,33 @@ function repairOrphanedToolMessages({ context, allMessages, tokenCounter, indexT
153
178
  * Returns null if the message has no content left after stripping.
154
179
  */
155
180
  function stripOrphanToolUseBlocks(message, presentToolResultIds) {
156
- const keptToolCalls = (message.tool_calls ?? []).filter((tc) => typeof tc.id === "string" && presentToolResultIds.has(tc.id));
181
+ const aiMessage = message;
182
+ const keptToolCalls = (aiMessage.tool_calls ?? []).filter((tc) => typeof tc.id === "string" && presentToolResultIds.has(tc.id));
157
183
  let keptContent;
158
- if (Array.isArray(message.content)) {
159
- const filtered = message.content.filter((block) => {
160
- if (typeof block !== "object") return true;
161
- const record = block;
162
- if ((record.type === "tool_use" || record.type === "tool_call") && typeof record.id === "string") return presentToolResultIds.has(record.id);
163
- return true;
164
- });
165
- if (filtered.length === 0) return null;
166
- keptContent = filtered;
167
- } else keptContent = message.content;
168
- return new AIMessage({
169
- ...message,
184
+ if (Array.isArray(aiMessage.content)) keptContent = aiMessage.content.filter((block) => {
185
+ if (typeof block !== "object") return true;
186
+ const record = block;
187
+ if ((record.type === "tool_use" || record.type === "tool_call") && typeof record.id === "string") return presentToolResultIds.has(record.id);
188
+ return true;
189
+ });
190
+ else keptContent = aiMessage.content;
191
+ const toolOutputSource = getResponsesToolOutputSource(message);
192
+ const keptToolOutputs = toolOutputSource?.items.filter((item) => {
193
+ if (item == null || typeof item !== "object") return true;
194
+ const record = item;
195
+ if (record.type !== "function_call" && record.type !== "custom_tool_call" && record.type !== "computer_call") return true;
196
+ return typeof record.call_id === "string" && presentToolResultIds.has(record.call_id);
197
+ }) ?? [];
198
+ if (keptToolCalls.length === 0 && Array.isArray(keptContent) && keptContent.length === 0 && keptToolOutputs.length === 0) return null;
199
+ let responseMetadata = message.response_metadata;
200
+ let additionalKwargs = message.additional_kwargs;
201
+ if (toolOutputSource?.source === "response_metadata") responseMetadata = cloneWithProjectedProperties(responseMetadata, { output: keptToolOutputs });
202
+ else if (toolOutputSource?.source === "tool_outputs") additionalKwargs = cloneWithProjectedProperties(additionalKwargs, { tool_outputs: keptToolOutputs });
203
+ return cloneWithProjectedProperties(message, {
170
204
  content: toLangChainContent(keptContent),
171
- tool_calls: keptToolCalls.length > 0 ? keptToolCalls : void 0
205
+ tool_calls: keptToolCalls.length > 0 ? keptToolCalls : void 0,
206
+ response_metadata: responseMetadata,
207
+ additional_kwargs: additionalKwargs
172
208
  });
173
209
  }
174
210
  /**
@@ -186,11 +222,12 @@ function stripOrphanToolUseBlocks(message, presentToolResultIds) {
186
222
  * Includes a fast-path: if every tool_call has a matching tool_result and
187
223
  * vice-versa, the original array is returned immediately with zero allocation.
188
224
  */
189
- function sanitizeOrphanToolBlocks(messages) {
225
+ function sanitizeOrphanToolBlocks(messages, onMessageCloned) {
190
226
  const allToolCallIds = /* @__PURE__ */ new Set();
191
227
  const allToolResultIds = /* @__PURE__ */ new Set();
192
228
  for (const msg of messages) {
193
229
  const msgAny = msg;
230
+ if (typeof msg.getType === "function") for (const id of getToolCallIds(msg)) allToolCallIds.add(id);
194
231
  const toolCalls = msgAny.tool_calls;
195
232
  if (Array.isArray(toolCalls)) {
196
233
  for (const tc of toolCalls) if (typeof tc.id === "string" && tc.id.length > 0 && !tc.id.startsWith("srvtoolu_")) allToolCallIds.add(tc.id);
@@ -227,6 +264,7 @@ function sanitizeOrphanToolBlocks(messages) {
227
264
  const stripped = stripOrphanToolUseBlocks(msg, allToolResultIds);
228
265
  if (stripped != null) {
229
266
  strippedAiIndices.add(result.length);
267
+ onMessageCloned?.(msg, stripped);
230
268
  result.push(stripped);
231
269
  }
232
270
  continue;
@@ -242,6 +280,7 @@ function sanitizeOrphanToolBlocks(messages) {
242
280
  const patched = Object.create(Object.getPrototypeOf(msg), Object.getOwnPropertyDescriptors(msg));
243
281
  patched.tool_calls = keptToolCalls.length > 0 ? keptToolCalls : [];
244
282
  patched.content = keptContent;
283
+ onMessageCloned?.(msg, patched);
245
284
  result.push(patched);
246
285
  continue;
247
286
  }
@@ -536,26 +575,21 @@ function maskConsumedToolResults(params) {
536
575
  for (let c = 0; c < count; c++) {
537
576
  const i = consumedIndices[c];
538
577
  const message = messages[i];
578
+ if (isComputerCallOutputMessage(message)) continue;
539
579
  const content = message.content;
540
- if (typeof content !== "string") continue;
541
580
  let maxChars;
542
581
  if (totalBudgetChars > 0) {
543
582
  const share = (.2 + .8 * (count > 1 ? c / (count - 1) : 1)) / (count > 1 ? .6 * count : 1) * totalBudgetChars;
544
583
  maxChars = Math.max(MASKED_RESULT_MAX_CHARS, Math.floor(share));
545
584
  } else maxChars = MASKED_RESULT_MAX_CHARS;
546
- if (content.length <= maxChars) continue;
585
+ const compacted = compactToolContent(content, maxChars);
586
+ if (!compacted.changed) continue;
547
587
  if (params.originalContentStore && !params.originalContentStore.has(i)) {
548
- params.originalContentStore.set(i, content);
549
- if (params.onContentStored) params.onContentStored(i, content);
588
+ const original = serializeToolContentBounded(content, ORIGINAL_CONTENT_MAX_CHARS);
589
+ params.originalContentStore.set(i, original);
590
+ if (params.onContentStored) params.onContentStored(i, original);
550
591
  }
551
- const cloned = new ToolMessage({
552
- content: truncateToolResultContent(content, maxChars),
553
- tool_call_id: message.tool_call_id,
554
- name: message.name,
555
- id: message.id,
556
- additional_kwargs: message.additional_kwargs,
557
- response_metadata: message.response_metadata
558
- });
592
+ const cloned = cloneToolMessageWithContent(message, compacted.content);
559
593
  messages[i] = cloned;
560
594
  indexTokenCountMap[i] = tokenCounter(cloned);
561
595
  maskedCount++;
@@ -578,23 +612,15 @@ function preFlightTruncateToolResults(params) {
578
612
  const baseMaxChars = calculateMaxToolResultChars(maxContextTokens);
579
613
  let truncatedCount = 0;
580
614
  const toolIndices = [];
581
- for (let i = 0; i < messages.length; i++) if (messages[i].getType() === "tool") toolIndices.push(i);
615
+ for (let i = 0; i < messages.length; i++) if (messages[i].getType() === "tool" && !isComputerCallOutputMessage(messages[i])) toolIndices.push(i);
582
616
  for (let t = 0; t < toolIndices.length; t++) {
583
617
  const i = toolIndices[t];
584
618
  const message = messages[i];
585
619
  const content = message.content;
586
- if (typeof content !== "string") continue;
587
620
  const recencyFactor = .2 + .8 * (toolIndices.length > 1 ? t / (toolIndices.length - 1) : 1);
588
- const maxChars = Math.max(200, Math.floor(baseMaxChars * recencyFactor));
589
- if (content.length <= maxChars) continue;
590
- const cloned = new ToolMessage({
591
- content: truncateToolResultContent(content, maxChars),
592
- tool_call_id: message.tool_call_id,
593
- name: message.name,
594
- id: message.id,
595
- additional_kwargs: message.additional_kwargs,
596
- response_metadata: message.response_metadata
597
- });
621
+ const compacted = compactToolContent(content, Math.max(200, Math.floor(baseMaxChars * recencyFactor)));
622
+ if (!compacted.changed) continue;
623
+ const cloned = cloneToolMessageWithContent(message, compacted.content);
598
624
  messages[i] = cloned;
599
625
  indexTokenCountMap[i] = tokenCounter(cloned);
600
626
  truncatedCount++;
@@ -614,46 +640,385 @@ function preFlightTruncateToolResults(params) {
614
640
  *
615
641
  * @returns The number of AI messages that had tool_use inputs truncated.
616
642
  */
617
- function preFlightTruncateToolCallInputs(params) {
618
- const { messages, maxContextTokens, indexTokenCountMap, tokenCounter } = params;
619
- const maxInputChars = Math.min(Math.floor(maxContextTokens * .15) * 4, 2e5);
620
- let truncatedCount = 0;
621
- for (let i = 0; i < messages.length; i++) {
622
- const message = messages[i];
623
- if (message.getType() !== "ai") continue;
624
- if (!Array.isArray(message.content)) continue;
625
- const originalContent = message.content;
626
- const state = { changed: false };
627
- const newContent = originalContent.map((block) => {
628
- if (typeof block !== "object") return block;
629
- const record = block;
630
- if (record.type !== "tool_use" && record.type !== "tool_call") return block;
631
- const input = record.input;
632
- if (input == null) return block;
633
- const serialized = typeof input === "string" ? input : JSON.stringify(input);
634
- if (serialized.length <= maxInputChars) return block;
635
- state.changed = true;
643
+ const HARD_MAX_TOOL_CALL_INPUT_CHARS = 2e5;
644
+ const MIN_JSON_VALUE_CHARS = 4;
645
+ const ACCESSOR_INPUT_PLACEHOLDER = "[Property accessor omitted]";
646
+ function normalizeToolInputLimit(maxChars) {
647
+ if (!Number.isFinite(maxChars)) return HARD_MAX_TOOL_CALL_INPUT_CHARS;
648
+ return Math.max(MIN_JSON_VALUE_CHARS, Math.floor(maxChars));
649
+ }
650
+ function readPropertyWithoutAccessors(value, key) {
651
+ let current = value;
652
+ for (let depth = 0; current != null && depth < 100; depth++) {
653
+ if (isProxy(current)) return {
654
+ found: true,
655
+ own: current === value,
656
+ accessor: true
657
+ };
658
+ let descriptor;
659
+ try {
660
+ descriptor = Object.getOwnPropertyDescriptor(current, key);
661
+ } catch {
636
662
  return {
637
- ...record,
638
- input: truncateToolInput(serialized, maxInputChars)
663
+ found: true,
664
+ own: current === value,
665
+ accessor: true
666
+ };
667
+ }
668
+ if (descriptor != null) {
669
+ if (!("value" in descriptor)) return {
670
+ found: true,
671
+ own: current === value,
672
+ accessor: true
639
673
  };
640
- });
641
- if (!state.changed) continue;
642
- const aiMsg = message;
643
- const newToolCalls = (aiMsg.tool_calls ?? []).map((tc) => {
644
- const serializedArgs = JSON.stringify(tc.args);
645
- if (serializedArgs.length <= maxInputChars) return tc;
646
674
  return {
647
- ...tc,
648
- args: truncateToolInput(serializedArgs, maxInputChars)
675
+ found: true,
676
+ own: current === value,
677
+ accessor: false,
678
+ value: descriptor.value
679
+ };
680
+ }
681
+ try {
682
+ current = Object.getPrototypeOf(current);
683
+ } catch {
684
+ return {
685
+ found: true,
686
+ own: false,
687
+ accessor: true
688
+ };
689
+ }
690
+ }
691
+ return current == null ? {
692
+ found: false,
693
+ own: false,
694
+ accessor: false
695
+ } : {
696
+ found: true,
697
+ own: false,
698
+ accessor: true
699
+ };
700
+ }
701
+ function cloneWithProjectedProperties(value, changes) {
702
+ const descriptors = Object.getOwnPropertyDescriptors(value);
703
+ for (const [key, projectedValue] of Object.entries(changes)) {
704
+ const descriptor = descriptors[key];
705
+ descriptors[key] = {
706
+ configurable: descriptor?.configurable ?? true,
707
+ enumerable: descriptor?.enumerable ?? true,
708
+ value: projectedValue,
709
+ writable: descriptor?.writable ?? true
710
+ };
711
+ }
712
+ return Object.create(Object.getPrototypeOf(value), descriptors);
713
+ }
714
+ function createBoundedTruncationValue(preview, originalChars, maxChars) {
715
+ const normalizedMaxChars = normalizeToolInputLimit(maxChars);
716
+ if (JSON.stringify({
717
+ _truncated: "",
718
+ _originalChars: originalChars
719
+ }).length > normalizedMaxChars) return null;
720
+ let low = 0;
721
+ let high = Math.min(preview.length, normalizedMaxChars);
722
+ while (low < high) {
723
+ const next = Math.ceil((low + high) / 2);
724
+ const candidate = {
725
+ _truncated: preview.slice(0, next),
726
+ _originalChars: originalChars
727
+ };
728
+ if (JSON.stringify(candidate).length <= normalizedMaxChars) low = next;
729
+ else high = next - 1;
730
+ }
731
+ return {
732
+ _truncated: low < preview.length && low >= 13 ? preview.slice(0, low - 13) + "… [truncated]" : preview.slice(0, low),
733
+ _originalChars: originalChars
734
+ };
735
+ }
736
+ function projectToolInputWithinLimit(input, maxChars) {
737
+ const normalizedMaxChars = normalizeToolInputLimit(maxChars);
738
+ const serialized = serializeStructuredValueBounded(input, normalizedMaxChars);
739
+ if (serialized.truncated) return {
740
+ value: createBoundedTruncationValue(serialized.content, serialized.originalChars, normalizedMaxChars),
741
+ changed: true
742
+ };
743
+ if (!hasUnsafeStructuredSerialization(input)) return {
744
+ value: input,
745
+ changed: false
746
+ };
747
+ try {
748
+ return {
749
+ value: JSON.parse(serialized.content),
750
+ changed: true
751
+ };
752
+ } catch {
753
+ return {
754
+ value: createBoundedTruncationValue(serialized.content, serialized.originalChars, normalizedMaxChars),
755
+ changed: true
756
+ };
757
+ }
758
+ }
759
+ /**
760
+ * Serializes one structured tool-call input as valid, bounded JSON without
761
+ * invoking user-defined accessors or `toJSON`.
762
+ */
763
+ function serializeToolCallInput(input, maxChars = HARD_MAX_TOOL_CALL_INPUT_CHARS) {
764
+ const normalizedMaxChars = normalizeToolInputLimit(maxChars);
765
+ const serialized = serializeStructuredValueBounded(projectToolInputWithinLimit(input, normalizedMaxChars).value, normalizedMaxChars);
766
+ if (!serialized.truncated) return serialized.content === "undefined" ? "null" : serialized.content;
767
+ const fallback = createBoundedTruncationValue(serialized.content, serialized.originalChars, normalizedMaxChars);
768
+ return JSON.stringify(fallback);
769
+ }
770
+ function projectKnownInputProperty(value, key, maxChars, changes) {
771
+ const property = readPropertyWithoutAccessors(value, key);
772
+ if (!property.found) return;
773
+ const projected = property.accessor ? {
774
+ value: ACCESSOR_INPUT_PLACEHOLDER,
775
+ changed: true
776
+ } : projectToolInputWithinLimit(property.value, maxChars);
777
+ if (projected.changed || !property.own) changes[key] = projected.value;
778
+ }
779
+ function getStringProperty(value, key) {
780
+ const property = readPropertyWithoutAccessors(value, key);
781
+ return !property.accessor && typeof property.value === "string" ? property.value : void 0;
782
+ }
783
+ function projectInlineToolInput(block, maxChars) {
784
+ const type = getStringProperty(block, "type");
785
+ if (type !== "tool_use" && type !== "tool_call") return block;
786
+ const changes = {};
787
+ projectKnownInputProperty(block, "input", maxChars, changes);
788
+ projectKnownInputProperty(block, "args", maxChars, changes);
789
+ const nestedProperty = readPropertyWithoutAccessors(block, "tool_call");
790
+ if (nestedProperty.found && !nestedProperty.accessor && nestedProperty.value != null && typeof nestedProperty.value === "object") {
791
+ const nestedChanges = {};
792
+ projectKnownInputProperty(nestedProperty.value, "args", maxChars, nestedChanges);
793
+ if (Object.keys(nestedChanges).length > 0) changes.tool_call = cloneWithProjectedProperties(nestedProperty.value, nestedChanges);
794
+ else if (!nestedProperty.own) changes.tool_call = nestedProperty.value;
795
+ } else if (nestedProperty.accessor) changes.tool_call = { args: ACCESSOR_INPUT_PLACEHOLDER };
796
+ return Object.keys(changes).length > 0 ? cloneWithProjectedProperties(block, changes) : block;
797
+ }
798
+ function projectSerializedArguments(value, maxChars) {
799
+ const normalizedMaxChars = normalizeToolInputLimit(maxChars);
800
+ if (typeof value === "string" && value.length <= normalizedMaxChars) return {
801
+ value,
802
+ changed: false
803
+ };
804
+ return {
805
+ value: serializeToolCallInput(value, normalizedMaxChars),
806
+ changed: true
807
+ };
808
+ }
809
+ function selectProjectedSerializedArguments(property, canonical, maxChars) {
810
+ if (canonical != null) return {
811
+ value: canonical,
812
+ changed: property.value !== canonical
813
+ };
814
+ if (property.accessor) return {
815
+ value: serializeToolCallInput(ACCESSOR_INPUT_PLACEHOLDER, maxChars),
816
+ changed: true
817
+ };
818
+ return projectSerializedArguments(property.value, maxChars);
819
+ }
820
+ function projectRawOpenAIToolCalls(rawToolCalls, maxChars, canonicalArguments) {
821
+ if (!Array.isArray(rawToolCalls)) return {
822
+ value: rawToolCalls,
823
+ changed: false
824
+ };
825
+ const state = { changed: false };
826
+ const projected = rawToolCalls.map((rawToolCall) => {
827
+ if (rawToolCall == null || typeof rawToolCall !== "object") return rawToolCall;
828
+ const functionProperty = readPropertyWithoutAccessors(rawToolCall, "function");
829
+ if (!functionProperty.found || functionProperty.accessor || functionProperty.value == null || typeof functionProperty.value !== "object") return rawToolCall;
830
+ const argsProperty = readPropertyWithoutAccessors(functionProperty.value, "arguments");
831
+ if (!argsProperty.found) return rawToolCall;
832
+ const callId = getStringProperty(rawToolCall, "id");
833
+ const projectedArgs = selectProjectedSerializedArguments(argsProperty, callId != null ? canonicalArguments.get(callId) : void 0, maxChars);
834
+ if (!projectedArgs.changed && functionProperty.own && argsProperty.own) return rawToolCall;
835
+ state.changed = true;
836
+ return cloneWithProjectedProperties(rawToolCall, { function: cloneWithProjectedProperties(functionProperty.value, { arguments: projectedArgs.value }) });
837
+ });
838
+ return {
839
+ value: state.changed ? projected : rawToolCalls,
840
+ changed: state.changed
841
+ };
842
+ }
843
+ function projectLegacyFunctionCall(property, maxChars) {
844
+ if (!property.found) return {
845
+ value: property.value,
846
+ changed: false
847
+ };
848
+ if (property.own && !property.accessor && property.value === void 0) return {
849
+ value: void 0,
850
+ changed: false
851
+ };
852
+ if (property.accessor || !property.own || property.value == null || typeof property.value !== "object") return {
853
+ value: void 0,
854
+ changed: true
855
+ };
856
+ const nameProperty = readPropertyWithoutAccessors(property.value, "name");
857
+ const argsProperty = readPropertyWithoutAccessors(property.value, "arguments");
858
+ if (nameProperty.accessor || !nameProperty.own || typeof nameProperty.value !== "string" || nameProperty.value === "" || nameProperty.value.length > normalizeToolInputLimit(maxChars) || !argsProperty.found || argsProperty.accessor || !argsProperty.own) return {
859
+ value: void 0,
860
+ changed: true
861
+ };
862
+ try {
863
+ const prototype = Object.getPrototypeOf(property.value);
864
+ const enumerableKeys = Object.keys(property.value);
865
+ if ((prototype === Object.prototype || prototype === null) && enumerableKeys.length === 2 && enumerableKeys.includes("name") && enumerableKeys.includes("arguments") && typeof argsProperty.value === "string" && argsProperty.value.length <= normalizeToolInputLimit(maxChars) && !hasUnsafeStructuredSerialization(property.value)) return {
866
+ value: property.value,
867
+ changed: false
868
+ };
869
+ } catch {
870
+ return {
871
+ value: void 0,
872
+ changed: true
873
+ };
874
+ }
875
+ const projectedArgs = selectProjectedSerializedArguments(argsProperty, void 0, maxChars);
876
+ return {
877
+ value: {
878
+ name: nameProperty.value,
879
+ arguments: projectedArgs.value
880
+ },
881
+ changed: true
882
+ };
883
+ }
884
+ function projectResponsesOutput(output, maxChars, canonicalArguments, canonicalInputs) {
885
+ if (!Array.isArray(output)) return {
886
+ value: output,
887
+ changed: false
888
+ };
889
+ const state = { changed: false };
890
+ const projected = output.map((item) => {
891
+ if (item == null || typeof item !== "object") return item;
892
+ const type = getStringProperty(item, "type");
893
+ if (type !== "function_call" && type !== "custom_tool_call" && type !== "computer_call") return item;
894
+ let inputKey = "action";
895
+ if (type === "function_call") inputKey = "arguments";
896
+ else if (type === "custom_tool_call") inputKey = "input";
897
+ const inputProperty = readPropertyWithoutAccessors(item, inputKey);
898
+ if (!inputProperty.found) return item;
899
+ const callId = getStringProperty(item, "call_id") ?? getStringProperty(item, "id");
900
+ let projectedInput;
901
+ if (type === "function_call") projectedInput = selectProjectedSerializedArguments(inputProperty, callId != null ? canonicalArguments.get(callId) : void 0, maxChars);
902
+ else {
903
+ const canonicalArgs = callId != null ? canonicalInputs.get(callId) : void 0;
904
+ const canonicalProperty = canonicalArgs != null && typeof canonicalArgs === "object" ? readPropertyWithoutAccessors(canonicalArgs, inputKey) : void 0;
905
+ let source = inputProperty.accessor ? ACCESSOR_INPUT_PLACEHOLDER : inputProperty.value;
906
+ if (canonicalProperty != null && canonicalProperty.found) source = canonicalProperty.accessor ? ACCESSOR_INPUT_PLACEHOLDER : canonicalProperty.value;
907
+ if (type === "custom_tool_call" && typeof source === "string" && source.length <= normalizeToolInputLimit(maxChars)) projectedInput = {
908
+ value: source,
909
+ changed: !inputProperty.own || inputProperty.value !== source
649
910
  };
911
+ else if (type === "custom_tool_call") {
912
+ const value = typeof source === "string" ? truncateToolResultContent(source, normalizeToolInputLimit(maxChars)) : serializeToolCallInput(source, maxChars);
913
+ projectedInput = {
914
+ value,
915
+ changed: !inputProperty.own || inputProperty.value !== value
916
+ };
917
+ } else {
918
+ const projected = projectToolInputWithinLimit(source, maxChars);
919
+ projectedInput = {
920
+ value: projected.value,
921
+ changed: projected.changed || !inputProperty.own || inputProperty.value !== projected.value
922
+ };
923
+ }
924
+ }
925
+ if (!projectedInput.changed && inputProperty.own) return item;
926
+ state.changed = true;
927
+ return cloneWithProjectedProperties(item, { [inputKey]: projectedInput.value });
928
+ });
929
+ return {
930
+ value: state.changed ? projected : output,
931
+ changed: state.changed
932
+ };
933
+ }
934
+ /** Per-input cap: 15% of context at ~4 chars/token, never above 200K chars. */
935
+ function calculateMaxToolCallInputChars(maxContextTokens) {
936
+ if (maxContextTokens == null || maxContextTokens <= 0) return HARD_MAX_TOOL_CALL_INPUT_CHARS;
937
+ return Math.max(MIN_JSON_VALUE_CHARS, Math.min(Math.floor(maxContextTokens * .15) * 4, HARD_MAX_TOOL_CALL_INPUT_CHARS));
938
+ }
939
+ /**
940
+ * Projects historical tool-call inputs into a provider-safe bounded form.
941
+ * Returns the original array when no message changes and otherwise clones only
942
+ * the array and AI messages whose inline input or `tool_calls` args changed.
943
+ */
944
+ function projectToolCallInputs(messages, maxInputChars) {
945
+ const normalizedMaxInputChars = normalizeToolInputLimit(maxInputChars);
946
+ let projectedMessages;
947
+ for (let i = 0; i < messages.length; i++) {
948
+ const message = messages[i];
949
+ const messageRole = message.role;
950
+ if (message.getType() !== "ai" && messageRole !== "assistant") continue;
951
+ const aiMessage = message;
952
+ let projectedContent = aiMessage.content;
953
+ let contentChanged = false;
954
+ if (Array.isArray(aiMessage.content)) {
955
+ const originalContent = aiMessage.content;
956
+ const mappedContent = originalContent.map((block) => projectInlineToolInput(block, normalizedMaxInputChars));
957
+ contentChanged = mappedContent.some((block, blockIndex) => block !== originalContent[blockIndex]);
958
+ projectedContent = toLangChainContent(mappedContent);
959
+ }
960
+ const originalToolCalls = aiMessage.tool_calls ?? [];
961
+ const projectedToolCalls = originalToolCalls.map((toolCall) => {
962
+ const argsProperty = readPropertyWithoutAccessors(toolCall, "args");
963
+ const projected = argsProperty.accessor ? {
964
+ value: ACCESSOR_INPUT_PLACEHOLDER,
965
+ changed: true
966
+ } : projectToolInputWithinLimit(argsProperty.value, normalizedMaxInputChars);
967
+ if (argsProperty.own && !projected.changed) return toolCall;
968
+ return cloneWithProjectedProperties(toolCall, { args: projected.value });
650
969
  });
651
- messages[i] = new AIMessage({
652
- ...aiMsg,
653
- content: toLangChainContent(newContent),
654
- tool_calls: newToolCalls.length > 0 ? newToolCalls : void 0
970
+ const toolCallsChanged = projectedToolCalls.some((toolCall, toolCallIndex) => toolCall !== originalToolCalls[toolCallIndex]);
971
+ const rawAdditionalToolCallsProperty = readPropertyWithoutAccessors(aiMessage.additional_kwargs, "tool_calls");
972
+ const rawAdditionalToolCalls = rawAdditionalToolCallsProperty.accessor ? void 0 : rawAdditionalToolCallsProperty.value;
973
+ const rawResponsesOutputProperty = readPropertyWithoutAccessors(aiMessage.response_metadata, "output");
974
+ const rawResponsesOutput = rawResponsesOutputProperty.accessor ? void 0 : rawResponsesOutputProperty.value;
975
+ const canonicalArguments = /* @__PURE__ */ new Map();
976
+ const canonicalInputs = /* @__PURE__ */ new Map();
977
+ if (Array.isArray(rawAdditionalToolCalls) || Array.isArray(rawResponsesOutput)) for (const toolCall of projectedToolCalls) {
978
+ const id = getStringProperty(toolCall, "id");
979
+ if (id == null) continue;
980
+ const args = readPropertyWithoutAccessors(toolCall, "args");
981
+ const canonicalInput = args.accessor ? ACCESSOR_INPUT_PLACEHOLDER : args.value;
982
+ canonicalInputs.set(id, canonicalInput);
983
+ canonicalArguments.set(id, serializeToolCallInput(canonicalInput, normalizedMaxInputChars));
984
+ }
985
+ const additionalKwargsChanges = rawAdditionalToolCallsProperty.accessor ? { tool_calls: void 0 } : {};
986
+ const rawToolCalls = projectRawOpenAIToolCalls(rawAdditionalToolCalls, normalizedMaxInputChars, canonicalArguments);
987
+ if (rawToolCalls.changed) additionalKwargsChanges.tool_calls = rawToolCalls.value;
988
+ const legacyFunctionCall = projectLegacyFunctionCall(readPropertyWithoutAccessors(aiMessage.additional_kwargs, "function_call"), normalizedMaxInputChars);
989
+ if (legacyFunctionCall.changed) additionalKwargsChanges.function_call = legacyFunctionCall.value;
990
+ const additionalKwargsChanged = Object.keys(additionalKwargsChanges).length > 0;
991
+ let projectedAdditionalKwargs = aiMessage.additional_kwargs;
992
+ if (additionalKwargsChanged) projectedAdditionalKwargs = isProxy(aiMessage.additional_kwargs) ? { ...additionalKwargsChanges } : cloneWithProjectedProperties(aiMessage.additional_kwargs, additionalKwargsChanges);
993
+ let projectedResponseMetadata = aiMessage.response_metadata;
994
+ let responseMetadataChanged = rawResponsesOutputProperty.accessor;
995
+ const responsesOutput = projectResponsesOutput(rawResponsesOutput, normalizedMaxInputChars, canonicalArguments, canonicalInputs);
996
+ if (responsesOutput.changed || rawResponsesOutputProperty.accessor) {
997
+ const output = rawResponsesOutputProperty.accessor ? void 0 : responsesOutput.value;
998
+ projectedResponseMetadata = isProxy(aiMessage.response_metadata) ? { output } : cloneWithProjectedProperties(aiMessage.response_metadata, { output });
999
+ responseMetadataChanged = true;
1000
+ }
1001
+ if (!contentChanged && !toolCallsChanged && !additionalKwargsChanged && !responseMetadataChanged) continue;
1002
+ const projectedMessage = cloneWithProjectedProperties(aiMessage, {
1003
+ content: projectedContent,
1004
+ tool_calls: projectedToolCalls.length > 0 ? projectedToolCalls : [],
1005
+ additional_kwargs: projectedAdditionalKwargs,
1006
+ response_metadata: projectedResponseMetadata
655
1007
  });
656
- indexTokenCountMap[i] = tokenCounter(messages[i]);
1008
+ projectedMessages ??= [...messages];
1009
+ projectedMessages[i] = projectedMessage;
1010
+ }
1011
+ return projectedMessages ?? messages;
1012
+ }
1013
+ function preFlightTruncateToolCallInputs(params) {
1014
+ const { messages, maxContextTokens, indexTokenCountMap, tokenCounter } = params;
1015
+ const projected = projectToolCallInputs(messages, calculateMaxToolCallInputChars(maxContextTokens));
1016
+ if (projected === messages) return 0;
1017
+ let truncatedCount = 0;
1018
+ for (let i = 0; i < messages.length; i++) {
1019
+ if (projected[i] === messages[i]) continue;
1020
+ messages[i] = projected[i];
1021
+ indexTokenCountMap[i] = tokenCounter(projected[i]);
657
1022
  truncatedCount++;
658
1023
  }
659
1024
  return truncatedCount;
@@ -664,6 +1029,7 @@ function createPruneMessages(factoryParams) {
664
1029
  let lastCutOffIndex = 0;
665
1030
  let totalTokens = 0;
666
1031
  for (const key in indexTokenCountMap) totalTokens += indexTokenCountMap[key] ?? 0;
1032
+ const reconciledToolMessages = /* @__PURE__ */ new WeakSet();
667
1033
  let runThinkingStartIndex = -1;
668
1034
  /** Cumulative raw tiktoken tokens we've sent to the provider (messages only,
669
1035
  * excludes instruction overhead and new outputs not yet seen by provider). */
@@ -676,6 +1042,7 @@ function createPruneMessages(factoryParams) {
676
1042
  /** Best observed instruction overhead from a near-zero variance turn.
677
1043
  * Self-seeds from provider observations within the run. */
678
1044
  let bestInstructionOverhead;
1045
+ const reconciledLegacyAiMessages = /* @__PURE__ */ new WeakSet();
679
1046
  let bestVarianceAbs = Infinity;
680
1047
  /** Local estimate at the time bestInstructionOverhead was observed.
681
1048
  * Used to invalidate the cached overhead when instructions change
@@ -696,12 +1063,13 @@ function createPruneMessages(factoryParams) {
696
1063
  const emptyInstructionTokens = factoryParams.getInstructionTokens?.() ?? 0;
697
1064
  const emptyReserveRatio = factoryParams.reserveRatio ?? .05;
698
1065
  const emptyBudget = factoryParams.maxTokens - (emptyReserveRatio > 0 && emptyReserveRatio < 1 ? Math.round(factoryParams.maxTokens * emptyReserveRatio) : 0);
1066
+ const emptyReplyPrimerTokens = Math.round(3 * calibrationRatio);
699
1067
  return {
700
1068
  context: [],
701
1069
  indexTokenCountMap,
702
1070
  messagesToRefine: [],
703
1071
  prePruneContextTokens: 0,
704
- remainingContextTokens: Math.max(0, emptyBudget - emptyInstructionTokens),
1072
+ remainingContextTokens: Math.max(0, emptyBudget - emptyInstructionTokens - emptyReplyPrimerTokens),
705
1073
  calibrationRatio,
706
1074
  resolvedInstructionOverhead: bestInstructionOverhead,
707
1075
  contextBudget: emptyBudget,
@@ -731,6 +1099,45 @@ function createPruneMessages(factoryParams) {
731
1099
  let currentUsage;
732
1100
  if (params.usageMetadata && (checkValidNumber(params.usageMetadata.input_tokens) || checkValidNumber(params.usageMetadata.input_token_details) && (checkValidNumber(params.usageMetadata.input_token_details.cache_creation) || checkValidNumber(params.usageMetadata.input_token_details.cache_read))) && checkValidNumber(params.usageMetadata.output_tokens)) currentUsage = calculateTotalTokens(params.usageMetadata);
733
1101
  const newOutputs = /* @__PURE__ */ new Set();
1102
+ const applyReconciledCount = (index, cachedCount, reconciledCount) => {
1103
+ if (cachedCount === void 0) {
1104
+ indexTokenCountMap[index] = reconciledCount;
1105
+ totalTokens += reconciledCount;
1106
+ if (index >= lastTurnStartIndex) newOutputs.add(index);
1107
+ return;
1108
+ }
1109
+ if (reconciledCount <= cachedCount) return;
1110
+ indexTokenCountMap[index] = reconciledCount;
1111
+ totalTokens += reconciledCount - cachedCount;
1112
+ if (index >= lastTurnStartIndex) newOutputs.add(index);
1113
+ };
1114
+ for (let i = 0; i < params.messages.length; i++) {
1115
+ let message = params.messages[i];
1116
+ const cachedCount = indexTokenCountMap[i];
1117
+ const messageType = message.getType();
1118
+ const messageRole = message.role;
1119
+ if ((messageType === "ai" || messageRole === "assistant" ? readPropertyWithoutAccessors(message.additional_kwargs, "function_call") : void 0)?.found === true && !reconciledLegacyAiMessages.has(message)) {
1120
+ const [projected] = projectToolCallInputs([message], calculateMaxToolCallInputChars(factoryParams.maxTokens));
1121
+ if (projected !== message) {
1122
+ message = projected;
1123
+ params.messages[i] = message;
1124
+ }
1125
+ reconciledLegacyAiMessages.add(message);
1126
+ if (cachedCount !== void 0 || i < lastTurnStartIndex) applyReconciledCount(i, cachedCount, factoryParams.tokenCounter(message));
1127
+ continue;
1128
+ }
1129
+ if (messageType !== "tool" || reconciledToolMessages.has(message)) continue;
1130
+ if (!isComputerCallOutputMessage(message)) {
1131
+ const normalized = compactToolContent(message.content, factoryParams.maxToolResultChars ?? 4e5);
1132
+ if (normalized.changed) {
1133
+ message = cloneToolMessageWithContent(message, normalized.content);
1134
+ params.messages[i] = message;
1135
+ }
1136
+ }
1137
+ const reconciledCount = factoryParams.tokenCounter(message);
1138
+ reconciledToolMessages.add(message);
1139
+ applyReconciledCount(i, cachedCount, reconciledCount);
1140
+ }
734
1141
  let outputTokensAssigned = false;
735
1142
  for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
736
1143
  const message = params.messages[i];
@@ -912,12 +1319,13 @@ function createPruneMessages(factoryParams) {
912
1319
  tokensSaved: preTruncationTotalTokens - totalTokens
913
1320
  });
914
1321
  lastTurnStartIndex = params.messages.length;
915
- if (lastCutOffIndex === 0 && calibratedTotalTokens + currentInstructionTokens <= pruningBudget) return {
1322
+ const calibratedReplyPrimerTokens = Math.round(3 * calibrationRatio);
1323
+ if (lastCutOffIndex === 0 && calibratedTotalTokens + calibratedReplyPrimerTokens + currentInstructionTokens <= pruningBudget) return {
916
1324
  context: params.messages,
917
1325
  indexTokenCountMap,
918
1326
  messagesToRefine: [],
919
1327
  prePruneContextTokens: calibratedTotalTokens,
920
- remainingContextTokens: pruningBudget - calibratedTotalTokens - currentInstructionTokens,
1328
+ remainingContextTokens: pruningBudget - calibratedTotalTokens - calibratedReplyPrimerTokens - currentInstructionTokens,
921
1329
  contextPressure,
922
1330
  originalToolContent: originalToolContent.size > 0 ? originalToolContent : void 0,
923
1331
  newOriginalToolContent,
@@ -1027,60 +1435,24 @@ function createPruneMessages(factoryParams) {
1027
1435
  let emergencyTruncatedCount = 0;
1028
1436
  for (let i = 0; i < emergencyMessages.length; i++) {
1029
1437
  const message = emergencyMessages[i];
1030
- if (message.getType() === "tool") {
1438
+ if (message.getType() === "tool" && !isComputerCallOutputMessage(message)) {
1031
1439
  const content = message.content;
1032
- if (typeof content === "string" && content.length > emergencyMaxChars) {
1033
- const cloned = new ToolMessage({
1034
- content: truncateToolResultContent(content, emergencyMaxChars),
1035
- tool_call_id: message.tool_call_id,
1036
- name: message.name,
1037
- id: message.id,
1038
- additional_kwargs: message.additional_kwargs,
1039
- response_metadata: message.response_metadata
1040
- });
1440
+ if (getToolContentCharLength(content) > emergencyMaxChars) {
1441
+ const compacted = compactToolContent(content, emergencyMaxChars);
1442
+ if (!compacted.changed) continue;
1443
+ const cloned = cloneToolMessageWithContent(message, compacted.content);
1041
1444
  emergencyMessages[i] = cloned;
1042
1445
  indexTokenCountMap[i] = factoryParams.tokenCounter(cloned);
1043
1446
  emergencyTruncatedCount++;
1044
1447
  }
1045
1448
  }
1046
- if (message.getType() === "ai" && Array.isArray(message.content)) {
1047
- const aiMsg = message;
1048
- const contentBlocks = aiMsg.content;
1049
- if (contentBlocks.some((block) => {
1050
- if (typeof block !== "object") return false;
1051
- const record = block;
1052
- if ((record.type === "tool_use" || record.type === "tool_call") && record.input != null) return (typeof record.input === "string" ? record.input : JSON.stringify(record.input)).length > emergencyMaxChars;
1053
- return false;
1054
- })) {
1055
- const newContent = contentBlocks.map((block) => {
1056
- if (typeof block !== "object") return block;
1057
- const record = block;
1058
- if ((record.type === "tool_use" || record.type === "tool_call") && record.input != null) {
1059
- const serialized = typeof record.input === "string" ? record.input : JSON.stringify(record.input);
1060
- if (serialized.length > emergencyMaxChars) return {
1061
- ...record,
1062
- input: truncateToolInput(serialized, emergencyMaxChars)
1063
- };
1064
- }
1065
- return block;
1066
- });
1067
- const newToolCalls = (aiMsg.tool_calls ?? []).map((tc) => {
1068
- const serializedArgs = JSON.stringify(tc.args);
1069
- if (serializedArgs.length > emergencyMaxChars) return {
1070
- ...tc,
1071
- args: truncateToolInput(serializedArgs, emergencyMaxChars)
1072
- };
1073
- return tc;
1074
- });
1075
- emergencyMessages[i] = new AIMessage({
1076
- ...aiMsg,
1077
- content: toLangChainContent(newContent),
1078
- tool_calls: newToolCalls.length > 0 ? newToolCalls : void 0
1079
- });
1080
- indexTokenCountMap[i] = factoryParams.tokenCounter(emergencyMessages[i]);
1081
- emergencyTruncatedCount++;
1082
- }
1083
- }
1449
+ }
1450
+ const projectedToolInputs = projectToolCallInputs(emergencyMessages, emergencyMaxChars);
1451
+ if (projectedToolInputs !== emergencyMessages) for (let i = 0; i < emergencyMessages.length; i++) {
1452
+ if (projectedToolInputs[i] === emergencyMessages[i]) continue;
1453
+ emergencyMessages[i] = projectedToolInputs[i];
1454
+ indexTokenCountMap[i] = factoryParams.tokenCounter(projectedToolInputs[i]);
1455
+ emergencyTruncatedCount++;
1084
1456
  }
1085
1457
  factoryParams.log?.("info", "Emergency truncation complete");
1086
1458
  factoryParams.log?.("debug", "Emergency truncation details", {
@@ -1141,6 +1513,6 @@ function createPruneMessages(factoryParams) {
1141
1513
  };
1142
1514
  }
1143
1515
  //#endregion
1144
- export { CALIBRATION_RATIO_MAX, CALIBRATION_RATIO_MIN, DEFAULT_RESERVE_RATIO, ORIGINAL_CONTENT_MAX_CHARS, calculateTotalTokens, checkValidNumber, clampCalibrationRatio, createPruneMessages, enforceOriginalContentCap, getMessagesWithinTokenLimit, maskConsumedToolResults, preFlightTruncateToolCallInputs, preFlightTruncateToolResults, repairOrphanedToolMessages, sanitizeOrphanToolBlocks };
1516
+ export { CALIBRATION_RATIO_MAX, CALIBRATION_RATIO_MIN, DEFAULT_RESERVE_RATIO, ORIGINAL_CONTENT_MAX_CHARS, REPLY_PRIMER_TOKENS, calculateMaxToolCallInputChars, calculateTotalTokens, checkValidNumber, clampCalibrationRatio, createPruneMessages, enforceOriginalContentCap, getMessagesWithinTokenLimit, maskConsumedToolResults, preFlightTruncateToolCallInputs, preFlightTruncateToolResults, projectToolCallInputs, repairOrphanedToolMessages, sanitizeOrphanToolBlocks, serializeToolCallInput };
1145
1517
 
1146
1518
  //# sourceMappingURL=prune.mjs.map