@librechat/agents 3.2.0 → 3.2.2
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/graphs/Graph.cjs +154 -67
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +3 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +93 -7
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/stream.cjs +10 -8
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +155 -68
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +94 -8
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/stream.mjs +10 -8
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +246 -104
- package/src/graphs/__tests__/Graph.reasoning.test.ts +747 -0
- package/src/index.ts +1 -0
- package/src/messages/core.ts +126 -11
- package/src/messages/formatAgentMessages.test.ts +122 -0
- package/src/specs/deepseek.simple.test.ts +8 -3
- package/src/specs/moonshot.simple.test.ts +8 -3
- package/src/splitStream.test.ts +64 -0
- package/src/stream.ts +12 -10
package/src/index.ts
CHANGED
|
@@ -74,6 +74,7 @@ export type {
|
|
|
74
74
|
ChatOpenRouterCallOptions,
|
|
75
75
|
} from './llm/openrouter';
|
|
76
76
|
export { getChatModelClass } from './llm/providers';
|
|
77
|
+
export { FakeChatModel, createFakeStreamingLLM } from './llm/fake';
|
|
77
78
|
export { initializeModel } from './llm/init';
|
|
78
79
|
export { attemptInvoke, tryFallbackProviders } from './llm/invoke';
|
|
79
80
|
export { isThinkingEnabled, getMaxOutputTokensKey } from './llm/request';
|
package/src/messages/core.ts
CHANGED
|
@@ -8,9 +8,17 @@ import {
|
|
|
8
8
|
} from '@langchain/core/messages';
|
|
9
9
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
10
10
|
import type * as t from '@/types';
|
|
11
|
-
import { Providers } from '@/common';
|
|
11
|
+
import { ContentTypes, Providers } from '@/common';
|
|
12
12
|
import { toLangChainContent } from './langchain';
|
|
13
13
|
|
|
14
|
+
type ReasoningSummary = { summary?: Array<{ text?: string }> };
|
|
15
|
+
type ReasoningDetail = { type?: string; text?: string };
|
|
16
|
+
type ReasoningAdditionalKwargs = {
|
|
17
|
+
reasoning_content?: string | Partial<ReasoningSummary> | null;
|
|
18
|
+
reasoning?: string | Partial<ReasoningSummary> | null;
|
|
19
|
+
reasoning_details?: ReasoningDetail[] | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
14
22
|
export function getConverseOverrideMessage({
|
|
15
23
|
userMessage,
|
|
16
24
|
lastMessageX,
|
|
@@ -143,6 +151,75 @@ function reduceBlocks(blocks: ContentBlock[]): ContentBlock[] {
|
|
|
143
151
|
return reduced;
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
function getReasoningText(
|
|
155
|
+
value: string | Partial<ReasoningSummary> | null | undefined
|
|
156
|
+
): string | undefined {
|
|
157
|
+
if (typeof value === 'string') {
|
|
158
|
+
return value !== '' ? value : undefined;
|
|
159
|
+
}
|
|
160
|
+
const summaryText = value?.summary
|
|
161
|
+
?.map((summary) => summary.text ?? '')
|
|
162
|
+
.filter((text) => text !== '')
|
|
163
|
+
.join('');
|
|
164
|
+
return summaryText != null && summaryText !== '' ? summaryText : undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getReasoningDetailsText(
|
|
168
|
+
value: ReasoningDetail[] | null | undefined
|
|
169
|
+
): string | undefined {
|
|
170
|
+
if (!Array.isArray(value)) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const reasoningText = value
|
|
174
|
+
.filter((detail) => detail.type === 'reasoning.text')
|
|
175
|
+
.map((detail) => detail.text ?? '')
|
|
176
|
+
.filter((text) => text !== '')
|
|
177
|
+
.join('');
|
|
178
|
+
return reasoningText !== '' ? reasoningText : undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getAdditionalReasoningContent(
|
|
182
|
+
message: BaseMessage
|
|
183
|
+
): string | undefined {
|
|
184
|
+
const additionalKwargs =
|
|
185
|
+
message.additional_kwargs as ReasoningAdditionalKwargs | undefined;
|
|
186
|
+
if (additionalKwargs == null) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const reasoningContent = getReasoningText(
|
|
191
|
+
additionalKwargs.reasoning_content
|
|
192
|
+
);
|
|
193
|
+
if (reasoningContent != null) {
|
|
194
|
+
return reasoningContent;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const reasoning = getReasoningText(additionalKwargs.reasoning);
|
|
198
|
+
if (reasoning != null) {
|
|
199
|
+
return reasoning;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return getReasoningDetailsText(additionalKwargs.reasoning_details);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function hasReasoningContent(content: BaseMessage['content']): boolean {
|
|
206
|
+
if (!Array.isArray(content)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
return content.some((item) => {
|
|
210
|
+
if (typeof item !== 'object' || !('type' in item)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return (
|
|
214
|
+
item.type === ContentTypes.THINK ||
|
|
215
|
+
item.type === ContentTypes.THINKING ||
|
|
216
|
+
item.type === ContentTypes.REASONING ||
|
|
217
|
+
item.type === ContentTypes.REASONING_CONTENT ||
|
|
218
|
+
item.type === 'redacted_thinking'
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
146
223
|
export function modifyDeltaProperties(
|
|
147
224
|
provider: Providers,
|
|
148
225
|
obj?: AIMessageChunk
|
|
@@ -287,25 +364,52 @@ export function convertMessagesToContent(
|
|
|
287
364
|
): t.MessageContentComplex[] {
|
|
288
365
|
const processedContent: t.MessageContentComplex[] = [];
|
|
289
366
|
|
|
290
|
-
const
|
|
367
|
+
const addToolCallBoundary = (): number => {
|
|
368
|
+
processedContent.push({ type: ContentTypes.TEXT, text: '' });
|
|
369
|
+
return processedContent.length - 1;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const addContentPart = (message: BaseMessage | null): number | undefined => {
|
|
291
373
|
const content =
|
|
292
374
|
message?.lc_kwargs.content != null
|
|
293
375
|
? message.lc_kwargs.content
|
|
294
376
|
: message?.content;
|
|
295
377
|
if (content === undefined) {
|
|
296
|
-
return;
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
const reasoningContent =
|
|
381
|
+
message?._getType() === 'ai' && !hasReasoningContent(content)
|
|
382
|
+
? getAdditionalReasoningContent(message)
|
|
383
|
+
: undefined;
|
|
384
|
+
if (reasoningContent != null) {
|
|
385
|
+
processedContent.push({
|
|
386
|
+
type: ContentTypes.THINK,
|
|
387
|
+
think: reasoningContent,
|
|
388
|
+
});
|
|
297
389
|
}
|
|
298
390
|
if (typeof content === 'string') {
|
|
391
|
+
if (content === '') {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
299
394
|
processedContent.push({
|
|
300
|
-
type:
|
|
395
|
+
type: ContentTypes.TEXT,
|
|
301
396
|
text: content,
|
|
302
397
|
});
|
|
398
|
+
return processedContent.length - 1;
|
|
303
399
|
} else if (Array.isArray(content)) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
400
|
+
let textContentIndex: number | undefined;
|
|
401
|
+
for (const item of content) {
|
|
402
|
+
if (item == null || item.type === 'tool_use') {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
processedContent.push(item);
|
|
406
|
+
if (item.type === ContentTypes.TEXT) {
|
|
407
|
+
textContentIndex = processedContent.length - 1;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return textContentIndex;
|
|
308
411
|
}
|
|
412
|
+
return undefined;
|
|
309
413
|
};
|
|
310
414
|
|
|
311
415
|
let currentAIMessageIndex = -1;
|
|
@@ -328,8 +432,8 @@ export function convertMessagesToContent(
|
|
|
328
432
|
toolCallMap.set(tool_call.id, tool_call);
|
|
329
433
|
}
|
|
330
434
|
|
|
331
|
-
|
|
332
|
-
|
|
435
|
+
currentAIMessageIndex =
|
|
436
|
+
addContentPart(message) ?? addToolCallBoundary();
|
|
333
437
|
continue;
|
|
334
438
|
} else if (
|
|
335
439
|
messageType === 'tool' &&
|
|
@@ -361,6 +465,12 @@ export function convertMessagesToContent(
|
|
|
361
465
|
return processedContent;
|
|
362
466
|
}
|
|
363
467
|
|
|
468
|
+
function stringifyToolMessageContent(
|
|
469
|
+
content: ToolMessage['content'] | null | undefined
|
|
470
|
+
): string {
|
|
471
|
+
return content == null ? '' : String(content);
|
|
472
|
+
}
|
|
473
|
+
|
|
364
474
|
export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
|
|
365
475
|
const lastMessage = messages[messages.length - 1];
|
|
366
476
|
if (!(lastMessage instanceof ToolMessage)) return;
|
|
@@ -398,7 +508,12 @@ export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
|
|
|
398
508
|
) {
|
|
399
509
|
const base = Array.isArray(msg.content)
|
|
400
510
|
? msg.content
|
|
401
|
-
: [
|
|
511
|
+
: [
|
|
512
|
+
{
|
|
513
|
+
type: ContentTypes.TEXT,
|
|
514
|
+
text: stringifyToolMessageContent(msg.content),
|
|
515
|
+
},
|
|
516
|
+
];
|
|
402
517
|
msg.content = base.concat(msg.artifact.content);
|
|
403
518
|
}
|
|
404
519
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HumanMessage,
|
|
3
3
|
AIMessage,
|
|
4
|
+
AIMessageChunk,
|
|
4
5
|
SystemMessage,
|
|
5
6
|
ToolMessage,
|
|
6
7
|
} from '@langchain/core/messages';
|
|
7
8
|
import type { MessageContentComplex, TPayload } from '@/types';
|
|
8
9
|
import { formatAgentMessages } from './format';
|
|
10
|
+
import {
|
|
11
|
+
convertMessagesToContent,
|
|
12
|
+
formatAnthropicArtifactContent,
|
|
13
|
+
} from './core';
|
|
9
14
|
import { _convertMessagesToAnthropicPayload } from '@/llm/anthropic/utils/message_inputs';
|
|
10
15
|
import { Constants, ContentTypes, Providers } from '@/common';
|
|
11
16
|
|
|
@@ -619,6 +624,123 @@ describe('formatAgentMessages', () => {
|
|
|
619
624
|
]);
|
|
620
625
|
});
|
|
621
626
|
|
|
627
|
+
it('preserves tool-only assistant turn boundaries when converting messages to content', () => {
|
|
628
|
+
const messages = [
|
|
629
|
+
new AIMessage({
|
|
630
|
+
content: '',
|
|
631
|
+
tool_calls: [
|
|
632
|
+
{
|
|
633
|
+
id: 'call_1',
|
|
634
|
+
name: 'lookup',
|
|
635
|
+
args: { step: 1 },
|
|
636
|
+
type: 'tool_call' as const,
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
}),
|
|
640
|
+
new ToolMessage({
|
|
641
|
+
content: 'first result',
|
|
642
|
+
tool_call_id: 'call_1',
|
|
643
|
+
name: 'lookup',
|
|
644
|
+
}),
|
|
645
|
+
new AIMessage({
|
|
646
|
+
content: '',
|
|
647
|
+
tool_calls: [
|
|
648
|
+
{
|
|
649
|
+
id: 'call_2',
|
|
650
|
+
name: 'lookup',
|
|
651
|
+
args: { step: 2 },
|
|
652
|
+
type: 'tool_call' as const,
|
|
653
|
+
},
|
|
654
|
+
],
|
|
655
|
+
}),
|
|
656
|
+
new ToolMessage({
|
|
657
|
+
content: 'second result',
|
|
658
|
+
tool_call_id: 'call_2',
|
|
659
|
+
name: 'lookup',
|
|
660
|
+
}),
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
const content = convertMessagesToContent(messages);
|
|
664
|
+
expect(content).toHaveLength(4);
|
|
665
|
+
expect(content[0]).toMatchObject({
|
|
666
|
+
type: ContentTypes.TEXT,
|
|
667
|
+
text: '',
|
|
668
|
+
tool_call_ids: ['call_1'],
|
|
669
|
+
});
|
|
670
|
+
expect(content[1]).toMatchObject({
|
|
671
|
+
type: ContentTypes.TOOL_CALL,
|
|
672
|
+
tool_call: {
|
|
673
|
+
id: 'call_1',
|
|
674
|
+
name: 'lookup',
|
|
675
|
+
output: 'first result',
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
expect(content[2]).toMatchObject({
|
|
679
|
+
type: ContentTypes.TEXT,
|
|
680
|
+
text: '',
|
|
681
|
+
tool_call_ids: ['call_2'],
|
|
682
|
+
});
|
|
683
|
+
expect(content[3]).toMatchObject({
|
|
684
|
+
type: ContentTypes.TOOL_CALL,
|
|
685
|
+
tool_call: {
|
|
686
|
+
id: 'call_2',
|
|
687
|
+
name: 'lookup',
|
|
688
|
+
output: 'second result',
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const result = formatAgentMessages([{ role: 'assistant', content }]);
|
|
693
|
+
expect(result.messages).toHaveLength(4);
|
|
694
|
+
expect(result.messages[0]).toBeInstanceOf(AIMessage);
|
|
695
|
+
expect(result.messages[1]).toBeInstanceOf(ToolMessage);
|
|
696
|
+
expect(result.messages[2]).toBeInstanceOf(AIMessage);
|
|
697
|
+
expect(result.messages[3]).toBeInstanceOf(ToolMessage);
|
|
698
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[0].id).toBe(
|
|
699
|
+
'call_1'
|
|
700
|
+
);
|
|
701
|
+
expect((result.messages[1] as ToolMessage).tool_call_id).toBe('call_1');
|
|
702
|
+
expect((result.messages[2] as AIMessage).tool_calls?.[0].id).toBe(
|
|
703
|
+
'call_2'
|
|
704
|
+
);
|
|
705
|
+
expect((result.messages[3] as ToolMessage).tool_call_id).toBe('call_2');
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it('keeps absent tool content empty when merging Anthropic artifacts', () => {
|
|
709
|
+
const toolMessage = new ToolMessage({
|
|
710
|
+
content: '',
|
|
711
|
+
tool_call_id: 'call_artifact',
|
|
712
|
+
name: 'render',
|
|
713
|
+
artifact: {
|
|
714
|
+
content: [{ type: ContentTypes.TEXT, text: 'artifact text' }],
|
|
715
|
+
},
|
|
716
|
+
});
|
|
717
|
+
Object.defineProperty(toolMessage, 'content', {
|
|
718
|
+
value: undefined,
|
|
719
|
+
writable: true,
|
|
720
|
+
configurable: true,
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
formatAnthropicArtifactContent([
|
|
724
|
+
new AIMessageChunk({
|
|
725
|
+
content: '',
|
|
726
|
+
tool_calls: [
|
|
727
|
+
{
|
|
728
|
+
id: 'call_artifact',
|
|
729
|
+
name: 'render',
|
|
730
|
+
args: {},
|
|
731
|
+
type: 'tool_call' as const,
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
}),
|
|
735
|
+
toolMessage,
|
|
736
|
+
]);
|
|
737
|
+
|
|
738
|
+
expect(toolMessage.content).toEqual([
|
|
739
|
+
{ type: ContentTypes.TEXT, text: '' },
|
|
740
|
+
{ type: ContentTypes.TEXT, text: 'artifact text' },
|
|
741
|
+
]);
|
|
742
|
+
});
|
|
743
|
+
|
|
622
744
|
it('should dynamically discover tools from tool_search output and keep their tool calls', () => {
|
|
623
745
|
const tools = new Set(['tool_search', 'calculator']);
|
|
624
746
|
const payload = [
|
|
@@ -265,10 +265,15 @@ const skipTests = process.env.DEEPSEEK_API_KEY == null;
|
|
|
265
265
|
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
266
266
|
expect(finalContentParts).toBeDefined();
|
|
267
267
|
|
|
268
|
-
const
|
|
269
|
-
(part) =>
|
|
268
|
+
const supportedContentParts = finalContentParts?.every(
|
|
269
|
+
(part) =>
|
|
270
|
+
part.type === ContentTypes.TEXT || part.type === ContentTypes.THINK
|
|
270
271
|
);
|
|
271
|
-
expect(
|
|
272
|
+
expect(supportedContentParts).toBe(true);
|
|
273
|
+
const textParts =
|
|
274
|
+
finalContentParts?.filter((part) => part.type === ContentTypes.TEXT) ??
|
|
275
|
+
[];
|
|
276
|
+
expect(textParts.length).toBeGreaterThan(0);
|
|
272
277
|
|
|
273
278
|
expect(collectedUsage.length).toBeGreaterThan(0);
|
|
274
279
|
expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
|
|
@@ -283,10 +283,15 @@ const skipTests = process.env.MOONSHOT_API_KEY == null;
|
|
|
283
283
|
const finalContentParts = await run.processStream(inputs, testConfig);
|
|
284
284
|
expect(finalContentParts).toBeDefined();
|
|
285
285
|
|
|
286
|
-
const
|
|
287
|
-
(part) =>
|
|
286
|
+
const supportedContentParts = finalContentParts?.every(
|
|
287
|
+
(part) =>
|
|
288
|
+
part.type === ContentTypes.TEXT || part.type === ContentTypes.THINK
|
|
288
289
|
);
|
|
289
|
-
expect(
|
|
290
|
+
expect(supportedContentParts).toBe(true);
|
|
291
|
+
const textParts =
|
|
292
|
+
finalContentParts?.filter((part) => part.type === ContentTypes.TEXT) ??
|
|
293
|
+
[];
|
|
294
|
+
expect(textParts.length).toBeGreaterThan(0);
|
|
290
295
|
|
|
291
296
|
expect(collectedUsage.length).toBeGreaterThan(0);
|
|
292
297
|
expect(collectedUsage[0].input_tokens).toBeGreaterThan(0);
|
package/src/splitStream.test.ts
CHANGED
|
@@ -11,6 +11,18 @@ jest.mock('@/utils', () => ({
|
|
|
11
11
|
sleep: (): Promise<void> => Promise.resolve(),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
|
+
const createRunStep = (id: string): t.RunStep => ({
|
|
15
|
+
id,
|
|
16
|
+
stepIndex: 0,
|
|
17
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
18
|
+
index: 0,
|
|
19
|
+
stepDetails: {
|
|
20
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
21
|
+
message_creation: { message_id: id },
|
|
22
|
+
},
|
|
23
|
+
usage: null,
|
|
24
|
+
});
|
|
25
|
+
|
|
14
26
|
describe('Stream Generation and Handling', () => {
|
|
15
27
|
let mockHandlers: {
|
|
16
28
|
[GraphEvents.ON_RUN_STEP]: jest.Mock;
|
|
@@ -161,6 +173,58 @@ End code.`;
|
|
|
161
173
|
});
|
|
162
174
|
});
|
|
163
175
|
|
|
176
|
+
describe('ContentAggregator empty deltas', () => {
|
|
177
|
+
it('should ignore empty message delta content arrays', () => {
|
|
178
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
179
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
aggregateContent({
|
|
183
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
184
|
+
data: createRunStep('step_empty_message'),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
aggregateContent({
|
|
188
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
189
|
+
data: {
|
|
190
|
+
id: 'step_empty_message',
|
|
191
|
+
delta: { content: [] },
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
196
|
+
expect(contentParts).toEqual([]);
|
|
197
|
+
} finally {
|
|
198
|
+
warnSpy.mockRestore();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should ignore empty reasoning delta content arrays', () => {
|
|
203
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
204
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
aggregateContent({
|
|
208
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
209
|
+
data: createRunStep('step_empty_reasoning'),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
aggregateContent({
|
|
213
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
214
|
+
data: {
|
|
215
|
+
id: 'step_empty_reasoning',
|
|
216
|
+
delta: { content: [] },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
221
|
+
expect(contentParts).toEqual([]);
|
|
222
|
+
} finally {
|
|
223
|
+
warnSpy.mockRestore();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
164
228
|
describe('ContentAggregator with SplitStreamHandler', () => {
|
|
165
229
|
it('should aggregate content from multiple message blocks', async () => {
|
|
166
230
|
const runId = nanoid();
|
package/src/stream.ts
CHANGED
|
@@ -1317,6 +1317,14 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
1317
1317
|
number,
|
|
1318
1318
|
{ agentId?: string; groupId?: number }
|
|
1319
1319
|
>();
|
|
1320
|
+
const getFirstContentPart = (
|
|
1321
|
+
content?: t.MessageDelta['content'] | t.MessageContentComplex
|
|
1322
|
+
): t.MessageContentComplex | undefined => {
|
|
1323
|
+
if (content == null) {
|
|
1324
|
+
return undefined;
|
|
1325
|
+
}
|
|
1326
|
+
return Array.isArray(content) ? content[0] : content;
|
|
1327
|
+
};
|
|
1320
1328
|
|
|
1321
1329
|
const updateContent = (
|
|
1322
1330
|
index: number,
|
|
@@ -1588,11 +1596,8 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
1588
1596
|
return;
|
|
1589
1597
|
}
|
|
1590
1598
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
? messageDelta.delta.content[0]
|
|
1594
|
-
: messageDelta.delta.content;
|
|
1595
|
-
|
|
1599
|
+
const contentPart = getFirstContentPart(messageDelta.delta.content);
|
|
1600
|
+
if (contentPart != null) {
|
|
1596
1601
|
updateContent(runStep.index, contentPart);
|
|
1597
1602
|
}
|
|
1598
1603
|
} else if (
|
|
@@ -1612,11 +1617,8 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
1612
1617
|
return;
|
|
1613
1618
|
}
|
|
1614
1619
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
? reasoningDelta.delta.content[0]
|
|
1618
|
-
: reasoningDelta.delta.content;
|
|
1619
|
-
|
|
1620
|
+
const contentPart = getFirstContentPart(reasoningDelta.delta.content);
|
|
1621
|
+
if (contentPart != null) {
|
|
1620
1622
|
updateContent(runStep.index, contentPart);
|
|
1621
1623
|
}
|
|
1622
1624
|
} else if (event === GraphEvents.ON_RUN_STEP_DELTA) {
|