@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/graphs/Graph.ts
CHANGED
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
3
|
import { tool } from '@langchain/core/tools';
|
|
4
4
|
import { ToolNode } from '@langchain/langgraph/prebuilt';
|
|
5
|
+
import { ContextOverflowError } from '@langchain/core/errors';
|
|
5
6
|
import { Runnable, RunnableConfig } from '@langchain/core/runnables';
|
|
6
|
-
import { ToolMessage, AIMessageChunk } from '@langchain/core/messages';
|
|
7
7
|
import { START, END, StateGraph, Annotation } from '@langchain/langgraph';
|
|
8
|
+
import {
|
|
9
|
+
ToolMessage,
|
|
10
|
+
HumanMessage,
|
|
11
|
+
AIMessageChunk,
|
|
12
|
+
} from '@langchain/core/messages';
|
|
8
13
|
import type {
|
|
9
14
|
UsageMetadata,
|
|
10
15
|
BaseMessage,
|
|
@@ -16,55 +21,66 @@ import type { FallbackErrorContext } from '@/llm/invoke';
|
|
|
16
21
|
import type { HookRegistry } from '@/hooks';
|
|
17
22
|
import type * as t from '@/types';
|
|
18
23
|
import {
|
|
19
|
-
|
|
24
|
+
projectAnthropicArtifactContent,
|
|
20
25
|
ensureThinkingBlockInMessages,
|
|
21
26
|
foldToolBlocksForToollessAgent,
|
|
22
27
|
convertMessagesToContent,
|
|
23
28
|
sanitizeOrphanToolBlocks,
|
|
24
29
|
extractToolDiscoveries,
|
|
25
30
|
addBedrockTailCacheControl,
|
|
26
|
-
|
|
31
|
+
projectArtifactPayload,
|
|
27
32
|
formatContentStrings,
|
|
28
|
-
isLegacyConvertible,
|
|
29
33
|
CALIBRATION_RATIO_MAX,
|
|
34
|
+
REPLY_PRIMER_TOKENS,
|
|
30
35
|
createPruneMessages,
|
|
36
|
+
projectToolCallInputs,
|
|
37
|
+
calculateMaxToolCallInputChars,
|
|
38
|
+
projectToolStreamContentForProvider,
|
|
31
39
|
syncBudgetDerivedFields,
|
|
32
40
|
addTailCacheControl,
|
|
33
41
|
resolvePromptCacheTtl,
|
|
34
42
|
resolveBedrockPromptCacheTtl,
|
|
35
43
|
supportsBedrockToolCache,
|
|
44
|
+
isSyntheticProviderContextMessage,
|
|
36
45
|
getMessageId,
|
|
37
46
|
makeIsDeferred,
|
|
38
47
|
partitionAndMarkAnthropicToolCache,
|
|
39
48
|
DEFAULT_RETAIN_RECENT_TURNS,
|
|
40
49
|
splitAtRecencyBoundary,
|
|
41
50
|
} from '@/messages';
|
|
42
|
-
import {
|
|
43
|
-
createLangfuseHandler,
|
|
44
|
-
createLangfuseTraceMetadata,
|
|
45
|
-
disposeLangfuseHandler,
|
|
46
|
-
isLangfuseCallbackHandler,
|
|
47
|
-
} from '@/langfuse';
|
|
48
51
|
import {
|
|
49
52
|
resetIfNotEmpty,
|
|
50
53
|
isAnthropicLike,
|
|
51
54
|
isOpenAILike,
|
|
52
55
|
isGoogleLike,
|
|
53
56
|
apportionTokenCounts,
|
|
57
|
+
calculateMaxToolResultChars,
|
|
54
58
|
joinKeys,
|
|
55
59
|
sleep,
|
|
56
60
|
} from '@/utils';
|
|
57
|
-
import {
|
|
58
|
-
getBlindRecoveryBudget,
|
|
59
|
-
planContextOverflowRecovery,
|
|
60
|
-
translateRecoveryBudget,
|
|
61
|
-
} from '@/llm/contextOverflowRecovery';
|
|
62
61
|
import {
|
|
63
62
|
attemptInvoke,
|
|
64
63
|
tryFallbackProviders,
|
|
65
64
|
getFallbackErrorContext,
|
|
66
65
|
getFallbackOverflowCandidates,
|
|
66
|
+
projectMessagesForProvider,
|
|
67
67
|
} from '@/llm/invoke';
|
|
68
|
+
import {
|
|
69
|
+
createLangfuseHandler,
|
|
70
|
+
createLangfuseTraceMetadata,
|
|
71
|
+
disposeLangfuseHandler,
|
|
72
|
+
isLangfuseCallbackHandler,
|
|
73
|
+
} from '@/langfuse';
|
|
74
|
+
import {
|
|
75
|
+
getBlindRecoveryBudget,
|
|
76
|
+
planContextOverflowRecovery,
|
|
77
|
+
translateRecoveryBudget,
|
|
78
|
+
} from '@/llm/contextOverflowRecovery';
|
|
79
|
+
import {
|
|
80
|
+
compactToolContent,
|
|
81
|
+
getToolContentCharLength,
|
|
82
|
+
serializeToolContentBounded,
|
|
83
|
+
} from '@/utils/toolContent';
|
|
68
84
|
import {
|
|
69
85
|
Constants,
|
|
70
86
|
GraphNodeKeys,
|
|
@@ -73,6 +89,10 @@ import {
|
|
|
73
89
|
Providers,
|
|
74
90
|
StepTypes,
|
|
75
91
|
} from '@/common';
|
|
92
|
+
import {
|
|
93
|
+
annotateMessagesForLLM,
|
|
94
|
+
ToolOutputReferenceRegistry,
|
|
95
|
+
} from '@/tools/toolOutputReferences';
|
|
76
96
|
import {
|
|
77
97
|
resolveLangfuseRuntimeScope,
|
|
78
98
|
withLangfuseRuntimeScope,
|
|
@@ -87,7 +107,6 @@ import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
|
|
|
87
107
|
import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
|
|
88
108
|
import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
|
|
89
109
|
import { SubagentExecutor, resolveSubagentConfigs } from '@/tools/subagent';
|
|
90
|
-
import { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
91
110
|
import { partitionAndMarkBedrockToolCache } from '@/llm/bedrock/toolCache';
|
|
92
111
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
93
112
|
import { createCloudflareCodingToolBundle } from '@/tools/cloudflare';
|
|
@@ -124,26 +143,6 @@ function createToolHandlerRegistry(
|
|
|
124
143
|
return registry;
|
|
125
144
|
}
|
|
126
145
|
|
|
127
|
-
/**
|
|
128
|
-
* Start index of the span post-prune formatters can mutate in place: the
|
|
129
|
-
* trailing tool batch plus its owning AI message (artifact formatting touches
|
|
130
|
-
* every tool result after the last AI tool call; Bedrock rewrites the AI
|
|
131
|
-
* message before a trailing tool result). Capped so the usage-snapshot
|
|
132
|
-
* recount stays constant-cost.
|
|
133
|
-
*/
|
|
134
|
-
function trailingMutationStart(messages: BaseMessage[]): number {
|
|
135
|
-
const MAX_SPAN = 16;
|
|
136
|
-
let index = messages.length - 1;
|
|
137
|
-
while (
|
|
138
|
-
index >= 0 &&
|
|
139
|
-
messages[index]?.getType() === 'tool' &&
|
|
140
|
-
messages.length - index < MAX_SPAN
|
|
141
|
-
) {
|
|
142
|
-
index--;
|
|
143
|
-
}
|
|
144
|
-
return Math.max(0, Math.min(index, messages.length - 2));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
146
|
type ReasoningKey = 'reasoning_content' | 'reasoning';
|
|
148
147
|
type ReasoningSummary = { summary?: Array<{ text?: string }> };
|
|
149
148
|
type ReasoningDetail = { type?: string; text?: string };
|
|
@@ -1747,6 +1746,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1747
1746
|
provider: agentContext.provider,
|
|
1748
1747
|
tokenCounter: agentContext.tokenCounter,
|
|
1749
1748
|
maxTokens: agentContext.maxContextTokens,
|
|
1749
|
+
maxToolResultChars: agentContext.maxToolResultChars,
|
|
1750
1750
|
thinkingEnabled: isThinkingEnabled(
|
|
1751
1751
|
agentContext.provider,
|
|
1752
1752
|
agentContext.clientOptions
|
|
@@ -1923,37 +1923,136 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1923
1923
|
}
|
|
1924
1924
|
|
|
1925
1925
|
let finalMessages = messagesToUse;
|
|
1926
|
-
/**
|
|
1927
|
-
*
|
|
1928
|
-
*
|
|
1929
|
-
*
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
1932
|
-
|
|
1933
|
-
let
|
|
1934
|
-
|
|
1935
|
-
|
|
1926
|
+
/**
|
|
1927
|
+
* Keep the pruner's provider-grounded aggregate as the authoritative
|
|
1928
|
+
* baseline, then attribute it across retained messages. Provider
|
|
1929
|
+
* transforms can shrink one message while expanding or adding another;
|
|
1930
|
+
* per-origin accounting prevents that unrelated shrink from canceling
|
|
1931
|
+
* the expansion. Raw counts are frozen before in-place formatters run.
|
|
1932
|
+
*/
|
|
1933
|
+
let providerMessageBaseline:
|
|
1934
|
+
| Array<{ rawTokens: number; accountingWeight: number }>
|
|
1935
|
+
| undefined;
|
|
1936
|
+
const providerMessageOrigins = new WeakMap<BaseMessage, number>();
|
|
1936
1937
|
if (contextUsage != null && agentContext.tokenCounter != null) {
|
|
1937
|
-
|
|
1938
|
-
for (
|
|
1939
|
-
|
|
1938
|
+
const sourceIndices = new WeakMap<BaseMessage, number>();
|
|
1939
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1940
|
+
sourceIndices.set(messages[i], i);
|
|
1940
1941
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1942
|
+
providerMessageBaseline = messagesToUse.map((message, index) => {
|
|
1943
|
+
const rawTokens = agentContext.tokenCounter!(message);
|
|
1944
|
+
const sourceIndex = sourceIndices.get(message);
|
|
1945
|
+
const indexedTokens =
|
|
1946
|
+
sourceIndex != null
|
|
1947
|
+
? agentContext.indexTokenCountMap[sourceIndex]
|
|
1948
|
+
: undefined;
|
|
1949
|
+
const accountingWeight =
|
|
1950
|
+
indexedTokens != null &&
|
|
1951
|
+
Number.isFinite(indexedTokens) &&
|
|
1952
|
+
indexedTokens >= 0
|
|
1953
|
+
? indexedTokens
|
|
1954
|
+
: rawTokens;
|
|
1955
|
+
if (!providerMessageOrigins.has(message)) {
|
|
1956
|
+
providerMessageOrigins.set(message, index);
|
|
1957
|
+
}
|
|
1958
|
+
return { rawTokens, accountingWeight };
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
const getProviderMessageOriginKey = (
|
|
1963
|
+
message: BaseMessage
|
|
1964
|
+
): string | undefined => {
|
|
1965
|
+
const type = message.getType();
|
|
1966
|
+
if (
|
|
1967
|
+
message instanceof ToolMessage &&
|
|
1968
|
+
typeof message.tool_call_id === 'string' &&
|
|
1969
|
+
message.tool_call_id.length > 0
|
|
1970
|
+
) {
|
|
1971
|
+
return `tool:call:${message.tool_call_id}`;
|
|
1972
|
+
}
|
|
1973
|
+
if (typeof message.id === 'string' && message.id.length > 0) {
|
|
1974
|
+
return `${type}:id:${message.id}`;
|
|
1975
|
+
}
|
|
1976
|
+
return undefined;
|
|
1977
|
+
};
|
|
1978
|
+
|
|
1979
|
+
/**
|
|
1980
|
+
* Provider projections clone messages. Preserve their baseline origin
|
|
1981
|
+
* without writing tracking metadata onto the wire. Synthetic fold
|
|
1982
|
+
* messages intentionally remain unattributed and are charged in full.
|
|
1983
|
+
*/
|
|
1984
|
+
const trackProviderMessageOrigins = (
|
|
1985
|
+
before: BaseMessage[],
|
|
1986
|
+
after: BaseMessage[]
|
|
1987
|
+
): BaseMessage[] => {
|
|
1988
|
+
if (providerMessageBaseline == null || before === after) {
|
|
1989
|
+
return after;
|
|
1990
|
+
}
|
|
1991
|
+
if (before.length === after.length) {
|
|
1992
|
+
for (let i = 0; i < after.length; i++) {
|
|
1993
|
+
const origin = providerMessageOrigins.get(before[i]);
|
|
1994
|
+
if (
|
|
1995
|
+
origin != null &&
|
|
1996
|
+
!providerMessageOrigins.has(after[i]) &&
|
|
1997
|
+
before[i].getType() === after[i].getType() &&
|
|
1998
|
+
!isSyntheticProviderContextMessage(after[i])
|
|
1999
|
+
) {
|
|
2000
|
+
providerMessageOrigins.set(after[i], origin);
|
|
1949
2001
|
}
|
|
1950
2002
|
}
|
|
2003
|
+
return after;
|
|
1951
2004
|
}
|
|
1952
|
-
|
|
2005
|
+
|
|
2006
|
+
const keyedOrigins = new Map<string, number | null>();
|
|
2007
|
+
for (const message of before) {
|
|
2008
|
+
const origin = providerMessageOrigins.get(message);
|
|
2009
|
+
const key = getProviderMessageOriginKey(message);
|
|
2010
|
+
if (origin == null || key == null) {
|
|
2011
|
+
continue;
|
|
2012
|
+
}
|
|
2013
|
+
keyedOrigins.set(key, keyedOrigins.has(key) ? null : origin);
|
|
2014
|
+
}
|
|
2015
|
+
for (const message of after) {
|
|
2016
|
+
if (
|
|
2017
|
+
providerMessageOrigins.has(message) ||
|
|
2018
|
+
isSyntheticProviderContextMessage(message)
|
|
2019
|
+
) {
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
const key = getProviderMessageOriginKey(message);
|
|
2023
|
+
const origin = key != null ? keyedOrigins.get(key) : undefined;
|
|
2024
|
+
if (origin != null) {
|
|
2025
|
+
providerMessageOrigins.set(message, origin);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
return after;
|
|
2029
|
+
};
|
|
2030
|
+
|
|
1953
2031
|
if (agentContext.useLegacyContent) {
|
|
1954
|
-
|
|
2032
|
+
const before = finalMessages;
|
|
2033
|
+
finalMessages = trackProviderMessageOrigins(
|
|
2034
|
+
before,
|
|
2035
|
+
formatContentStrings(before)
|
|
2036
|
+
);
|
|
1955
2037
|
}
|
|
1956
2038
|
|
|
2039
|
+
const maxProviderToolResultChars =
|
|
2040
|
+
agentContext.maxToolResultChars ??
|
|
2041
|
+
calculateMaxToolResultChars(agentContext.maxContextTokens);
|
|
2042
|
+
const beforeToolStreamProjection = finalMessages;
|
|
2043
|
+
finalMessages = trackProviderMessageOrigins(
|
|
2044
|
+
beforeToolStreamProjection,
|
|
2045
|
+
projectToolStreamContentForProvider(beforeToolStreamProjection)
|
|
2046
|
+
);
|
|
2047
|
+
const beforeToolInputProjection = finalMessages;
|
|
2048
|
+
finalMessages = trackProviderMessageOrigins(
|
|
2049
|
+
beforeToolInputProjection,
|
|
2050
|
+
projectToolCallInputs(
|
|
2051
|
+
beforeToolInputProjection,
|
|
2052
|
+
calculateMaxToolCallInputChars(agentContext.maxContextTokens)
|
|
2053
|
+
)
|
|
2054
|
+
);
|
|
2055
|
+
|
|
1957
2056
|
const lastMessageX =
|
|
1958
2057
|
finalMessages.length >= 2
|
|
1959
2058
|
? finalMessages[finalMessages.length - 2]
|
|
@@ -1979,58 +2078,368 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1979
2078
|
trimmed.length > 0 ? [{ type: 'text' as const, text: trimmed }] : '';
|
|
1980
2079
|
}
|
|
1981
2080
|
|
|
2081
|
+
const localProviderOverflowMeasurements = new WeakMap<
|
|
2082
|
+
object,
|
|
2083
|
+
{
|
|
2084
|
+
contextBudget: number;
|
|
2085
|
+
estimatedPromptTokens: number;
|
|
2086
|
+
}
|
|
2087
|
+
>();
|
|
2088
|
+
const measureProviderPayload = (
|
|
2089
|
+
candidate: BaseMessage[],
|
|
2090
|
+
contextBudgetOverride?: number,
|
|
2091
|
+
forceRawRecount = false
|
|
2092
|
+
): {
|
|
2093
|
+
fits: boolean;
|
|
2094
|
+
projectedMessageTokens?: number;
|
|
2095
|
+
availableMessageTokens?: number;
|
|
2096
|
+
contextBudget?: number;
|
|
2097
|
+
effectiveInstructionTokens?: number;
|
|
2098
|
+
} => {
|
|
2099
|
+
const contextBudget =
|
|
2100
|
+
contextBudgetOverride ?? contextUsage?.contextBudget;
|
|
2101
|
+
const effectiveInstructionTokens =
|
|
2102
|
+
contextUsage?.effectiveInstructionTokens ??
|
|
2103
|
+
(forceRawRecount ? agentContext.instructionTokens : undefined);
|
|
2104
|
+
if (
|
|
2105
|
+
agentContext.tokenCounter == null ||
|
|
2106
|
+
contextBudget == null ||
|
|
2107
|
+
effectiveInstructionTokens == null
|
|
2108
|
+
) {
|
|
2109
|
+
return { fits: true };
|
|
2110
|
+
}
|
|
2111
|
+
const availableMessageTokens = Math.max(
|
|
2112
|
+
0,
|
|
2113
|
+
contextBudget - effectiveInstructionTokens
|
|
2114
|
+
);
|
|
2115
|
+
let usageRatio =
|
|
2116
|
+
agentContext.calibrationRatio > 0 ? agentContext.calibrationRatio : 1;
|
|
2117
|
+
if (
|
|
2118
|
+
contextUsage?.calibrationRatio != null &&
|
|
2119
|
+
contextUsage.calibrationRatio > 0
|
|
2120
|
+
) {
|
|
2121
|
+
usageRatio = contextUsage.calibrationRatio;
|
|
2122
|
+
}
|
|
2123
|
+
if (forceRawRecount) {
|
|
2124
|
+
usageRatio = Math.max(1, usageRatio);
|
|
2125
|
+
}
|
|
2126
|
+
const baselineRemaining = contextUsage?.remainingContextTokens;
|
|
2127
|
+
const accountedMessageTokens =
|
|
2128
|
+
!forceRawRecount &&
|
|
2129
|
+
providerMessageBaseline != null &&
|
|
2130
|
+
baselineRemaining != null &&
|
|
2131
|
+
Number.isFinite(baselineRemaining)
|
|
2132
|
+
? availableMessageTokens -
|
|
2133
|
+
Math.min(availableMessageTokens, Math.max(0, baselineRemaining))
|
|
2134
|
+
: undefined;
|
|
2135
|
+
|
|
2136
|
+
let projectedMessageTokens: number;
|
|
2137
|
+
if (accountedMessageTokens != null && providerMessageBaseline != null) {
|
|
2138
|
+
const replyPrimerTokens = Math.round(
|
|
2139
|
+
REPLY_PRIMER_TOKENS * usageRatio
|
|
2140
|
+
);
|
|
2141
|
+
const rawWeights: Record<string, number> = {};
|
|
2142
|
+
let totalWeight = 0;
|
|
2143
|
+
for (let i = 0; i < providerMessageBaseline.length; i++) {
|
|
2144
|
+
const weight = providerMessageBaseline[i].accountingWeight;
|
|
2145
|
+
rawWeights[i] = weight;
|
|
2146
|
+
totalWeight += weight;
|
|
2147
|
+
}
|
|
2148
|
+
const attributableTokens =
|
|
2149
|
+
totalWeight > 0
|
|
2150
|
+
? Math.min(
|
|
2151
|
+
Math.max(0, accountedMessageTokens - replyPrimerTokens),
|
|
2152
|
+
Math.round(totalWeight * usageRatio)
|
|
2153
|
+
)
|
|
2154
|
+
: 0;
|
|
2155
|
+
const apportionedTokens =
|
|
2156
|
+
totalWeight > 0
|
|
2157
|
+
? apportionTokenCounts(
|
|
2158
|
+
rawWeights,
|
|
2159
|
+
attributableTokens / totalWeight,
|
|
2160
|
+
attributableTokens
|
|
2161
|
+
)
|
|
2162
|
+
: {};
|
|
2163
|
+
const attributedByOrigin = providerMessageBaseline.map(
|
|
2164
|
+
(_, origin) => apportionedTokens[origin] || 0
|
|
2165
|
+
);
|
|
2166
|
+
projectedMessageTokens = Math.max(
|
|
2167
|
+
replyPrimerTokens,
|
|
2168
|
+
accountedMessageTokens - attributableTokens
|
|
2169
|
+
);
|
|
2170
|
+
let newRawTokens = 0;
|
|
2171
|
+
const usedOrigins = new Set<number>();
|
|
2172
|
+
for (const message of candidate) {
|
|
2173
|
+
const rawTokens = agentContext.tokenCounter(message);
|
|
2174
|
+
const origin = providerMessageOrigins.get(message);
|
|
2175
|
+
if (origin == null || usedOrigins.has(origin)) {
|
|
2176
|
+
newRawTokens += rawTokens;
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
usedOrigins.add(origin);
|
|
2180
|
+
projectedMessageTokens += Math.max(
|
|
2181
|
+
0,
|
|
2182
|
+
attributedByOrigin[origin] +
|
|
2183
|
+
Math.round(
|
|
2184
|
+
(rawTokens - providerMessageBaseline[origin].rawTokens) *
|
|
2185
|
+
usageRatio
|
|
2186
|
+
)
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
projectedMessageTokens += Math.round(newRawTokens * usageRatio);
|
|
2190
|
+
} else {
|
|
2191
|
+
let rawTokens = REPLY_PRIMER_TOKENS;
|
|
2192
|
+
for (const message of candidate) {
|
|
2193
|
+
rawTokens += agentContext.tokenCounter(message);
|
|
2194
|
+
}
|
|
2195
|
+
projectedMessageTokens = Math.round(rawTokens * usageRatio);
|
|
2196
|
+
}
|
|
2197
|
+
return {
|
|
2198
|
+
fits: projectedMessageTokens <= availableMessageTokens,
|
|
2199
|
+
projectedMessageTokens,
|
|
2200
|
+
availableMessageTokens,
|
|
2201
|
+
contextBudget,
|
|
2202
|
+
effectiveInstructionTokens,
|
|
2203
|
+
};
|
|
2204
|
+
};
|
|
2205
|
+
|
|
2206
|
+
const createProviderPayloadOverflowError = ({
|
|
2207
|
+
projection,
|
|
2208
|
+
provider,
|
|
2209
|
+
info,
|
|
2210
|
+
}: {
|
|
2211
|
+
projection: ReturnType<typeof measureProviderPayload>;
|
|
2212
|
+
provider?: Providers;
|
|
2213
|
+
info: string;
|
|
2214
|
+
}): ContextOverflowError => {
|
|
2215
|
+
const error = new ContextOverflowError(
|
|
2216
|
+
JSON.stringify({
|
|
2217
|
+
type: 'final_context_overflow',
|
|
2218
|
+
info,
|
|
2219
|
+
provider,
|
|
2220
|
+
projectedMessageTokens: projection.projectedMessageTokens,
|
|
2221
|
+
availableMessageTokens: projection.availableMessageTokens,
|
|
2222
|
+
})
|
|
2223
|
+
);
|
|
2224
|
+
if (
|
|
2225
|
+
projection.projectedMessageTokens != null &&
|
|
2226
|
+
projection.contextBudget != null &&
|
|
2227
|
+
projection.effectiveInstructionTokens != null
|
|
2228
|
+
) {
|
|
2229
|
+
localProviderOverflowMeasurements.set(error, {
|
|
2230
|
+
contextBudget: projection.contextBudget,
|
|
2231
|
+
estimatedPromptTokens:
|
|
2232
|
+
projection.projectedMessageTokens +
|
|
2233
|
+
projection.effectiveInstructionTokens,
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
return error;
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
const applyProviderMessageTransforms = (
|
|
2240
|
+
candidate: BaseMessage[]
|
|
2241
|
+
): BaseMessage[] => {
|
|
2242
|
+
let transformed = candidate;
|
|
2243
|
+
if (
|
|
2244
|
+
isThinkingEnabled(agentContext.provider, agentContext.clientOptions)
|
|
2245
|
+
) {
|
|
2246
|
+
/**
|
|
2247
|
+
* Current-run AI messages may validly omit a thinking block. The
|
|
2248
|
+
* boundary prevents them from being mistaken for foreign history.
|
|
2249
|
+
*/
|
|
2250
|
+
const before = transformed;
|
|
2251
|
+
transformed = trackProviderMessageOrigins(
|
|
2252
|
+
before,
|
|
2253
|
+
ensureThinkingBlockInMessages(
|
|
2254
|
+
before,
|
|
2255
|
+
agentContext.provider,
|
|
2256
|
+
config,
|
|
2257
|
+
this.startIndex
|
|
2258
|
+
)
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
/**
|
|
2263
|
+
* Tool-less destinations cannot send inherited tool blocks without a
|
|
2264
|
+
* tool schema, so fold those interactions into provider-valid content.
|
|
2265
|
+
*/
|
|
2266
|
+
if (toolsForBinding == null || toolsForBinding.length === 0) {
|
|
2267
|
+
const before = transformed;
|
|
2268
|
+
transformed = trackProviderMessageOrigins(
|
|
2269
|
+
before,
|
|
2270
|
+
foldToolBlocksForToollessAgent(before, config)
|
|
2271
|
+
);
|
|
2272
|
+
if (agentContext.useLegacyContent) {
|
|
2273
|
+
const beforeLegacyFormat = transformed;
|
|
2274
|
+
transformed = trackProviderMessageOrigins(
|
|
2275
|
+
beforeLegacyFormat,
|
|
2276
|
+
formatContentStrings(beforeLegacyFormat)
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
return transformed;
|
|
2281
|
+
};
|
|
2282
|
+
|
|
2283
|
+
const toolOutputRegistry = this.getOrCreateToolOutputRegistry();
|
|
2284
|
+
const providerRunId = config.configurable?.run_id as string | undefined;
|
|
2285
|
+
const projectProviderReferences = (
|
|
2286
|
+
candidate: BaseMessage[]
|
|
2287
|
+
): BaseMessage[] =>
|
|
2288
|
+
trackProviderMessageOrigins(
|
|
2289
|
+
candidate,
|
|
2290
|
+
annotateMessagesForLLM(candidate, toolOutputRegistry, providerRunId)
|
|
2291
|
+
);
|
|
2292
|
+
|
|
2293
|
+
const compactSyntheticProviderContext = (
|
|
2294
|
+
candidate: BaseMessage[]
|
|
2295
|
+
): BaseMessage[] => {
|
|
2296
|
+
const synthetic: Array<{
|
|
2297
|
+
index: number;
|
|
2298
|
+
message: HumanMessage;
|
|
2299
|
+
chars: number;
|
|
2300
|
+
}> = [];
|
|
2301
|
+
for (let i = 0; i < candidate.length; i++) {
|
|
2302
|
+
const message = candidate[i];
|
|
2303
|
+
if (
|
|
2304
|
+
!(message instanceof HumanMessage) ||
|
|
2305
|
+
!isSyntheticProviderContextMessage(message)
|
|
2306
|
+
) {
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
const content = message.content;
|
|
2310
|
+
synthetic.push({
|
|
2311
|
+
index: i,
|
|
2312
|
+
message,
|
|
2313
|
+
chars: getToolContentCharLength(content),
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
if (synthetic.length === 0) {
|
|
2317
|
+
return candidate;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
const buildCandidate = (scale: number): BaseMessage[] => {
|
|
2321
|
+
const compacted = [...candidate];
|
|
2322
|
+
for (const { index, message, chars } of synthetic) {
|
|
2323
|
+
const content = compactToolContent(
|
|
2324
|
+
message.content,
|
|
2325
|
+
Math.floor(chars * scale)
|
|
2326
|
+
).content;
|
|
2327
|
+
compacted[index] = new HumanMessage({
|
|
2328
|
+
content,
|
|
2329
|
+
id: message.id,
|
|
2330
|
+
name: message.name,
|
|
2331
|
+
additional_kwargs: message.additional_kwargs,
|
|
2332
|
+
response_metadata: message.response_metadata,
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
return compacted;
|
|
2336
|
+
};
|
|
2337
|
+
|
|
2338
|
+
let best = buildCandidate(0);
|
|
2339
|
+
if (!measureProviderPayload(best).fits) {
|
|
2340
|
+
return candidate;
|
|
2341
|
+
}
|
|
2342
|
+
let low = 0;
|
|
2343
|
+
let high = 1;
|
|
2344
|
+
for (let i = 0; i < 12; i++) {
|
|
2345
|
+
const scale = (low + high) / 2;
|
|
2346
|
+
const attempt = buildCandidate(scale);
|
|
2347
|
+
if (measureProviderPayload(attempt).fits) {
|
|
2348
|
+
best = attempt;
|
|
2349
|
+
low = scale;
|
|
2350
|
+
} else {
|
|
2351
|
+
high = scale;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return best;
|
|
2355
|
+
};
|
|
2356
|
+
|
|
2357
|
+
let artifactBaseMessages: BaseMessage[] | undefined;
|
|
1982
2358
|
if (lastMessageY instanceof ToolMessage) {
|
|
2359
|
+
let artifactCandidate = finalMessages;
|
|
1983
2360
|
if (anthropicLike) {
|
|
1984
|
-
|
|
2361
|
+
artifactCandidate = trackProviderMessageOrigins(
|
|
2362
|
+
finalMessages,
|
|
2363
|
+
projectAnthropicArtifactContent(
|
|
2364
|
+
finalMessages,
|
|
2365
|
+
maxProviderToolResultChars
|
|
2366
|
+
)
|
|
2367
|
+
);
|
|
1985
2368
|
} else if (
|
|
1986
2369
|
(isOpenAILike(agentContext.provider) &&
|
|
1987
2370
|
agentContext.provider !== Providers.DEEPSEEK) ||
|
|
1988
2371
|
isGoogleLike(agentContext.provider)
|
|
1989
2372
|
) {
|
|
1990
|
-
|
|
2373
|
+
artifactCandidate = trackProviderMessageOrigins(
|
|
2374
|
+
finalMessages,
|
|
2375
|
+
projectArtifactPayload(finalMessages, maxProviderToolResultChars)
|
|
2376
|
+
);
|
|
1991
2377
|
}
|
|
1992
|
-
}
|
|
1993
2378
|
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2379
|
+
if (artifactCandidate !== finalMessages) {
|
|
2380
|
+
const projection = measureProviderPayload(artifactCandidate);
|
|
2381
|
+
if (projection.fits) {
|
|
2382
|
+
artifactBaseMessages = finalMessages;
|
|
2383
|
+
finalMessages = artifactCandidate;
|
|
2384
|
+
} else {
|
|
2385
|
+
emitAgentLog(
|
|
2386
|
+
config,
|
|
2387
|
+
'warn',
|
|
2388
|
+
'graph',
|
|
2389
|
+
'Artifact payload omitted because it exceeds the remaining context budget',
|
|
2390
|
+
{
|
|
2391
|
+
projectedMessageTokens: projection.projectedMessageTokens,
|
|
2392
|
+
availableMessageTokens: projection.availableMessageTokens,
|
|
2393
|
+
},
|
|
2394
|
+
{ runId: this.runId, agentId }
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2013
2398
|
}
|
|
2014
2399
|
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2400
|
+
finalMessages = projectProviderReferences(
|
|
2401
|
+
applyProviderMessageTransforms(finalMessages)
|
|
2402
|
+
);
|
|
2403
|
+
let finalProjection = measureProviderPayload(finalMessages);
|
|
2404
|
+
if (artifactBaseMessages != null) {
|
|
2405
|
+
if (!finalProjection.fits) {
|
|
2406
|
+
finalMessages = projectProviderReferences(
|
|
2407
|
+
applyProviderMessageTransforms(artifactBaseMessages)
|
|
2408
|
+
);
|
|
2409
|
+
finalProjection = measureProviderPayload(finalMessages);
|
|
2410
|
+
emitAgentLog(
|
|
2411
|
+
config,
|
|
2412
|
+
'warn',
|
|
2413
|
+
'graph',
|
|
2414
|
+
'Artifact payload omitted after final provider formatting exceeded the remaining context budget',
|
|
2415
|
+
{
|
|
2416
|
+
projectedMessageTokens: finalProjection.projectedMessageTokens,
|
|
2417
|
+
availableMessageTokens: finalProjection.availableMessageTokens,
|
|
2418
|
+
},
|
|
2419
|
+
{ runId: this.runId, agentId }
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (!finalProjection.fits) {
|
|
2424
|
+
const compacted = compactSyntheticProviderContext(finalMessages);
|
|
2425
|
+
if (compacted !== finalMessages) {
|
|
2426
|
+
finalMessages = compacted;
|
|
2427
|
+
finalProjection = measureProviderPayload(finalMessages);
|
|
2428
|
+
emitAgentLog(
|
|
2429
|
+
config,
|
|
2430
|
+
finalProjection.fits ? 'warn' : 'error',
|
|
2431
|
+
'graph',
|
|
2432
|
+
finalProjection.fits
|
|
2433
|
+
? 'Synthetic provider context compacted to fit the final payload budget'
|
|
2434
|
+
: 'Final provider payload still exceeds budget after synthetic context compaction',
|
|
2435
|
+
{
|
|
2436
|
+
projectedMessageTokens: finalProjection.projectedMessageTokens,
|
|
2437
|
+
availableMessageTokens: finalProjection.availableMessageTokens,
|
|
2438
|
+
},
|
|
2439
|
+
{ runId: this.runId, agentId }
|
|
2440
|
+
);
|
|
2031
2441
|
}
|
|
2032
2442
|
}
|
|
2033
|
-
|
|
2034
2443
|
// Determine the prompt-cache strategy up front. Two distinct facts:
|
|
2035
2444
|
//
|
|
2036
2445
|
// `providerPromptCacheEnabled` — prompt caching is on for this provider
|
|
@@ -2083,7 +2492,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2083
2492
|
providerPromptCacheEnabled);
|
|
2084
2493
|
if (needsOrphanSanitize) {
|
|
2085
2494
|
const beforeSanitize = finalMessages.length;
|
|
2086
|
-
|
|
2495
|
+
const beforeSanitizeMessages = finalMessages;
|
|
2496
|
+
finalMessages = trackProviderMessageOrigins(
|
|
2497
|
+
beforeSanitizeMessages,
|
|
2498
|
+
sanitizeOrphanToolBlocks(beforeSanitizeMessages, (source, clone) => {
|
|
2499
|
+
const origin = providerMessageOrigins.get(source);
|
|
2500
|
+
if (origin != null) {
|
|
2501
|
+
providerMessageOrigins.set(clone, origin);
|
|
2502
|
+
}
|
|
2503
|
+
})
|
|
2504
|
+
);
|
|
2087
2505
|
if (finalMessages.length !== beforeSanitize) {
|
|
2088
2506
|
emitAgentLog(
|
|
2089
2507
|
config,
|
|
@@ -2113,20 +2531,24 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2113
2531
|
(anthropicPromptCacheEnabled || openRouterPromptCacheEnabled) &&
|
|
2114
2532
|
!agentContext.systemRunnable
|
|
2115
2533
|
) {
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2534
|
+
const beforeCacheControl = finalMessages;
|
|
2535
|
+
finalMessages = trackProviderMessageOrigins(
|
|
2536
|
+
beforeCacheControl,
|
|
2537
|
+
addTailCacheControl<BaseMessage>(
|
|
2538
|
+
beforeCacheControl,
|
|
2539
|
+
resolvePromptCacheTtl(
|
|
2540
|
+
anthropicPromptCacheEnabled
|
|
2541
|
+
? (
|
|
2542
|
+
agentContext.clientOptions as
|
|
2543
|
+
| t.AnthropicClientOptions
|
|
2544
|
+
| undefined
|
|
2545
|
+
)?.promptCacheTtl
|
|
2546
|
+
: (
|
|
2547
|
+
agentContext.clientOptions as
|
|
2548
|
+
| t.ProviderOptionsMap[Providers.OPENROUTER]
|
|
2549
|
+
| undefined
|
|
2550
|
+
)?.promptCacheTtl
|
|
2551
|
+
)
|
|
2130
2552
|
)
|
|
2131
2553
|
);
|
|
2132
2554
|
} else if (bedrockPromptCacheEnabled) {
|
|
@@ -2135,15 +2557,46 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2135
2557
|
| undefined;
|
|
2136
2558
|
// Non-Claude models (Nova) reject the extended 1h TTL, so resolve it
|
|
2137
2559
|
// against the model — message/system caching stays on, clamped to 5m.
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2560
|
+
const beforeCacheControl = finalMessages;
|
|
2561
|
+
finalMessages = trackProviderMessageOrigins(
|
|
2562
|
+
beforeCacheControl,
|
|
2563
|
+
addBedrockTailCacheControl<BaseMessage>(
|
|
2564
|
+
beforeCacheControl,
|
|
2565
|
+
resolveBedrockPromptCacheTtl(
|
|
2566
|
+
bedrockOptions?.promptCacheTtl,
|
|
2567
|
+
(bedrockOptions as { model?: string } | undefined)?.model
|
|
2568
|
+
)
|
|
2143
2569
|
)
|
|
2144
2570
|
);
|
|
2145
2571
|
}
|
|
2146
2572
|
|
|
2573
|
+
const fallbackBaseMessages = finalMessages;
|
|
2574
|
+
const beforeFinalProviderProjection = fallbackBaseMessages;
|
|
2575
|
+
finalMessages = trackProviderMessageOrigins(
|
|
2576
|
+
beforeFinalProviderProjection,
|
|
2577
|
+
projectMessagesForProvider({
|
|
2578
|
+
model: (this.overrideModel ?? model) as t.ChatModel,
|
|
2579
|
+
messages: beforeFinalProviderProjection,
|
|
2580
|
+
provider: agentContext.provider,
|
|
2581
|
+
maxToolResultChars: maxProviderToolResultChars,
|
|
2582
|
+
callOptions: config,
|
|
2583
|
+
})
|
|
2584
|
+
);
|
|
2585
|
+
|
|
2586
|
+
/**
|
|
2587
|
+
* Prompt-cache placement and orphan sanitization are provider-wire
|
|
2588
|
+
* transforms too. Re-measure after both so no content added after the
|
|
2589
|
+
* earlier artifact/synthetic compaction decision can bypass the guard.
|
|
2590
|
+
*/
|
|
2591
|
+
finalProjection = measureProviderPayload(finalMessages);
|
|
2592
|
+
const preInvokeContextOverflowError = !finalProjection.fits
|
|
2593
|
+
? createProviderPayloadOverflowError({
|
|
2594
|
+
projection: finalProjection,
|
|
2595
|
+
provider: agentContext.provider,
|
|
2596
|
+
info: 'Provider message formatting exceeded the context budget and no safe synthetic-context compaction could make it fit.',
|
|
2597
|
+
})
|
|
2598
|
+
: undefined;
|
|
2599
|
+
|
|
2147
2600
|
if (
|
|
2148
2601
|
agentContext.lastStreamCall != null &&
|
|
2149
2602
|
agentContext.streamBuffer != null
|
|
@@ -2219,66 +2672,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2219
2672
|
|
|
2220
2673
|
/** Past the empty-prompt guard — a model call is now guaranteed */
|
|
2221
2674
|
if (contextUsage != null) {
|
|
2222
|
-
const usageRatio =
|
|
2223
|
-
contextUsage.calibrationRatio != null &&
|
|
2224
|
-
contextUsage.calibrationRatio > 0
|
|
2225
|
-
? contextUsage.calibrationRatio
|
|
2226
|
-
: 1;
|
|
2227
2675
|
if (
|
|
2228
|
-
|
|
2229
|
-
|
|
2676
|
+
finalProjection.projectedMessageTokens != null &&
|
|
2677
|
+
finalProjection.availableMessageTokens != null
|
|
2230
2678
|
) {
|
|
2231
|
-
/** Post-prune formatting restructured the payload (e.g. thinking
|
|
2232
|
-
* placeholder collapse, orphan drops) — recount so the gauge
|
|
2233
|
-
* reflects what is actually sent */
|
|
2234
|
-
let rawTokens = 0;
|
|
2235
|
-
for (const message of finalMessages) {
|
|
2236
|
-
rawTokens += agentContext.tokenCounter(message);
|
|
2237
|
-
}
|
|
2238
2679
|
contextUsage.breakdown.messageCount = finalMessages.length;
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
0,
|
|
2245
|
-
contextUsage.contextBudget -
|
|
2246
|
-
contextUsage.effectiveInstructionTokens -
|
|
2247
|
-
Math.round(rawTokens * usageRatio)
|
|
2248
|
-
);
|
|
2249
|
-
}
|
|
2250
|
-
} else if (
|
|
2251
|
-
preFormatTailTokens != null &&
|
|
2252
|
-
agentContext.tokenCounter != null &&
|
|
2253
|
-
contextUsage.remainingContextTokens != null
|
|
2254
|
-
) {
|
|
2255
|
-
/** Same-length formatting can still mutate in place — the trailing
|
|
2256
|
-
* tool batch (artifacts, Bedrock rewrites) and any legacy-converted
|
|
2257
|
-
* messages before it — adjust remaining by the calibrated delta */
|
|
2258
|
-
let postFormatTailTokens = 0;
|
|
2259
|
-
for (const message of finalMessages.slice(tailStart)) {
|
|
2260
|
-
postFormatTailTokens += agentContext.tokenCounter(message);
|
|
2261
|
-
}
|
|
2262
|
-
let formatDelta = postFormatTailTokens - preFormatTailTokens;
|
|
2263
|
-
if (legacyIndices != null && legacyIndices.length > 0) {
|
|
2264
|
-
let postFormatLegacyTokens = 0;
|
|
2265
|
-
for (const index of legacyIndices) {
|
|
2266
|
-
postFormatLegacyTokens += agentContext.tokenCounter(
|
|
2267
|
-
finalMessages[index]
|
|
2268
|
-
);
|
|
2269
|
-
}
|
|
2270
|
-
formatDelta += postFormatLegacyTokens - preFormatLegacyTokens;
|
|
2271
|
-
}
|
|
2272
|
-
if (formatDelta !== 0) {
|
|
2273
|
-
contextUsage.remainingContextTokens = Math.max(
|
|
2274
|
-
0,
|
|
2275
|
-
Math.min(
|
|
2276
|
-
contextUsage.contextBudget ?? Number.MAX_SAFE_INTEGER,
|
|
2277
|
-
contextUsage.remainingContextTokens -
|
|
2278
|
-
Math.round(formatDelta * usageRatio)
|
|
2279
|
-
)
|
|
2280
|
-
);
|
|
2281
|
-
}
|
|
2680
|
+
contextUsage.remainingContextTokens = Math.max(
|
|
2681
|
+
0,
|
|
2682
|
+
finalProjection.availableMessageTokens -
|
|
2683
|
+
finalProjection.projectedMessageTokens
|
|
2684
|
+
);
|
|
2282
2685
|
}
|
|
2283
2686
|
syncBudgetDerivedFields(contextUsage);
|
|
2284
2687
|
/** Awaited so async host handlers receive the pre-invoke snapshot
|
|
@@ -2346,6 +2749,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2346
2749
|
const metadata = config.metadata as Record<string, unknown>;
|
|
2347
2750
|
|
|
2348
2751
|
try {
|
|
2752
|
+
if (preInvokeContextOverflowError != null) {
|
|
2753
|
+
throw preInvokeContextOverflowError;
|
|
2754
|
+
}
|
|
2349
2755
|
result = await withLangfuseRuntimeScope(
|
|
2350
2756
|
resolveLangfuseRuntimeScope({
|
|
2351
2757
|
runLangfuse: this.langfuse,
|
|
@@ -2383,15 +2789,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2383
2789
|
*/
|
|
2384
2790
|
const estimatedPromptTokens = getEstimatedPromptTokens(contextUsage);
|
|
2385
2791
|
|
|
2386
|
-
/**
|
|
2387
|
-
* A previous correction that left the prompt no smaller proves this
|
|
2388
|
-
* state has nothing left to compact — an emptied message list whose
|
|
2389
|
-
* content rides along in an injected summary, for instance. Measuring
|
|
2390
|
-
* that beats trying to predict every such configuration.
|
|
2391
|
-
*/
|
|
2392
|
-
const recoveryStalled = agentContext.overflowRecoveryStalled(
|
|
2393
|
-
estimatedPromptTokens
|
|
2394
|
-
);
|
|
2395
2792
|
const canSummarizeOverflow =
|
|
2396
2793
|
agentContext.summarizationEnabled === true &&
|
|
2397
2794
|
splitAtRecencyBoundary(messages, {
|
|
@@ -2402,13 +2799,36 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2402
2799
|
tokenCounter: agentContext.tokenCounter,
|
|
2403
2800
|
}).head.length > 0;
|
|
2404
2801
|
|
|
2802
|
+
const getLocalProviderOverflowMeasurement = (
|
|
2803
|
+
error: unknown
|
|
2804
|
+
):
|
|
2805
|
+
| {
|
|
2806
|
+
contextBudget: number;
|
|
2807
|
+
estimatedPromptTokens: number;
|
|
2808
|
+
}
|
|
2809
|
+
| undefined =>
|
|
2810
|
+
typeof error === 'object' && error !== null
|
|
2811
|
+
? localProviderOverflowMeasurements.get(error)
|
|
2812
|
+
: undefined;
|
|
2813
|
+
|
|
2814
|
+
const getRecoveryPromptEstimate = (
|
|
2815
|
+
error: unknown,
|
|
2816
|
+
fallbackContext?: FallbackErrorContext
|
|
2817
|
+
): number | undefined => {
|
|
2818
|
+
const resolvedFallbackContext =
|
|
2819
|
+
fallbackContext ?? getFallbackErrorContext(error);
|
|
2820
|
+
return (
|
|
2821
|
+
getLocalProviderOverflowMeasurement(error)?.estimatedPromptTokens ??
|
|
2822
|
+
(resolvedFallbackContext == null
|
|
2823
|
+
? estimatedPromptTokens
|
|
2824
|
+
: undefined)
|
|
2825
|
+
);
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2405
2828
|
const planRecovery = (
|
|
2406
2829
|
error: unknown,
|
|
2407
2830
|
attributedFallbackContext?: FallbackErrorContext
|
|
2408
2831
|
): OverflowRecoveryPlan | null => {
|
|
2409
|
-
if (recoveryStalled) {
|
|
2410
|
-
return null;
|
|
2411
|
-
}
|
|
2412
2832
|
/**
|
|
2413
2833
|
* When the rejection came from a fallback, plan against *that*
|
|
2414
2834
|
* client: its window and output allowance are why it was configured
|
|
@@ -2416,13 +2836,28 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2416
2836
|
*/
|
|
2417
2837
|
const fallbackContext =
|
|
2418
2838
|
attributedFallbackContext ?? getFallbackErrorContext(error);
|
|
2839
|
+
const localMeasurement = getLocalProviderOverflowMeasurement(error);
|
|
2840
|
+
const recoveryPromptEstimate = getRecoveryPromptEstimate(
|
|
2841
|
+
error,
|
|
2842
|
+
fallbackContext
|
|
2843
|
+
);
|
|
2844
|
+
/**
|
|
2845
|
+
* A previous correction that left the rejected prompt no smaller
|
|
2846
|
+
* proves this state has nothing left to compact. Use the fallback
|
|
2847
|
+
* projection when one exists so unlike provider formats are never
|
|
2848
|
+
* compared through the primary's cheaper pre-projection estimate.
|
|
2849
|
+
*/
|
|
2850
|
+
if (agentContext.overflowRecoveryStalled(recoveryPromptEstimate)) {
|
|
2851
|
+
return null;
|
|
2852
|
+
}
|
|
2419
2853
|
const recovery = planContextOverflowRecovery({
|
|
2420
2854
|
error,
|
|
2421
2855
|
provider: fallbackContext?.provider ?? agentContext.provider,
|
|
2422
2856
|
maxContextTokens:
|
|
2857
|
+
localMeasurement?.contextBudget ??
|
|
2423
2858
|
fallbackContext?.maxContextTokens ??
|
|
2424
2859
|
agentContext.maxContextTokens,
|
|
2425
|
-
estimatedPromptTokens,
|
|
2860
|
+
estimatedPromptTokens: recoveryPromptEstimate,
|
|
2426
2861
|
calibrationRatio: agentContext.calibrationRatio,
|
|
2427
2862
|
instructionTokens: agentContext.instructionTokens,
|
|
2428
2863
|
canSummarize: agentContext.summarizationEnabled === true,
|
|
@@ -2440,12 +2875,14 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2440
2875
|
...recovery,
|
|
2441
2876
|
budgetTokens: minDefined(
|
|
2442
2877
|
getBlindRecoveryBudget(agentContext.maxContextTokens),
|
|
2443
|
-
|
|
2444
|
-
recovery.budgetTokens
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2878
|
+
localMeasurement != null
|
|
2879
|
+
? recovery.budgetTokens
|
|
2880
|
+
: translateRecoveryBudget(
|
|
2881
|
+
recovery.budgetTokens,
|
|
2882
|
+
recovery.observedCalibrationRatio ??
|
|
2883
|
+
CALIBRATION_RATIO_MAX,
|
|
2884
|
+
agentContext.calibrationRatio
|
|
2885
|
+
)
|
|
2449
2886
|
),
|
|
2450
2887
|
observedCalibrationRatio: undefined,
|
|
2451
2888
|
}
|
|
@@ -2459,16 +2896,17 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2459
2896
|
|
|
2460
2897
|
const recovery = planRecovery(primaryError);
|
|
2461
2898
|
if (recovery != null) {
|
|
2899
|
+
const recoveryPromptEstimate =
|
|
2900
|
+
getRecoveryPromptEstimate(primaryError);
|
|
2462
2901
|
return this.beginOverflowRecovery({
|
|
2463
2902
|
recovery,
|
|
2464
2903
|
agentContext,
|
|
2465
2904
|
agentId,
|
|
2466
2905
|
config,
|
|
2467
2906
|
originalToolContent: prunedOriginalToolContent,
|
|
2468
|
-
estimatedPromptTokens,
|
|
2907
|
+
estimatedPromptTokens: recoveryPromptEstimate,
|
|
2469
2908
|
});
|
|
2470
2909
|
}
|
|
2471
|
-
|
|
2472
2910
|
/**
|
|
2473
2911
|
* A fallback can reject the same prompt as too large even when the
|
|
2474
2912
|
* primary failed for an unrelated reason — a fallback with a smaller
|
|
@@ -2485,7 +2923,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2485
2923
|
tryFallbackProviders({
|
|
2486
2924
|
fallbacks,
|
|
2487
2925
|
tools: agentContext.tools,
|
|
2488
|
-
messages:
|
|
2926
|
+
messages: fallbackBaseMessages,
|
|
2489
2927
|
config: invokeConfig,
|
|
2490
2928
|
primaryError,
|
|
2491
2929
|
context: this,
|
|
@@ -2499,20 +2937,71 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2499
2937
|
estimatedPromptTokens: getEstimatedPromptTokens(contextUsage),
|
|
2500
2938
|
maxContextTokens: agentContext.maxContextTokens,
|
|
2501
2939
|
},
|
|
2940
|
+
prepareProviderMessages: ({
|
|
2941
|
+
model: fallbackModel,
|
|
2942
|
+
messages: fallbackMessages,
|
|
2943
|
+
provider: fallbackProvider,
|
|
2944
|
+
maxContextTokens: fallbackMaxContextTokens,
|
|
2945
|
+
config: fallbackConfig,
|
|
2946
|
+
}) => {
|
|
2947
|
+
const fallbackToolResultChars =
|
|
2948
|
+
agentContext.maxToolResultChars ??
|
|
2949
|
+
calculateMaxToolResultChars(
|
|
2950
|
+
fallbackMaxContextTokens ?? agentContext.maxContextTokens
|
|
2951
|
+
);
|
|
2952
|
+
const projectedFallbackMessages = trackProviderMessageOrigins(
|
|
2953
|
+
fallbackMessages,
|
|
2954
|
+
projectMessagesForProvider({
|
|
2955
|
+
model: fallbackModel,
|
|
2956
|
+
messages: fallbackMessages,
|
|
2957
|
+
provider: fallbackProvider,
|
|
2958
|
+
maxToolResultChars: fallbackToolResultChars,
|
|
2959
|
+
callOptions: fallbackConfig,
|
|
2960
|
+
})
|
|
2961
|
+
);
|
|
2962
|
+
const primaryContextBudget = contextUsage?.contextBudget;
|
|
2963
|
+
const fallbackContextBudget =
|
|
2964
|
+
fallbackMaxContextTokens == null
|
|
2965
|
+
? primaryContextBudget
|
|
2966
|
+
: Math.min(
|
|
2967
|
+
primaryContextBudget ?? fallbackMaxContextTokens,
|
|
2968
|
+
fallbackMaxContextTokens
|
|
2969
|
+
);
|
|
2970
|
+
const projection = measureProviderPayload(
|
|
2971
|
+
projectedFallbackMessages,
|
|
2972
|
+
fallbackContextBudget,
|
|
2973
|
+
true
|
|
2974
|
+
);
|
|
2975
|
+
if (!projection.fits) {
|
|
2976
|
+
throw createProviderPayloadOverflowError({
|
|
2977
|
+
projection,
|
|
2978
|
+
provider: fallbackProvider,
|
|
2979
|
+
info: 'Fallback provider message formatting exceeded the context budget before invocation.',
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
return projectedFallbackMessages;
|
|
2983
|
+
},
|
|
2502
2984
|
})
|
|
2503
2985
|
);
|
|
2504
2986
|
} catch (fallbackError) {
|
|
2505
2987
|
const overflowCandidates =
|
|
2506
2988
|
getFallbackOverflowCandidates(fallbackError);
|
|
2507
2989
|
let fallbackRecovery: OverflowRecoveryPlan | null = null;
|
|
2990
|
+
let fallbackRecoveryPromptEstimate: number | undefined;
|
|
2508
2991
|
for (const candidate of overflowCandidates) {
|
|
2509
2992
|
fallbackRecovery = planRecovery(candidate.error, candidate.context);
|
|
2510
2993
|
if (fallbackRecovery != null) {
|
|
2994
|
+
fallbackRecoveryPromptEstimate = getRecoveryPromptEstimate(
|
|
2995
|
+
candidate.error,
|
|
2996
|
+
candidate.context
|
|
2997
|
+
);
|
|
2511
2998
|
break;
|
|
2512
2999
|
}
|
|
2513
3000
|
}
|
|
2514
3001
|
if (overflowCandidates.length === 0) {
|
|
2515
3002
|
fallbackRecovery = planRecovery(fallbackError);
|
|
3003
|
+
fallbackRecoveryPromptEstimate =
|
|
3004
|
+
getRecoveryPromptEstimate(fallbackError);
|
|
2516
3005
|
}
|
|
2517
3006
|
if (fallbackRecovery == null) {
|
|
2518
3007
|
throw fallbackError;
|
|
@@ -2523,7 +3012,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2523
3012
|
agentId,
|
|
2524
3013
|
config,
|
|
2525
3014
|
originalToolContent: prunedOriginalToolContent,
|
|
2526
|
-
estimatedPromptTokens,
|
|
3015
|
+
estimatedPromptTokens: fallbackRecoveryPromptEstimate,
|
|
2527
3016
|
});
|
|
2528
3017
|
}
|
|
2529
3018
|
} finally {
|
|
@@ -3165,6 +3654,15 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3165
3654
|
}
|
|
3166
3655
|
|
|
3167
3656
|
const { name, input: args, error } = data;
|
|
3657
|
+
const eventValueLimit = calculateMaxToolResultChars();
|
|
3658
|
+
const errorOutputPrefix = 'Error processing tool';
|
|
3659
|
+
const errorDetail =
|
|
3660
|
+
error?.message != null
|
|
3661
|
+
? `: ${serializeToolContentBounded(
|
|
3662
|
+
error.message,
|
|
3663
|
+
Math.max(0, eventValueLimit - errorOutputPrefix.length - 2)
|
|
3664
|
+
)}`
|
|
3665
|
+
: '';
|
|
3168
3666
|
|
|
3169
3667
|
const runStep = graph.getRunStep(stepId);
|
|
3170
3668
|
if (!runStep) {
|
|
@@ -3174,8 +3672,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3174
3672
|
const tool_call: t.ProcessedToolCall = {
|
|
3175
3673
|
id: data.id,
|
|
3176
3674
|
name: name || '',
|
|
3177
|
-
args:
|
|
3178
|
-
output:
|
|
3675
|
+
args: serializeToolContentBounded(args, eventValueLimit),
|
|
3676
|
+
output: `${errorOutputPrefix}${errorDetail}`,
|
|
3179
3677
|
progress: 1,
|
|
3180
3678
|
};
|
|
3181
3679
|
|