@librechat/agents 3.3.3 → 3.3.5
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/common/constants.cjs +21 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/events.cjs +10 -1
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +793 -111
- 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/hooks/HookRegistry.cjs +25 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +12 -0
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +8 -0
- package/dist/cjs/langfuse.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 +38 -13
- 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 +331 -5
- 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/llm/preempt.cjs +132 -0
- package/dist/cjs/llm/preempt.cjs.map +1 -0
- package/dist/cjs/main.cjs +36 -5
- package/dist/cjs/messages/alternation.cjs +92 -0
- package/dist/cjs/messages/alternation.cjs.map +1 -0
- 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 +290 -60
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/handoffCue.cjs +63 -0
- package/dist/cjs/messages/handoffCue.cjs.map +1 -0
- package/dist/cjs/messages/index.cjs +4 -1
- package/dist/cjs/messages/injected.cjs +60 -0
- package/dist/cjs/messages/injected.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +80 -7
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +18 -9
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +35 -10
- 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 +56 -63
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +90 -0
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +9 -1
- package/dist/cjs/tools/search/tool.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/common/constants.mjs +19 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/events.mjs +10 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +798 -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/hooks/HookRegistry.mjs +25 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +12 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +9 -1
- package/dist/esm/langfuse.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 +38 -13
- 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 +332 -8
- 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/llm/preempt.mjs +131 -0
- package/dist/esm/llm/preempt.mjs.map +1 -0
- package/dist/esm/main.mjs +14 -11
- package/dist/esm/messages/alternation.mjs +91 -0
- package/dist/esm/messages/alternation.mjs.map +1 -0
- 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 +290 -61
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/handoffCue.mjs +61 -0
- package/dist/esm/messages/handoffCue.mjs.map +1 -0
- package/dist/esm/messages/index.mjs +4 -1
- package/dist/esm/messages/injected.mjs +60 -0
- package/dist/esm/messages/injected.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +80 -7
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +19 -10
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/stream.mjs +34 -11
- 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 +56 -63
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +88 -0
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +9 -1
- package/dist/esm/tools/search/tool.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/common/constants.d.ts +18 -0
- package/dist/types/graphs/Graph.d.ts +139 -2
- package/dist/types/hooks/HookRegistry.d.ts +15 -0
- package/dist/types/hooks/index.d.ts +12 -1
- package/dist/types/hooks/types.d.ts +45 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +34 -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/llm/preempt.d.ts +40 -0
- package/dist/types/messages/alternation.d.ts +23 -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/handoffCue.d.ts +40 -0
- package/dist/types/messages/index.d.ts +3 -0
- package/dist/types/messages/injected.d.ts +3 -0
- package/dist/types/messages/prune.d.ts +16 -12
- package/dist/types/run.d.ts +7 -0
- package/dist/types/stream.d.ts +14 -0
- package/dist/types/tools/ToolNode.d.ts +0 -7
- package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
- package/dist/types/tools/search/types.d.ts +31 -2
- package/dist/types/types/graph.d.ts +8 -1
- package/dist/types/types/run.d.ts +65 -0
- package/dist/types/types/stream.d.ts +1 -26
- 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/{splitStream.test.ts → aggregator.test.ts} +59 -666
- package/src/common/constants.ts +21 -0
- package/src/events.ts +15 -1
- package/src/graphs/Graph.ts +1261 -198
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1139 -4
- package/src/graphs/__tests__/Graph.preemptSignal.test.ts +126 -0
- package/src/hooks/HookRegistry.ts +40 -0
- package/src/hooks/__tests__/preemptBoundary.test.ts +152 -0
- package/src/hooks/index.ts +16 -2
- package/src/hooks/types.ts +47 -3
- package/src/index.ts +1 -1
- package/src/langfuse.ts +26 -1
- 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 +131 -1
- package/src/llm/bedrock/utils/message_inputs.ts +100 -24
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.alternation.test.ts +87 -0
- package/src/llm/invoke.handoffCue.test.ts +112 -0
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +611 -8
- 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/llm/preempt.test.ts +323 -0
- package/src/llm/preempt.ts +178 -0
- package/src/messages/__tests__/observationMasking.test.ts +93 -2
- package/src/messages/alternation.test.ts +212 -0
- package/src/messages/alternation.ts +112 -0
- 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 +454 -91
- package/src/messages/formatAgentMessages.steer.test.ts +267 -0
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -0
- package/src/messages/handoffCue.test.ts +96 -0
- package/src/messages/handoffCue.ts +78 -0
- package/src/messages/index.ts +3 -0
- package/src/messages/injected.test.ts +90 -0
- package/src/messages/injected.ts +74 -0
- package/src/messages/prune.ts +996 -183
- package/src/run.ts +91 -6
- package/src/scripts/preempt-probe.ts +330 -0
- package/src/scripts/preempt-scenarios.ts +388 -0
- package/src/session/handlers.ts +32 -12
- package/src/specs/handoffCue.test.ts +165 -0
- package/src/specs/langfuse-callbacks.test.ts +352 -2
- package/src/specs/preemptSeal.test.ts +309 -0
- 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.dispatch.test.ts +63 -0
- package/src/stream.ts +59 -23
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +150 -99
- 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/search/keenable-scraper.test.ts +153 -0
- package/src/tools/search/keenable-scraper.ts +137 -0
- package/src/tools/search/tool.ts +13 -2
- package/src/tools/search/types.ts +50 -3
- package/src/tools/toolOutputReferences.ts +6 -0
- package/src/types/graph.ts +12 -1
- package/src/types/run.ts +67 -0
- package/src/types/stream.ts +1 -42
- 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/dist/cjs/splitStream.cjs +0 -151
- package/dist/cjs/splitStream.cjs.map +0 -1
- package/dist/esm/splitStream.mjs +0 -150
- package/dist/esm/splitStream.mjs.map +0 -1
- package/dist/types/mockStream.d.ts +0 -32
- package/dist/types/splitStream.d.ts +0 -37
- package/src/mockStream.ts +0 -99
- package/src/splitStream.ts +0 -234
package/src/messages/cache.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
BaseMessage,
|
|
4
|
-
ToolMessage,
|
|
5
|
-
HumanMessage,
|
|
6
|
-
SystemMessage,
|
|
7
|
-
MessageContentComplex,
|
|
8
|
-
} from '@langchain/core/messages';
|
|
1
|
+
import { isProxy } from 'node:util/types';
|
|
2
|
+
import { BaseMessage, MessageContentComplex } from '@langchain/core/messages';
|
|
9
3
|
import type Anthropic from '@anthropic-ai/sdk';
|
|
10
4
|
import type { AnthropicMessage } from '@/types/messages';
|
|
5
|
+
import { hasComputerCallOutputMarker } from '@/utils/toolContent';
|
|
11
6
|
import { toLangChainContent } from './langchain';
|
|
12
7
|
import { ContentTypes } from '@/common/enum';
|
|
13
|
-
import { withMessageRole } from './format';
|
|
14
8
|
|
|
15
9
|
type MessageWithContent = {
|
|
16
10
|
content?: string | MessageContentComplex[];
|
|
@@ -134,13 +128,30 @@ export function resolveBedrockPromptCacheTtl(
|
|
|
134
128
|
* Deep clones a message's content to prevent mutation of the original.
|
|
135
129
|
*/
|
|
136
130
|
function deepCloneContent<T extends string | MessageContentComplex[]>(
|
|
137
|
-
content: T
|
|
131
|
+
content: T,
|
|
132
|
+
omittedProperty?: string
|
|
138
133
|
): T {
|
|
139
134
|
if (typeof content === 'string') {
|
|
140
135
|
return content;
|
|
141
136
|
}
|
|
142
137
|
if (Array.isArray(content)) {
|
|
143
|
-
return content.map((block) =>
|
|
138
|
+
return content.map((block) => {
|
|
139
|
+
if (typeof block !== 'object' || block == null || isProxy(block)) {
|
|
140
|
+
return block;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const descriptors = Object.getOwnPropertyDescriptors(block);
|
|
144
|
+
if (omittedProperty != null) {
|
|
145
|
+
delete descriptors[omittedProperty];
|
|
146
|
+
}
|
|
147
|
+
return Object.create(
|
|
148
|
+
Object.getPrototypeOf(block),
|
|
149
|
+
descriptors
|
|
150
|
+
) as MessageContentComplex;
|
|
151
|
+
} catch {
|
|
152
|
+
return block;
|
|
153
|
+
}
|
|
154
|
+
}) as T;
|
|
144
155
|
}
|
|
145
156
|
return content;
|
|
146
157
|
}
|
|
@@ -156,45 +167,36 @@ export function cloneMessage<T extends MessageWithContent>(
|
|
|
156
167
|
content: string | MessageContentComplex[]
|
|
157
168
|
): T {
|
|
158
169
|
if (message instanceof BaseMessage) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
const descriptors = Object.getOwnPropertyDescriptors(message) as Record<
|
|
171
|
+
PropertyKey,
|
|
172
|
+
PropertyDescriptor | undefined
|
|
173
|
+
>;
|
|
174
|
+
const contentDescriptor = descriptors.content;
|
|
175
|
+
descriptors.content = {
|
|
176
|
+
configurable: contentDescriptor?.configurable ?? true,
|
|
177
|
+
enumerable: contentDescriptor?.enumerable ?? true,
|
|
178
|
+
value: toLangChainContent(content),
|
|
179
|
+
writable: contentDescriptor?.writable ?? true,
|
|
165
180
|
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'user'
|
|
181
|
-
) as unknown as T;
|
|
182
|
-
case 'system':
|
|
183
|
-
return withMessageRole(
|
|
184
|
-
new SystemMessage(baseParams),
|
|
185
|
-
'system'
|
|
186
|
-
) as unknown as T;
|
|
187
|
-
case 'tool':
|
|
188
|
-
return withMessageRole(
|
|
189
|
-
new ToolMessage({
|
|
190
|
-
...baseParams,
|
|
191
|
-
tool_call_id: (message as unknown as ToolMessage).tool_call_id,
|
|
192
|
-
}),
|
|
193
|
-
'tool'
|
|
194
|
-
) as unknown as T;
|
|
195
|
-
default:
|
|
196
|
-
break;
|
|
181
|
+
const lcKwargs = descriptors.lc_kwargs;
|
|
182
|
+
if (
|
|
183
|
+
lcKwargs != null &&
|
|
184
|
+
'value' in lcKwargs &&
|
|
185
|
+
typeof lcKwargs.value === 'object' &&
|
|
186
|
+
lcKwargs.value != null
|
|
187
|
+
) {
|
|
188
|
+
descriptors.lc_kwargs = {
|
|
189
|
+
...lcKwargs,
|
|
190
|
+
value: {
|
|
191
|
+
...(lcKwargs.value as Record<string, unknown>),
|
|
192
|
+
content: toLangChainContent(content),
|
|
193
|
+
},
|
|
194
|
+
};
|
|
197
195
|
}
|
|
196
|
+
return Object.create(
|
|
197
|
+
Object.getPrototypeOf(message),
|
|
198
|
+
descriptors as PropertyDescriptorMap
|
|
199
|
+
) as T;
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
const {
|
|
@@ -251,7 +253,11 @@ function sanitizeBedrockSystemMessage<T extends MessageWithContent>(
|
|
|
251
253
|
let modified = false;
|
|
252
254
|
for (const block of content) {
|
|
253
255
|
if (isCachePoint(block)) {
|
|
254
|
-
const
|
|
256
|
+
const descriptor = Object.getOwnPropertyDescriptor(block, 'cachePoint');
|
|
257
|
+
const existing =
|
|
258
|
+
descriptor != null && 'value' in descriptor
|
|
259
|
+
? (descriptor.value as { ttl?: unknown } | undefined)
|
|
260
|
+
: undefined;
|
|
255
261
|
const desired = buildBedrockCachePoint(ttl);
|
|
256
262
|
if (existing?.ttl !== desired.ttl) {
|
|
257
263
|
modified = true;
|
|
@@ -378,8 +384,17 @@ export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
|
|
|
378
384
|
/**
|
|
379
385
|
* Checks if a content block is a cache point
|
|
380
386
|
*/
|
|
381
|
-
function isCachePoint(block:
|
|
382
|
-
|
|
387
|
+
function isCachePoint(block: unknown): boolean {
|
|
388
|
+
if (typeof block !== 'object' || block == null || isProxy(block)) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const cachePoint = Object.getOwnPropertyDescriptor(block, 'cachePoint');
|
|
393
|
+
const type = Object.getOwnPropertyDescriptor(block, 'type');
|
|
394
|
+
return cachePoint?.enumerable === true && type == null;
|
|
395
|
+
} catch {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
383
398
|
}
|
|
384
399
|
|
|
385
400
|
/**
|
|
@@ -460,8 +475,13 @@ export function addTailCacheControl<T extends AnthropicMessage | BaseMessage>(
|
|
|
460
475
|
const originalMessage = updatedMessages[i];
|
|
461
476
|
const content = originalMessage.content;
|
|
462
477
|
const hasArrayContent = Array.isArray(content);
|
|
478
|
+
const isComputerOutput =
|
|
479
|
+
originalMessage instanceof BaseMessage &&
|
|
480
|
+
hasComputerCallOutputMarker(originalMessage);
|
|
463
481
|
const canPlaceMarker =
|
|
464
|
-
!markerPlaced &&
|
|
482
|
+
!markerPlaced &&
|
|
483
|
+
!isSyntheticMetaMessage(originalMessage) &&
|
|
484
|
+
!isComputerOutput;
|
|
465
485
|
|
|
466
486
|
// Earlier string-content messages carry no markers to strip.
|
|
467
487
|
if (!canPlaceMarker && !hasArrayContent) {
|
|
@@ -706,7 +726,21 @@ export function addCacheControlToStablePrefixMessages<
|
|
|
706
726
|
*/
|
|
707
727
|
function hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {
|
|
708
728
|
for (let i = 0; i < content.length; i++) {
|
|
709
|
-
|
|
729
|
+
const block = content[i];
|
|
730
|
+
if (typeof block !== 'object' || block == null || isProxy(block)) {
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
const cacheControl = Object.getOwnPropertyDescriptor(
|
|
735
|
+
block,
|
|
736
|
+
'cache_control'
|
|
737
|
+
);
|
|
738
|
+
if (cacheControl?.enumerable === true) {
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
} catch {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
710
744
|
}
|
|
711
745
|
return false;
|
|
712
746
|
}
|
|
@@ -733,13 +767,7 @@ export function stripAnthropicCacheControl<T extends MessageWithContent>(
|
|
|
733
767
|
continue;
|
|
734
768
|
}
|
|
735
769
|
|
|
736
|
-
const clonedContent = deepCloneContent(content);
|
|
737
|
-
for (let j = 0; j < clonedContent.length; j++) {
|
|
738
|
-
const block = clonedContent[j] as Record<string, unknown>;
|
|
739
|
-
if ('cache_control' in block) {
|
|
740
|
-
delete block.cache_control;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
770
|
+
const clonedContent = deepCloneContent(content, 'cache_control');
|
|
743
771
|
updatedMessages[i] = cloneMessage(originalMessage, clonedContent);
|
|
744
772
|
}
|
|
745
773
|
|
|
@@ -961,10 +989,14 @@ export function addBedrockTailCacheControl<
|
|
|
961
989
|
'lc_namespace' in originalMessage;
|
|
962
990
|
const hasArrayContent = Array.isArray(content);
|
|
963
991
|
const isEmptyString = typeof content === 'string' && content === '';
|
|
992
|
+
const isComputerOutput =
|
|
993
|
+
originalMessage instanceof BaseMessage &&
|
|
994
|
+
hasComputerCallOutputMarker(originalMessage);
|
|
964
995
|
const canPlaceCachePoint =
|
|
965
996
|
!cachePointPlaced &&
|
|
966
997
|
!isEmptyString &&
|
|
967
998
|
!isSyntheticMetaMessage(originalMessage) &&
|
|
999
|
+
!isComputerOutput &&
|
|
968
1000
|
(typeof content === 'string' || hasArrayContent);
|
|
969
1001
|
|
|
970
1002
|
if (!canPlaceCachePoint && !hasArrayContent && !hasSerializationProps) {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AIMessage,
|
|
3
|
+
HumanMessage,
|
|
4
|
+
ToolMessage,
|
|
5
|
+
type BaseMessage,
|
|
6
|
+
} from '@langchain/core/messages';
|
|
7
|
+
import { applyContextPruning } from './contextPruning';
|
|
8
|
+
|
|
9
|
+
function charCounter(message: BaseMessage): number {
|
|
10
|
+
return (
|
|
11
|
+
typeof message.content === 'string'
|
|
12
|
+
? message.content
|
|
13
|
+
: JSON.stringify(message.content)
|
|
14
|
+
).length;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('applyContextPruning', () => {
|
|
18
|
+
it('soft-trims old structured tool results and preserves metadata', () => {
|
|
19
|
+
const toolCallId = 'tc-old';
|
|
20
|
+
const messages: BaseMessage[] = [
|
|
21
|
+
new HumanMessage('query the table'),
|
|
22
|
+
new AIMessage({
|
|
23
|
+
content: '',
|
|
24
|
+
tool_calls: [
|
|
25
|
+
{
|
|
26
|
+
id: toolCallId,
|
|
27
|
+
name: 'run_select_query',
|
|
28
|
+
args: {},
|
|
29
|
+
type: 'tool_call',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}),
|
|
33
|
+
new ToolMessage({
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: 'json',
|
|
37
|
+
rows: Array.from({ length: 20 }, (_, index) => ({
|
|
38
|
+
id: index,
|
|
39
|
+
value: `${'x'.repeat(100)}-${index}`,
|
|
40
|
+
})),
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
tool_call_id: toolCallId,
|
|
44
|
+
name: 'run_select_query',
|
|
45
|
+
status: 'success',
|
|
46
|
+
artifact: { source: 'clickhouse' },
|
|
47
|
+
}),
|
|
48
|
+
new AIMessage('The query returned 20 rows.'),
|
|
49
|
+
new HumanMessage('continue'),
|
|
50
|
+
new AIMessage('Ready.'),
|
|
51
|
+
new HumanMessage('next question'),
|
|
52
|
+
];
|
|
53
|
+
const indexTokenCountMap: Record<string, number | undefined> = {};
|
|
54
|
+
for (let i = 0; i < messages.length; i++) {
|
|
55
|
+
indexTokenCountMap[i] = charCounter(messages[i]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = applyContextPruning({
|
|
59
|
+
messages,
|
|
60
|
+
indexTokenCountMap,
|
|
61
|
+
tokenCounter: charCounter,
|
|
62
|
+
config: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
keepLastAssistants: 1,
|
|
65
|
+
softTrimRatio: 0,
|
|
66
|
+
minPrunableToolChars: 1,
|
|
67
|
+
softTrim: { maxChars: 200, headChars: 80, tailChars: 40 },
|
|
68
|
+
hardClear: { enabled: false },
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(result.softTrimmed).toBe(1);
|
|
73
|
+
expect(typeof messages[2].content).toBe('string');
|
|
74
|
+
expect(messages[2].content).toContain('soft-trimmed');
|
|
75
|
+
const trimmed = messages[2] as ToolMessage;
|
|
76
|
+
expect(trimmed.tool_call_id).toBe(toolCallId);
|
|
77
|
+
expect(trimmed.name).toBe('run_select_query');
|
|
78
|
+
expect(trimmed.status).toBe('success');
|
|
79
|
+
expect(trimmed.artifact).toEqual({ source: 'clickhouse' });
|
|
80
|
+
expect(indexTokenCountMap[2]).toBe(charCounter(trimmed));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('preserves small non-image media blocks while trimming adjacent text', () => {
|
|
84
|
+
const document = {
|
|
85
|
+
type: 'document',
|
|
86
|
+
source: { type: 'url', url: 'https://example.com/report.pdf' },
|
|
87
|
+
};
|
|
88
|
+
const messages: BaseMessage[] = [
|
|
89
|
+
new HumanMessage('read the report'),
|
|
90
|
+
new AIMessage({
|
|
91
|
+
content: '',
|
|
92
|
+
tool_calls: [
|
|
93
|
+
{
|
|
94
|
+
id: 'tc-document',
|
|
95
|
+
name: 'read_document',
|
|
96
|
+
args: {},
|
|
97
|
+
type: 'tool_call',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}),
|
|
101
|
+
new ToolMessage({
|
|
102
|
+
content: [{ type: 'text', text: 'x'.repeat(2_000) }, document],
|
|
103
|
+
tool_call_id: 'tc-document',
|
|
104
|
+
}),
|
|
105
|
+
new AIMessage('I read the report.'),
|
|
106
|
+
new HumanMessage('continue'),
|
|
107
|
+
new AIMessage('Ready.'),
|
|
108
|
+
new HumanMessage('next question'),
|
|
109
|
+
];
|
|
110
|
+
const indexTokenCountMap: Record<string, number | undefined> = {};
|
|
111
|
+
for (let i = 0; i < messages.length; i++) {
|
|
112
|
+
indexTokenCountMap[i] = charCounter(messages[i]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = applyContextPruning({
|
|
116
|
+
messages,
|
|
117
|
+
indexTokenCountMap,
|
|
118
|
+
tokenCounter: charCounter,
|
|
119
|
+
config: {
|
|
120
|
+
enabled: true,
|
|
121
|
+
keepLastAssistants: 1,
|
|
122
|
+
softTrimRatio: 0,
|
|
123
|
+
minPrunableToolChars: 1,
|
|
124
|
+
softTrim: { maxChars: 400, headChars: 80, tailChars: 40 },
|
|
125
|
+
hardClear: { enabled: false },
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(result.softTrimmed).toBe(1);
|
|
130
|
+
expect(Array.isArray(messages[2].content)).toBe(true);
|
|
131
|
+
expect(messages[2].content).toContain(document);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('does not hard-clear native computer screenshots', () => {
|
|
135
|
+
const screenshot = `data:image/png;base64,${'A'.repeat(2_000)}`;
|
|
136
|
+
const computerOutput = new ToolMessage({
|
|
137
|
+
content: screenshot,
|
|
138
|
+
tool_call_id: 'tc-computer',
|
|
139
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
140
|
+
});
|
|
141
|
+
const messages: BaseMessage[] = [
|
|
142
|
+
new HumanMessage('take a screenshot'),
|
|
143
|
+
new AIMessage({
|
|
144
|
+
content: '',
|
|
145
|
+
tool_calls: [
|
|
146
|
+
{
|
|
147
|
+
id: 'tc-computer',
|
|
148
|
+
name: 'computer',
|
|
149
|
+
args: {},
|
|
150
|
+
type: 'tool_call',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
}),
|
|
154
|
+
computerOutput,
|
|
155
|
+
new AIMessage('Done.'),
|
|
156
|
+
new HumanMessage('continue'),
|
|
157
|
+
new AIMessage('Ready.'),
|
|
158
|
+
new HumanMessage('next question'),
|
|
159
|
+
];
|
|
160
|
+
const indexTokenCountMap: Record<string, number | undefined> = {};
|
|
161
|
+
for (let i = 0; i < messages.length; i++) {
|
|
162
|
+
indexTokenCountMap[i] = charCounter(messages[i]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const result = applyContextPruning({
|
|
166
|
+
messages,
|
|
167
|
+
indexTokenCountMap,
|
|
168
|
+
tokenCounter: charCounter,
|
|
169
|
+
config: {
|
|
170
|
+
enabled: true,
|
|
171
|
+
keepLastAssistants: 1,
|
|
172
|
+
softTrimRatio: 0,
|
|
173
|
+
minPrunableToolChars: 1,
|
|
174
|
+
softTrim: { maxChars: 80, headChars: 20, tailChars: 20 },
|
|
175
|
+
hardClear: { enabled: true },
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(result.softTrimmed).toBe(0);
|
|
180
|
+
expect(result.hardCleared).toBe(0);
|
|
181
|
+
expect(messages[2]).toBe(computerOutput);
|
|
182
|
+
expect(messages[2].content).toBe(screenshot);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -9,32 +9,24 @@
|
|
|
9
9
|
* - Hard-clear: Replace entire content with a placeholder.
|
|
10
10
|
*
|
|
11
11
|
* Messages in the "protected zone" (recent assistant turns, system/pre-first-human
|
|
12
|
-
* messages
|
|
12
|
+
* messages) are never pruned. Atomic media/resource blocks are preserved when
|
|
13
|
+
* they fit; oversized inline payloads are replaced with bounded placeholders.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import { ToolMessage, type BaseMessage } from '@langchain/core/messages';
|
|
16
17
|
import type { ContextPruningSettings } from './contextPruningSettings';
|
|
17
18
|
import type { ContextPruningConfig } from '@/types/graph';
|
|
18
19
|
import type { TokenCounter } from '@/types/run';
|
|
20
|
+
import {
|
|
21
|
+
cloneToolMessageWithContent,
|
|
22
|
+
compactToolContent,
|
|
23
|
+
getToolContentCharLength,
|
|
24
|
+
isAtomicToolContentBlock,
|
|
25
|
+
isComputerCallOutputMessage,
|
|
26
|
+
serializeToolContentBounded,
|
|
27
|
+
} from '@/utils/toolContent';
|
|
19
28
|
import { resolveContextPruningSettings } from './contextPruningSettings';
|
|
20
29
|
|
|
21
|
-
/**
|
|
22
|
-
* Checks if a message contains image content blocks.
|
|
23
|
-
* Messages with images are skipped by position-based content degradation
|
|
24
|
-
* because images cannot be meaningfully soft-trimmed or replaced with placeholders.
|
|
25
|
-
*/
|
|
26
|
-
function hasImageContent(message: BaseMessage): boolean {
|
|
27
|
-
if (!Array.isArray(message.content)) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
return message.content.some(
|
|
31
|
-
(block) =>
|
|
32
|
-
typeof block === 'object' &&
|
|
33
|
-
'type' in block &&
|
|
34
|
-
(block.type === 'image_url' || block.type === 'image')
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
30
|
/**
|
|
39
31
|
* Applies head+tail soft-trim to tool result content.
|
|
40
32
|
*/
|
|
@@ -138,48 +130,63 @@ export function applyContextPruning(params: {
|
|
|
138
130
|
if (message.getType() !== 'tool') {
|
|
139
131
|
continue;
|
|
140
132
|
}
|
|
141
|
-
if (
|
|
133
|
+
if (isComputerCallOutputMessage(message)) {
|
|
142
134
|
continue;
|
|
143
135
|
}
|
|
144
|
-
if (
|
|
136
|
+
if (protectedIndices.has(i)) {
|
|
145
137
|
continue;
|
|
146
138
|
}
|
|
147
|
-
|
|
148
139
|
const content = message.content;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
if (content.length < settings.minPrunableToolChars) {
|
|
140
|
+
const contentLength = getToolContentCharLength(content);
|
|
141
|
+
if (contentLength < settings.minPrunableToolChars) {
|
|
153
142
|
continue;
|
|
154
143
|
}
|
|
155
144
|
|
|
156
145
|
// Compute age ratio: how far back from the end (0 = latest, 1 = oldest)
|
|
157
146
|
const ageRatio = (totalMessages - i) / totalMessages;
|
|
158
147
|
|
|
148
|
+
if (
|
|
149
|
+
Array.isArray(content) &&
|
|
150
|
+
content.some(isAtomicToolContentBlock) &&
|
|
151
|
+
ageRatio >= settings.softTrimRatio
|
|
152
|
+
) {
|
|
153
|
+
const compacted = compactToolContent(content, settings.softTrim.maxChars);
|
|
154
|
+
if (compacted.changed) {
|
|
155
|
+
const cloned = cloneToolMessageWithContent(
|
|
156
|
+
message as ToolMessage,
|
|
157
|
+
compacted.content
|
|
158
|
+
);
|
|
159
|
+
messages[i] = cloned;
|
|
160
|
+
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
161
|
+
softTrimmed++;
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
159
166
|
if (ageRatio >= settings.hardClearRatio && settings.hardClear.enabled) {
|
|
160
167
|
// Hard-clear: replace with placeholder
|
|
161
|
-
const cloned =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
id: message.id,
|
|
166
|
-
additional_kwargs: message.additional_kwargs,
|
|
167
|
-
response_metadata: message.response_metadata,
|
|
168
|
-
});
|
|
168
|
+
const cloned = cloneToolMessageWithContent(
|
|
169
|
+
message as ToolMessage,
|
|
170
|
+
settings.hardClear.placeholder
|
|
171
|
+
);
|
|
169
172
|
messages[i] = cloned;
|
|
170
173
|
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
171
174
|
hardCleared++;
|
|
172
175
|
} else if (ageRatio >= settings.softTrimRatio) {
|
|
173
176
|
// Soft-trim: keep head + tail
|
|
174
|
-
if (
|
|
175
|
-
const cloned =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
if (contentLength > settings.softTrim.maxChars) {
|
|
178
|
+
const cloned = cloneToolMessageWithContent(
|
|
179
|
+
message as ToolMessage,
|
|
180
|
+
softTrimContent(
|
|
181
|
+
typeof content === 'string'
|
|
182
|
+
? content
|
|
183
|
+
: serializeToolContentBounded(
|
|
184
|
+
content,
|
|
185
|
+
settings.softTrim.maxChars
|
|
186
|
+
),
|
|
187
|
+
settings.softTrim
|
|
188
|
+
)
|
|
189
|
+
);
|
|
183
190
|
messages[i] = cloned;
|
|
184
191
|
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
185
192
|
softTrimmed++;
|