@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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type * as t from '@/types';
|
|
2
|
+
import {
|
|
3
|
+
ChatModelStreamHandler,
|
|
4
|
+
dispatchesChatModelStream,
|
|
5
|
+
SDK_STREAM_DISPATCH,
|
|
6
|
+
} from '@/stream';
|
|
7
|
+
import { composeEventHandlers } from '@/events';
|
|
8
|
+
import { GraphEvents } from '@/common';
|
|
9
|
+
|
|
10
|
+
const inert: t.EventHandler = {
|
|
11
|
+
handle: (): void => {
|
|
12
|
+
/* renders nothing */
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('dispatchesChatModelStream', () => {
|
|
17
|
+
it('recognizes the dispatcher itself', () => {
|
|
18
|
+
expect(dispatchesChatModelStream(new ChatModelStreamHandler())).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('is false for a handler that owns no content-part dispatch', () => {
|
|
22
|
+
expect(dispatchesChatModelStream(inert)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('is false for nothing registered', () => {
|
|
26
|
+
expect(dispatchesChatModelStream(undefined)).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The reason identity is not a usable test: hosts wrap handlers, and every
|
|
31
|
+
* wrapper fails `instanceof` while still driving the same dispatch. A run
|
|
32
|
+
* that seals on that basis would be sealing against its own contract.
|
|
33
|
+
*/
|
|
34
|
+
it('sees through a composed wrapper in either position', () => {
|
|
35
|
+
const first = composeEventHandlers(
|
|
36
|
+
{ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler() },
|
|
37
|
+
{ [GraphEvents.CHAT_MODEL_STREAM]: inert }
|
|
38
|
+
)[GraphEvents.CHAT_MODEL_STREAM];
|
|
39
|
+
const second = composeEventHandlers(
|
|
40
|
+
{ [GraphEvents.CHAT_MODEL_STREAM]: inert },
|
|
41
|
+
{ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler() }
|
|
42
|
+
)[GraphEvents.CHAT_MODEL_STREAM];
|
|
43
|
+
|
|
44
|
+
expect(first).not.toBeInstanceOf(ChatModelStreamHandler);
|
|
45
|
+
expect(second).not.toBeInstanceOf(ChatModelStreamHandler);
|
|
46
|
+
expect(dispatchesChatModelStream(first)).toBe(true);
|
|
47
|
+
expect(dispatchesChatModelStream(second)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('does not brand a composition of inert handlers', () => {
|
|
51
|
+
const composed = composeEventHandlers(
|
|
52
|
+
{ [GraphEvents.CHAT_MODEL_STREAM]: inert },
|
|
53
|
+
{ [GraphEvents.CHAT_MODEL_STREAM]: inert }
|
|
54
|
+
)[GraphEvents.CHAT_MODEL_STREAM];
|
|
55
|
+
expect(dispatchesChatModelStream(composed)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('exposes the brand as a cross-realm-safe registered symbol', () => {
|
|
59
|
+
expect(SDK_STREAM_DISPATCH).toBe(
|
|
60
|
+
Symbol.for('@librechat/agents:chatModelStreamDispatch')
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/stream.ts
CHANGED
|
@@ -26,6 +26,10 @@ import {
|
|
|
26
26
|
coerceRecordArgs,
|
|
27
27
|
normalizeError,
|
|
28
28
|
} from '@/tools/eagerEventExecution';
|
|
29
|
+
import {
|
|
30
|
+
serializeStructuredValueBounded,
|
|
31
|
+
serializeToolContentBounded,
|
|
32
|
+
} from '@/utils/toolContent';
|
|
29
33
|
import {
|
|
30
34
|
handleServerToolResult,
|
|
31
35
|
handleToolCallChunks,
|
|
@@ -841,15 +845,20 @@ async function dispatchEagerToolCompletions(args: {
|
|
|
841
845
|
if (stepId === '') {
|
|
842
846
|
continue;
|
|
843
847
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
:
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
848
|
+
let output: string;
|
|
849
|
+
if (result.status === 'error') {
|
|
850
|
+
output = truncateToolResultContent(
|
|
851
|
+
`Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`,
|
|
852
|
+
maxToolResultChars
|
|
853
|
+
);
|
|
854
|
+
} else if (typeof result.content === 'string') {
|
|
855
|
+
output = truncateToolResultContent(result.content, maxToolResultChars);
|
|
856
|
+
} else {
|
|
857
|
+
output = serializeStructuredValueBounded(
|
|
858
|
+
result.content,
|
|
859
|
+
maxToolResultChars
|
|
860
|
+
).content;
|
|
861
|
+
}
|
|
853
862
|
|
|
854
863
|
try {
|
|
855
864
|
const dispatched = await safeDispatchCustomEvent(
|
|
@@ -861,7 +870,10 @@ async function dispatchEagerToolCompletions(args: {
|
|
|
861
870
|
type: 'tool_call' as const,
|
|
862
871
|
eager: true,
|
|
863
872
|
tool_call: {
|
|
864
|
-
args:
|
|
873
|
+
args: serializeToolContentBounded(
|
|
874
|
+
record.request.args,
|
|
875
|
+
maxToolResultChars
|
|
876
|
+
),
|
|
865
877
|
name: record.toolName,
|
|
866
878
|
id: result.toolCallId,
|
|
867
879
|
output,
|
|
@@ -1388,7 +1400,34 @@ function shouldSkipLateOpenRouterReasoningChunk({
|
|
|
1388
1400
|
);
|
|
1389
1401
|
}
|
|
1390
1402
|
|
|
1403
|
+
/**
|
|
1404
|
+
* Brands a handler as one that dispatches content parts for the SDK — either
|
|
1405
|
+
* `ChatModelStreamHandler` itself or a wrapper forwarding to one.
|
|
1406
|
+
*
|
|
1407
|
+
* Identity alone is not a usable contract here. Hosts compose and wrap
|
|
1408
|
+
* handlers (`composeEventHandlers`, `createRunHandlers`), and every wrapper
|
|
1409
|
+
* fails `instanceof` while still driving the same dispatch. A brand survives
|
|
1410
|
+
* wrapping, so "does this handler own content-part dispatch" can be answered
|
|
1411
|
+
* about a value the SDK did not construct.
|
|
1412
|
+
*/
|
|
1413
|
+
export const SDK_STREAM_DISPATCH = Symbol.for(
|
|
1414
|
+
'@librechat/agents:chatModelStreamDispatch'
|
|
1415
|
+
);
|
|
1416
|
+
|
|
1417
|
+
/** True when `handler` is, or forwards to, the SDK's stream dispatcher. */
|
|
1418
|
+
export function dispatchesChatModelStream(handler?: t.EventHandler): boolean {
|
|
1419
|
+
if (handler == null) {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
if (handler instanceof ChatModelStreamHandler) {
|
|
1423
|
+
return true;
|
|
1424
|
+
}
|
|
1425
|
+
return Reflect.get(handler, SDK_STREAM_DISPATCH) === true;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1391
1428
|
export class ChatModelStreamHandler implements t.EventHandler {
|
|
1429
|
+
readonly [SDK_STREAM_DISPATCH] = true;
|
|
1430
|
+
|
|
1392
1431
|
async handle(
|
|
1393
1432
|
event: string,
|
|
1394
1433
|
data: t.StreamEventData,
|
|
@@ -1834,13 +1873,16 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
1834
1873
|
number,
|
|
1835
1874
|
{ agentId?: string; groupId?: number }
|
|
1836
1875
|
>();
|
|
1837
|
-
|
|
1876
|
+
/** A delta's content may carry several parts (e.g. Google server-side tool
|
|
1877
|
+
* chunks emit multiple reasoning entries at once); every entry must reach
|
|
1878
|
+
* the step's slot, in order, or streamed text is silently lost. */
|
|
1879
|
+
const getDeltaContentParts = (
|
|
1838
1880
|
content?: t.MessageDelta['content'] | t.MessageContentComplex
|
|
1839
|
-
): t.MessageContentComplex
|
|
1881
|
+
): t.MessageContentComplex[] => {
|
|
1840
1882
|
if (content == null) {
|
|
1841
|
-
return
|
|
1883
|
+
return [];
|
|
1842
1884
|
}
|
|
1843
|
-
return Array.isArray(content) ? content
|
|
1885
|
+
return Array.isArray(content) ? content : [content];
|
|
1844
1886
|
};
|
|
1845
1887
|
const indexContentPart = (
|
|
1846
1888
|
index: number,
|
|
@@ -2248,11 +2290,7 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
2248
2290
|
const contentIndex =
|
|
2249
2291
|
toolCallIndices[toolCallIndex] ?? runStep.index;
|
|
2250
2292
|
const toolCallId = toolCall.id ?? '';
|
|
2251
|
-
registerToolContentIndex(
|
|
2252
|
-
toolStepContent,
|
|
2253
|
-
contentIndex,
|
|
2254
|
-
toolCallId
|
|
2255
|
-
);
|
|
2293
|
+
registerToolContentIndex(toolStepContent, contentIndex, toolCallId);
|
|
2256
2294
|
const contentPart: t.MessageContentComplex = {
|
|
2257
2295
|
type: ContentTypes.TOOL_CALL,
|
|
2258
2296
|
tool_call: {
|
|
@@ -2276,8 +2314,7 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
2276
2314
|
return;
|
|
2277
2315
|
}
|
|
2278
2316
|
|
|
2279
|
-
const contentPart
|
|
2280
|
-
if (contentPart != null) {
|
|
2317
|
+
for (const contentPart of getDeltaContentParts(messageDelta.delta.content)) {
|
|
2281
2318
|
updateContent(runStep.index, contentPart);
|
|
2282
2319
|
}
|
|
2283
2320
|
} else if (
|
|
@@ -2306,8 +2343,7 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
2306
2343
|
return;
|
|
2307
2344
|
}
|
|
2308
2345
|
|
|
2309
|
-
const contentPart
|
|
2310
|
-
if (contentPart != null) {
|
|
2346
|
+
for (const contentPart of getDeltaContentParts(reasoningDelta.delta.content)) {
|
|
2311
2347
|
updateContent(runStep.index, contentPart);
|
|
2312
2348
|
}
|
|
2313
2349
|
} else if (event === GraphEvents.ON_RUN_STEP_DELTA) {
|
|
@@ -1045,6 +1045,83 @@ describe('recency window — first-turn protection', () => {
|
|
|
1045
1045
|
expect(tailToolMsg!.content).not.toContain('FULL_ORIGINAL_OUTPUT');
|
|
1046
1046
|
});
|
|
1047
1047
|
|
|
1048
|
+
it('bounds restored tool originals and preserves ToolMessage metadata', async () => {
|
|
1049
|
+
captureEvents();
|
|
1050
|
+
|
|
1051
|
+
let restoredToolMessage: ToolMessage | undefined;
|
|
1052
|
+
const invokeMock = jest.fn().mockImplementation((messages: unknown) => {
|
|
1053
|
+
restoredToolMessage = (messages as Array<unknown>).find(
|
|
1054
|
+
(message) => message instanceof ToolMessage
|
|
1055
|
+
) as ToolMessage | undefined;
|
|
1056
|
+
return Promise.resolve({ content: 'summary' });
|
|
1057
|
+
});
|
|
1058
|
+
jest.spyOn(providers, 'getChatModelClass').mockReturnValue(
|
|
1059
|
+
class {
|
|
1060
|
+
constructor() {
|
|
1061
|
+
return { invoke: invokeMock };
|
|
1062
|
+
}
|
|
1063
|
+
} as never
|
|
1064
|
+
);
|
|
1065
|
+
|
|
1066
|
+
const artifact = { source: 'clickhouse', rows: 1_000 };
|
|
1067
|
+
const originalToolMessage = new ToolMessage({
|
|
1068
|
+
content: 'masked-stub',
|
|
1069
|
+
tool_call_id: 'bounded',
|
|
1070
|
+
name: 'run_select_query',
|
|
1071
|
+
status: 'success',
|
|
1072
|
+
artifact,
|
|
1073
|
+
metadata: { traceId: 'trace-1' },
|
|
1074
|
+
additional_kwargs: { retained: true },
|
|
1075
|
+
response_metadata: { requestId: 'request-1' },
|
|
1076
|
+
});
|
|
1077
|
+
const agentContext = createAgentContext({
|
|
1078
|
+
maxContextTokens: 2_000,
|
|
1079
|
+
summarizationConfig: { retainRecent: { turns: 0 } },
|
|
1080
|
+
setSummary: jest.fn(),
|
|
1081
|
+
});
|
|
1082
|
+
agentContext.pendingOriginalToolContent = new Map([
|
|
1083
|
+
[2, `[{"rows":"${'x'.repeat(20_000)}"}]`],
|
|
1084
|
+
]);
|
|
1085
|
+
|
|
1086
|
+
const summarizeNode = createSummarizeNode({
|
|
1087
|
+
agentContext,
|
|
1088
|
+
graph: mockGraph() as never,
|
|
1089
|
+
generateStepId,
|
|
1090
|
+
});
|
|
1091
|
+
await summarizeNode(
|
|
1092
|
+
{
|
|
1093
|
+
messages: [
|
|
1094
|
+
new HumanMessage('query the table'),
|
|
1095
|
+
new AIMessage({
|
|
1096
|
+
content: '',
|
|
1097
|
+
tool_calls: [{ id: 'bounded', name: 'run_select_query', args: {} }],
|
|
1098
|
+
}),
|
|
1099
|
+
originalToolMessage,
|
|
1100
|
+
],
|
|
1101
|
+
summarizationRequest: {
|
|
1102
|
+
remainingContextTokens: 0,
|
|
1103
|
+
agentId: 'agent_0',
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
{} as RunnableConfig
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
expect(restoredToolMessage).toBeDefined();
|
|
1110
|
+
expect(typeof restoredToolMessage!.content).toBe('string');
|
|
1111
|
+
expect(String(restoredToolMessage!.content).length).toBeLessThanOrEqual(
|
|
1112
|
+
2_400
|
|
1113
|
+
);
|
|
1114
|
+
expect(restoredToolMessage!.content).toContain('[truncated:');
|
|
1115
|
+
expect(restoredToolMessage!.status).toBe('success');
|
|
1116
|
+
expect(restoredToolMessage!.artifact).toBe(artifact);
|
|
1117
|
+
expect(restoredToolMessage!.metadata).toEqual({ traceId: 'trace-1' });
|
|
1118
|
+
expect(restoredToolMessage!.additional_kwargs).toEqual({ retained: true });
|
|
1119
|
+
expect(restoredToolMessage!.response_metadata).toEqual({
|
|
1120
|
+
requestId: 'request-1',
|
|
1121
|
+
});
|
|
1122
|
+
expect(originalToolMessage.content).toBe('masked-stub');
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1048
1125
|
it('preserves tail-relevant pendingOriginalToolContent entries (reindexed) for future summaries', async () => {
|
|
1049
1126
|
captureEvents();
|
|
1050
1127
|
|
|
@@ -10,6 +10,12 @@ import type { AgentContext } from '@/agents/AgentContext';
|
|
|
10
10
|
import type { HookRegistry } from '@/hooks';
|
|
11
11
|
import type { OnChunk } from '@/llm/invoke';
|
|
12
12
|
import type * as t from '@/types';
|
|
13
|
+
import {
|
|
14
|
+
cloneToolMessageWithContent,
|
|
15
|
+
compactToolContent,
|
|
16
|
+
isComputerCallOutputMessage,
|
|
17
|
+
serializeToolContentBounded,
|
|
18
|
+
} from '@/utils/toolContent';
|
|
13
19
|
import {
|
|
14
20
|
addTailCacheControl,
|
|
15
21
|
resolvePromptCacheTtl,
|
|
@@ -28,6 +34,7 @@ import {
|
|
|
28
34
|
} from '@/common';
|
|
29
35
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
30
36
|
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
37
|
+
import { calculateMaxToolResultChars } from '@/utils/truncation';
|
|
31
38
|
import { createRemoveAllMessage } from '@/messages/reducer';
|
|
32
39
|
import { getMaxOutputTokensKey } from '@/llm/request';
|
|
33
40
|
import { initializeModel } from '@/llm/init';
|
|
@@ -206,10 +213,10 @@ function extractToolFailuresSection(messages: BaseMessage[]): string {
|
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
const toolName = toolMsg.name ?? 'tool';
|
|
209
|
-
const content =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
const content = serializeToolContentBounded(
|
|
217
|
+
toolMsg.content,
|
|
218
|
+
MAX_TOOL_FAILURE_CHARS * 4
|
|
219
|
+
);
|
|
213
220
|
const normalized = content.replace(/\s+/g, ' ').trim();
|
|
214
221
|
const summary =
|
|
215
222
|
normalized.length > MAX_TOOL_FAILURE_CHARS
|
|
@@ -250,25 +257,45 @@ function enrichSummary(summaryText: string, messages: BaseMessage[]): string {
|
|
|
250
257
|
*/
|
|
251
258
|
function restoreOriginalToolContent(
|
|
252
259
|
messages: BaseMessage[],
|
|
253
|
-
originalToolContent: Map<number, string> | undefined
|
|
260
|
+
originalToolContent: Map<number, string> | undefined,
|
|
261
|
+
maxContextTokens?: number
|
|
254
262
|
): BaseMessage[] {
|
|
255
263
|
if (originalToolContent == null || originalToolContent.size === 0) {
|
|
256
264
|
return messages;
|
|
257
265
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
|
|
267
|
+
const restorable: Array<{
|
|
268
|
+
index: number;
|
|
269
|
+
message: ToolMessage;
|
|
270
|
+
content: string;
|
|
271
|
+
}> = [];
|
|
272
|
+
for (const [index, content] of originalToolContent) {
|
|
273
|
+
const message = messages[index];
|
|
274
|
+
if (
|
|
275
|
+
message instanceof ToolMessage &&
|
|
276
|
+
!isComputerCallOutputMessage(message)
|
|
277
|
+
) {
|
|
278
|
+
restorable.push({ index, message, content });
|
|
270
279
|
}
|
|
271
280
|
}
|
|
281
|
+
if (restorable.length === 0) {
|
|
282
|
+
return messages;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Restored originals improve checkpoint quality, but they still feed a
|
|
287
|
+
* provider call. Share one tool-result budget across every restoration so
|
|
288
|
+
* several previously masked results cannot overflow the summarizer.
|
|
289
|
+
*/
|
|
290
|
+
let remainingChars = calculateMaxToolResultChars(maxContextTokens);
|
|
291
|
+
const restored = [...messages];
|
|
292
|
+
for (let i = 0; i < restorable.length; i++) {
|
|
293
|
+
const { index, message, content } = restorable[i];
|
|
294
|
+
const maxChars = Math.floor(remainingChars / (restorable.length - i));
|
|
295
|
+
const compacted = compactToolContent(content, maxChars).content;
|
|
296
|
+
restored[index] = cloneToolMessageWithContent(message, compacted);
|
|
297
|
+
remainingChars -= serializeToolContentBounded(compacted, maxChars).length;
|
|
298
|
+
}
|
|
272
299
|
return restored;
|
|
273
300
|
}
|
|
274
301
|
|
|
@@ -845,7 +872,8 @@ export function createSummarizeNode({
|
|
|
845
872
|
|
|
846
873
|
const restoredMessages = restoreOriginalToolContent(
|
|
847
874
|
state.messages,
|
|
848
|
-
originalPending
|
|
875
|
+
originalPending,
|
|
876
|
+
agentContext.maxContextTokens
|
|
849
877
|
);
|
|
850
878
|
|
|
851
879
|
const runnableConfig = config ?? graph.config;
|