@librechat/agents 3.3.3 → 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.
- package/dist/cjs/agents/AgentContext.cjs +2 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +342 -109
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +37 -10
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +19 -7
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +64 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +41 -5
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +25 -4
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +4 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +19 -2
- package/dist/cjs/messages/cache.cjs +54 -33
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +17 -43
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +315 -23
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +218 -60
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/stream.cjs +7 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +28 -14
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +54 -41
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/llm.cjs +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +307 -20
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +1514 -0
- package/dist/cjs/utils/toolContent.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +2 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +349 -116
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +37 -10
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +19 -7
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +65 -5
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +41 -5
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +25 -4
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -7
- package/dist/esm/messages/cache.mjs +55 -34
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +17 -43
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +305 -24
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +218 -61
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/stream.mjs +7 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +28 -14
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +54 -41
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +6 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/llm.mjs +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +307 -21
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +1503 -0
- package/dist/esm/utils/toolContent.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +27 -1
- package/dist/types/llm/openai/index.d.ts +4 -0
- package/dist/types/llm/openai/utils/index.d.ts +2 -0
- package/dist/types/llm/openrouter/index.d.ts +5 -0
- package/dist/types/messages/contextPruning.d.ts +2 -1
- package/dist/types/messages/core.d.ts +36 -0
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/prune.d.ts +16 -12
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/utils/tokens.d.ts +7 -0
- package/dist/types/utils/toolContent.d.ts +107 -0
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
- package/src/agents/AgentContext.ts +1 -0
- package/src/graphs/Graph.ts +698 -200
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1128 -4
- package/src/llm/anthropic/utils/message_inputs.ts +158 -12
- package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +49 -1
- package/src/llm/bedrock/utils/message_inputs.ts +86 -13
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +196 -2
- package/src/llm/openai/index.ts +106 -6
- package/src/llm/openai/utils/index.ts +109 -53
- package/src/llm/openai/utils/messages.test.ts +330 -1
- package/src/llm/openrouter/index.ts +12 -2
- package/src/llm/openrouter/reasoning.test.ts +314 -0
- package/src/messages/__tests__/observationMasking.test.ts +93 -2
- package/src/messages/cache.tail.test.ts +193 -0
- package/src/messages/cache.test.ts +113 -0
- package/src/messages/cache.ts +92 -60
- package/src/messages/contextPruning.test.ts +184 -0
- package/src/messages/contextPruning.ts +49 -42
- package/src/messages/core.ts +653 -40
- package/src/messages/ensureThinkingBlock.test.ts +49 -1
- package/src/messages/foldToollessToolBlocks.test.ts +163 -5
- package/src/messages/format.ts +363 -91
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -0
- package/src/messages/prune.ts +996 -183
- package/src/specs/prune.test.ts +1083 -6
- package/src/specs/summarization-unit.test.ts +105 -0
- package/src/specs/tokens.test.ts +609 -32
- package/src/stream.ts +23 -15
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +147 -68
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
- package/src/tools/__tests__/directToolHooks.test.ts +255 -7
- package/src/tools/toolOutputReferences.ts +6 -0
- package/src/types/stream.ts +1 -1
- package/src/utils/llm.test.ts +18 -0
- package/src/utils/llm.ts +4 -1
- package/src/utils/tokens.ts +586 -31
- package/src/utils/toolContent.test.ts +1432 -0
- package/src/utils/toolContent.ts +2707 -0
package/src/messages/prune.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { isProxy } from 'node:util/types';
|
|
1
2
|
import {
|
|
2
3
|
AIMessage,
|
|
3
4
|
BaseMessage,
|
|
4
5
|
ToolMessage,
|
|
5
6
|
UsageMetadata,
|
|
6
7
|
} from '@langchain/core/messages';
|
|
8
|
+
import type { AIMessageChunk } from '@langchain/core/messages';
|
|
7
9
|
import type {
|
|
8
10
|
ThinkingContentText,
|
|
9
11
|
MessageContentComplex,
|
|
@@ -12,11 +14,20 @@ import type {
|
|
|
12
14
|
import type { ContextPruningConfig } from '@/types/graph';
|
|
13
15
|
import type { TokenCounter } from '@/types/run';
|
|
14
16
|
import {
|
|
17
|
+
cloneToolMessageWithContent,
|
|
18
|
+
compactToolContent,
|
|
19
|
+
getToolContentCharLength,
|
|
20
|
+
isComputerCallOutputMessage,
|
|
21
|
+
serializeStructuredValueBounded,
|
|
22
|
+
serializeToolContentBounded,
|
|
23
|
+
} from '@/utils/toolContent';
|
|
24
|
+
import {
|
|
25
|
+
HARD_MAX_TOOL_RESULT_CHARS,
|
|
15
26
|
calculateMaxToolResultChars,
|
|
16
27
|
truncateToolResultContent,
|
|
17
|
-
truncateToolInput,
|
|
18
28
|
} from '@/utils/truncation';
|
|
19
29
|
import { resolveContextPruningSettings } from './contextPruningSettings';
|
|
30
|
+
import { hasUnsafeStructuredSerialization } from '@/utils/tokens';
|
|
20
31
|
import { ContentTypes, Providers, Constants } from '@/common';
|
|
21
32
|
import { applyContextPruning } from './contextPruning';
|
|
22
33
|
import { toLangChainContent } from './langchain';
|
|
@@ -35,6 +46,9 @@ function sumTokenCounts(
|
|
|
35
46
|
/** Default fraction of the token budget reserved as headroom (5 %). */
|
|
36
47
|
export const DEFAULT_RESERVE_RATIO = 0.05;
|
|
37
48
|
|
|
49
|
+
/** Provider framing reserved for the assistant reply label. */
|
|
50
|
+
export const REPLY_PRIMER_TOKENS = 3;
|
|
51
|
+
|
|
38
52
|
/** Context pressure at which observation masking and context fading activate. */
|
|
39
53
|
const PRESSURE_THRESHOLD_MASKING = 0.8;
|
|
40
54
|
|
|
@@ -96,6 +110,8 @@ export function clampCalibrationRatio(ratio: number): number {
|
|
|
96
110
|
export type PruneMessagesFactoryParams = {
|
|
97
111
|
provider?: Providers;
|
|
98
112
|
maxTokens: number;
|
|
113
|
+
/** Per-tool-result character cap applied while reconciling cached counts. */
|
|
114
|
+
maxToolResultChars?: number;
|
|
99
115
|
startIndex: number;
|
|
100
116
|
tokenCounter: TokenCounter;
|
|
101
117
|
indexTokenCountMap: Record<string, number | undefined>;
|
|
@@ -161,7 +177,8 @@ export type PruneMessagesParams = {
|
|
|
161
177
|
};
|
|
162
178
|
|
|
163
179
|
function getToolCallIds(message: BaseMessage): Set<string> {
|
|
164
|
-
|
|
180
|
+
const messageRole = (message as BaseMessage & { role?: unknown }).role;
|
|
181
|
+
if (message.getType() !== 'ai' && messageRole !== 'assistant') {
|
|
165
182
|
return new Set<string>();
|
|
166
183
|
}
|
|
167
184
|
|
|
@@ -189,9 +206,71 @@ function getToolCallIds(message: BaseMessage): Set<string> {
|
|
|
189
206
|
}
|
|
190
207
|
}
|
|
191
208
|
|
|
209
|
+
const toolOutputSource = getResponsesToolOutputSource(message);
|
|
210
|
+
if (toolOutputSource != null) {
|
|
211
|
+
for (const item of toolOutputSource.items) {
|
|
212
|
+
if (item == null || typeof item !== 'object') {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const record = item as {
|
|
216
|
+
type?: unknown;
|
|
217
|
+
call_id?: unknown;
|
|
218
|
+
};
|
|
219
|
+
if (
|
|
220
|
+
(record.type === 'function_call' ||
|
|
221
|
+
record.type === 'custom_tool_call' ||
|
|
222
|
+
record.type === 'computer_call') &&
|
|
223
|
+
typeof record.call_id === 'string' &&
|
|
224
|
+
record.call_id !== ''
|
|
225
|
+
) {
|
|
226
|
+
ids.add(record.call_id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
192
231
|
return ids;
|
|
193
232
|
}
|
|
194
233
|
|
|
234
|
+
type ResponsesToolOutputSource = {
|
|
235
|
+
source: 'response_metadata' | 'tool_outputs';
|
|
236
|
+
items: unknown[];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
function getResponsesToolOutputSource(
|
|
240
|
+
message: BaseMessage
|
|
241
|
+
): ResponsesToolOutputSource | undefined {
|
|
242
|
+
const responseOutput = readPropertyWithoutAccessors(
|
|
243
|
+
message.response_metadata,
|
|
244
|
+
'output'
|
|
245
|
+
);
|
|
246
|
+
if (
|
|
247
|
+
!responseOutput.accessor &&
|
|
248
|
+
Array.isArray(responseOutput.value) &&
|
|
249
|
+
!isProxy(responseOutput.value) &&
|
|
250
|
+
responseOutput.value.length > 0
|
|
251
|
+
) {
|
|
252
|
+
return {
|
|
253
|
+
source: 'response_metadata',
|
|
254
|
+
items: responseOutput.value,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const fallbackOutput = readPropertyWithoutAccessors(
|
|
258
|
+
message.additional_kwargs,
|
|
259
|
+
'tool_outputs'
|
|
260
|
+
);
|
|
261
|
+
if (
|
|
262
|
+
!fallbackOutput.accessor &&
|
|
263
|
+
Array.isArray(fallbackOutput.value) &&
|
|
264
|
+
!isProxy(fallbackOutput.value)
|
|
265
|
+
) {
|
|
266
|
+
return {
|
|
267
|
+
source: 'tool_outputs',
|
|
268
|
+
items: fallbackOutput.value,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
195
274
|
function getToolResultId(message: BaseMessage): string | null {
|
|
196
275
|
if (message.getType() !== 'tool') {
|
|
197
276
|
return null;
|
|
@@ -292,7 +371,8 @@ export function repairOrphanedToolMessages({
|
|
|
292
371
|
continue;
|
|
293
372
|
}
|
|
294
373
|
|
|
295
|
-
|
|
374
|
+
const messageRole = (message as BaseMessage & { role?: unknown }).role;
|
|
375
|
+
if (message.getType() === 'ai' || messageRole === 'assistant') {
|
|
296
376
|
const toolCallIds = getToolCallIds(message);
|
|
297
377
|
if (toolCallIds.size > 0) {
|
|
298
378
|
let hasOrphanToolCalls = false;
|
|
@@ -344,16 +424,17 @@ export function repairOrphanedToolMessages({
|
|
|
344
424
|
* Returns null if the message has no content left after stripping.
|
|
345
425
|
*/
|
|
346
426
|
function stripOrphanToolUseBlocks(
|
|
347
|
-
message:
|
|
427
|
+
message: BaseMessage,
|
|
348
428
|
presentToolResultIds: Set<string>
|
|
349
|
-
):
|
|
350
|
-
const
|
|
429
|
+
): BaseMessage | null {
|
|
430
|
+
const aiMessage = message as AIMessage;
|
|
431
|
+
const keptToolCalls = (aiMessage.tool_calls ?? []).filter(
|
|
351
432
|
(tc) => typeof tc.id === 'string' && presentToolResultIds.has(tc.id)
|
|
352
433
|
);
|
|
353
434
|
|
|
354
435
|
let keptContent: MessageContentComplex[] | string;
|
|
355
|
-
if (Array.isArray(
|
|
356
|
-
const filtered = (
|
|
436
|
+
if (Array.isArray(aiMessage.content)) {
|
|
437
|
+
const filtered = (aiMessage.content as MessageContentComplex[]).filter(
|
|
357
438
|
(block) => {
|
|
358
439
|
if (typeof block !== 'object') {
|
|
359
440
|
return true;
|
|
@@ -369,18 +450,60 @@ function stripOrphanToolUseBlocks(
|
|
|
369
450
|
}
|
|
370
451
|
);
|
|
371
452
|
|
|
372
|
-
if (filtered.length === 0) {
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
453
|
keptContent = filtered;
|
|
376
454
|
} else {
|
|
377
|
-
keptContent =
|
|
455
|
+
keptContent = aiMessage.content;
|
|
378
456
|
}
|
|
379
457
|
|
|
380
|
-
|
|
381
|
-
|
|
458
|
+
const toolOutputSource = getResponsesToolOutputSource(message);
|
|
459
|
+
const keptToolOutputs =
|
|
460
|
+
toolOutputSource?.items.filter((item) => {
|
|
461
|
+
if (item == null || typeof item !== 'object') {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
const record = item as {
|
|
465
|
+
type?: unknown;
|
|
466
|
+
call_id?: unknown;
|
|
467
|
+
};
|
|
468
|
+
if (
|
|
469
|
+
record.type !== 'function_call' &&
|
|
470
|
+
record.type !== 'custom_tool_call' &&
|
|
471
|
+
record.type !== 'computer_call'
|
|
472
|
+
) {
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
return (
|
|
476
|
+
typeof record.call_id === 'string' &&
|
|
477
|
+
presentToolResultIds.has(record.call_id)
|
|
478
|
+
);
|
|
479
|
+
}) ?? [];
|
|
480
|
+
|
|
481
|
+
if (
|
|
482
|
+
keptToolCalls.length === 0 &&
|
|
483
|
+
Array.isArray(keptContent) &&
|
|
484
|
+
keptContent.length === 0 &&
|
|
485
|
+
keptToolOutputs.length === 0
|
|
486
|
+
) {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
let responseMetadata = message.response_metadata;
|
|
491
|
+
let additionalKwargs = message.additional_kwargs;
|
|
492
|
+
if (toolOutputSource?.source === 'response_metadata') {
|
|
493
|
+
responseMetadata = cloneWithProjectedProperties(responseMetadata, {
|
|
494
|
+
output: keptToolOutputs,
|
|
495
|
+
});
|
|
496
|
+
} else if (toolOutputSource?.source === 'tool_outputs') {
|
|
497
|
+
additionalKwargs = cloneWithProjectedProperties(additionalKwargs, {
|
|
498
|
+
tool_outputs: keptToolOutputs,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return cloneWithProjectedProperties(message, {
|
|
382
503
|
content: toLangChainContent(keptContent),
|
|
383
504
|
tool_calls: keptToolCalls.length > 0 ? keptToolCalls : undefined,
|
|
505
|
+
response_metadata: responseMetadata,
|
|
506
|
+
additional_kwargs: additionalKwargs,
|
|
384
507
|
});
|
|
385
508
|
}
|
|
386
509
|
|
|
@@ -400,13 +523,19 @@ function stripOrphanToolUseBlocks(
|
|
|
400
523
|
* vice-versa, the original array is returned immediately with zero allocation.
|
|
401
524
|
*/
|
|
402
525
|
export function sanitizeOrphanToolBlocks(
|
|
403
|
-
messages: BaseMessage[]
|
|
526
|
+
messages: BaseMessage[],
|
|
527
|
+
onMessageCloned?: (source: BaseMessage, clone: BaseMessage) => void
|
|
404
528
|
): BaseMessage[] {
|
|
405
529
|
const allToolCallIds = new Set<string>();
|
|
406
530
|
const allToolResultIds = new Set<string>();
|
|
407
531
|
|
|
408
532
|
for (const msg of messages) {
|
|
409
533
|
const msgAny = msg as unknown as Record<string, unknown>;
|
|
534
|
+
if (typeof (msg as { getType?: unknown }).getType === 'function') {
|
|
535
|
+
for (const id of getToolCallIds(msg)) {
|
|
536
|
+
allToolCallIds.add(id);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
410
539
|
const toolCalls = msgAny.tool_calls as Array<{ id?: string }> | undefined;
|
|
411
540
|
if (Array.isArray(toolCalls)) {
|
|
412
541
|
for (const tc of toolCalls) {
|
|
@@ -492,6 +621,7 @@ export function sanitizeOrphanToolBlocks(
|
|
|
492
621
|
const stripped = stripOrphanToolUseBlocks(msg, allToolResultIds);
|
|
493
622
|
if (stripped != null) {
|
|
494
623
|
strippedAiIndices.add(result.length);
|
|
624
|
+
onMessageCloned?.(msg, stripped);
|
|
495
625
|
result.push(stripped);
|
|
496
626
|
}
|
|
497
627
|
continue;
|
|
@@ -527,6 +657,7 @@ export function sanitizeOrphanToolBlocks(
|
|
|
527
657
|
);
|
|
528
658
|
patched.tool_calls = keptToolCalls.length > 0 ? keptToolCalls : [];
|
|
529
659
|
patched.content = keptContent;
|
|
660
|
+
onMessageCloned?.(msg, patched as BaseMessage);
|
|
530
661
|
result.push(patched as BaseMessage);
|
|
531
662
|
continue;
|
|
532
663
|
}
|
|
@@ -680,7 +811,7 @@ export function getMessagesWithinTokenLimit({
|
|
|
680
811
|
}): PruningResult {
|
|
681
812
|
// Every reply is primed with <|start|>assistant<|message|>, so we
|
|
682
813
|
// start with 3 tokens for the label after all messages have been counted.
|
|
683
|
-
let currentTokenCount =
|
|
814
|
+
let currentTokenCount = REPLY_PRIMER_TOKENS;
|
|
684
815
|
const instructions =
|
|
685
816
|
_messages[0]?.getType() === 'system' ? _messages[0] : undefined;
|
|
686
817
|
const instructionsTokenCount =
|
|
@@ -908,7 +1039,7 @@ export function getMessagesWithinTokenLimit({
|
|
|
908
1039
|
const newThinkingMessageTokenCount =
|
|
909
1040
|
(indexTokenCountMap[thinkingStartIndex] ?? 0) + thinkingTokenCount;
|
|
910
1041
|
remainingContextTokens = initialContextTokens - newThinkingMessageTokenCount;
|
|
911
|
-
currentTokenCount =
|
|
1042
|
+
currentTokenCount = REPLY_PRIMER_TOKENS;
|
|
912
1043
|
let newContext: BaseMessage[] = [];
|
|
913
1044
|
const secondRoundMessages = [..._messages];
|
|
914
1045
|
let currentIndex = secondRoundMessages.length;
|
|
@@ -1064,10 +1195,10 @@ export function maskConsumedToolResults(params: {
|
|
|
1064
1195
|
for (let c = 0; c < count; c++) {
|
|
1065
1196
|
const i = consumedIndices[c];
|
|
1066
1197
|
const message = messages[i];
|
|
1067
|
-
|
|
1068
|
-
if (typeof content !== 'string') {
|
|
1198
|
+
if (isComputerCallOutputMessage(message)) {
|
|
1069
1199
|
continue;
|
|
1070
1200
|
}
|
|
1201
|
+
const content = message.content;
|
|
1071
1202
|
|
|
1072
1203
|
let maxChars: number;
|
|
1073
1204
|
if (totalBudgetChars > 0) {
|
|
@@ -1080,25 +1211,26 @@ export function maskConsumedToolResults(params: {
|
|
|
1080
1211
|
maxChars = MASKED_RESULT_MAX_CHARS;
|
|
1081
1212
|
}
|
|
1082
1213
|
|
|
1083
|
-
|
|
1214
|
+
const compacted = compactToolContent(content, maxChars);
|
|
1215
|
+
if (!compacted.changed) {
|
|
1084
1216
|
continue;
|
|
1085
1217
|
}
|
|
1086
1218
|
|
|
1087
1219
|
if (params.originalContentStore && !params.originalContentStore.has(i)) {
|
|
1088
|
-
|
|
1220
|
+
const original = serializeToolContentBounded(
|
|
1221
|
+
content,
|
|
1222
|
+
ORIGINAL_CONTENT_MAX_CHARS
|
|
1223
|
+
);
|
|
1224
|
+
params.originalContentStore.set(i, original);
|
|
1089
1225
|
if (params.onContentStored) {
|
|
1090
|
-
params.onContentStored(i,
|
|
1226
|
+
params.onContentStored(i, original);
|
|
1091
1227
|
}
|
|
1092
1228
|
}
|
|
1093
1229
|
|
|
1094
|
-
const cloned =
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
id: message.id,
|
|
1099
|
-
additional_kwargs: message.additional_kwargs,
|
|
1100
|
-
response_metadata: message.response_metadata,
|
|
1101
|
-
});
|
|
1230
|
+
const cloned = cloneToolMessageWithContent(
|
|
1231
|
+
message as ToolMessage,
|
|
1232
|
+
compacted.content
|
|
1233
|
+
);
|
|
1102
1234
|
messages[i] = cloned;
|
|
1103
1235
|
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
1104
1236
|
maskedCount++;
|
|
@@ -1131,7 +1263,10 @@ export function preFlightTruncateToolResults(params: {
|
|
|
1131
1263
|
|
|
1132
1264
|
const toolIndices: number[] = [];
|
|
1133
1265
|
for (let i = 0; i < messages.length; i++) {
|
|
1134
|
-
if (
|
|
1266
|
+
if (
|
|
1267
|
+
messages[i].getType() === 'tool' &&
|
|
1268
|
+
!isComputerCallOutputMessage(messages[i])
|
|
1269
|
+
) {
|
|
1135
1270
|
toolIndices.push(i);
|
|
1136
1271
|
}
|
|
1137
1272
|
}
|
|
@@ -1140,27 +1275,19 @@ export function preFlightTruncateToolResults(params: {
|
|
|
1140
1275
|
const i = toolIndices[t];
|
|
1141
1276
|
const message = messages[i];
|
|
1142
1277
|
const content = message.content;
|
|
1143
|
-
if (typeof content !== 'string') {
|
|
1144
|
-
continue;
|
|
1145
|
-
}
|
|
1146
1278
|
|
|
1147
1279
|
const position = toolIndices.length > 1 ? t / (toolIndices.length - 1) : 1;
|
|
1148
1280
|
const recencyFactor = 0.2 + 0.8 * position;
|
|
1149
1281
|
const maxChars = Math.max(200, Math.floor(baseMaxChars * recencyFactor));
|
|
1150
1282
|
|
|
1151
|
-
|
|
1283
|
+
const compacted = compactToolContent(content, maxChars);
|
|
1284
|
+
if (!compacted.changed) {
|
|
1152
1285
|
continue;
|
|
1153
1286
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
tool_call_id: (message as ToolMessage).tool_call_id,
|
|
1159
|
-
name: message.name,
|
|
1160
|
-
id: message.id,
|
|
1161
|
-
additional_kwargs: message.additional_kwargs,
|
|
1162
|
-
response_metadata: message.response_metadata,
|
|
1163
|
-
});
|
|
1287
|
+
const cloned = cloneToolMessageWithContent(
|
|
1288
|
+
message as ToolMessage,
|
|
1289
|
+
compacted.content
|
|
1290
|
+
);
|
|
1164
1291
|
messages[i] = cloned;
|
|
1165
1292
|
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
1166
1293
|
truncatedCount++;
|
|
@@ -1182,86 +1309,721 @@ export function preFlightTruncateToolResults(params: {
|
|
|
1182
1309
|
*
|
|
1183
1310
|
* @returns The number of AI messages that had tool_use inputs truncated.
|
|
1184
1311
|
*/
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1312
|
+
const HARD_MAX_TOOL_CALL_INPUT_CHARS = 200_000;
|
|
1313
|
+
const MIN_JSON_VALUE_CHARS = 4;
|
|
1314
|
+
const ACCESSOR_INPUT_PLACEHOLDER = '[Property accessor omitted]';
|
|
1315
|
+
|
|
1316
|
+
type ToolInputProjection = {
|
|
1317
|
+
value: unknown;
|
|
1318
|
+
changed: boolean;
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
type PropertyRead = {
|
|
1322
|
+
found: boolean;
|
|
1323
|
+
own: boolean;
|
|
1324
|
+
accessor: boolean;
|
|
1325
|
+
value?: unknown;
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
function normalizeToolInputLimit(maxChars: number): number {
|
|
1329
|
+
if (!Number.isFinite(maxChars)) {
|
|
1330
|
+
return HARD_MAX_TOOL_CALL_INPUT_CHARS;
|
|
1331
|
+
}
|
|
1332
|
+
return Math.max(MIN_JSON_VALUE_CHARS, Math.floor(maxChars));
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function readPropertyWithoutAccessors(
|
|
1336
|
+
value: object,
|
|
1337
|
+
key: PropertyKey
|
|
1338
|
+
): PropertyRead {
|
|
1339
|
+
let current: object | null = value;
|
|
1340
|
+
for (let depth = 0; current != null && depth < 100; depth++) {
|
|
1341
|
+
if (isProxy(current)) {
|
|
1342
|
+
return { found: true, own: current === value, accessor: true };
|
|
1343
|
+
}
|
|
1344
|
+
let descriptor: PropertyDescriptor | undefined;
|
|
1345
|
+
try {
|
|
1346
|
+
descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
1347
|
+
} catch {
|
|
1348
|
+
return { found: true, own: current === value, accessor: true };
|
|
1349
|
+
}
|
|
1350
|
+
if (descriptor != null) {
|
|
1351
|
+
if (!('value' in descriptor)) {
|
|
1352
|
+
return { found: true, own: current === value, accessor: true };
|
|
1353
|
+
}
|
|
1354
|
+
return {
|
|
1355
|
+
found: true,
|
|
1356
|
+
own: current === value,
|
|
1357
|
+
accessor: false,
|
|
1358
|
+
value: descriptor.value,
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
try {
|
|
1362
|
+
current = Object.getPrototypeOf(current) as object | null;
|
|
1363
|
+
} catch {
|
|
1364
|
+
return { found: true, own: false, accessor: true };
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return current == null
|
|
1368
|
+
? { found: false, own: false, accessor: false }
|
|
1369
|
+
: { found: true, own: false, accessor: true };
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
function cloneWithProjectedProperties<T extends object>(
|
|
1373
|
+
value: T,
|
|
1374
|
+
changes: Readonly<Record<string, unknown>>
|
|
1375
|
+
): T {
|
|
1376
|
+
const descriptors = Object.getOwnPropertyDescriptors(value) as Record<
|
|
1377
|
+
string,
|
|
1378
|
+
PropertyDescriptor | undefined
|
|
1379
|
+
>;
|
|
1380
|
+
for (const [key, projectedValue] of Object.entries(changes)) {
|
|
1381
|
+
const descriptor = descriptors[key];
|
|
1382
|
+
descriptors[key] = {
|
|
1383
|
+
configurable: descriptor?.configurable ?? true,
|
|
1384
|
+
enumerable: descriptor?.enumerable ?? true,
|
|
1385
|
+
value: projectedValue,
|
|
1386
|
+
writable: descriptor?.writable ?? true,
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
return Object.create(
|
|
1390
|
+
Object.getPrototypeOf(value),
|
|
1391
|
+
descriptors as PropertyDescriptorMap
|
|
1392
|
+
) as T;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function createBoundedTruncationValue(
|
|
1396
|
+
preview: string,
|
|
1397
|
+
originalChars: number,
|
|
1398
|
+
maxChars: number
|
|
1399
|
+
): unknown {
|
|
1400
|
+
const normalizedMaxChars = normalizeToolInputLimit(maxChars);
|
|
1401
|
+
const emptyEnvelope = {
|
|
1402
|
+
_truncated: '',
|
|
1403
|
+
_originalChars: originalChars,
|
|
1404
|
+
};
|
|
1405
|
+
if (JSON.stringify(emptyEnvelope).length > normalizedMaxChars) {
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
let low = 0;
|
|
1410
|
+
let high = Math.min(preview.length, normalizedMaxChars);
|
|
1411
|
+
while (low < high) {
|
|
1412
|
+
const next = Math.ceil((low + high) / 2);
|
|
1413
|
+
const candidate = {
|
|
1414
|
+
_truncated: preview.slice(0, next),
|
|
1415
|
+
_originalChars: originalChars,
|
|
1416
|
+
};
|
|
1417
|
+
if (JSON.stringify(candidate).length <= normalizedMaxChars) {
|
|
1418
|
+
low = next;
|
|
1419
|
+
} else {
|
|
1420
|
+
high = next - 1;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const truncationMarker = '… [truncated]';
|
|
1424
|
+
const boundedPreview =
|
|
1425
|
+
low < preview.length && low >= truncationMarker.length
|
|
1426
|
+
? preview.slice(0, low - truncationMarker.length) + truncationMarker
|
|
1427
|
+
: preview.slice(0, low);
|
|
1428
|
+
return {
|
|
1429
|
+
_truncated: boundedPreview,
|
|
1430
|
+
_originalChars: originalChars,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function projectToolInputWithinLimit(
|
|
1435
|
+
input: unknown,
|
|
1436
|
+
maxChars: number
|
|
1437
|
+
): ToolInputProjection {
|
|
1438
|
+
const normalizedMaxChars = normalizeToolInputLimit(maxChars);
|
|
1439
|
+
const serialized = serializeStructuredValueBounded(input, normalizedMaxChars);
|
|
1440
|
+
if (serialized.truncated) {
|
|
1441
|
+
return {
|
|
1442
|
+
value: createBoundedTruncationValue(
|
|
1443
|
+
serialized.content,
|
|
1444
|
+
serialized.originalChars,
|
|
1445
|
+
normalizedMaxChars
|
|
1446
|
+
),
|
|
1447
|
+
changed: true,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
if (!hasUnsafeStructuredSerialization(input)) {
|
|
1451
|
+
return { value: input, changed: false };
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
try {
|
|
1455
|
+
return { value: JSON.parse(serialized.content), changed: true };
|
|
1456
|
+
} catch {
|
|
1457
|
+
return {
|
|
1458
|
+
value: createBoundedTruncationValue(
|
|
1459
|
+
serialized.content,
|
|
1460
|
+
serialized.originalChars,
|
|
1461
|
+
normalizedMaxChars
|
|
1462
|
+
),
|
|
1463
|
+
changed: true,
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Serializes one structured tool-call input as valid, bounded JSON without
|
|
1470
|
+
* invoking user-defined accessors or `toJSON`.
|
|
1471
|
+
*/
|
|
1472
|
+
export function serializeToolCallInput(
|
|
1473
|
+
input: unknown,
|
|
1474
|
+
maxChars = HARD_MAX_TOOL_CALL_INPUT_CHARS
|
|
1475
|
+
): string {
|
|
1476
|
+
const normalizedMaxChars = normalizeToolInputLimit(maxChars);
|
|
1477
|
+
const projected = projectToolInputWithinLimit(input, normalizedMaxChars);
|
|
1478
|
+
const serialized = serializeStructuredValueBounded(
|
|
1479
|
+
projected.value,
|
|
1480
|
+
normalizedMaxChars
|
|
1196
1481
|
);
|
|
1197
|
-
|
|
1482
|
+
if (!serialized.truncated) {
|
|
1483
|
+
return serialized.content === 'undefined' ? 'null' : serialized.content;
|
|
1484
|
+
}
|
|
1485
|
+
const fallback = createBoundedTruncationValue(
|
|
1486
|
+
serialized.content,
|
|
1487
|
+
serialized.originalChars,
|
|
1488
|
+
normalizedMaxChars
|
|
1489
|
+
);
|
|
1490
|
+
return JSON.stringify(fallback);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function projectKnownInputProperty(
|
|
1494
|
+
value: object,
|
|
1495
|
+
key: string,
|
|
1496
|
+
maxChars: number,
|
|
1497
|
+
changes: Record<string, unknown>
|
|
1498
|
+
): void {
|
|
1499
|
+
const property = readPropertyWithoutAccessors(value, key);
|
|
1500
|
+
if (!property.found) {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const projected = property.accessor
|
|
1504
|
+
? { value: ACCESSOR_INPUT_PLACEHOLDER, changed: true }
|
|
1505
|
+
: projectToolInputWithinLimit(property.value, maxChars);
|
|
1506
|
+
if (projected.changed || !property.own) {
|
|
1507
|
+
changes[key] = projected.value;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function getStringProperty(value: object, key: string): string | undefined {
|
|
1512
|
+
const property = readPropertyWithoutAccessors(value, key);
|
|
1513
|
+
return !property.accessor && typeof property.value === 'string'
|
|
1514
|
+
? property.value
|
|
1515
|
+
: undefined;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function projectInlineToolInput(
|
|
1519
|
+
block: MessageContentComplex,
|
|
1520
|
+
maxChars: number
|
|
1521
|
+
): MessageContentComplex {
|
|
1522
|
+
const type = getStringProperty(block, 'type');
|
|
1523
|
+
if (type !== 'tool_use' && type !== 'tool_call') {
|
|
1524
|
+
return block;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const changes: Record<string, unknown> = {};
|
|
1528
|
+
projectKnownInputProperty(block, 'input', maxChars, changes);
|
|
1529
|
+
projectKnownInputProperty(block, 'args', maxChars, changes);
|
|
1530
|
+
|
|
1531
|
+
const nestedProperty = readPropertyWithoutAccessors(block, 'tool_call');
|
|
1532
|
+
if (
|
|
1533
|
+
nestedProperty.found &&
|
|
1534
|
+
!nestedProperty.accessor &&
|
|
1535
|
+
nestedProperty.value != null &&
|
|
1536
|
+
typeof nestedProperty.value === 'object'
|
|
1537
|
+
) {
|
|
1538
|
+
const nestedChanges: Record<string, unknown> = {};
|
|
1539
|
+
projectKnownInputProperty(
|
|
1540
|
+
nestedProperty.value,
|
|
1541
|
+
'args',
|
|
1542
|
+
maxChars,
|
|
1543
|
+
nestedChanges
|
|
1544
|
+
);
|
|
1545
|
+
if (Object.keys(nestedChanges).length > 0) {
|
|
1546
|
+
changes.tool_call = cloneWithProjectedProperties(
|
|
1547
|
+
nestedProperty.value,
|
|
1548
|
+
nestedChanges
|
|
1549
|
+
);
|
|
1550
|
+
} else if (!nestedProperty.own) {
|
|
1551
|
+
changes.tool_call = nestedProperty.value;
|
|
1552
|
+
}
|
|
1553
|
+
} else if (nestedProperty.accessor) {
|
|
1554
|
+
changes.tool_call = { args: ACCESSOR_INPUT_PLACEHOLDER };
|
|
1555
|
+
}
|
|
1198
1556
|
|
|
1557
|
+
return Object.keys(changes).length > 0
|
|
1558
|
+
? cloneWithProjectedProperties(block, changes)
|
|
1559
|
+
: block;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function projectSerializedArguments(
|
|
1563
|
+
value: unknown,
|
|
1564
|
+
maxChars: number
|
|
1565
|
+
): { value: string; changed: boolean } {
|
|
1566
|
+
const normalizedMaxChars = normalizeToolInputLimit(maxChars);
|
|
1567
|
+
if (typeof value === 'string' && value.length <= normalizedMaxChars) {
|
|
1568
|
+
return { value, changed: false };
|
|
1569
|
+
}
|
|
1570
|
+
return {
|
|
1571
|
+
value: serializeToolCallInput(value, normalizedMaxChars),
|
|
1572
|
+
changed: true,
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function selectProjectedSerializedArguments(
|
|
1577
|
+
property: PropertyRead,
|
|
1578
|
+
canonical: string | undefined,
|
|
1579
|
+
maxChars: number
|
|
1580
|
+
): { value: string; changed: boolean } {
|
|
1581
|
+
if (canonical != null) {
|
|
1582
|
+
return { value: canonical, changed: property.value !== canonical };
|
|
1583
|
+
}
|
|
1584
|
+
if (property.accessor) {
|
|
1585
|
+
return {
|
|
1586
|
+
value: serializeToolCallInput(ACCESSOR_INPUT_PLACEHOLDER, maxChars),
|
|
1587
|
+
changed: true,
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
return projectSerializedArguments(property.value, maxChars);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
function projectRawOpenAIToolCalls(
|
|
1594
|
+
rawToolCalls: unknown,
|
|
1595
|
+
maxChars: number,
|
|
1596
|
+
canonicalArguments: ReadonlyMap<string, string>
|
|
1597
|
+
): { value: unknown; changed: boolean } {
|
|
1598
|
+
if (!Array.isArray(rawToolCalls)) {
|
|
1599
|
+
return { value: rawToolCalls, changed: false };
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
const state = { changed: false };
|
|
1603
|
+
const projected = rawToolCalls.map((rawToolCall) => {
|
|
1604
|
+
if (rawToolCall == null || typeof rawToolCall !== 'object') {
|
|
1605
|
+
return rawToolCall;
|
|
1606
|
+
}
|
|
1607
|
+
const functionProperty = readPropertyWithoutAccessors(
|
|
1608
|
+
rawToolCall,
|
|
1609
|
+
'function'
|
|
1610
|
+
);
|
|
1611
|
+
if (
|
|
1612
|
+
!functionProperty.found ||
|
|
1613
|
+
functionProperty.accessor ||
|
|
1614
|
+
functionProperty.value == null ||
|
|
1615
|
+
typeof functionProperty.value !== 'object'
|
|
1616
|
+
) {
|
|
1617
|
+
return rawToolCall;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
const argsProperty = readPropertyWithoutAccessors(
|
|
1621
|
+
functionProperty.value,
|
|
1622
|
+
'arguments'
|
|
1623
|
+
);
|
|
1624
|
+
if (!argsProperty.found) {
|
|
1625
|
+
return rawToolCall;
|
|
1626
|
+
}
|
|
1627
|
+
const callId = getStringProperty(rawToolCall, 'id');
|
|
1628
|
+
const canonical =
|
|
1629
|
+
callId != null ? canonicalArguments.get(callId) : undefined;
|
|
1630
|
+
const projectedArgs = selectProjectedSerializedArguments(
|
|
1631
|
+
argsProperty,
|
|
1632
|
+
canonical,
|
|
1633
|
+
maxChars
|
|
1634
|
+
);
|
|
1635
|
+
if (!projectedArgs.changed && functionProperty.own && argsProperty.own) {
|
|
1636
|
+
return rawToolCall;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
state.changed = true;
|
|
1640
|
+
const projectedFunction = cloneWithProjectedProperties(
|
|
1641
|
+
functionProperty.value,
|
|
1642
|
+
{ arguments: projectedArgs.value }
|
|
1643
|
+
);
|
|
1644
|
+
return cloneWithProjectedProperties(rawToolCall, {
|
|
1645
|
+
function: projectedFunction,
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
1648
|
+
return {
|
|
1649
|
+
value: state.changed ? projected : rawToolCalls,
|
|
1650
|
+
changed: state.changed,
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function projectLegacyFunctionCall(
|
|
1655
|
+
property: PropertyRead,
|
|
1656
|
+
maxChars: number
|
|
1657
|
+
): { value: unknown; changed: boolean } {
|
|
1658
|
+
if (!property.found) {
|
|
1659
|
+
return { value: property.value, changed: false };
|
|
1660
|
+
}
|
|
1661
|
+
if (property.own && !property.accessor && property.value === undefined) {
|
|
1662
|
+
return { value: undefined, changed: false };
|
|
1663
|
+
}
|
|
1664
|
+
if (
|
|
1665
|
+
property.accessor ||
|
|
1666
|
+
!property.own ||
|
|
1667
|
+
property.value == null ||
|
|
1668
|
+
typeof property.value !== 'object'
|
|
1669
|
+
) {
|
|
1670
|
+
return { value: undefined, changed: true };
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
const nameProperty = readPropertyWithoutAccessors(property.value, 'name');
|
|
1674
|
+
const argsProperty = readPropertyWithoutAccessors(
|
|
1675
|
+
property.value,
|
|
1676
|
+
'arguments'
|
|
1677
|
+
);
|
|
1678
|
+
if (
|
|
1679
|
+
nameProperty.accessor ||
|
|
1680
|
+
!nameProperty.own ||
|
|
1681
|
+
typeof nameProperty.value !== 'string' ||
|
|
1682
|
+
nameProperty.value === '' ||
|
|
1683
|
+
nameProperty.value.length > normalizeToolInputLimit(maxChars) ||
|
|
1684
|
+
!argsProperty.found ||
|
|
1685
|
+
argsProperty.accessor ||
|
|
1686
|
+
!argsProperty.own
|
|
1687
|
+
) {
|
|
1688
|
+
return { value: undefined, changed: true };
|
|
1689
|
+
}
|
|
1690
|
+
try {
|
|
1691
|
+
const prototype = Object.getPrototypeOf(property.value);
|
|
1692
|
+
const enumerableKeys = Object.keys(property.value);
|
|
1693
|
+
if (
|
|
1694
|
+
(prototype === Object.prototype || prototype === null) &&
|
|
1695
|
+
enumerableKeys.length === 2 &&
|
|
1696
|
+
enumerableKeys.includes('name') &&
|
|
1697
|
+
enumerableKeys.includes('arguments') &&
|
|
1698
|
+
typeof argsProperty.value === 'string' &&
|
|
1699
|
+
argsProperty.value.length <= normalizeToolInputLimit(maxChars) &&
|
|
1700
|
+
!hasUnsafeStructuredSerialization(property.value)
|
|
1701
|
+
) {
|
|
1702
|
+
return { value: property.value, changed: false };
|
|
1703
|
+
}
|
|
1704
|
+
} catch {
|
|
1705
|
+
return { value: undefined, changed: true };
|
|
1706
|
+
}
|
|
1707
|
+
const projectedArgs = selectProjectedSerializedArguments(
|
|
1708
|
+
argsProperty,
|
|
1709
|
+
undefined,
|
|
1710
|
+
maxChars
|
|
1711
|
+
);
|
|
1712
|
+
return {
|
|
1713
|
+
value: {
|
|
1714
|
+
name: nameProperty.value,
|
|
1715
|
+
arguments: projectedArgs.value,
|
|
1716
|
+
},
|
|
1717
|
+
changed: true,
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
function projectResponsesOutput(
|
|
1722
|
+
output: unknown,
|
|
1723
|
+
maxChars: number,
|
|
1724
|
+
canonicalArguments: ReadonlyMap<string, string>,
|
|
1725
|
+
canonicalInputs: ReadonlyMap<string, unknown>
|
|
1726
|
+
): { value: unknown; changed: boolean } {
|
|
1727
|
+
if (!Array.isArray(output)) {
|
|
1728
|
+
return { value: output, changed: false };
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
const state = { changed: false };
|
|
1732
|
+
const projected = output.map((item) => {
|
|
1733
|
+
if (item == null || typeof item !== 'object') {
|
|
1734
|
+
return item;
|
|
1735
|
+
}
|
|
1736
|
+
const type = getStringProperty(item, 'type');
|
|
1737
|
+
if (
|
|
1738
|
+
type !== 'function_call' &&
|
|
1739
|
+
type !== 'custom_tool_call' &&
|
|
1740
|
+
type !== 'computer_call'
|
|
1741
|
+
) {
|
|
1742
|
+
return item;
|
|
1743
|
+
}
|
|
1744
|
+
let inputKey = 'action';
|
|
1745
|
+
if (type === 'function_call') {
|
|
1746
|
+
inputKey = 'arguments';
|
|
1747
|
+
} else if (type === 'custom_tool_call') {
|
|
1748
|
+
inputKey = 'input';
|
|
1749
|
+
}
|
|
1750
|
+
const inputProperty = readPropertyWithoutAccessors(item, inputKey);
|
|
1751
|
+
if (!inputProperty.found) {
|
|
1752
|
+
return item;
|
|
1753
|
+
}
|
|
1754
|
+
const callId =
|
|
1755
|
+
getStringProperty(item, 'call_id') ?? getStringProperty(item, 'id');
|
|
1756
|
+
let projectedInput: ToolInputProjection;
|
|
1757
|
+
if (type === 'function_call') {
|
|
1758
|
+
const canonical =
|
|
1759
|
+
callId != null ? canonicalArguments.get(callId) : undefined;
|
|
1760
|
+
projectedInput = selectProjectedSerializedArguments(
|
|
1761
|
+
inputProperty,
|
|
1762
|
+
canonical,
|
|
1763
|
+
maxChars
|
|
1764
|
+
);
|
|
1765
|
+
} else {
|
|
1766
|
+
const canonicalArgs =
|
|
1767
|
+
callId != null ? canonicalInputs.get(callId) : undefined;
|
|
1768
|
+
const canonicalProperty =
|
|
1769
|
+
canonicalArgs != null && typeof canonicalArgs === 'object'
|
|
1770
|
+
? readPropertyWithoutAccessors(canonicalArgs, inputKey)
|
|
1771
|
+
: undefined;
|
|
1772
|
+
let source = inputProperty.accessor
|
|
1773
|
+
? ACCESSOR_INPUT_PLACEHOLDER
|
|
1774
|
+
: inputProperty.value;
|
|
1775
|
+
if (canonicalProperty != null && canonicalProperty.found) {
|
|
1776
|
+
source = canonicalProperty.accessor
|
|
1777
|
+
? ACCESSOR_INPUT_PLACEHOLDER
|
|
1778
|
+
: canonicalProperty.value;
|
|
1779
|
+
}
|
|
1780
|
+
if (
|
|
1781
|
+
type === 'custom_tool_call' &&
|
|
1782
|
+
typeof source === 'string' &&
|
|
1783
|
+
source.length <= normalizeToolInputLimit(maxChars)
|
|
1784
|
+
) {
|
|
1785
|
+
projectedInput = {
|
|
1786
|
+
value: source,
|
|
1787
|
+
changed: !inputProperty.own || inputProperty.value !== source,
|
|
1788
|
+
};
|
|
1789
|
+
} else if (type === 'custom_tool_call') {
|
|
1790
|
+
const value =
|
|
1791
|
+
typeof source === 'string'
|
|
1792
|
+
? truncateToolResultContent(
|
|
1793
|
+
source,
|
|
1794
|
+
normalizeToolInputLimit(maxChars)
|
|
1795
|
+
)
|
|
1796
|
+
: serializeToolCallInput(source, maxChars);
|
|
1797
|
+
projectedInput = {
|
|
1798
|
+
value,
|
|
1799
|
+
changed: !inputProperty.own || inputProperty.value !== value,
|
|
1800
|
+
};
|
|
1801
|
+
} else {
|
|
1802
|
+
const projected = projectToolInputWithinLimit(source, maxChars);
|
|
1803
|
+
projectedInput = {
|
|
1804
|
+
value: projected.value,
|
|
1805
|
+
changed:
|
|
1806
|
+
projected.changed ||
|
|
1807
|
+
!inputProperty.own ||
|
|
1808
|
+
inputProperty.value !== projected.value,
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (!projectedInput.changed && inputProperty.own) {
|
|
1813
|
+
return item;
|
|
1814
|
+
}
|
|
1815
|
+
state.changed = true;
|
|
1816
|
+
return cloneWithProjectedProperties(item, {
|
|
1817
|
+
[inputKey]: projectedInput.value,
|
|
1818
|
+
});
|
|
1819
|
+
});
|
|
1820
|
+
return {
|
|
1821
|
+
value: state.changed ? projected : output,
|
|
1822
|
+
changed: state.changed,
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/** Per-input cap: 15% of context at ~4 chars/token, never above 200K chars. */
|
|
1827
|
+
export function calculateMaxToolCallInputChars(
|
|
1828
|
+
maxContextTokens?: number
|
|
1829
|
+
): number {
|
|
1830
|
+
if (maxContextTokens == null || maxContextTokens <= 0) {
|
|
1831
|
+
return HARD_MAX_TOOL_CALL_INPUT_CHARS;
|
|
1832
|
+
}
|
|
1833
|
+
return Math.max(
|
|
1834
|
+
MIN_JSON_VALUE_CHARS,
|
|
1835
|
+
Math.min(
|
|
1836
|
+
Math.floor(maxContextTokens * 0.15) * 4,
|
|
1837
|
+
HARD_MAX_TOOL_CALL_INPUT_CHARS
|
|
1838
|
+
)
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
/**
|
|
1843
|
+
* Projects historical tool-call inputs into a provider-safe bounded form.
|
|
1844
|
+
* Returns the original array when no message changes and otherwise clones only
|
|
1845
|
+
* the array and AI messages whose inline input or `tool_calls` args changed.
|
|
1846
|
+
*/
|
|
1847
|
+
export function projectToolCallInputs(
|
|
1848
|
+
messages: BaseMessage[],
|
|
1849
|
+
maxInputChars: number
|
|
1850
|
+
): BaseMessage[] {
|
|
1851
|
+
const normalizedMaxInputChars = normalizeToolInputLimit(maxInputChars);
|
|
1852
|
+
let projectedMessages: BaseMessage[] | undefined;
|
|
1199
1853
|
for (let i = 0; i < messages.length; i++) {
|
|
1200
1854
|
const message = messages[i];
|
|
1201
|
-
|
|
1855
|
+
const messageRole = (message as BaseMessage & { role?: unknown }).role;
|
|
1856
|
+
if (message.getType() !== 'ai' && messageRole !== 'assistant') {
|
|
1202
1857
|
continue;
|
|
1203
1858
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1859
|
+
|
|
1860
|
+
const aiMessage = message as AIMessage | AIMessageChunk;
|
|
1861
|
+
let projectedContent = aiMessage.content;
|
|
1862
|
+
let contentChanged = false;
|
|
1863
|
+
if (Array.isArray(aiMessage.content)) {
|
|
1864
|
+
const originalContent = aiMessage.content as MessageContentComplex[];
|
|
1865
|
+
const mappedContent = originalContent.map((block) =>
|
|
1866
|
+
projectInlineToolInput(block, normalizedMaxInputChars)
|
|
1867
|
+
);
|
|
1868
|
+
contentChanged = mappedContent.some(
|
|
1869
|
+
(block, blockIndex) => block !== originalContent[blockIndex]
|
|
1870
|
+
);
|
|
1871
|
+
projectedContent = toLangChainContent(mappedContent);
|
|
1206
1872
|
}
|
|
1207
1873
|
|
|
1208
|
-
const
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1874
|
+
const originalToolCalls = aiMessage.tool_calls ?? [];
|
|
1875
|
+
const projectedToolCalls = originalToolCalls.map((toolCall) => {
|
|
1876
|
+
const argsProperty = readPropertyWithoutAccessors(toolCall, 'args');
|
|
1877
|
+
const projected = argsProperty.accessor
|
|
1878
|
+
? { value: ACCESSOR_INPUT_PLACEHOLDER, changed: true }
|
|
1879
|
+
: projectToolInputWithinLimit(
|
|
1880
|
+
argsProperty.value,
|
|
1881
|
+
normalizedMaxInputChars
|
|
1882
|
+
);
|
|
1883
|
+
if (argsProperty.own && !projected.changed) {
|
|
1884
|
+
return toolCall;
|
|
1217
1885
|
}
|
|
1886
|
+
return cloneWithProjectedProperties(toolCall, {
|
|
1887
|
+
args: projected.value as Record<string, unknown>,
|
|
1888
|
+
});
|
|
1889
|
+
});
|
|
1890
|
+
const toolCallsChanged = projectedToolCalls.some(
|
|
1891
|
+
(toolCall, toolCallIndex) => toolCall !== originalToolCalls[toolCallIndex]
|
|
1892
|
+
);
|
|
1218
1893
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1894
|
+
const rawAdditionalToolCallsProperty = readPropertyWithoutAccessors(
|
|
1895
|
+
aiMessage.additional_kwargs,
|
|
1896
|
+
'tool_calls'
|
|
1897
|
+
);
|
|
1898
|
+
const rawAdditionalToolCalls = rawAdditionalToolCallsProperty.accessor
|
|
1899
|
+
? undefined
|
|
1900
|
+
: rawAdditionalToolCallsProperty.value;
|
|
1901
|
+
const rawResponsesOutputProperty = readPropertyWithoutAccessors(
|
|
1902
|
+
aiMessage.response_metadata,
|
|
1903
|
+
'output'
|
|
1904
|
+
);
|
|
1905
|
+
const rawResponsesOutput = rawResponsesOutputProperty.accessor
|
|
1906
|
+
? undefined
|
|
1907
|
+
: rawResponsesOutputProperty.value;
|
|
1908
|
+
const canonicalArguments = new Map<string, string>();
|
|
1909
|
+
const canonicalInputs = new Map<string, unknown>();
|
|
1910
|
+
if (
|
|
1911
|
+
Array.isArray(rawAdditionalToolCalls) ||
|
|
1912
|
+
Array.isArray(rawResponsesOutput)
|
|
1913
|
+
) {
|
|
1914
|
+
for (const toolCall of projectedToolCalls) {
|
|
1915
|
+
const id = getStringProperty(toolCall, 'id');
|
|
1916
|
+
if (id == null) {
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
const args = readPropertyWithoutAccessors(toolCall, 'args');
|
|
1920
|
+
const canonicalInput = args.accessor
|
|
1921
|
+
? ACCESSOR_INPUT_PLACEHOLDER
|
|
1922
|
+
: args.value;
|
|
1923
|
+
canonicalInputs.set(id, canonicalInput);
|
|
1924
|
+
canonicalArguments.set(
|
|
1925
|
+
id,
|
|
1926
|
+
serializeToolCallInput(canonicalInput, normalizedMaxInputChars)
|
|
1927
|
+
);
|
|
1227
1928
|
}
|
|
1929
|
+
}
|
|
1228
1930
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1931
|
+
const additionalKwargsChanges: Record<string, unknown> =
|
|
1932
|
+
rawAdditionalToolCallsProperty.accessor ? { tool_calls: undefined } : {};
|
|
1933
|
+
const rawToolCalls = projectRawOpenAIToolCalls(
|
|
1934
|
+
rawAdditionalToolCalls,
|
|
1935
|
+
normalizedMaxInputChars,
|
|
1936
|
+
canonicalArguments
|
|
1937
|
+
);
|
|
1938
|
+
if (rawToolCalls.changed) {
|
|
1939
|
+
additionalKwargsChanges.tool_calls = rawToolCalls.value;
|
|
1940
|
+
}
|
|
1941
|
+
const legacyFunctionCall = projectLegacyFunctionCall(
|
|
1942
|
+
readPropertyWithoutAccessors(
|
|
1943
|
+
aiMessage.additional_kwargs,
|
|
1944
|
+
'function_call'
|
|
1945
|
+
),
|
|
1946
|
+
normalizedMaxInputChars
|
|
1947
|
+
);
|
|
1948
|
+
if (legacyFunctionCall.changed) {
|
|
1949
|
+
additionalKwargsChanges.function_call = legacyFunctionCall.value;
|
|
1950
|
+
}
|
|
1951
|
+
const additionalKwargsChanged =
|
|
1952
|
+
Object.keys(additionalKwargsChanges).length > 0;
|
|
1953
|
+
let projectedAdditionalKwargs = aiMessage.additional_kwargs;
|
|
1954
|
+
if (additionalKwargsChanged) {
|
|
1955
|
+
projectedAdditionalKwargs = isProxy(aiMessage.additional_kwargs)
|
|
1956
|
+
? { ...additionalKwargsChanges }
|
|
1957
|
+
: cloneWithProjectedProperties(
|
|
1958
|
+
aiMessage.additional_kwargs,
|
|
1959
|
+
additionalKwargsChanges
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
let projectedResponseMetadata = aiMessage.response_metadata;
|
|
1964
|
+
let responseMetadataChanged = rawResponsesOutputProperty.accessor;
|
|
1965
|
+
const responsesOutput = projectResponsesOutput(
|
|
1966
|
+
rawResponsesOutput,
|
|
1967
|
+
normalizedMaxInputChars,
|
|
1968
|
+
canonicalArguments,
|
|
1969
|
+
canonicalInputs
|
|
1970
|
+
);
|
|
1971
|
+
if (responsesOutput.changed || rawResponsesOutputProperty.accessor) {
|
|
1972
|
+
const output = rawResponsesOutputProperty.accessor
|
|
1973
|
+
? undefined
|
|
1974
|
+
: responsesOutput.value;
|
|
1975
|
+
projectedResponseMetadata = isProxy(aiMessage.response_metadata)
|
|
1976
|
+
? { output }
|
|
1977
|
+
: cloneWithProjectedProperties(aiMessage.response_metadata, {
|
|
1978
|
+
output,
|
|
1979
|
+
});
|
|
1980
|
+
responseMetadataChanged = true;
|
|
1981
|
+
}
|
|
1237
1982
|
|
|
1238
|
-
if (
|
|
1983
|
+
if (
|
|
1984
|
+
!contentChanged &&
|
|
1985
|
+
!toolCallsChanged &&
|
|
1986
|
+
!additionalKwargsChanged &&
|
|
1987
|
+
!responseMetadataChanged
|
|
1988
|
+
) {
|
|
1239
1989
|
continue;
|
|
1240
1990
|
}
|
|
1241
1991
|
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
}
|
|
1248
|
-
// Replaces original args with { _truncated, _originalChars } —
|
|
1249
|
-
// safe because the tool call already executed in a prior turn.
|
|
1250
|
-
return {
|
|
1251
|
-
...tc,
|
|
1252
|
-
args: truncateToolInput(serializedArgs, maxInputChars),
|
|
1253
|
-
};
|
|
1992
|
+
const projectedMessage = cloneWithProjectedProperties(aiMessage, {
|
|
1993
|
+
content: projectedContent,
|
|
1994
|
+
tool_calls: projectedToolCalls.length > 0 ? projectedToolCalls : [],
|
|
1995
|
+
additional_kwargs: projectedAdditionalKwargs,
|
|
1996
|
+
response_metadata: projectedResponseMetadata,
|
|
1254
1997
|
});
|
|
1998
|
+
projectedMessages ??= [...messages];
|
|
1999
|
+
projectedMessages[i] = projectedMessage;
|
|
2000
|
+
}
|
|
2001
|
+
return projectedMessages ?? messages;
|
|
2002
|
+
}
|
|
1255
2003
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
2004
|
+
export function preFlightTruncateToolCallInputs(params: {
|
|
2005
|
+
messages: BaseMessage[];
|
|
2006
|
+
maxContextTokens: number;
|
|
2007
|
+
indexTokenCountMap: Record<string, number | undefined>;
|
|
2008
|
+
tokenCounter: TokenCounter;
|
|
2009
|
+
}): number {
|
|
2010
|
+
const { messages, maxContextTokens, indexTokenCountMap, tokenCounter } =
|
|
2011
|
+
params;
|
|
2012
|
+
const maxInputChars = calculateMaxToolCallInputChars(maxContextTokens);
|
|
2013
|
+
const projected = projectToolCallInputs(messages, maxInputChars);
|
|
2014
|
+
if (projected === messages) {
|
|
2015
|
+
return 0;
|
|
1263
2016
|
}
|
|
1264
2017
|
|
|
2018
|
+
let truncatedCount = 0;
|
|
2019
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2020
|
+
if (projected[i] === messages[i]) {
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
messages[i] = projected[i];
|
|
2024
|
+
indexTokenCountMap[i] = tokenCounter(projected[i]);
|
|
2025
|
+
truncatedCount++;
|
|
2026
|
+
}
|
|
1265
2027
|
return truncatedCount;
|
|
1266
2028
|
}
|
|
1267
2029
|
|
|
@@ -1281,6 +2043,7 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
1281
2043
|
for (const key in indexTokenCountMap) {
|
|
1282
2044
|
totalTokens += indexTokenCountMap[key] ?? 0;
|
|
1283
2045
|
}
|
|
2046
|
+
const reconciledToolMessages = new WeakSet<BaseMessage>();
|
|
1284
2047
|
let runThinkingStartIndex = -1;
|
|
1285
2048
|
/** Cumulative raw tiktoken tokens we've sent to the provider (messages only,
|
|
1286
2049
|
* excludes instruction overhead and new outputs not yet seen by provider). */
|
|
@@ -1296,6 +2059,7 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
1296
2059
|
/** Best observed instruction overhead from a near-zero variance turn.
|
|
1297
2060
|
* Self-seeds from provider observations within the run. */
|
|
1298
2061
|
let bestInstructionOverhead: number | undefined;
|
|
2062
|
+
const reconciledLegacyAiMessages = new WeakSet<BaseMessage>();
|
|
1299
2063
|
let bestVarianceAbs = Infinity;
|
|
1300
2064
|
/** Local estimate at the time bestInstructionOverhead was observed.
|
|
1301
2065
|
* Used to invalidate the cached overhead when instructions change
|
|
@@ -1340,6 +2104,9 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
1340
2104
|
(emptyReserveRatio > 0 && emptyReserveRatio < 1
|
|
1341
2105
|
? Math.round(factoryParams.maxTokens * emptyReserveRatio)
|
|
1342
2106
|
: 0);
|
|
2107
|
+
const emptyReplyPrimerTokens = Math.round(
|
|
2108
|
+
REPLY_PRIMER_TOKENS * calibrationRatio
|
|
2109
|
+
);
|
|
1343
2110
|
return {
|
|
1344
2111
|
context: [],
|
|
1345
2112
|
indexTokenCountMap,
|
|
@@ -1347,7 +2114,7 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
1347
2114
|
prePruneContextTokens: 0,
|
|
1348
2115
|
remainingContextTokens: Math.max(
|
|
1349
2116
|
0,
|
|
1350
|
-
emptyBudget - emptyInstructionTokens
|
|
2117
|
+
emptyBudget - emptyInstructionTokens - emptyReplyPrimerTokens
|
|
1351
2118
|
),
|
|
1352
2119
|
calibrationRatio,
|
|
1353
2120
|
resolvedInstructionOverhead: bestInstructionOverhead,
|
|
@@ -1416,6 +2183,90 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
1416
2183
|
}
|
|
1417
2184
|
|
|
1418
2185
|
const newOutputs = new Set<number>();
|
|
2186
|
+
|
|
2187
|
+
// Host token maps predate LangChain formatting and may omit tool output or
|
|
2188
|
+
// legacy function-call arguments. Reconcile those provider-bound shapes
|
|
2189
|
+
// once before making any budget decision.
|
|
2190
|
+
const applyReconciledCount = (
|
|
2191
|
+
index: number,
|
|
2192
|
+
cachedCount: number | undefined,
|
|
2193
|
+
reconciledCount: number
|
|
2194
|
+
): void => {
|
|
2195
|
+
if (cachedCount === undefined) {
|
|
2196
|
+
indexTokenCountMap[index] = reconciledCount;
|
|
2197
|
+
totalTokens += reconciledCount;
|
|
2198
|
+
if (index >= lastTurnStartIndex) {
|
|
2199
|
+
newOutputs.add(index);
|
|
2200
|
+
}
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
// Preserve a larger host estimate: reconciliation is a safety floor,
|
|
2204
|
+
// not permission to reduce an upstream count that may include provider
|
|
2205
|
+
// serialization overhead unavailable to the local counter.
|
|
2206
|
+
if (reconciledCount <= cachedCount) {
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
indexTokenCountMap[index] = reconciledCount;
|
|
2210
|
+
totalTokens += reconciledCount - cachedCount;
|
|
2211
|
+
if (index >= lastTurnStartIndex) {
|
|
2212
|
+
newOutputs.add(index);
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
for (let i = 0; i < params.messages.length; i++) {
|
|
2216
|
+
let message = params.messages[i];
|
|
2217
|
+
const cachedCount = indexTokenCountMap[i];
|
|
2218
|
+
const messageType = message.getType();
|
|
2219
|
+
const messageRole = (message as BaseMessage & { role?: unknown }).role;
|
|
2220
|
+
const isAssistant = messageType === 'ai' || messageRole === 'assistant';
|
|
2221
|
+
const legacyFunctionCall = isAssistant
|
|
2222
|
+
? readPropertyWithoutAccessors(
|
|
2223
|
+
message.additional_kwargs,
|
|
2224
|
+
'function_call'
|
|
2225
|
+
)
|
|
2226
|
+
: undefined;
|
|
2227
|
+
if (
|
|
2228
|
+
legacyFunctionCall?.found === true &&
|
|
2229
|
+
!reconciledLegacyAiMessages.has(message)
|
|
2230
|
+
) {
|
|
2231
|
+
const [projected] = projectToolCallInputs(
|
|
2232
|
+
[message],
|
|
2233
|
+
calculateMaxToolCallInputChars(factoryParams.maxTokens)
|
|
2234
|
+
);
|
|
2235
|
+
if (projected !== message) {
|
|
2236
|
+
message = projected;
|
|
2237
|
+
params.messages[i] = message;
|
|
2238
|
+
}
|
|
2239
|
+
reconciledLegacyAiMessages.add(message);
|
|
2240
|
+
if (cachedCount !== undefined || i < lastTurnStartIndex) {
|
|
2241
|
+
applyReconciledCount(
|
|
2242
|
+
i,
|
|
2243
|
+
cachedCount,
|
|
2244
|
+
factoryParams.tokenCounter(message)
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
if (messageType !== 'tool' || reconciledToolMessages.has(message)) {
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
if (!isComputerCallOutputMessage(message)) {
|
|
2253
|
+
const normalized = compactToolContent(
|
|
2254
|
+
message.content,
|
|
2255
|
+
factoryParams.maxToolResultChars ?? HARD_MAX_TOOL_RESULT_CHARS
|
|
2256
|
+
);
|
|
2257
|
+
if (normalized.changed) {
|
|
2258
|
+
message = cloneToolMessageWithContent(
|
|
2259
|
+
message as ToolMessage,
|
|
2260
|
+
normalized.content
|
|
2261
|
+
);
|
|
2262
|
+
params.messages[i] = message;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
const reconciledCount = factoryParams.tokenCounter(message);
|
|
2266
|
+
reconciledToolMessages.add(message);
|
|
2267
|
+
applyReconciledCount(i, cachedCount, reconciledCount);
|
|
2268
|
+
}
|
|
2269
|
+
|
|
1419
2270
|
let outputTokensAssigned = false;
|
|
1420
2271
|
for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
|
|
1421
2272
|
const message = params.messages[i];
|
|
@@ -1795,9 +2646,15 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
1795
2646
|
}
|
|
1796
2647
|
|
|
1797
2648
|
lastTurnStartIndex = params.messages.length;
|
|
2649
|
+
const calibratedReplyPrimerTokens = Math.round(
|
|
2650
|
+
REPLY_PRIMER_TOKENS * calibrationRatio
|
|
2651
|
+
);
|
|
1798
2652
|
if (
|
|
1799
2653
|
lastCutOffIndex === 0 &&
|
|
1800
|
-
calibratedTotalTokens +
|
|
2654
|
+
calibratedTotalTokens +
|
|
2655
|
+
calibratedReplyPrimerTokens +
|
|
2656
|
+
currentInstructionTokens <=
|
|
2657
|
+
pruningBudget
|
|
1801
2658
|
) {
|
|
1802
2659
|
return {
|
|
1803
2660
|
context: params.messages,
|
|
@@ -1805,7 +2662,10 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
1805
2662
|
messagesToRefine: [],
|
|
1806
2663
|
prePruneContextTokens: calibratedTotalTokens,
|
|
1807
2664
|
remainingContextTokens:
|
|
1808
|
-
pruningBudget -
|
|
2665
|
+
pruningBudget -
|
|
2666
|
+
calibratedTotalTokens -
|
|
2667
|
+
calibratedReplyPrimerTokens -
|
|
2668
|
+
currentInstructionTokens,
|
|
1809
2669
|
contextPressure,
|
|
1810
2670
|
originalToolContent:
|
|
1811
2671
|
originalToolContent.size > 0 ? originalToolContent : undefined,
|
|
@@ -2022,88 +2882,41 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
|
|
|
2022
2882
|
let emergencyTruncatedCount = 0;
|
|
2023
2883
|
for (let i = 0; i < emergencyMessages.length; i++) {
|
|
2024
2884
|
const message = emergencyMessages[i];
|
|
2025
|
-
if (
|
|
2885
|
+
if (
|
|
2886
|
+
message.getType() === 'tool' &&
|
|
2887
|
+
!isComputerCallOutputMessage(message)
|
|
2888
|
+
) {
|
|
2026
2889
|
const content = message.content;
|
|
2027
|
-
if (
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
additional_kwargs: message.additional_kwargs,
|
|
2037
|
-
response_metadata: message.response_metadata,
|
|
2038
|
-
});
|
|
2890
|
+
if (getToolContentCharLength(content) > emergencyMaxChars) {
|
|
2891
|
+
const compacted = compactToolContent(content, emergencyMaxChars);
|
|
2892
|
+
if (!compacted.changed) {
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
const cloned = cloneToolMessageWithContent(
|
|
2896
|
+
message as ToolMessage,
|
|
2897
|
+
compacted.content
|
|
2898
|
+
);
|
|
2039
2899
|
emergencyMessages[i] = cloned;
|
|
2040
2900
|
indexTokenCountMap[i] = factoryParams.tokenCounter(cloned);
|
|
2041
2901
|
emergencyTruncatedCount++;
|
|
2042
2902
|
}
|
|
2043
2903
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const serialized =
|
|
2055
|
-
typeof record.input === 'string'
|
|
2056
|
-
? record.input
|
|
2057
|
-
: JSON.stringify(record.input);
|
|
2058
|
-
return serialized.length > emergencyMaxChars;
|
|
2059
|
-
}
|
|
2060
|
-
return false;
|
|
2061
|
-
});
|
|
2062
|
-
if (needsTruncation) {
|
|
2063
|
-
const newContent = contentBlocks.map((block) => {
|
|
2064
|
-
if (typeof block !== 'object') return block;
|
|
2065
|
-
const record = block as Record<string, unknown>;
|
|
2066
|
-
if (
|
|
2067
|
-
(record.type === 'tool_use' || record.type === 'tool_call') &&
|
|
2068
|
-
record.input != null
|
|
2069
|
-
) {
|
|
2070
|
-
const serialized =
|
|
2071
|
-
typeof record.input === 'string'
|
|
2072
|
-
? record.input
|
|
2073
|
-
: JSON.stringify(record.input);
|
|
2074
|
-
if (serialized.length > emergencyMaxChars) {
|
|
2075
|
-
// Replaces original input with { _truncated, _originalChars } —
|
|
2076
|
-
// safe because the tool call already executed in a prior turn.
|
|
2077
|
-
return {
|
|
2078
|
-
...record,
|
|
2079
|
-
input: truncateToolInput(serialized, emergencyMaxChars),
|
|
2080
|
-
};
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
return block;
|
|
2084
|
-
});
|
|
2085
|
-
const newToolCalls = (aiMsg.tool_calls ?? []).map((tc) => {
|
|
2086
|
-
const serializedArgs = JSON.stringify(tc.args);
|
|
2087
|
-
if (serializedArgs.length > emergencyMaxChars) {
|
|
2088
|
-
// Replaces original args with { _truncated, _originalChars } —
|
|
2089
|
-
// safe because the tool call already executed in a prior turn.
|
|
2090
|
-
return {
|
|
2091
|
-
...tc,
|
|
2092
|
-
args: truncateToolInput(serializedArgs, emergencyMaxChars),
|
|
2093
|
-
};
|
|
2094
|
-
}
|
|
2095
|
-
return tc;
|
|
2096
|
-
});
|
|
2097
|
-
emergencyMessages[i] = new AIMessage({
|
|
2098
|
-
...aiMsg,
|
|
2099
|
-
content: toLangChainContent(newContent),
|
|
2100
|
-
tool_calls: newToolCalls.length > 0 ? newToolCalls : undefined,
|
|
2101
|
-
});
|
|
2102
|
-
indexTokenCountMap[i] = factoryParams.tokenCounter(
|
|
2103
|
-
emergencyMessages[i]
|
|
2104
|
-
);
|
|
2105
|
-
emergencyTruncatedCount++;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
const projectedToolInputs = projectToolCallInputs(
|
|
2907
|
+
emergencyMessages,
|
|
2908
|
+
emergencyMaxChars
|
|
2909
|
+
);
|
|
2910
|
+
if (projectedToolInputs !== emergencyMessages) {
|
|
2911
|
+
for (let i = 0; i < emergencyMessages.length; i++) {
|
|
2912
|
+
if (projectedToolInputs[i] === emergencyMessages[i]) {
|
|
2913
|
+
continue;
|
|
2106
2914
|
}
|
|
2915
|
+
emergencyMessages[i] = projectedToolInputs[i];
|
|
2916
|
+
indexTokenCountMap[i] = factoryParams.tokenCounter(
|
|
2917
|
+
projectedToolInputs[i]
|
|
2918
|
+
);
|
|
2919
|
+
emergencyTruncatedCount++;
|
|
2107
2920
|
}
|
|
2108
2921
|
}
|
|
2109
2922
|
|