@librechat/agents 3.7.22 → 3.8.1
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 +13 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +174 -53
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +15 -5
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +48 -2
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +42 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +7 -1
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +5 -9
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +2 -7
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/contextPressureMeter.cjs +51 -10
- package/dist/cjs/llm/contextPressureMeter.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +2 -2
- package/dist/cjs/llm/openai/index.cjs +4 -3
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +5 -4
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/preempt.cjs +3 -2
- package/dist/cjs/llm/preempt.cjs.map +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +9 -7
- package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +1 -1
- package/dist/cjs/main.cjs +11 -5
- package/dist/cjs/messages/alternation.cjs +1 -5
- package/dist/cjs/messages/alternation.cjs.map +1 -1
- package/dist/cjs/messages/budget.cjs +206 -7
- package/dist/cjs/messages/budget.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +3 -9
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +25 -37
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +131 -42
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +2 -0
- package/dist/cjs/messages/prune.cjs +12 -7
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/reasoningTypes.cjs +21 -0
- package/dist/cjs/messages/reasoningTypes.cjs.map +1 -0
- package/dist/cjs/messages/recency.cjs +15 -94
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/messages/toolHistoryProjection.cjs +243 -0
- package/dist/cjs/messages/toolHistoryProjection.cjs.map +1 -0
- package/dist/cjs/messages/toolResultTypes.cjs +145 -4
- package/dist/cjs/messages/toolResultTypes.cjs.map +1 -1
- package/dist/cjs/run.cjs +5 -4
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +6 -4
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +3 -0
- package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
- package/dist/cjs/session/index.cjs +1 -1
- package/dist/cjs/session/sessionProjection.cjs +75 -0
- package/dist/cjs/session/sessionProjection.cjs.map +1 -0
- package/dist/cjs/stream.cjs +18 -17
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/index.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +18 -8
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/summarization/shared.cjs +9 -0
- package/dist/cjs/summarization/shared.cjs.map +1 -1
- package/dist/cjs/tools/ArtifactDelivery.cjs +27 -0
- package/dist/cjs/tools/ArtifactDelivery.cjs.map +1 -0
- package/dist/cjs/tools/BashExecutor.cjs +6 -1
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +6 -1
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/local/CompileCheckTool.cjs +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +1 -1
- package/dist/cjs/utils/events.cjs +13 -0
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +105 -0
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +13 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +176 -55
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +15 -5
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +49 -4
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +41 -1
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +7 -1
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +5 -9
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +2 -7
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/contextPressureMeter.mjs +52 -11
- package/dist/esm/llm/contextPressureMeter.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +2 -2
- package/dist/esm/llm/openai/index.mjs +2 -1
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +5 -4
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/preempt.mjs +2 -1
- package/dist/esm/llm/preempt.mjs.map +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +9 -7
- package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs +1 -1
- package/dist/esm/main.mjs +10 -7
- package/dist/esm/messages/alternation.mjs +2 -6
- package/dist/esm/messages/alternation.mjs.map +1 -1
- package/dist/esm/messages/budget.mjs +206 -8
- package/dist/esm/messages/budget.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +3 -9
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +23 -33
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +132 -43
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +2 -0
- package/dist/esm/messages/prune.mjs +12 -7
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/reasoningTypes.mjs +20 -0
- package/dist/esm/messages/reasoningTypes.mjs.map +1 -0
- package/dist/esm/messages/recency.mjs +16 -95
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/messages/toolHistoryProjection.mjs +237 -0
- package/dist/esm/messages/toolHistoryProjection.mjs.map +1 -0
- package/dist/esm/messages/toolResultTypes.mjs +141 -4
- package/dist/esm/messages/toolResultTypes.mjs.map +1 -1
- package/dist/esm/run.mjs +5 -4
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +6 -4
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +3 -0
- package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
- package/dist/esm/session/index.mjs +1 -1
- package/dist/esm/session/sessionProjection.mjs +72 -0
- package/dist/esm/session/sessionProjection.mjs.map +1 -0
- package/dist/esm/stream.mjs +15 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/index.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +19 -9
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/summarization/shared.mjs +9 -1
- package/dist/esm/summarization/shared.mjs.map +1 -1
- package/dist/esm/tools/ArtifactDelivery.mjs +26 -0
- package/dist/esm/tools/ArtifactDelivery.mjs.map +1 -0
- package/dist/esm/tools/BashExecutor.mjs +6 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +6 -1
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/local/CompileCheckTool.mjs +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +1 -1
- package/dist/esm/utils/events.mjs +13 -0
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +105 -1
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +13 -1
- package/dist/types/graphs/Graph.d.ts +27 -0
- package/dist/types/hooks/types.d.ts +4 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/langfuse.d.ts +10 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +2 -0
- package/dist/types/llm/contextPressureMeter.d.ts +2 -1
- package/dist/types/llm/prepareProviderRequest.d.ts +7 -1
- package/dist/types/messages/budget.d.ts +15 -10
- package/dist/types/messages/core.d.ts +8 -17
- package/dist/types/messages/format.d.ts +3 -2
- package/dist/types/messages/index.d.ts +1 -1
- package/dist/types/messages/prune.d.ts +1 -1
- package/dist/types/messages/reasoningTypes.d.ts +7 -0
- package/dist/types/messages/recency.d.ts +3 -0
- package/dist/types/messages/toolHistoryProjection.d.ts +65 -0
- package/dist/types/messages/toolResultTypes.d.ts +31 -1
- package/dist/types/session/sessionProjection.d.ts +10 -0
- package/dist/types/summarization/index.d.ts +2 -1
- package/dist/types/summarization/node.d.ts +1 -0
- package/dist/types/summarization/shared.d.ts +12 -0
- package/dist/types/tools/ArtifactDelivery.d.ts +4 -0
- package/dist/types/types/graph.d.ts +28 -0
- package/dist/types/types/run.d.ts +4 -0
- package/dist/types/types/summarize.d.ts +4 -1
- package/dist/types/types/tools.d.ts +11 -0
- package/dist/types/utils/tokens.d.ts +2 -1
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +34 -3
- package/src/graphs/Graph.ts +465 -147
- package/src/graphs/MultiAgentGraph.ts +28 -6
- package/src/hooks/types.ts +4 -1
- package/src/index.ts +1 -0
- package/src/langfuse.ts +84 -11
- package/src/langfuseToolOutputTracing.ts +91 -0
- package/src/langfuseTraceShaping.ts +17 -4
- package/src/llm/anthropic/utils/message_inputs.ts +5 -10
- package/src/llm/bedrock/utils/message_inputs.ts +2 -8
- package/src/llm/contextPressureMeter.ts +80 -12
- package/src/llm/openai/utils/index.ts +5 -4
- package/src/llm/prepareProviderRequest.ts +21 -16
- package/src/messages/alternation.ts +2 -15
- package/src/messages/budget.ts +439 -23
- package/src/messages/cache.ts +8 -9
- package/src/messages/core.ts +65 -95
- package/src/messages/format.ts +262 -78
- package/src/messages/index.ts +1 -1
- package/src/messages/prune.ts +31 -12
- package/src/messages/reasoningTypes.ts +23 -0
- package/src/messages/recency.ts +35 -179
- package/src/messages/toolHistoryProjection.ts +460 -0
- package/src/messages/toolResultTypes.ts +331 -100
- package/src/run.ts +10 -2
- package/src/session/AgentSession.ts +33 -16
- package/src/session/JsonlSessionStore.ts +6 -0
- package/src/session/sessionProjection.ts +126 -0
- package/src/stream.ts +14 -19
- package/src/summarization/index.ts +2 -0
- package/src/summarization/node.ts +62 -13
- package/src/summarization/shared.ts +23 -0
- package/src/tools/ArtifactDelivery.ts +50 -0
- package/src/tools/BashExecutor.ts +18 -1
- package/src/tools/CodeExecutor.ts +18 -1
- package/src/tools/ProgrammaticToolCalling.ts +15 -1
- package/src/types/graph.ts +28 -0
- package/src/types/run.ts +4 -0
- package/src/types/summarize.ts +4 -1
- package/src/types/tools.ts +12 -0
- package/src/utils/events.ts +19 -0
- package/src/utils/tokens.ts +183 -0
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
serializeToolCallInput,
|
|
57
57
|
} from '@/messages/prune';
|
|
58
58
|
import { toLangChainContent } from '@/messages/langchain';
|
|
59
|
+
import { isAnthropicThinkingContentBlock } from '@/messages/reasoningTypes';
|
|
59
60
|
|
|
60
61
|
export type { OpenAICallOptions, OpenAIChatInput };
|
|
61
62
|
|
|
@@ -361,7 +362,7 @@ export function _convertMessagesToOpenAIParams(
|
|
|
361
362
|
role = 'developer';
|
|
362
363
|
}
|
|
363
364
|
|
|
364
|
-
let
|
|
365
|
+
let hasReasoningBlock = false;
|
|
365
366
|
|
|
366
367
|
let content: unknown;
|
|
367
368
|
if (
|
|
@@ -384,8 +385,8 @@ export function _convertMessagesToOpenAIParams(
|
|
|
384
385
|
content = message.content;
|
|
385
386
|
} else {
|
|
386
387
|
content = message.content.map((m) => {
|
|
387
|
-
if (
|
|
388
|
-
|
|
388
|
+
if (isAnthropicThinkingContentBlock(m)) {
|
|
389
|
+
hasReasoningBlock = true;
|
|
389
390
|
return m;
|
|
390
391
|
}
|
|
391
392
|
if (isDataContentBlock(m)) {
|
|
@@ -416,7 +417,7 @@ export function _convertMessagesToOpenAIParams(
|
|
|
416
417
|
completionParam.tool_calls = message.tool_calls.map(
|
|
417
418
|
convertLangChainToolCallToBoundedOpenAI
|
|
418
419
|
);
|
|
419
|
-
completionParam.content =
|
|
420
|
+
completionParam.content = hasReasoningBlock ? content : '';
|
|
420
421
|
if (
|
|
421
422
|
options?.includeReasoningDetails === true &&
|
|
422
423
|
message.additional_kwargs.reasoning_details != null
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
8
8
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
9
9
|
import type * as t from '@/types';
|
|
10
|
+
import type { ToolHistoryPreparation } from '@/messages/toolHistoryProjection';
|
|
10
11
|
import {
|
|
11
12
|
projectCacheControlledToolOutputsToText,
|
|
12
13
|
projectComputerCallOutputsToText,
|
|
@@ -27,15 +28,12 @@ import {
|
|
|
27
28
|
stripBedrockCacheControl,
|
|
28
29
|
cloneMessage,
|
|
29
30
|
} from '@/messages/cache';
|
|
30
|
-
import {
|
|
31
|
-
isAnthropicLike,
|
|
32
|
-
isGoogleLike,
|
|
33
|
-
isOpenAILike,
|
|
34
|
-
} from '@/utils/llm';
|
|
31
|
+
import { isAnthropicLike, isGoogleLike, isOpenAILike } from '@/utils/llm';
|
|
35
32
|
import { annotateMessagesForLLM } from '@/tools/toolOutputReferences';
|
|
36
33
|
import { providerRequiresStrictAlternation } from '@/llm/providers';
|
|
37
34
|
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
38
35
|
import { Providers } from '@/common';
|
|
36
|
+
import { createToolHistoryPreparation } from '@/messages/toolHistoryProjection';
|
|
39
37
|
|
|
40
38
|
const preparedProviderRequestBrand = Symbol('PreparedProviderRequest');
|
|
41
39
|
const OMITTED_ATTACHMENT_TEXT =
|
|
@@ -90,6 +88,9 @@ export interface ProviderPayloadMeasurement {
|
|
|
90
88
|
readonly availableMessageTokens?: number;
|
|
91
89
|
readonly contextBudget?: number;
|
|
92
90
|
readonly effectiveInstructionTokens?: number;
|
|
91
|
+
readonly toolMessageTokens?: number;
|
|
92
|
+
readonly toolMessageTokenCounts?: Record<string, number>;
|
|
93
|
+
readonly toolMessageUsageError?: Error;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export interface PreparedProviderRequest {
|
|
@@ -120,6 +121,7 @@ export interface PrepareProviderRequestParams {
|
|
|
120
121
|
config?: RunnableConfig;
|
|
121
122
|
maxToolResultChars?: number;
|
|
122
123
|
measure?: (messages: BaseMessage[]) => ProviderPayloadMeasurement;
|
|
124
|
+
toolHistory?: ToolHistoryPreparation;
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
export function usesNativeOpenAIResponses(
|
|
@@ -164,11 +166,8 @@ export function usesNativeOpenAIResponses(
|
|
|
164
166
|
} else if (effectiveCallOptions == null) {
|
|
165
167
|
effectiveCallOptions = runnable.defaultOptions;
|
|
166
168
|
}
|
|
167
|
-
if (
|
|
168
|
-
runnable._useResponsesApi
|
|
169
|
-
runnable._useResponsesApi?.(undefined) === true
|
|
170
|
-
) {
|
|
171
|
-
return true;
|
|
169
|
+
if (typeof runnable._useResponsesApi === 'function') {
|
|
170
|
+
return runnable._useResponsesApi(effectiveCallOptions) === true;
|
|
172
171
|
}
|
|
173
172
|
} catch {
|
|
174
173
|
// Continue through RunnableSequence/RunnableBinding wrappers.
|
|
@@ -208,6 +207,7 @@ interface ProjectMessagesForProviderParams {
|
|
|
208
207
|
provider: t.ProviderName;
|
|
209
208
|
maxToolResultChars?: number;
|
|
210
209
|
callOptions?: unknown;
|
|
210
|
+
toolHistory?: ToolHistoryPreparation;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
function isSerializedBuffer(value: object): value is SerializedBuffer {
|
|
@@ -380,10 +380,7 @@ function projectAttachmentsForProvider(
|
|
|
380
380
|
}
|
|
381
381
|
content ??= copyUsableContentPrefix(sourceContent, blockIndex);
|
|
382
382
|
const mimeType = BEDROCK_DOCUMENT_MIME_TYPES[block.document.format];
|
|
383
|
-
if (
|
|
384
|
-
mimeType == null ||
|
|
385
|
-
!canProjectBedrockDocument(provider, mimeType)
|
|
386
|
-
) {
|
|
383
|
+
if (mimeType == null || !canProjectBedrockDocument(provider, mimeType)) {
|
|
387
384
|
continue;
|
|
388
385
|
}
|
|
389
386
|
const standardFile = toStandardFileBlock(block);
|
|
@@ -408,7 +405,12 @@ function projectAttachmentsForProvider(
|
|
|
408
405
|
}
|
|
409
406
|
|
|
410
407
|
function projectMessagesForProviderMode(
|
|
411
|
-
{
|
|
408
|
+
{
|
|
409
|
+
messages,
|
|
410
|
+
provider,
|
|
411
|
+
maxToolResultChars,
|
|
412
|
+
toolHistory,
|
|
413
|
+
}: ProjectMessagesForProviderParams,
|
|
412
414
|
projectionMode: ProviderMessageProjectionMode
|
|
413
415
|
): BaseMessage[] {
|
|
414
416
|
const providerFamily = getProviderFamily(provider);
|
|
@@ -417,7 +419,8 @@ function projectMessagesForProviderMode(
|
|
|
417
419
|
projectToolStreamContentForProvider(
|
|
418
420
|
messages,
|
|
419
421
|
nativeOpenAIResponses ? 'native' : 'fallback',
|
|
420
|
-
maxToolResultChars
|
|
422
|
+
maxToolResultChars,
|
|
423
|
+
toolHistory
|
|
421
424
|
),
|
|
422
425
|
provider
|
|
423
426
|
);
|
|
@@ -529,6 +532,7 @@ export function prepareProviderRequest({
|
|
|
529
532
|
config,
|
|
530
533
|
maxToolResultChars,
|
|
531
534
|
measure,
|
|
535
|
+
toolHistory = createToolHistoryPreparation(),
|
|
532
536
|
}: PrepareProviderRequestParams): PreparedProviderRequest {
|
|
533
537
|
const projectionMode = resolveProviderMessageProjectionMode(
|
|
534
538
|
model,
|
|
@@ -542,6 +546,7 @@ export function prepareProviderRequest({
|
|
|
542
546
|
provider,
|
|
543
547
|
maxToolResultChars,
|
|
544
548
|
callOptions: config,
|
|
549
|
+
toolHistory,
|
|
545
550
|
},
|
|
546
551
|
projectionMode
|
|
547
552
|
);
|
|
@@ -4,10 +4,10 @@ import type { BaseMessage, MessageContent } from '@langchain/core/messages';
|
|
|
4
4
|
import type { ProviderMessageProvenancePart } from './provenance';
|
|
5
5
|
import type { ProviderToolCallIndex } from './toolResultTypes';
|
|
6
6
|
import {
|
|
7
|
+
appendProviderMessageToolCalls,
|
|
7
8
|
appendProviderToolCallDescriptor,
|
|
8
9
|
consumeProviderToolResultPair,
|
|
9
10
|
getBoundedProviderPairingArrayProperty,
|
|
10
|
-
getProviderAIMessageToolCallDescriptor,
|
|
11
11
|
getProviderToolCallPartDescriptor,
|
|
12
12
|
getProviderToolResultPartDescriptor,
|
|
13
13
|
} from './toolResultTypes';
|
|
@@ -42,6 +42,7 @@ export const strictAlternationProviders: ReadonlySet<Providers> = new Set([
|
|
|
42
42
|
*/
|
|
43
43
|
function collectProviderToolCalls(message: BaseMessage): ProviderToolCallIndex {
|
|
44
44
|
const calls: ProviderToolCallIndex = new Map();
|
|
45
|
+
appendProviderMessageToolCalls(message, calls);
|
|
45
46
|
const content = getBoundedProviderPairingArrayProperty(message, 'content');
|
|
46
47
|
if (content != null) {
|
|
47
48
|
for (let index = 0; index < content.length; index++) {
|
|
@@ -51,20 +52,6 @@ function collectProviderToolCalls(message: BaseMessage): ProviderToolCallIndex {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
|
-
const toolCalls = getBoundedProviderPairingArrayProperty(
|
|
55
|
-
message,
|
|
56
|
-
'tool_calls'
|
|
57
|
-
);
|
|
58
|
-
if (toolCalls != null) {
|
|
59
|
-
for (let index = 0; index < toolCalls.length; index++) {
|
|
60
|
-
const descriptor = getProviderAIMessageToolCallDescriptor(
|
|
61
|
-
toolCalls[index]
|
|
62
|
-
);
|
|
63
|
-
if (descriptor != null) {
|
|
64
|
-
appendProviderToolCallDescriptor(calls, descriptor);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
55
|
return calls;
|
|
69
56
|
}
|
|
70
57
|
|
package/src/messages/budget.ts
CHANGED
|
@@ -1,32 +1,448 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaseMessage,
|
|
3
|
+
MessageContentComplex,
|
|
4
|
+
} from '@langchain/core/messages';
|
|
5
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
6
|
+
import type {
|
|
7
|
+
ProviderToolCallIndex,
|
|
8
|
+
ProviderToolResultPartDescriptor,
|
|
9
|
+
} from './toolResultTypes';
|
|
1
10
|
import type * as t from '@/types';
|
|
11
|
+
import {
|
|
12
|
+
appendProviderMessageToolCalls,
|
|
13
|
+
appendProviderToolCallDescriptor,
|
|
14
|
+
consumeProviderToolResultPair,
|
|
15
|
+
getProviderMessageRole,
|
|
16
|
+
getProviderToolCallPartDescriptor,
|
|
17
|
+
getProviderToolMessageResultDescriptor,
|
|
18
|
+
getProviderToolResultPartDescriptor,
|
|
19
|
+
isExecutableCodePart,
|
|
20
|
+
} from './toolResultTypes';
|
|
21
|
+
import { getProviderMessageProvenance } from './provenance';
|
|
22
|
+
import { apportionTokenCounts } from '@/utils/tokens';
|
|
23
|
+
import { isReasoningContentBlock } from './reasoningTypes';
|
|
24
|
+
import { emitAgentLog } from '@/utils/events';
|
|
25
|
+
import { ContentTypes } from '@/common';
|
|
2
26
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
export function syncBudgetDerivedFields(usage: t.ContextUsageEvent): void {
|
|
13
|
-
const { breakdown, contextBudget, effectiveInstructionTokens } = usage;
|
|
14
|
-
if (effectiveInstructionTokens == null) {
|
|
15
|
-
return;
|
|
27
|
+
const UNKNOWN_TOOL = 'unknown_tool';
|
|
28
|
+
const BLANK_TEXT = /^\s*$/u;
|
|
29
|
+
|
|
30
|
+
/** Guards an aggregate: sums of rounded counts must stay safe integers.
|
|
31
|
+
* Callers must degrade rather than propagate: this runs on the live pre-invoke path. */
|
|
32
|
+
function safeCount(value: number): number {
|
|
33
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
34
|
+
throw new RangeError('Invalid tool context token count');
|
|
16
35
|
}
|
|
17
|
-
|
|
18
|
-
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Accepts what the `TokenCounter` contract allows, an approximate `number`
|
|
40
|
+
* such as `length / 4`, by rounding it, and rejects only what no subset claim
|
|
41
|
+
* can be derived from: NaN, infinities, negatives and values past the safe range. */
|
|
42
|
+
function toCount(value: number): number {
|
|
43
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
44
|
+
throw new RangeError('Invalid tool context token count');
|
|
45
|
+
}
|
|
46
|
+
return safeCount(Math.round(value));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function safeRawCount(value: number): number {
|
|
50
|
+
if (!Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER) {
|
|
51
|
+
throw new RangeError('Invalid tool context token count');
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let warnedUnavailableToolShare = false;
|
|
57
|
+
|
|
58
|
+
/** Warns once per process: an unusable counter is a permanent host-integration
|
|
59
|
+
* fault, while the share is dropped on every affected call regardless. The
|
|
60
|
+
* latch is spent only when a config can carry the event, so a config-less
|
|
61
|
+
* caller (the pre-send projection) cannot swallow the live path's one warning. */
|
|
62
|
+
function warnUnavailableToolShare(
|
|
63
|
+
config: RunnableConfig | undefined,
|
|
64
|
+
error: unknown
|
|
65
|
+
): void {
|
|
66
|
+
if (warnedUnavailableToolShare || config == null) {
|
|
19
67
|
return;
|
|
20
68
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
69
|
+
warnedUnavailableToolShare = true;
|
|
70
|
+
emitAgentLog(
|
|
71
|
+
config,
|
|
72
|
+
'warn',
|
|
73
|
+
'budget',
|
|
74
|
+
'Tool-message context share unavailable: the token counter must return finite, non-negative counts within the safe range',
|
|
75
|
+
{ error: error instanceof Error ? error.message : String(error) }
|
|
24
76
|
);
|
|
25
|
-
|
|
26
|
-
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isBlankTextPart(part: string | MessageContentComplex): boolean {
|
|
80
|
+
if (typeof part === 'string') {
|
|
81
|
+
return BLANK_TEXT.test(part);
|
|
27
82
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
83
|
+
return (
|
|
84
|
+
part.type === ContentTypes.TEXT &&
|
|
85
|
+
typeof part.text === 'string' &&
|
|
86
|
+
BLANK_TEXT.test(part.text)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readLegacyFunctionName(message: BaseMessage): string | undefined {
|
|
91
|
+
const name = message.additional_kwargs.function_call?.name;
|
|
92
|
+
return typeof name === 'string' && name.length > 0 ? name : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface ResultPairing {
|
|
96
|
+
readonly paired: boolean;
|
|
97
|
+
readonly name?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Pairs a result with its call the way the wire walkers do: the call is
|
|
101
|
+
* validated for kind and name and then consumed, so a provider that reuses a
|
|
102
|
+
* call id in a later turn is attributed to the current call rather than
|
|
103
|
+
* poisoning the id, and the bounded index never fills with answered calls. */
|
|
104
|
+
function pairResult(
|
|
105
|
+
descriptor: ProviderToolResultPartDescriptor,
|
|
106
|
+
calls: ProviderToolCallIndex,
|
|
107
|
+
previousPart?: unknown
|
|
108
|
+
): ResultPairing {
|
|
109
|
+
const name =
|
|
110
|
+
descriptor.toolCallId == null
|
|
111
|
+
? undefined
|
|
112
|
+
: calls.get(descriptor.toolCallId)?.descriptor.name;
|
|
113
|
+
const paired = consumeProviderToolResultPair(descriptor, calls, previousPart);
|
|
114
|
+
return paired ? { paired, name } : { paired };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A user turn a provider transform built from tool history: a tool-less
|
|
119
|
+
* destination inheriting tool turns folds each call and its results into one
|
|
120
|
+
* synthetic `HumanMessage`, and compaction of that fold keeps the lineage. The
|
|
121
|
+
* role is user on the wire, the bytes are retained tool output, and the fold
|
|
122
|
+
* is what the provenance stamp records. The counter measures whole messages,
|
|
123
|
+
* so per-tool attribution is lost with the fold, and any visible model text the
|
|
124
|
+
* fold carried alongside its calls (a "let me search" preamble, the fold's own
|
|
125
|
+
* scaffolding) is counted with it. That is a bounded over-count on a turn that
|
|
126
|
+
* exists only because of tool content; the alternative is reporting zero.
|
|
127
|
+
*/
|
|
128
|
+
function isFoldedToolHistory(message: BaseMessage): boolean {
|
|
129
|
+
const parts = getProviderMessageProvenance(message)?.parts;
|
|
130
|
+
return parts != null && parts.some((part) => part.attribution === 'tool');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface InvocationScan {
|
|
134
|
+
readonly recognizedCalls: number;
|
|
135
|
+
readonly toolOnly: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Registers an assistant turn's calls and decides whether the turn is
|
|
140
|
+
* tool-only. Tool-only means every part is blank text, a tool call, a provider
|
|
141
|
+
* tool result returned inline (Anthropic server tools), or reasoning attached
|
|
142
|
+
* to the turn. Visible text, media and unrecognized blocks keep the whole turn
|
|
143
|
+
* in the conversation share. Shape recognition is the taxonomy's, so a call or
|
|
144
|
+
* result representation this repo's converters accept is accepted here.
|
|
145
|
+
*/
|
|
146
|
+
function scanInvocation(
|
|
147
|
+
message: BaseMessage,
|
|
148
|
+
calls: ProviderToolCallIndex,
|
|
149
|
+
provider?: t.ProviderName
|
|
150
|
+
): InvocationScan {
|
|
151
|
+
let recognizedCalls = appendProviderMessageToolCalls(
|
|
152
|
+
message,
|
|
153
|
+
calls,
|
|
154
|
+
provider
|
|
31
155
|
);
|
|
156
|
+
if (typeof message.content === 'string') {
|
|
157
|
+
return { recognizedCalls, toolOnly: BLANK_TEXT.test(message.content) };
|
|
158
|
+
}
|
|
159
|
+
const parts: ReadonlyArray<string | MessageContentComplex> = message.content;
|
|
160
|
+
let toolOnly = true;
|
|
161
|
+
let previousPart: string | MessageContentComplex | undefined;
|
|
162
|
+
for (const part of parts) {
|
|
163
|
+
if (typeof part === 'string') {
|
|
164
|
+
toolOnly &&= BLANK_TEXT.test(part);
|
|
165
|
+
previousPart = part;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const call = getProviderToolCallPartDescriptor(part);
|
|
169
|
+
if (call != null) {
|
|
170
|
+
appendProviderToolCallDescriptor(calls, call);
|
|
171
|
+
recognizedCalls += 1;
|
|
172
|
+
previousPart = part;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (isExecutableCodePart(part)) {
|
|
176
|
+
recognizedCalls += 1;
|
|
177
|
+
previousPart = part;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const result = getProviderToolResultPartDescriptor(part);
|
|
181
|
+
if (result != null) {
|
|
182
|
+
pairResult(result, calls, previousPart);
|
|
183
|
+
previousPart = part;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
previousPart = part;
|
|
187
|
+
if (isReasoningContentBlock(part)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
toolOnly &&= isBlankTextPart(part);
|
|
191
|
+
}
|
|
192
|
+
return { recognizedCalls, toolOnly };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Result attribution: the paired call's name, then the message's own name,
|
|
196
|
+
* then the legacy call still pending for a nameless `FunctionMessage`. */
|
|
197
|
+
function getToolMessageResultName(
|
|
198
|
+
message: BaseMessage,
|
|
199
|
+
calls: ProviderToolCallIndex,
|
|
200
|
+
pendingLegacyName: string | undefined,
|
|
201
|
+
provider?: t.ProviderName
|
|
202
|
+
): string {
|
|
203
|
+
const descriptor = getProviderToolMessageResultDescriptor(message, provider);
|
|
204
|
+
const pairing =
|
|
205
|
+
descriptor == null ? undefined : pairResult(descriptor, calls);
|
|
206
|
+
if (pairing?.name != null) {
|
|
207
|
+
return pairing.name;
|
|
208
|
+
}
|
|
209
|
+
if (message.name != null && message.name.length > 0) {
|
|
210
|
+
return message.name;
|
|
211
|
+
}
|
|
212
|
+
return pendingLegacyName ?? UNKNOWN_TOOL;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A user turn made only of `tool_result` parts that each pair with a pending
|
|
217
|
+
* call, the split `AIMessage(tool_call)` + `HumanMessage(tool_result)` history
|
|
218
|
+
* the converters accept. Pairing runs against a candidate copy and commits only
|
|
219
|
+
* when every part pairs, as the recency walker does; anything else is an
|
|
220
|
+
* ordinary user turn. The counter measures whole messages, so the turn is
|
|
221
|
+
* attributed to its one paired tool, or to `unknown_tool` when parts name
|
|
222
|
+
* several. Returns undefined for any other user turn.
|
|
223
|
+
*/
|
|
224
|
+
function takeUserToolResultName(
|
|
225
|
+
message: BaseMessage,
|
|
226
|
+
calls: ProviderToolCallIndex
|
|
227
|
+
): string | undefined {
|
|
228
|
+
if (typeof message.content === 'string' || message.content.length === 0) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
const candidate: ProviderToolCallIndex = new Map(calls);
|
|
232
|
+
let name: string | undefined;
|
|
233
|
+
let mixed = false;
|
|
234
|
+
let previousPart: unknown;
|
|
235
|
+
for (const part of message.content) {
|
|
236
|
+
const descriptor = getProviderToolResultPartDescriptor(part);
|
|
237
|
+
if (descriptor?.allowHumanMessagePairing !== true) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
const pairing = pairResult(descriptor, candidate, previousPart);
|
|
241
|
+
if (!pairing.paired) {
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
mixed ||= name != null && pairing.name !== name;
|
|
245
|
+
name = pairing.name;
|
|
246
|
+
previousPart = part;
|
|
247
|
+
}
|
|
248
|
+
calls.clear();
|
|
249
|
+
for (const [callId, entry] of candidate) {
|
|
250
|
+
calls.set(callId, entry);
|
|
251
|
+
}
|
|
252
|
+
return mixed || name == null ? UNKNOWN_TOOL : name;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Counts retained tool exchanges without serializing arguments or result content. */
|
|
256
|
+
export interface ToolMessageUsageAccumulator {
|
|
257
|
+
add(message: BaseMessage, getRawTokens: () => number): void;
|
|
258
|
+
finish(
|
|
259
|
+
calibrationRatio?: number
|
|
260
|
+
): Pick<
|
|
261
|
+
t.TokenBudgetBreakdown,
|
|
262
|
+
'toolMessageTokens' | 'toolMessageTokenCounts'
|
|
263
|
+
>;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Accumulates tool-share attribution while its caller walks a provider payload. */
|
|
267
|
+
export function createToolMessageUsageAccumulator(
|
|
268
|
+
provider?: t.ProviderName
|
|
269
|
+
): ToolMessageUsageAccumulator {
|
|
270
|
+
const calls: ProviderToolCallIndex = new Map();
|
|
271
|
+
const counts: Record<string, number> = Object.create(null);
|
|
272
|
+
let pendingLegacyName: string | undefined;
|
|
273
|
+
let total = 0;
|
|
274
|
+
let resultTotal = 0;
|
|
275
|
+
const addRaw = (current: number, increment: number): number =>
|
|
276
|
+
safeRawCount(current + safeRawCount(increment));
|
|
277
|
+
const countResult = (tokens: number, name: string): void => {
|
|
278
|
+
total = addRaw(total, tokens);
|
|
279
|
+
if (tokens === 0) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
counts[name] = addRaw(counts[name] ?? 0, tokens);
|
|
283
|
+
resultTotal = addRaw(resultTotal, tokens);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const add = (message: BaseMessage, getRawTokens: () => number): void => {
|
|
287
|
+
const readTokens = (): number => safeRawCount(getRawTokens());
|
|
288
|
+
const role = getProviderMessageRole(message, provider);
|
|
289
|
+
if (role === 'assistant') {
|
|
290
|
+
const scan = scanInvocation(message, calls, provider);
|
|
291
|
+
pendingLegacyName = readLegacyFunctionName(message);
|
|
292
|
+
if (scan.recognizedCalls > 0 && scan.toolOnly) {
|
|
293
|
+
total = addRaw(total, readTokens());
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (role === 'tool' || role === 'function') {
|
|
298
|
+
const legacyName = role === 'function' ? pendingLegacyName : undefined;
|
|
299
|
+
countResult(
|
|
300
|
+
readTokens(),
|
|
301
|
+
getToolMessageResultName(message, calls, legacyName, provider)
|
|
302
|
+
);
|
|
303
|
+
if (role === 'function') {
|
|
304
|
+
pendingLegacyName = undefined;
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (role === 'user' && isFoldedToolHistory(message)) {
|
|
309
|
+
total = addRaw(total, readTokens());
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const userResultName =
|
|
313
|
+
role === 'user' ? takeUserToolResultName(message, calls) : undefined;
|
|
314
|
+
if (userResultName != null) {
|
|
315
|
+
countResult(readTokens(), userResultName);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (role === 'user') {
|
|
319
|
+
calls.clear();
|
|
320
|
+
}
|
|
321
|
+
pendingLegacyName = undefined;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const finish = (
|
|
325
|
+
calibrationRatio = 1
|
|
326
|
+
): Pick<
|
|
327
|
+
t.TokenBudgetBreakdown,
|
|
328
|
+
'toolMessageTokens' | 'toolMessageTokenCounts'
|
|
329
|
+
> => {
|
|
330
|
+
const ratio =
|
|
331
|
+
Number.isFinite(calibrationRatio) && calibrationRatio > 0
|
|
332
|
+
? calibrationRatio
|
|
333
|
+
: 1;
|
|
334
|
+
const toolMessageTokens = safeCount(Math.round(total * ratio));
|
|
335
|
+
const resultTokens = Math.min(
|
|
336
|
+
toolMessageTokens,
|
|
337
|
+
safeCount(Math.round(resultTotal * ratio))
|
|
338
|
+
);
|
|
339
|
+
return {
|
|
340
|
+
toolMessageTokens,
|
|
341
|
+
toolMessageTokenCounts:
|
|
342
|
+
resultTotal > 0 && resultTokens > 0
|
|
343
|
+
? apportionTokenCounts(counts, ratio, resultTokens)
|
|
344
|
+
: undefined,
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
return { add, finish };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function computeToolMessageUsage(
|
|
352
|
+
context: readonly BaseMessage[],
|
|
353
|
+
tokenCounter: t.TokenCounter,
|
|
354
|
+
calibrationRatio = 1,
|
|
355
|
+
provider?: t.ProviderName
|
|
356
|
+
): Pick<
|
|
357
|
+
t.TokenBudgetBreakdown,
|
|
358
|
+
'toolMessageTokens' | 'toolMessageTokenCounts'
|
|
359
|
+
> {
|
|
360
|
+
const accumulator = createToolMessageUsageAccumulator(provider);
|
|
361
|
+
for (const message of context) {
|
|
362
|
+
accumulator.add(message, () => tokenCounter(message));
|
|
363
|
+
}
|
|
364
|
+
return accumulator.finish(calibrationRatio);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Applies the retained tool share and clamps it to the conversation total it
|
|
368
|
+
* is a subset of. Throws when a count cannot support that claim. */
|
|
369
|
+
function syncToolMessageShare(
|
|
370
|
+
usage: t.ContextUsageEvent,
|
|
371
|
+
context?: readonly BaseMessage[],
|
|
372
|
+
tokenCounter?: t.TokenCounter,
|
|
373
|
+
provider?: t.ProviderName
|
|
374
|
+
): void {
|
|
375
|
+
const { breakdown } = usage;
|
|
376
|
+
if (context != null && tokenCounter != null) {
|
|
377
|
+
Object.assign(
|
|
378
|
+
breakdown,
|
|
379
|
+
computeToolMessageUsage(
|
|
380
|
+
context,
|
|
381
|
+
tokenCounter,
|
|
382
|
+
usage.calibrationRatio,
|
|
383
|
+
provider
|
|
384
|
+
)
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
if (breakdown.toolMessageTokens == null) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const total = toCount(breakdown.toolMessageTokens);
|
|
391
|
+
const clamped = Math.min(total, toCount(breakdown.messageTokens));
|
|
392
|
+
if (clamped !== total && breakdown.toolMessageTokenCounts != null) {
|
|
393
|
+
let resultTotal = 0;
|
|
394
|
+
for (const count of Object.values(breakdown.toolMessageTokenCounts)) {
|
|
395
|
+
resultTotal = safeCount(resultTotal + toCount(count));
|
|
396
|
+
}
|
|
397
|
+
const factor = total > 0 ? clamped / total : 0;
|
|
398
|
+
const target = Math.min(clamped, Math.round(resultTotal * factor));
|
|
399
|
+
breakdown.toolMessageTokenCounts =
|
|
400
|
+
target > 0
|
|
401
|
+
? apportionTokenCounts(breakdown.toolMessageTokenCounts, factor, target)
|
|
402
|
+
: undefined;
|
|
403
|
+
}
|
|
404
|
+
breakdown.toolMessageTokens = clamped;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Reconciles derived budget fields and, when supplied, the retained tool share.
|
|
408
|
+
* The index map is keyed before pruning, so it cannot index the retained context.
|
|
409
|
+
* Callers may pass the existing exact token cache's counter, never a stale index lookup.
|
|
410
|
+
* Runs on the live pre-invoke path, so an unusable count drops the tool share
|
|
411
|
+
* (warned once) instead of failing the model call it only measures. */
|
|
412
|
+
export function syncBudgetDerivedFields(
|
|
413
|
+
usage: t.ContextUsageEvent,
|
|
414
|
+
context?: readonly BaseMessage[],
|
|
415
|
+
tokenCounter?: t.TokenCounter,
|
|
416
|
+
config?: RunnableConfig,
|
|
417
|
+
provider?: t.ProviderName,
|
|
418
|
+
toolMessageUsageError?: Error
|
|
419
|
+
): void {
|
|
420
|
+
const { breakdown, contextBudget, effectiveInstructionTokens } = usage;
|
|
421
|
+
if (effectiveInstructionTokens != null) {
|
|
422
|
+
breakdown.instructionTokens = effectiveInstructionTokens;
|
|
423
|
+
if (contextBudget != null) {
|
|
424
|
+
breakdown.availableForMessages = Math.max(
|
|
425
|
+
0,
|
|
426
|
+
contextBudget - effectiveInstructionTokens
|
|
427
|
+
);
|
|
428
|
+
if (usage.remainingContextTokens != null) {
|
|
429
|
+
breakdown.messageTokens = Math.max(
|
|
430
|
+
0,
|
|
431
|
+
contextBudget -
|
|
432
|
+
effectiveInstructionTokens -
|
|
433
|
+
usage.remainingContextTokens
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
if (toolMessageUsageError != null) {
|
|
440
|
+
throw toolMessageUsageError;
|
|
441
|
+
}
|
|
442
|
+
syncToolMessageShare(usage, context, tokenCounter, provider);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
warnUnavailableToolShare(config, error);
|
|
445
|
+
delete breakdown.toolMessageTokens;
|
|
446
|
+
delete breakdown.toolMessageTokenCounts;
|
|
447
|
+
}
|
|
32
448
|
}
|