ai 7.0.82 → 7.0.84
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/CHANGELOG.md +24 -0
- package/dist/index.d.ts +77 -37
- package/dist/index.js +352 -129
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +76 -1
- package/docs/03-ai-sdk-harnesses/04-skills.mdx +6 -2
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +10 -3
- package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +4 -2
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +35 -6
- package/package.json +11 -12
- package/src/agent/create-agent-ui-stream.ts +2 -2
- package/src/agent/tool-loop-agent-settings.ts +9 -0
- package/src/generate-text/index.ts +2 -0
- package/src/generate-text/smooth-stream.ts +15 -3
- package/src/generate-text/stream-text.ts +111 -52
- package/src/ui/direct-chat-transport.ts +2 -2
- package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +1 -0
- package/src/ui/validate-ui-messages.ts +136 -55
- package/src/ui-message-stream/create-ui-message-stream.ts +47 -17
- package/src/ui-message-stream/handle-ui-message-stream-finish.ts +51 -14
- package/src/ui-message-stream/index.ts +5 -1
- package/src/ui-message-stream/to-ui-message-stream.ts +108 -26
- package/src/ui-message-stream/ui-message-stream-on-end-callback.ts +7 -0
- package/src/ui-message-stream/ui-message-stream-outcome.ts +12 -0
- package/src/ui-message-stream/ui-message-stream-writer.ts +15 -0
- package/src/util/create-stitchable-stream.ts +31 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TypeValidationError, type JSONObject } from '@ai-sdk/provider';
|
|
2
2
|
import {
|
|
3
3
|
lazySchema,
|
|
4
|
+
safeValidateTypes,
|
|
4
5
|
validateTypes,
|
|
5
6
|
zodSchema,
|
|
6
7
|
type FlexibleSchema,
|
|
@@ -13,6 +14,7 @@ import { providerMetadataSchema } from '../types/provider-metadata';
|
|
|
13
14
|
import { z, type ZodType } from '../util/zod';
|
|
14
15
|
import type {
|
|
15
16
|
DataUIPart,
|
|
17
|
+
DynamicToolUIPart,
|
|
16
18
|
InferUIMessageData,
|
|
17
19
|
InferUIMessageTools,
|
|
18
20
|
ToolUIPart,
|
|
@@ -26,6 +28,25 @@ const toolMetadataSchema: ZodType<JSONObject> = z.record(
|
|
|
26
28
|
|
|
27
29
|
const providerReferenceSchema = z.record(z.string(), z.string());
|
|
28
30
|
|
|
31
|
+
function isEmptyObject(value: unknown): value is Record<string, never> {
|
|
32
|
+
return (
|
|
33
|
+
value != null &&
|
|
34
|
+
typeof value === 'object' &&
|
|
35
|
+
!Array.isArray(value) &&
|
|
36
|
+
Object.keys(value).length === 0
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function asDynamicToolPart(toolPart: ToolUIPart): DynamicToolUIPart {
|
|
41
|
+
const { type, ...part } = toolPart;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
...part,
|
|
45
|
+
type: 'dynamic-tool',
|
|
46
|
+
toolName: type.slice(5),
|
|
47
|
+
} as DynamicToolUIPart;
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
const uiMessagesSchema = lazySchema(() =>
|
|
30
51
|
zodSchema(
|
|
31
52
|
z
|
|
@@ -384,17 +405,7 @@ export type SafeValidateUIMessagesResult<UI_MESSAGE extends UIMessage> =
|
|
|
384
405
|
error: Error;
|
|
385
406
|
};
|
|
386
407
|
|
|
387
|
-
|
|
388
|
-
* Validates a list of UI messages like `validateUIMessages`,
|
|
389
|
-
* but instead of throwing it returns `{ success: true, data }`
|
|
390
|
-
* or `{ success: false, error }`.
|
|
391
|
-
*/
|
|
392
|
-
export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
393
|
-
messages,
|
|
394
|
-
metadataSchema,
|
|
395
|
-
dataSchemas,
|
|
396
|
-
tools,
|
|
397
|
-
}: {
|
|
408
|
+
type ValidateUIMessagesOptions<UI_MESSAGE extends UIMessage> = {
|
|
398
409
|
messages: unknown;
|
|
399
410
|
metadataSchema?: FlexibleSchema<UIMessage['metadata']>;
|
|
400
411
|
dataSchemas?: {
|
|
@@ -408,7 +419,21 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
408
419
|
InferUIMessageTools<UI_MESSAGE>[NAME]['output']
|
|
409
420
|
>;
|
|
410
421
|
};
|
|
411
|
-
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
|
|
425
|
+
{
|
|
426
|
+
messages,
|
|
427
|
+
metadataSchema,
|
|
428
|
+
dataSchemas,
|
|
429
|
+
tools,
|
|
430
|
+
}: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
431
|
+
{
|
|
432
|
+
convertMissingTerminalToolsToDynamic,
|
|
433
|
+
}: {
|
|
434
|
+
convertMissingTerminalToolsToDynamic: boolean;
|
|
435
|
+
},
|
|
436
|
+
): Promise<SafeValidateUIMessagesResult<UI_MESSAGE>> {
|
|
412
437
|
try {
|
|
413
438
|
if (messages == null) {
|
|
414
439
|
return {
|
|
@@ -439,7 +464,10 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
439
464
|
}
|
|
440
465
|
}
|
|
441
466
|
|
|
442
|
-
|
|
467
|
+
const shouldValidateToolParts =
|
|
468
|
+
tools != null || convertMissingTerminalToolsToDynamic;
|
|
469
|
+
|
|
470
|
+
if (dataSchemas || shouldValidateToolParts) {
|
|
443
471
|
for (const [msgIdx, message] of validatedMessages.entries()) {
|
|
444
472
|
for (const [partIdx, part] of message.parts.entries()) {
|
|
445
473
|
// Data part validation
|
|
@@ -475,19 +503,26 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
475
503
|
}
|
|
476
504
|
|
|
477
505
|
// Tool part validation
|
|
478
|
-
if (
|
|
506
|
+
if (shouldValidateToolParts && part.type.startsWith('tool-')) {
|
|
479
507
|
const toolPart = part as ToolUIPart<
|
|
480
508
|
InferUIMessageTools<UI_MESSAGE>
|
|
481
509
|
>;
|
|
482
510
|
const toolName = toolPart.type.slice(5);
|
|
483
|
-
const tool = getOwn(tools, toolName);
|
|
511
|
+
const tool = tools == null ? undefined : getOwn(tools, toolName);
|
|
512
|
+
const isTerminal =
|
|
513
|
+
toolPart.state === 'output-available' ||
|
|
514
|
+
toolPart.state === 'output-error' ||
|
|
515
|
+
toolPart.state === 'output-denied';
|
|
484
516
|
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
517
|
+
if (!tool && isTerminal) {
|
|
518
|
+
if (tools != null || convertMissingTerminalToolsToDynamic) {
|
|
519
|
+
// Persisted terminal history can reference tools that are no
|
|
520
|
+
// longer registered. Normalize those parts so callers do not
|
|
521
|
+
// receive unvalidated values under current static tool types.
|
|
522
|
+
message.parts[partIdx] = asDynamicToolPart(
|
|
523
|
+
toolPart,
|
|
524
|
+
) as (typeof message.parts)[number];
|
|
525
|
+
}
|
|
491
526
|
continue;
|
|
492
527
|
}
|
|
493
528
|
|
|
@@ -507,19 +542,53 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
507
542
|
};
|
|
508
543
|
}
|
|
509
544
|
|
|
545
|
+
const inputValidationContext = {
|
|
546
|
+
field: `messages[${msgIdx}].parts[${partIdx}].input`,
|
|
547
|
+
entityName: toolName,
|
|
548
|
+
entityId: toolPart.toolCallId,
|
|
549
|
+
};
|
|
550
|
+
let convertToDynamic = false;
|
|
551
|
+
|
|
510
552
|
// Tool input validation
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
553
|
+
if (toolPart.state === 'output-error') {
|
|
554
|
+
// Failed calls can retain invalid input. Keep them loadable, but
|
|
555
|
+
// expose incompatible input as unknown instead of the current
|
|
556
|
+
// static tool input type.
|
|
557
|
+
if (toolPart.input !== undefined) {
|
|
558
|
+
const result = await safeValidateTypes({
|
|
559
|
+
value: toolPart.input,
|
|
560
|
+
schema: tool.inputSchema,
|
|
561
|
+
context: inputValidationContext,
|
|
562
|
+
});
|
|
563
|
+
convertToDynamic = !result.success;
|
|
564
|
+
}
|
|
565
|
+
} else if (toolPart.state === 'output-available') {
|
|
566
|
+
const result = await safeValidateTypes({
|
|
567
|
+
value: toolPart.input,
|
|
568
|
+
schema: tool.inputSchema,
|
|
569
|
+
context: inputValidationContext,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
if (!result.success) {
|
|
573
|
+
// Empty terminal input can represent aborted or incomplete
|
|
574
|
+
// history whose input was never streamed. Preserve it without
|
|
575
|
+
// claiming that it matches the current static input type.
|
|
576
|
+
if (isEmptyObject(toolPart.input)) {
|
|
577
|
+
convertToDynamic = true;
|
|
578
|
+
} else {
|
|
579
|
+
throw result.error;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} else if (
|
|
583
|
+
toolPart.state === 'input-available' ||
|
|
584
|
+
toolPart.state === 'approval-requested' ||
|
|
585
|
+
toolPart.state === 'approval-responded' ||
|
|
586
|
+
toolPart.state === 'output-denied'
|
|
587
|
+
) {
|
|
515
588
|
await validateTypes({
|
|
516
589
|
value: toolPart.input,
|
|
517
590
|
schema: tool.inputSchema,
|
|
518
|
-
context:
|
|
519
|
-
field: `messages[${msgIdx}].parts[${partIdx}].input`,
|
|
520
|
-
entityName: toolName,
|
|
521
|
-
entityId: toolPart.toolCallId,
|
|
522
|
-
},
|
|
591
|
+
context: inputValidationContext,
|
|
523
592
|
});
|
|
524
593
|
}
|
|
525
594
|
|
|
@@ -535,6 +604,12 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
535
604
|
},
|
|
536
605
|
});
|
|
537
606
|
}
|
|
607
|
+
|
|
608
|
+
if (convertToDynamic) {
|
|
609
|
+
message.parts[partIdx] = asDynamicToolPart(
|
|
610
|
+
toolPart,
|
|
611
|
+
) as (typeof message.parts)[number];
|
|
612
|
+
}
|
|
538
613
|
}
|
|
539
614
|
}
|
|
540
615
|
}
|
|
@@ -554,6 +629,19 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
554
629
|
}
|
|
555
630
|
}
|
|
556
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Validates a list of UI messages like `validateUIMessages`,
|
|
634
|
+
* but instead of throwing it returns `{ success: true, data }`
|
|
635
|
+
* or `{ success: false, error }`.
|
|
636
|
+
*/
|
|
637
|
+
export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>(
|
|
638
|
+
options: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
639
|
+
): Promise<SafeValidateUIMessagesResult<UI_MESSAGE>> {
|
|
640
|
+
return safeValidateUIMessagesInternal(options, {
|
|
641
|
+
convertMissingTerminalToolsToDynamic: false,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
557
645
|
/**
|
|
558
646
|
* Validates a list of UI messages.
|
|
559
647
|
*
|
|
@@ -561,31 +649,24 @@ export async function safeValidateUIMessages<UI_MESSAGE extends UIMessage>({
|
|
|
561
649
|
* the corresponding schemas are provided. Otherwise, they are assumed to be
|
|
562
650
|
* valid.
|
|
563
651
|
*/
|
|
564
|
-
export async function validateUIMessages<UI_MESSAGE extends UIMessage>(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
};
|
|
583
|
-
}): Promise<Array<UI_MESSAGE>> {
|
|
584
|
-
const response = await safeValidateUIMessages({
|
|
585
|
-
messages,
|
|
586
|
-
metadataSchema,
|
|
587
|
-
dataSchemas,
|
|
588
|
-
tools,
|
|
652
|
+
export async function validateUIMessages<UI_MESSAGE extends UIMessage>(
|
|
653
|
+
options: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
654
|
+
): Promise<Array<UI_MESSAGE>> {
|
|
655
|
+
const response = await safeValidateUIMessages(options);
|
|
656
|
+
|
|
657
|
+
if (!response.success) throw response.error;
|
|
658
|
+
|
|
659
|
+
return response.data;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export async function validateUIMessagesForAgent<UI_MESSAGE extends UIMessage>(
|
|
663
|
+
options: ValidateUIMessagesOptions<UI_MESSAGE>,
|
|
664
|
+
): Promise<Array<UI_MESSAGE>> {
|
|
665
|
+
const response = await safeValidateUIMessagesInternal(options, {
|
|
666
|
+
// Agent tool sets can include ephemeral tools (for example, tools from a
|
|
667
|
+
// disconnected MCP server), so terminal history is converted to dynamic
|
|
668
|
+
// tool parts when those tools are no longer registered.
|
|
669
|
+
convertMissingTerminalToolsToDynamic: true,
|
|
589
670
|
});
|
|
590
671
|
|
|
591
672
|
if (!response.success) throw response.error;
|
|
@@ -6,9 +6,10 @@ import type { UIMessage } from '../ui/ui-messages';
|
|
|
6
6
|
import { handleUIMessageStreamFinish } from './handle-ui-message-stream-finish';
|
|
7
7
|
import type { InferUIMessageChunk } from './ui-message-chunks';
|
|
8
8
|
import type { UIMessageStreamOnEndCallback } from './ui-message-stream-on-end-callback';
|
|
9
|
+
import type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
|
|
9
10
|
import type { UIMessageStreamOnStepEndCallback } from './ui-message-stream-on-step-end-callback';
|
|
10
11
|
import type { UIMessageStreamOnStepFinishCallback } from './ui-message-stream-on-step-finish-callback';
|
|
11
|
-
import type {
|
|
12
|
+
import type { UIMessageStreamWriterWithOutcome } from './ui-message-stream-writer';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Creates a UI message stream that can be used to send messages to the client.
|
|
@@ -36,7 +37,7 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
36
37
|
generateId = generateIdFunc,
|
|
37
38
|
}: {
|
|
38
39
|
execute: (options: {
|
|
39
|
-
writer:
|
|
40
|
+
writer: UIMessageStreamWriterWithOutcome<UI_MESSAGE>;
|
|
40
41
|
}) => Promise<void> | void;
|
|
41
42
|
onError?: (error: unknown) => string;
|
|
42
43
|
|
|
@@ -72,6 +73,7 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
72
73
|
>;
|
|
73
74
|
|
|
74
75
|
const ongoingStreamPromises: Promise<void>[] = [];
|
|
76
|
+
let outcome: UIMessageStreamOutcome = { status: 'unknown' };
|
|
75
77
|
|
|
76
78
|
const stream = new ReadableStream({
|
|
77
79
|
start(controllerArg) {
|
|
@@ -87,6 +89,42 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
function setOutcome(newOutcome: UIMessageStreamOutcome) {
|
|
93
|
+
if (outcome.status === 'unknown' && newOutcome.status !== 'unknown') {
|
|
94
|
+
outcome = newOutcome;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function failOutcome(error: unknown) {
|
|
99
|
+
outcome = { status: 'failed', error };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function safeError(error: unknown) {
|
|
103
|
+
try {
|
|
104
|
+
controller.error(error);
|
|
105
|
+
} catch {
|
|
106
|
+
// suppress errors when the stream has been closed
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleError(error: unknown) {
|
|
111
|
+
failOutcome(error);
|
|
112
|
+
|
|
113
|
+
let errorText: string;
|
|
114
|
+
try {
|
|
115
|
+
errorText = onError(error);
|
|
116
|
+
} catch (onErrorError) {
|
|
117
|
+
failOutcome(onErrorError);
|
|
118
|
+
safeError(onErrorError);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
safeEnqueue({
|
|
123
|
+
type: 'error',
|
|
124
|
+
errorText,
|
|
125
|
+
} as InferUIMessageChunk<UI_MESSAGE>);
|
|
126
|
+
}
|
|
127
|
+
|
|
90
128
|
try {
|
|
91
129
|
const result = execute({
|
|
92
130
|
writer: {
|
|
@@ -103,13 +141,11 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
103
141
|
safeEnqueue(value);
|
|
104
142
|
}
|
|
105
143
|
})().catch(error => {
|
|
106
|
-
|
|
107
|
-
type: 'error',
|
|
108
|
-
errorText: onError(error),
|
|
109
|
-
} as InferUIMessageChunk<UI_MESSAGE>);
|
|
144
|
+
handleError(error);
|
|
110
145
|
}),
|
|
111
146
|
);
|
|
112
147
|
},
|
|
148
|
+
setOutcome,
|
|
113
149
|
onError,
|
|
114
150
|
},
|
|
115
151
|
});
|
|
@@ -117,30 +153,23 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
117
153
|
if (result) {
|
|
118
154
|
ongoingStreamPromises.push(
|
|
119
155
|
result.catch(error => {
|
|
120
|
-
|
|
121
|
-
type: 'error',
|
|
122
|
-
errorText: onError(error),
|
|
123
|
-
} as InferUIMessageChunk<UI_MESSAGE>);
|
|
156
|
+
handleError(error);
|
|
124
157
|
}),
|
|
125
158
|
);
|
|
126
159
|
}
|
|
127
160
|
} catch (error) {
|
|
128
|
-
|
|
129
|
-
type: 'error',
|
|
130
|
-
errorText: onError(error),
|
|
131
|
-
} as InferUIMessageChunk<UI_MESSAGE>);
|
|
161
|
+
handleError(error);
|
|
132
162
|
}
|
|
133
163
|
|
|
134
164
|
// Wait until all ongoing streams are done. This approach enables merging
|
|
135
165
|
// streams even after execute has returned, as long as there is still an
|
|
136
166
|
// open merged stream. This is important to e.g. forward new streams and
|
|
137
167
|
// from callbacks.
|
|
138
|
-
const waitForStreams: Promise<void> =
|
|
168
|
+
const waitForStreams: Promise<void> = (async () => {
|
|
139
169
|
while (ongoingStreamPromises.length > 0) {
|
|
140
170
|
await ongoingStreamPromises.shift();
|
|
141
171
|
}
|
|
142
|
-
|
|
143
|
-
});
|
|
172
|
+
})();
|
|
144
173
|
|
|
145
174
|
waitForStreams.finally(() => {
|
|
146
175
|
try {
|
|
@@ -157,5 +186,6 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
157
186
|
onStepEnd: onStepEnd ?? onStepFinish,
|
|
158
187
|
onEnd: onEnd ?? onFinish,
|
|
159
188
|
onError,
|
|
189
|
+
getOutcome: () => outcome,
|
|
160
190
|
});
|
|
161
191
|
}
|
|
@@ -8,6 +8,7 @@ import type { UIMessage } from '../ui/ui-messages';
|
|
|
8
8
|
import type { ErrorHandler } from '../util/error-handler';
|
|
9
9
|
import type { InferUIMessageChunk, UIMessageChunk } from './ui-message-chunks';
|
|
10
10
|
import type { UIMessageStreamOnEndCallback } from './ui-message-stream-on-end-callback';
|
|
11
|
+
import type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
|
|
11
12
|
import type { UIMessageStreamOnStepEndCallback } from './ui-message-stream-on-step-end-callback';
|
|
12
13
|
import type { UIMessageStreamOnStepFinishCallback } from './ui-message-stream-on-step-finish-callback';
|
|
13
14
|
|
|
@@ -20,6 +21,7 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
|
|
|
20
21
|
onFinish,
|
|
21
22
|
onError,
|
|
22
23
|
stream,
|
|
24
|
+
getOutcome,
|
|
23
25
|
}: {
|
|
24
26
|
stream: ReadableStream<InferUIMessageChunk<UI_MESSAGE>>;
|
|
25
27
|
|
|
@@ -54,6 +56,11 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
|
|
|
54
56
|
* @deprecated Use `onEnd` instead.
|
|
55
57
|
*/
|
|
56
58
|
onFinish?: UIMessageStreamOnEndCallback<UI_MESSAGE>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns the operation-level outcome declared by the stream owner.
|
|
62
|
+
*/
|
|
63
|
+
getOutcome?: () => UIMessageStreamOutcome;
|
|
57
64
|
}): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
|
|
58
65
|
// last message is only relevant for assistant messages
|
|
59
66
|
let lastMessage: UI_MESSAGE | undefined =
|
|
@@ -66,6 +73,13 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
|
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
let isAborted = false;
|
|
76
|
+
let hasProcessingFailure = false;
|
|
77
|
+
let processingError: unknown;
|
|
78
|
+
|
|
79
|
+
const recordProcessingFailure = (error: unknown) => {
|
|
80
|
+
hasProcessingFailure = true;
|
|
81
|
+
processingError = error;
|
|
82
|
+
};
|
|
69
83
|
|
|
70
84
|
const idInjectedStream = stream.pipeThrough(
|
|
71
85
|
new TransformStream<
|
|
@@ -73,21 +87,31 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
|
|
|
73
87
|
InferUIMessageChunk<UI_MESSAGE>
|
|
74
88
|
>({
|
|
75
89
|
transform(chunk, controller) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
try {
|
|
91
|
+
let outputChunk = chunk;
|
|
92
|
+
|
|
93
|
+
// when there is no messageId in the start chunk,
|
|
94
|
+
// but the user checked for persistence,
|
|
95
|
+
// inject the messageId into the chunk
|
|
96
|
+
if (chunk.type === 'start') {
|
|
97
|
+
const startChunk = chunk as UIMessageChunk & { type: 'start' };
|
|
98
|
+
if (startChunk.messageId == null && messageId != null) {
|
|
99
|
+
outputChunk = {
|
|
100
|
+
...startChunk,
|
|
101
|
+
messageId,
|
|
102
|
+
} as InferUIMessageChunk<UI_MESSAGE>;
|
|
103
|
+
}
|
|
83
104
|
}
|
|
84
|
-
}
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
if (chunk.type === 'abort') {
|
|
107
|
+
isAborted = true;
|
|
108
|
+
}
|
|
89
109
|
|
|
90
|
-
|
|
110
|
+
controller.enqueue(outputChunk);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
recordProcessingFailure(error);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
91
115
|
},
|
|
92
116
|
}),
|
|
93
117
|
);
|
|
@@ -113,7 +137,12 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
|
|
|
113
137
|
write: (options?: UIMessageStreamWriteOptions) => void;
|
|
114
138
|
}) => Promise<void>,
|
|
115
139
|
) => {
|
|
116
|
-
|
|
140
|
+
try {
|
|
141
|
+
await job({ state, write: () => {} });
|
|
142
|
+
} catch (error) {
|
|
143
|
+
recordProcessingFailure(error);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
117
146
|
};
|
|
118
147
|
|
|
119
148
|
let finishCalled = false;
|
|
@@ -125,9 +154,17 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
|
|
|
125
154
|
finishCalled = true;
|
|
126
155
|
|
|
127
156
|
const isContinuation = state.message.id === lastMessage?.id;
|
|
157
|
+
const declaredOutcome = getOutcome?.() ?? { status: 'unknown' };
|
|
158
|
+
const outcome: UIMessageStreamOutcome = hasProcessingFailure
|
|
159
|
+
? { status: 'failed', error: processingError }
|
|
160
|
+
: declaredOutcome.status === 'unknown' && isAborted
|
|
161
|
+
? { status: 'aborted' }
|
|
162
|
+
: declaredOutcome;
|
|
163
|
+
|
|
128
164
|
await resolvedOnEnd({
|
|
129
|
-
isAborted,
|
|
165
|
+
isAborted: isAborted || outcome.status === 'aborted',
|
|
130
166
|
isContinuation,
|
|
167
|
+
outcome,
|
|
131
168
|
responseMessage: state.message as UI_MESSAGE,
|
|
132
169
|
messages: [
|
|
133
170
|
...(isContinuation ? originalMessages.slice(0, -1) : originalMessages),
|
|
@@ -16,6 +16,10 @@ export {
|
|
|
16
16
|
export { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
|
|
17
17
|
export type { UIMessageStreamOnEndCallback } from './ui-message-stream-on-end-callback';
|
|
18
18
|
export type { UIMessageStreamOnFinishCallback } from './ui-message-stream-on-finish-callback';
|
|
19
|
+
export type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
|
|
19
20
|
export type { UIMessageStreamOnStepEndCallback } from './ui-message-stream-on-step-end-callback';
|
|
20
21
|
export type { UIMessageStreamOnStepFinishCallback } from './ui-message-stream-on-step-finish-callback';
|
|
21
|
-
export type {
|
|
22
|
+
export type {
|
|
23
|
+
UIMessageStreamWriter,
|
|
24
|
+
UIMessageStreamWriterWithOutcome,
|
|
25
|
+
} from './ui-message-stream-writer';
|