@librechat/agents 3.1.57 → 3.1.61
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 +326 -62
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +13 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +7 -27
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +303 -222
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +4 -4
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +6 -2
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +60 -0
- package/dist/cjs/llm/init.cjs.map +1 -0
- package/dist/cjs/llm/invoke.cjs +90 -0
- package/dist/cjs/llm/invoke.cjs.map +1 -0
- package/dist/cjs/llm/openai/index.cjs +2 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/request.cjs +41 -0
- package/dist/cjs/llm/request.cjs.map +1 -0
- package/dist/cjs/main.cjs +40 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +76 -89
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +156 -0
- package/dist/cjs/messages/contextPruning.cjs.map +1 -0
- package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
- package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
- package/dist/cjs/messages/core.cjs +23 -37
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +156 -11
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +1161 -49
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/reducer.cjs +87 -0
- package/dist/cjs/messages/reducer.cjs.map +1 -0
- package/dist/cjs/run.cjs +81 -42
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +54 -7
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/index.cjs +75 -0
- package/dist/cjs/summarization/index.cjs.map +1 -0
- package/dist/cjs/summarization/node.cjs +663 -0
- package/dist/cjs/summarization/node.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +16 -8
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +2 -0
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +115 -0
- package/dist/cjs/utils/errors.cjs.map +1 -0
- package/dist/cjs/utils/events.cjs +17 -0
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs +16 -0
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs +10 -0
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +247 -14
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/truncation.cjs +107 -0
- package/dist/cjs/utils/truncation.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +325 -61
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +13 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +8 -28
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +307 -226
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +4 -4
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +6 -2
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +58 -0
- package/dist/esm/llm/init.mjs.map +1 -0
- package/dist/esm/llm/invoke.mjs +87 -0
- package/dist/esm/llm/invoke.mjs.map +1 -0
- package/dist/esm/llm/openai/index.mjs +2 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/request.mjs +38 -0
- package/dist/esm/llm/request.mjs.map +1 -0
- package/dist/esm/main.mjs +13 -3
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +76 -89
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +154 -0
- package/dist/esm/messages/contextPruning.mjs.map +1 -0
- package/dist/esm/messages/contextPruningSettings.mjs +50 -0
- package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
- package/dist/esm/messages/core.mjs +23 -37
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +156 -11
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1158 -52
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/reducer.mjs +83 -0
- package/dist/esm/messages/reducer.mjs.map +1 -0
- package/dist/esm/run.mjs +82 -43
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +54 -7
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/index.mjs +73 -0
- package/dist/esm/summarization/index.mjs.map +1 -0
- package/dist/esm/summarization/node.mjs +659 -0
- package/dist/esm/summarization/node.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +16 -8
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +2 -0
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +111 -0
- package/dist/esm/utils/errors.mjs.map +1 -0
- package/dist/esm/utils/events.mjs +17 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs +16 -0
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs +10 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +245 -15
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/truncation.mjs +102 -0
- package/dist/esm/utils/truncation.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +124 -6
- package/dist/types/common/enum.d.ts +14 -1
- package/dist/types/graphs/Graph.d.ts +22 -27
- package/dist/types/index.d.ts +5 -0
- package/dist/types/llm/init.d.ts +18 -0
- package/dist/types/llm/invoke.d.ts +48 -0
- package/dist/types/llm/request.d.ts +14 -0
- package/dist/types/messages/contextPruning.d.ts +42 -0
- package/dist/types/messages/contextPruningSettings.d.ts +44 -0
- package/dist/types/messages/core.d.ts +1 -1
- package/dist/types/messages/format.d.ts +17 -1
- package/dist/types/messages/index.d.ts +3 -0
- package/dist/types/messages/prune.d.ts +162 -1
- package/dist/types/messages/reducer.d.ts +18 -0
- package/dist/types/run.d.ts +12 -1
- package/dist/types/summarization/index.d.ts +20 -0
- package/dist/types/summarization/node.d.ts +29 -0
- package/dist/types/tools/ToolNode.d.ts +3 -1
- package/dist/types/types/graph.d.ts +44 -6
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +30 -0
- package/dist/types/types/stream.d.ts +31 -4
- package/dist/types/types/summarize.d.ts +47 -0
- package/dist/types/types/tools.d.ts +7 -0
- package/dist/types/utils/errors.d.ts +28 -0
- package/dist/types/utils/events.d.ts +13 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/llm.d.ts +4 -0
- package/dist/types/utils/tokens.d.ts +14 -1
- package/dist/types/utils/truncation.d.ts +49 -0
- package/package.json +3 -3
- package/src/agents/AgentContext.ts +388 -58
- package/src/agents/__tests__/AgentContext.test.ts +265 -5
- package/src/common/enum.ts +13 -0
- package/src/events.ts +9 -39
- package/src/graphs/Graph.ts +468 -331
- package/src/index.ts +7 -0
- package/src/llm/anthropic/llm.spec.ts +3 -3
- package/src/llm/anthropic/utils/message_inputs.ts +6 -4
- package/src/llm/bedrock/llm.spec.ts +1 -1
- package/src/llm/bedrock/utils/message_inputs.ts +6 -2
- package/src/llm/init.ts +63 -0
- package/src/llm/invoke.ts +144 -0
- package/src/llm/request.ts +55 -0
- package/src/messages/__tests__/observationMasking.test.ts +221 -0
- package/src/messages/cache.ts +77 -102
- package/src/messages/contextPruning.ts +191 -0
- package/src/messages/contextPruningSettings.ts +90 -0
- package/src/messages/core.ts +32 -53
- package/src/messages/ensureThinkingBlock.test.ts +39 -39
- package/src/messages/format.ts +227 -15
- package/src/messages/formatAgentMessages.test.ts +511 -1
- package/src/messages/index.ts +3 -0
- package/src/messages/prune.ts +1548 -62
- package/src/messages/reducer.ts +22 -0
- package/src/run.ts +104 -51
- package/src/scripts/bedrock-merge-test.ts +1 -1
- package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
- package/src/scripts/test-thinking-handoff.ts +1 -1
- package/src/scripts/thinking-bedrock.ts +1 -1
- package/src/scripts/thinking.ts +1 -1
- package/src/specs/anthropic.simple.test.ts +1 -1
- package/src/specs/multi-agent-summarization.test.ts +396 -0
- package/src/specs/prune.test.ts +1196 -23
- package/src/specs/summarization-unit.test.ts +868 -0
- package/src/specs/summarization.test.ts +3827 -0
- package/src/specs/summarize-prune.test.ts +376 -0
- package/src/specs/thinking-handoff.test.ts +10 -10
- package/src/specs/thinking-prune.test.ts +7 -4
- package/src/specs/token-accounting-e2e.test.ts +1034 -0
- package/src/specs/token-accounting-pipeline.test.ts +882 -0
- package/src/specs/token-distribution-edge-case.test.ts +25 -26
- package/src/splitStream.test.ts +42 -33
- package/src/stream.ts +64 -11
- package/src/summarization/__tests__/aggregator.test.ts +153 -0
- package/src/summarization/__tests__/node.test.ts +708 -0
- package/src/summarization/__tests__/trigger.test.ts +50 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/node.ts +982 -0
- package/src/tools/ToolNode.ts +25 -3
- package/src/types/graph.ts +62 -7
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +32 -0
- package/src/types/stream.ts +45 -5
- package/src/types/summarize.ts +58 -0
- package/src/types/tools.ts +7 -0
- package/src/utils/errors.ts +117 -0
- package/src/utils/events.ts +31 -0
- package/src/utils/handlers.ts +18 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/llm.ts +12 -0
- package/src/utils/tokens.ts +336 -18
- package/src/utils/truncation.ts +124 -0
- package/src/scripts/image.ts +0 -180
|
@@ -75,6 +75,7 @@ describe('Token Distribution Edge Case Tests', () => {
|
|
|
75
75
|
startIndex: 0,
|
|
76
76
|
tokenCounter,
|
|
77
77
|
indexTokenCountMap: { ...indexTokenCountMap },
|
|
78
|
+
reserveRatio: 0,
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
// First call to establish lastCutOffIndex
|
|
@@ -115,18 +116,21 @@ describe('Token Distribution Edge Case Tests', () => {
|
|
|
115
116
|
|
|
116
117
|
expect(atLeastOnePrunedMessageUnchanged).toBe(true);
|
|
117
118
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
119
|
+
// Calibration uses input_tokens (30) only (no output), minus instruction overhead (0).
|
|
120
|
+
// Map stays in raw tiktoken space — calibrationRatio captures the multiplier.
|
|
121
|
+
// Context messages: indices 0, 3, 4 → raw sum unchanged.
|
|
122
|
+
// calibrationRatio × rawSum should approximate input_tokens (30).
|
|
123
|
+
const rawContextTokens =
|
|
121
124
|
(result.indexTokenCountMap[0] ?? 0) +
|
|
122
125
|
(result.indexTokenCountMap[3] ?? 0) +
|
|
123
126
|
(result.indexTokenCountMap[4] ?? 0);
|
|
124
|
-
expect(
|
|
127
|
+
expect(rawContextTokens).toBeGreaterThan(0);
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
const calibratedTotal = Math.round(
|
|
130
|
+
rawContextTokens * (result.calibrationRatio ?? 1)
|
|
131
|
+
);
|
|
132
|
+
const tokenDifference = Math.abs(calibratedTotal - 30);
|
|
133
|
+
expect(tokenDifference).toBeLessThan(10);
|
|
130
134
|
});
|
|
131
135
|
|
|
132
136
|
it('should handle the case when all messages fit within the token limit', () => {
|
|
@@ -174,28 +178,22 @@ describe('Token Distribution Edge Case Tests', () => {
|
|
|
174
178
|
usageMetadata,
|
|
175
179
|
});
|
|
176
180
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
// Calibration uses input_tokens (20) only, minus instruction overhead (0).
|
|
182
|
+
// messageTokenSum = 17 + 9 + 10 = 36. ratio = 20/36 = 0.556. Safe.
|
|
183
|
+
// Map stays raw — calibrationRatio captures the multiplier
|
|
184
|
+
expect(result.indexTokenCountMap[0]).toBe(indexTokenCountMap[0]);
|
|
185
|
+
expect(result.indexTokenCountMap[1]).toBe(indexTokenCountMap[1]);
|
|
186
|
+
expect(result.indexTokenCountMap[2]).toBe(indexTokenCountMap[2]);
|
|
181
187
|
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
Math.round(indexTokenCountMap[0] * expectedRatio)
|
|
185
|
-
);
|
|
186
|
-
expect(result.indexTokenCountMap[1]).toBe(
|
|
187
|
-
Math.round(indexTokenCountMap[1] * expectedRatio)
|
|
188
|
-
);
|
|
189
|
-
expect(result.indexTokenCountMap[2]).toBe(
|
|
190
|
-
Math.round(indexTokenCountMap[2] * expectedRatio)
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
// Verify that the sum of all tokens equals the total_tokens from usageMetadata
|
|
194
|
-
const totalTokens =
|
|
188
|
+
// rawSum × calibrationRatio should approximate input_tokens (20)
|
|
189
|
+
const rawTotal =
|
|
195
190
|
(result.indexTokenCountMap[0] ?? 0) +
|
|
196
191
|
(result.indexTokenCountMap[1] ?? 0) +
|
|
197
192
|
(result.indexTokenCountMap[2] ?? 0);
|
|
198
|
-
|
|
193
|
+
const calibratedTotal = Math.round(
|
|
194
|
+
rawTotal * (result.calibrationRatio ?? 1)
|
|
195
|
+
);
|
|
196
|
+
expect(Math.abs(calibratedTotal - 20)).toBeLessThanOrEqual(3);
|
|
199
197
|
});
|
|
200
198
|
|
|
201
199
|
it('should handle multiple pruning operations with token redistribution', () => {
|
|
@@ -230,6 +228,7 @@ describe('Token Distribution Edge Case Tests', () => {
|
|
|
230
228
|
startIndex: 0,
|
|
231
229
|
tokenCounter,
|
|
232
230
|
indexTokenCountMap: { ...indexTokenCountMap },
|
|
231
|
+
reserveRatio: 0,
|
|
233
232
|
});
|
|
234
233
|
|
|
235
234
|
// First pruning operation
|
package/src/splitStream.test.ts
CHANGED
|
@@ -254,7 +254,8 @@ After code.`;
|
|
|
254
254
|
|
|
255
255
|
const codeBlockPart = contentParts.find(
|
|
256
256
|
(part) =>
|
|
257
|
-
part?.type === ContentTypes.TEXT &&
|
|
257
|
+
part?.type === ContentTypes.TEXT &&
|
|
258
|
+
part.text.includes('```python') === true
|
|
258
259
|
);
|
|
259
260
|
|
|
260
261
|
expect(codeBlockPart).toBeDefined();
|
|
@@ -493,46 +494,51 @@ describe('SplitStreamHandler', () => {
|
|
|
493
494
|
|
|
494
495
|
// Check that content before <think> was handled as regular text
|
|
495
496
|
expect(
|
|
496
|
-
messageDeltaEvents.some(
|
|
497
|
-
(
|
|
498
|
-
|
|
499
|
-
|
|
497
|
+
messageDeltaEvents.some(
|
|
498
|
+
(event) =>
|
|
499
|
+
(
|
|
500
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
501
|
+
)?.text.includes('Here\'s') === true
|
|
500
502
|
)
|
|
501
503
|
).toBe(true);
|
|
502
504
|
|
|
503
505
|
// Check that <think> tag was handled as reasoning
|
|
504
506
|
expect(
|
|
505
|
-
reasoningDeltaEvents.some(
|
|
506
|
-
(
|
|
507
|
-
|
|
508
|
-
|
|
507
|
+
reasoningDeltaEvents.some(
|
|
508
|
+
(event) =>
|
|
509
|
+
(
|
|
510
|
+
event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined
|
|
511
|
+
)?.think.includes('<think>') === true
|
|
509
512
|
)
|
|
510
513
|
).toBe(true);
|
|
511
514
|
|
|
512
515
|
// Check that content inside <think> tags was handled as reasoning
|
|
513
516
|
expect(
|
|
514
|
-
reasoningDeltaEvents.some(
|
|
515
|
-
(
|
|
516
|
-
|
|
517
|
-
|
|
517
|
+
reasoningDeltaEvents.some(
|
|
518
|
+
(event) =>
|
|
519
|
+
(
|
|
520
|
+
event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined
|
|
521
|
+
)?.think.includes('thinking') === true
|
|
518
522
|
)
|
|
519
523
|
).toBe(true);
|
|
520
524
|
|
|
521
525
|
// Check that </think> tag was handled as reasoning
|
|
522
526
|
expect(
|
|
523
|
-
reasoningDeltaEvents.some(
|
|
524
|
-
(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
+
reasoningDeltaEvents.some(
|
|
528
|
+
(event) =>
|
|
529
|
+
(
|
|
530
|
+
event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined
|
|
531
|
+
)?.think.includes('</think>') === true
|
|
527
532
|
)
|
|
528
533
|
).toBe(true);
|
|
529
534
|
|
|
530
535
|
// Check that content after </think> was handled as regular text
|
|
531
536
|
expect(
|
|
532
|
-
messageDeltaEvents.some(
|
|
533
|
-
(
|
|
534
|
-
|
|
535
|
-
|
|
537
|
+
messageDeltaEvents.some(
|
|
538
|
+
(event) =>
|
|
539
|
+
(
|
|
540
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
541
|
+
)?.text.includes('Back') === true
|
|
536
542
|
)
|
|
537
543
|
).toBe(true);
|
|
538
544
|
});
|
|
@@ -568,10 +574,11 @@ describe('SplitStreamHandler', () => {
|
|
|
568
574
|
|
|
569
575
|
// Check that think tags inside code blocks were treated as regular text
|
|
570
576
|
expect(
|
|
571
|
-
messageDeltaEvents.some(
|
|
572
|
-
(
|
|
573
|
-
|
|
574
|
-
|
|
577
|
+
messageDeltaEvents.some(
|
|
578
|
+
(event) =>
|
|
579
|
+
(
|
|
580
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
581
|
+
)?.text.includes('Regular') === true
|
|
575
582
|
)
|
|
576
583
|
).toBe(true);
|
|
577
584
|
|
|
@@ -622,10 +629,11 @@ describe('SplitStreamHandler', () => {
|
|
|
622
629
|
|
|
623
630
|
// Check that content before <think> was handled as regular text
|
|
624
631
|
expect(
|
|
625
|
-
messageDeltaEvents.some(
|
|
626
|
-
(
|
|
627
|
-
|
|
628
|
-
|
|
632
|
+
messageDeltaEvents.some(
|
|
633
|
+
(event) =>
|
|
634
|
+
(
|
|
635
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
636
|
+
)?.text.includes('regular') === true
|
|
629
637
|
)
|
|
630
638
|
).toBe(true);
|
|
631
639
|
|
|
@@ -673,10 +681,11 @@ describe('SplitStreamHandler', () => {
|
|
|
673
681
|
|
|
674
682
|
// Check that content after </think> was handled as regular text
|
|
675
683
|
expect(
|
|
676
|
-
messageDeltaEvents.some(
|
|
677
|
-
(
|
|
678
|
-
|
|
679
|
-
|
|
684
|
+
messageDeltaEvents.some(
|
|
685
|
+
(event) =>
|
|
686
|
+
(
|
|
687
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
688
|
+
)?.text.includes('Back') === true
|
|
680
689
|
)
|
|
681
690
|
).toBe(true);
|
|
682
691
|
|
package/src/stream.ts
CHANGED
|
@@ -148,6 +148,7 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
148
148
|
if (!graph.config) {
|
|
149
149
|
throw new Error('Config not found in graph');
|
|
150
150
|
}
|
|
151
|
+
|
|
151
152
|
if (!data.chunk) {
|
|
152
153
|
console.warn(`No chunk found in ${event} event`);
|
|
153
154
|
return;
|
|
@@ -513,6 +514,18 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
513
514
|
};
|
|
514
515
|
|
|
515
516
|
contentParts[index] = update;
|
|
517
|
+
} else if (partType === ContentTypes.SUMMARY) {
|
|
518
|
+
const currentSummary = contentParts[index] as
|
|
519
|
+
| t.SummaryContentBlock
|
|
520
|
+
| undefined;
|
|
521
|
+
const incoming = contentPart as t.SummaryContentBlock;
|
|
522
|
+
contentParts[index] = {
|
|
523
|
+
...incoming,
|
|
524
|
+
content: [
|
|
525
|
+
...(currentSummary?.content ?? []),
|
|
526
|
+
...(incoming.content ?? []),
|
|
527
|
+
],
|
|
528
|
+
};
|
|
516
529
|
} else if (
|
|
517
530
|
partType === ContentTypes.IMAGE_URL &&
|
|
518
531
|
'image_url' in contentPart
|
|
@@ -618,9 +631,41 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
618
631
|
| t.RunStep
|
|
619
632
|
| t.AgentUpdate
|
|
620
633
|
| t.MessageDeltaEvent
|
|
634
|
+
| t.ReasoningDeltaEvent
|
|
621
635
|
| t.RunStepDeltaEvent
|
|
636
|
+
| t.SummarizeDeltaData
|
|
637
|
+
| t.SummarizeCompleteEvent
|
|
622
638
|
| { result: t.ToolEndEvent };
|
|
623
639
|
}): void => {
|
|
640
|
+
if (event === GraphEvents.ON_SUMMARIZE_DELTA) {
|
|
641
|
+
const deltaData = data as t.SummarizeDeltaData;
|
|
642
|
+
const runStep = stepMap.get(deltaData.id);
|
|
643
|
+
if (!runStep) {
|
|
644
|
+
console.warn('No run step found for summarize delta event');
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
updateContent(runStep.index, deltaData.delta.summary);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (event === GraphEvents.ON_SUMMARIZE_COMPLETE) {
|
|
652
|
+
const completeData = data as t.SummarizeCompleteEvent;
|
|
653
|
+
const summary = completeData.summary;
|
|
654
|
+
if (!summary?.boundary) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const runStep = stepMap.get(summary.boundary.messageId);
|
|
658
|
+
if (!runStep) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
// Replace accumulated delta text with the authoritative final summary.
|
|
662
|
+
// Multi-stage summarization streams deltas from each chunk, which
|
|
663
|
+
// concatenate in updateContent. This event carries only the correct
|
|
664
|
+
// final text from the last stage.
|
|
665
|
+
contentParts[runStep.index] = summary;
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
624
669
|
if (event === GraphEvents.ON_RUN_STEP) {
|
|
625
670
|
const runStep = data as t.RunStep;
|
|
626
671
|
stepMap.set(runStep.id, runStep);
|
|
@@ -641,7 +686,10 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
641
686
|
contentMetaMap.set(runStep.index, existingMeta);
|
|
642
687
|
}
|
|
643
688
|
|
|
644
|
-
|
|
689
|
+
if (runStep.summary != null) {
|
|
690
|
+
updateContent(runStep.index, runStep.summary);
|
|
691
|
+
}
|
|
692
|
+
|
|
645
693
|
if (
|
|
646
694
|
runStep.stepDetails.type === StepTypes.TOOL_CALLS &&
|
|
647
695
|
runStep.stepDetails.tool_calls
|
|
@@ -732,24 +780,29 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
732
780
|
});
|
|
733
781
|
}
|
|
734
782
|
} else if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
735
|
-
const { result } = data as unknown as {
|
|
783
|
+
const { result } = data as unknown as {
|
|
784
|
+
result:
|
|
785
|
+
| t.ToolEndEvent
|
|
786
|
+
| (t.SummaryCompleted & { id: string; index: number });
|
|
787
|
+
};
|
|
736
788
|
|
|
737
789
|
const { id: stepId } = result;
|
|
738
790
|
|
|
739
791
|
const runStep = stepMap.get(stepId);
|
|
740
792
|
if (!runStep) {
|
|
741
|
-
console.warn(
|
|
742
|
-
'No run step or runId found for completed tool call event'
|
|
743
|
-
);
|
|
793
|
+
console.warn('No run step or runId found for completed step event');
|
|
744
794
|
return;
|
|
745
795
|
}
|
|
746
796
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
797
|
+
if (result.type === ContentTypes.SUMMARY && 'summary' in result) {
|
|
798
|
+
contentParts[runStep.index] = result.summary as t.MessageContentComplex;
|
|
799
|
+
} else if ('tool_call' in result) {
|
|
800
|
+
const contentPart: t.MessageContentComplex = {
|
|
801
|
+
type: ContentTypes.TOOL_CALL,
|
|
802
|
+
tool_call: (result as t.ToolEndEvent).tool_call,
|
|
803
|
+
};
|
|
804
|
+
updateContent(runStep.index, contentPart, true);
|
|
805
|
+
}
|
|
753
806
|
}
|
|
754
807
|
};
|
|
755
808
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createContentAggregator } from '@/stream';
|
|
2
|
+
import { ContentTypes, GraphEvents, StepTypes } from '@/common';
|
|
3
|
+
import type * as t from '@/types';
|
|
4
|
+
|
|
5
|
+
describe('createContentAggregator – SUMMARY accumulation', () => {
|
|
6
|
+
it('accumulates text from multiple ON_SUMMARIZE_DELTA events', () => {
|
|
7
|
+
const { aggregateContent, contentParts } = createContentAggregator();
|
|
8
|
+
|
|
9
|
+
// Register a run step with a summary placeholder
|
|
10
|
+
const runStep: t.RunStep = {
|
|
11
|
+
stepIndex: 0,
|
|
12
|
+
id: 'step_sum_1',
|
|
13
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
14
|
+
index: 0,
|
|
15
|
+
stepDetails: {
|
|
16
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
17
|
+
message_creation: { message_id: 'step_sum_1' },
|
|
18
|
+
},
|
|
19
|
+
summary: {
|
|
20
|
+
type: ContentTypes.SUMMARY,
|
|
21
|
+
content: [],
|
|
22
|
+
tokenCount: 0,
|
|
23
|
+
provider: 'openai',
|
|
24
|
+
},
|
|
25
|
+
usage: null,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
aggregateContent({
|
|
29
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
30
|
+
data: runStep,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// The run step registration sets the initial placeholder
|
|
34
|
+
expect(contentParts[0]).toEqual(
|
|
35
|
+
expect.objectContaining({ type: ContentTypes.SUMMARY, content: [] })
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Send multiple deltas with content chunks
|
|
39
|
+
aggregateContent({
|
|
40
|
+
event: GraphEvents.ON_SUMMARIZE_DELTA,
|
|
41
|
+
data: {
|
|
42
|
+
id: 'step_sum_1',
|
|
43
|
+
delta: {
|
|
44
|
+
summary: {
|
|
45
|
+
type: ContentTypes.SUMMARY,
|
|
46
|
+
content: [{ type: 'text', text: 'Hello ' }],
|
|
47
|
+
tokenCount: 0,
|
|
48
|
+
provider: 'openai',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
} as t.SummarizeDeltaData,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const afterFirst = contentParts[0] as t.SummaryContentBlock;
|
|
55
|
+
expect(afterFirst.content).toHaveLength(1);
|
|
56
|
+
expect((afterFirst.content![0] as { text: string }).text).toBe('Hello ');
|
|
57
|
+
|
|
58
|
+
aggregateContent({
|
|
59
|
+
event: GraphEvents.ON_SUMMARIZE_DELTA,
|
|
60
|
+
data: {
|
|
61
|
+
id: 'step_sum_1',
|
|
62
|
+
delta: {
|
|
63
|
+
summary: {
|
|
64
|
+
type: ContentTypes.SUMMARY,
|
|
65
|
+
content: [{ type: 'text', text: 'world!' }],
|
|
66
|
+
tokenCount: 0,
|
|
67
|
+
provider: 'openai',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
} as t.SummarizeDeltaData,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Should accumulate content blocks, not replace
|
|
74
|
+
const afterSecond = contentParts[0] as t.SummaryContentBlock;
|
|
75
|
+
expect(afterSecond.content).toHaveLength(2);
|
|
76
|
+
expect((afterSecond.content![0] as { text: string }).text).toBe('Hello ');
|
|
77
|
+
expect((afterSecond.content![1] as { text: string }).text).toBe('world!');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('preserves metadata fields from the latest delta', () => {
|
|
81
|
+
const { aggregateContent, contentParts } = createContentAggregator();
|
|
82
|
+
|
|
83
|
+
const runStep: t.RunStep = {
|
|
84
|
+
stepIndex: 0,
|
|
85
|
+
id: 'step_sum_2',
|
|
86
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
87
|
+
index: 0,
|
|
88
|
+
stepDetails: {
|
|
89
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
90
|
+
message_creation: { message_id: 'step_sum_2' },
|
|
91
|
+
},
|
|
92
|
+
summary: {
|
|
93
|
+
type: ContentTypes.SUMMARY,
|
|
94
|
+
content: [],
|
|
95
|
+
tokenCount: 0,
|
|
96
|
+
provider: 'anthropic',
|
|
97
|
+
model: 'claude-sonnet-4-5',
|
|
98
|
+
},
|
|
99
|
+
usage: null,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
aggregateContent({
|
|
103
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
104
|
+
data: runStep,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
aggregateContent({
|
|
108
|
+
event: GraphEvents.ON_SUMMARIZE_DELTA,
|
|
109
|
+
data: {
|
|
110
|
+
id: 'step_sum_2',
|
|
111
|
+
delta: {
|
|
112
|
+
summary: {
|
|
113
|
+
type: ContentTypes.SUMMARY,
|
|
114
|
+
content: [{ type: 'text', text: 'chunk' }],
|
|
115
|
+
tokenCount: 0,
|
|
116
|
+
provider: 'anthropic',
|
|
117
|
+
model: 'claude-sonnet-4-5',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
} as t.SummarizeDeltaData,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const part = contentParts[0] as t.SummaryContentBlock;
|
|
124
|
+
expect(part.provider).toBe('anthropic');
|
|
125
|
+
expect(part.model).toBe('claude-sonnet-4-5');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('handles delta when no run step exists', () => {
|
|
129
|
+
const { aggregateContent, contentParts } = createContentAggregator();
|
|
130
|
+
|
|
131
|
+
// No run step registered — should warn and not crash
|
|
132
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
133
|
+
|
|
134
|
+
aggregateContent({
|
|
135
|
+
event: GraphEvents.ON_SUMMARIZE_DELTA,
|
|
136
|
+
data: {
|
|
137
|
+
id: 'nonexistent',
|
|
138
|
+
delta: {
|
|
139
|
+
summary: {
|
|
140
|
+
type: ContentTypes.SUMMARY,
|
|
141
|
+
content: [{ type: 'text', text: 'orphan' }],
|
|
142
|
+
tokenCount: 0,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
} as t.SummarizeDeltaData,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
149
|
+
expect(contentParts.length).toBe(0);
|
|
150
|
+
|
|
151
|
+
consoleSpy.mockRestore();
|
|
152
|
+
});
|
|
153
|
+
});
|