ai 7.0.120 → 7.0.123
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 +38 -0
- package/dist/index.d.ts +7423 -7247
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19263 -22470
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +2044 -2004
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +3255 -3921
- package/dist/internal/index.js.map +1 -1
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/test/index.d.ts +294 -273
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +325 -469
- package/dist/test/index.js.map +1 -1
- package/docs/02-foundations/03-prompts.mdx +3 -1
- package/docs/02-foundations/06-provider-options.mdx +1 -1
- package/docs/02-getting-started/06-nodejs.mdx +4 -4
- package/docs/02-getting-started/08-tanstack-start.mdx +1 -1
- package/docs/03-agents/05-configuring-call-options.mdx +1 -1
- package/docs/03-ai-sdk-core/10-generating-structured-data.mdx +1 -1
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +3 -0
- package/docs/03-ai-sdk-core/17-mcp-apps.mdx +5 -5
- package/docs/03-ai-sdk-core/37-speech.mdx +1 -1
- package/docs/03-ai-sdk-core/42-batch.mdx +1 -1
- package/docs/03-ai-sdk-core/60-telemetry.mdx +2 -2
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +24 -0
- package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +12 -12
- package/docs/04-ai-sdk-ui/02-chatbot.mdx +9 -0
- package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +17 -7
- package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +12 -5
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +17 -10
- package/docs/04-ai-sdk-ui/05-completion.mdx +1 -1
- package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +2 -2
- package/docs/05-ai-sdk-rsc/03-generative-ui-state.mdx +8 -7
- package/docs/05-ai-sdk-rsc/04-multistep-interfaces.mdx +1 -1
- package/docs/05-ai-sdk-rsc/06-loading-state.mdx +19 -10
- package/docs/05-ai-sdk-rsc/08-error-handling.mdx +2 -0
- package/docs/05-ai-sdk-rsc/10-migrating-to-ui.mdx +4 -3
- package/docs/06-advanced/02-stopping-streams.mdx +2 -2
- package/docs/06-advanced/04-caching.mdx +9 -5
- package/docs/06-advanced/05-multiple-streamables.mdx +1 -1
- package/docs/06-advanced/07-rendering-ui-with-language-models.mdx +16 -16
- package/docs/06-advanced/09-multistep-interfaces.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -4
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +5 -5
- package/docs/07-reference/01-ai-sdk-core/14-upload-file.mdx +4 -4
- package/docs/07-reference/01-ai-sdk-core/14-upload-skill.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/15-agent.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +1 -4
- package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/30-model-message.mdx +2 -2
- package/docs/07-reference/01-ai-sdk-core/69-add-tool-input-examples-middleware.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +1 -1
- package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +5 -4
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +3 -2
- package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +7 -0
- package/docs/07-reference/02-ai-sdk-ui/42-pipe-ui-message-stream-to-response.mdx +7 -0
- package/docs/07-reference/02-ai-sdk-ui/50-direct-chat-transport.mdx +2 -2
- package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +2 -2
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +1 -1
- package/docs/08-migration-guides/25-migration-guide-5-0-data.mdx +4 -5
- package/docs/08-migration-guides/26-migration-guide-5-0.mdx +2 -1
- package/docs/08-migration-guides/29-migration-guide-4-0.mdx +9 -9
- package/docs/08-migration-guides/37-migration-guide-3-3.mdx +1 -1
- package/docs/08-migration-guides/38-migration-guide-3-2.mdx +1 -1
- package/docs/08-migration-guides/39-migration-guide-3-1.mdx +1 -1
- package/docs/09-troubleshooting/12-use-chat-an-error-occurred.mdx +8 -6
- package/docs/09-troubleshooting/50-react-maximum-update-depth-exceeded.mdx +6 -6
- package/docs/09-troubleshooting/70-high-memory-usage-with-images.mdx +1 -1
- package/package.json +17 -17
- package/src/agent/create-agent-ui-stream-response.ts +3 -0
- package/src/agent/pipe-agent-ui-stream-to-response.ts +3 -0
- package/src/embed/embed-many.ts +5 -3
- package/src/generate-image/generate-image.ts +8 -3
- package/src/generate-text/prune-messages.ts +1 -1
- package/src/generate-text/smooth-stream.ts +24 -9
- package/src/generate-text/stream-text-result.ts +5 -0
- package/src/generate-video/generate-video.ts +6 -4
- package/src/middleware/extract-reasoning-middleware.ts +27 -11
- package/src/registry/provider-registry.ts +1 -1
- package/src/ui/chat.ts +13 -4
- package/src/ui/convert-to-model-messages.ts +18 -6
- package/src/ui/http-chat-transport.ts +19 -3
- package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +15 -3
- package/src/ui/process-ui-message-stream.ts +51 -14
- package/src/ui/ui-messages.ts +14 -0
- package/src/ui/validate-ui-messages.ts +2 -0
- package/src/ui-message-stream/create-sse-stream-with-keep-alive.ts +89 -0
- package/src/ui-message-stream/create-ui-message-stream-response.ts +7 -1
- package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +7 -1
- package/src/ui-message-stream/to-ui-message-chunk.ts +9 -1
- package/src/ui-message-stream/to-ui-message-stream.ts +1 -1
- package/src/ui-message-stream/ui-message-stream-response-init.ts +7 -0
- package/src/util/set-own.ts +16 -0
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Resolvable,
|
|
6
6
|
} from '@ai-sdk/provider-utils';
|
|
7
7
|
import { EmptyResponseBodyError } from '@ai-sdk/provider';
|
|
8
|
+
import { InvalidArgumentError } from '../error/invalid-argument-error';
|
|
8
9
|
import type { UIMessageChunk } from '../ui-message-stream/ui-message-chunks';
|
|
9
10
|
import type { ChatTransport } from './chat-transport';
|
|
10
11
|
import { createUIApiCallError } from './create-ui-api-call-error';
|
|
@@ -247,9 +248,24 @@ export abstract class HttpChatTransport<
|
|
|
247
248
|
requestMetadata: options.metadata,
|
|
248
249
|
});
|
|
249
250
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
let api = preparedRequest?.api;
|
|
252
|
+
if (api == null) {
|
|
253
|
+
// encodeURIComponent leaves dot segments unchanged, and URL parsers
|
|
254
|
+
// normalize them even when their dots are percent-encoded.
|
|
255
|
+
if (options.chatId === '.' || options.chatId === '..') {
|
|
256
|
+
throw new InvalidArgumentError({
|
|
257
|
+
parameter: 'chatId',
|
|
258
|
+
value: options.chatId,
|
|
259
|
+
message:
|
|
260
|
+
'Chat IDs must not be "." or ".." when using the default reconnect URL.',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
api = appendPathToUrl(
|
|
265
|
+
this.api,
|
|
266
|
+
`/${encodeURIComponent(options.chatId)}/stream`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
253
269
|
const headers =
|
|
254
270
|
preparedRequest?.headers !== undefined
|
|
255
271
|
? normalizeHeaders(preparedRequest.headers)
|
|
@@ -2,7 +2,9 @@ import { isToolUIPart, type UIMessage } from './ui-messages';
|
|
|
2
2
|
/**
|
|
3
3
|
* Check if the last message is an assistant message with completed tool calls.
|
|
4
4
|
* The last step of the message must have at least one tool invocation and
|
|
5
|
-
* all tool invocations must have a result.
|
|
5
|
+
* all tool invocations must have a result. Completed model text may follow
|
|
6
|
+
* the tool invocation because client-side tool results update parts in place,
|
|
7
|
+
* but trailing text without a completed stream state prevents resubmission.
|
|
6
8
|
*/
|
|
7
9
|
export function lastAssistantMessageIsCompleteWithToolCalls({
|
|
8
10
|
messages,
|
|
@@ -23,13 +25,23 @@ export function lastAssistantMessageIsCompleteWithToolCalls({
|
|
|
23
25
|
return part.type === 'step-start' ? index : lastIndex;
|
|
24
26
|
}, -1);
|
|
25
27
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
+
const lastStepParts = message.parts.slice(lastStepStartIndex + 1);
|
|
29
|
+
|
|
30
|
+
const lastStepToolInvocations = lastStepParts
|
|
28
31
|
.filter(isToolUIPart)
|
|
29
32
|
.filter(part => !part.providerExecuted);
|
|
30
33
|
|
|
34
|
+
const lastToolInvocationIndex = lastStepParts.reduce(
|
|
35
|
+
(lastIndex, part, index) =>
|
|
36
|
+
isToolUIPart(part) && !part.providerExecuted ? index : lastIndex,
|
|
37
|
+
-1,
|
|
38
|
+
);
|
|
39
|
+
|
|
31
40
|
return (
|
|
32
41
|
lastStepToolInvocations.length > 0 &&
|
|
42
|
+
!lastStepParts
|
|
43
|
+
.slice(lastToolInvocationIndex + 1)
|
|
44
|
+
.some(part => part.type === 'text' && part.state !== 'done') &&
|
|
33
45
|
lastStepToolInvocations.every(
|
|
34
46
|
part =>
|
|
35
47
|
(part.state === 'output-available' && part.preliminary !== true) ||
|
|
@@ -60,22 +60,55 @@ export function createStreamingUIMessageState<UI_MESSAGE extends UIMessage>({
|
|
|
60
60
|
lastMessage: UI_MESSAGE | undefined;
|
|
61
61
|
messageId: string;
|
|
62
62
|
}): StreamingUIMessageState<UI_MESSAGE> {
|
|
63
|
+
const message =
|
|
64
|
+
lastMessage?.role === 'assistant'
|
|
65
|
+
? lastMessage
|
|
66
|
+
: ({
|
|
67
|
+
id: messageId,
|
|
68
|
+
metadata: undefined,
|
|
69
|
+
role: 'assistant',
|
|
70
|
+
parts: [] as UIMessagePart<
|
|
71
|
+
InferUIMessageData<UI_MESSAGE>,
|
|
72
|
+
InferUIMessageTools<UI_MESSAGE>
|
|
73
|
+
>[],
|
|
74
|
+
} as UI_MESSAGE);
|
|
75
|
+
const partialToolCalls: StreamingUIMessageState<UI_MESSAGE>['partialToolCalls'] =
|
|
76
|
+
createIdMap();
|
|
77
|
+
const lastStepStartIndex = message.parts.findLastIndex(
|
|
78
|
+
part => part.type === 'step-start',
|
|
79
|
+
);
|
|
80
|
+
let staticToolIndex = 0;
|
|
81
|
+
|
|
82
|
+
for (const part of message.parts.slice(lastStepStartIndex + 1)) {
|
|
83
|
+
if (!isToolUIPart(part)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const index = staticToolIndex;
|
|
88
|
+
if (isStaticToolUIPart(part)) {
|
|
89
|
+
staticToolIndex++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (part.state !== 'input-streaming') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
partialToolCalls[part.toolCallId] = {
|
|
97
|
+
text: part.rawInput ?? '',
|
|
98
|
+
index,
|
|
99
|
+
toolName:
|
|
100
|
+
part.type === 'dynamic-tool' ? part.toolName : getStaticToolName(part),
|
|
101
|
+
dynamic: part.type === 'dynamic-tool',
|
|
102
|
+
title: part.title,
|
|
103
|
+
toolMetadata: part.toolMetadata,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
63
107
|
return {
|
|
64
|
-
message
|
|
65
|
-
lastMessage?.role === 'assistant'
|
|
66
|
-
? lastMessage
|
|
67
|
-
: ({
|
|
68
|
-
id: messageId,
|
|
69
|
-
metadata: undefined,
|
|
70
|
-
role: 'assistant',
|
|
71
|
-
parts: [] as UIMessagePart<
|
|
72
|
-
InferUIMessageData<UI_MESSAGE>,
|
|
73
|
-
InferUIMessageTools<UI_MESSAGE>
|
|
74
|
-
>[],
|
|
75
|
-
} as UI_MESSAGE),
|
|
108
|
+
message,
|
|
76
109
|
activeTextParts: createIdMap(),
|
|
77
110
|
activeReasoningParts: createIdMap(),
|
|
78
|
-
partialToolCalls
|
|
111
|
+
partialToolCalls,
|
|
79
112
|
};
|
|
80
113
|
}
|
|
81
114
|
|
|
@@ -185,6 +218,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
185
218
|
| {
|
|
186
219
|
state: 'input-streaming';
|
|
187
220
|
input: unknown;
|
|
221
|
+
rawInput?: string;
|
|
188
222
|
providerExecuted?: boolean;
|
|
189
223
|
providerMetadata?: ProviderMetadata;
|
|
190
224
|
}
|
|
@@ -300,6 +334,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
300
334
|
| {
|
|
301
335
|
state: 'input-streaming';
|
|
302
336
|
input: unknown;
|
|
337
|
+
rawInput?: string;
|
|
303
338
|
providerMetadata?: ProviderMetadata;
|
|
304
339
|
}
|
|
305
340
|
| {
|
|
@@ -340,7 +375,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
340
375
|
anyPart.input = anyOptions.input;
|
|
341
376
|
anyPart.output = anyOptions.output;
|
|
342
377
|
anyPart.errorText = anyOptions.errorText;
|
|
343
|
-
anyPart.rawInput = anyOptions.rawInput
|
|
378
|
+
anyPart.rawInput = anyOptions.rawInput;
|
|
344
379
|
anyPart.preliminary = anyOptions.preliminary;
|
|
345
380
|
if (options.title !== undefined) {
|
|
346
381
|
anyPart.title = options.title;
|
|
@@ -645,6 +680,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
645
680
|
toolName: partialToolCall.toolName,
|
|
646
681
|
state: 'input-streaming',
|
|
647
682
|
input: partialArgs,
|
|
683
|
+
rawInput: partialToolCall.text,
|
|
648
684
|
title: partialToolCall.title,
|
|
649
685
|
toolMetadata: partialToolCall.toolMetadata,
|
|
650
686
|
});
|
|
@@ -654,6 +690,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
654
690
|
toolName: partialToolCall.toolName,
|
|
655
691
|
state: 'input-streaming',
|
|
656
692
|
input: partialArgs,
|
|
693
|
+
rawInput: partialToolCall.text,
|
|
657
694
|
title: partialToolCall.title,
|
|
658
695
|
toolMetadata: partialToolCall.toolMetadata,
|
|
659
696
|
});
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -297,6 +297,13 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
297
297
|
| {
|
|
298
298
|
state: 'input-streaming';
|
|
299
299
|
input?: DeepPartial<asUITool<TOOL>['input']> | undefined;
|
|
300
|
+
/**
|
|
301
|
+
* The raw tool input text received so far.
|
|
302
|
+
*
|
|
303
|
+
* This is used to continue input streaming when a message is persisted
|
|
304
|
+
* and later resumed.
|
|
305
|
+
*/
|
|
306
|
+
rawInput?: string;
|
|
300
307
|
output?: never;
|
|
301
308
|
errorText?: never;
|
|
302
309
|
callProviderMetadata?: ProviderMetadata;
|
|
@@ -434,6 +441,13 @@ export type DynamicToolUIPart = {
|
|
|
434
441
|
| {
|
|
435
442
|
state: 'input-streaming';
|
|
436
443
|
input?: unknown;
|
|
444
|
+
/**
|
|
445
|
+
* The raw tool input text received so far.
|
|
446
|
+
*
|
|
447
|
+
* This is used to continue input streaming when a message is persisted
|
|
448
|
+
* and later resumed.
|
|
449
|
+
*/
|
|
450
|
+
rawInput?: string;
|
|
437
451
|
output?: never;
|
|
438
452
|
errorText?: never;
|
|
439
453
|
callProviderMetadata?: ProviderMetadata;
|
|
@@ -154,6 +154,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
154
154
|
toolMetadata: toolMetadataSchema.optional(),
|
|
155
155
|
state: z.literal('input-streaming'),
|
|
156
156
|
input: z.unknown().optional(),
|
|
157
|
+
rawInput: z.string().optional(),
|
|
157
158
|
providerExecuted: z.boolean().optional(),
|
|
158
159
|
callProviderMetadata: providerMetadataSchema.optional(),
|
|
159
160
|
output: z.never().optional(),
|
|
@@ -257,6 +258,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
257
258
|
providerExecuted: z.boolean().optional(),
|
|
258
259
|
callProviderMetadata: providerMetadataSchema.optional(),
|
|
259
260
|
input: z.unknown().optional(),
|
|
261
|
+
rawInput: z.string().optional(),
|
|
260
262
|
output: z.never().optional(),
|
|
261
263
|
errorText: z.never().optional(),
|
|
262
264
|
approval: z.never().optional(),
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const STREAM_OPEN_COMMENT = ': stream-open\n\n';
|
|
2
|
+
const KEEP_ALIVE_COMMENT = ': keep-alive\n\n';
|
|
3
|
+
|
|
4
|
+
export function createSseStreamWithKeepAlive({
|
|
5
|
+
stream,
|
|
6
|
+
keepAliveMs,
|
|
7
|
+
}: {
|
|
8
|
+
stream: ReadableStream<string>;
|
|
9
|
+
keepAliveMs: number | undefined;
|
|
10
|
+
}): ReadableStream<string> {
|
|
11
|
+
if (keepAliveMs == null) {
|
|
12
|
+
return stream;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
!Number.isFinite(keepAliveMs) ||
|
|
17
|
+
keepAliveMs <= 0 ||
|
|
18
|
+
keepAliveMs > 2_147_483_647
|
|
19
|
+
) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'keepAliveMs must be a positive finite timer duration no greater than 2147483647',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const reader = stream.getReader();
|
|
26
|
+
let keepAliveTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
27
|
+
let isCancelled = false;
|
|
28
|
+
|
|
29
|
+
const clearKeepAliveTimeout = () => {
|
|
30
|
+
clearTimeout(keepAliveTimeout);
|
|
31
|
+
keepAliveTimeout = undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const scheduleKeepAlive = (
|
|
35
|
+
controller: ReadableStreamDefaultController<string>,
|
|
36
|
+
) => {
|
|
37
|
+
clearKeepAliveTimeout();
|
|
38
|
+
keepAliveTimeout = setTimeout(() => {
|
|
39
|
+
if (isCancelled) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (controller.desiredSize != null && controller.desiredSize > 0) {
|
|
44
|
+
controller.enqueue(KEEP_ALIVE_COMMENT);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
scheduleKeepAlive(controller);
|
|
48
|
+
}, keepAliveMs);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return new ReadableStream<string>({
|
|
52
|
+
start(controller) {
|
|
53
|
+
controller.enqueue(STREAM_OPEN_COMMENT);
|
|
54
|
+
scheduleKeepAlive(controller);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
pull(controller) {
|
|
58
|
+
return reader.read().then(
|
|
59
|
+
result => {
|
|
60
|
+
clearKeepAliveTimeout();
|
|
61
|
+
|
|
62
|
+
if (isCancelled) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (result.done) {
|
|
67
|
+
controller.close();
|
|
68
|
+
} else {
|
|
69
|
+
controller.enqueue(result.value);
|
|
70
|
+
scheduleKeepAlive(controller);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
error => {
|
|
74
|
+
clearKeepAliveTimeout();
|
|
75
|
+
|
|
76
|
+
if (!isCancelled) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async cancel(reason) {
|
|
84
|
+
isCancelled = true;
|
|
85
|
+
clearKeepAliveTimeout();
|
|
86
|
+
await reader.cancel(reason);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepareHeaders } from '../util/prepare-headers';
|
|
2
|
+
import { createSseStreamWithKeepAlive } from './create-sse-stream-with-keep-alive';
|
|
2
3
|
import { JsonToSseTransformStream } from './json-to-sse-transform-stream';
|
|
3
4
|
import { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
|
|
4
5
|
import type { UIMessageChunk } from './ui-message-chunks';
|
|
@@ -12,6 +13,7 @@ import type { UIMessageStreamResponseInit } from './ui-message-stream-response-i
|
|
|
12
13
|
* @param options.statusText - The HTTP status text for the response.
|
|
13
14
|
* @param options.headers - Additional HTTP headers to include in the response.
|
|
14
15
|
* @param options.stream - The UI message chunk stream to send.
|
|
16
|
+
* @param options.keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
15
17
|
* @param options.consumeSseStream - Optional callback to consume a copy of the SSE stream independently.
|
|
16
18
|
*
|
|
17
19
|
* @returns A `Response` object with the UI message stream as the body.
|
|
@@ -21,11 +23,15 @@ export function createUIMessageStreamResponse({
|
|
|
21
23
|
statusText,
|
|
22
24
|
headers,
|
|
23
25
|
stream,
|
|
26
|
+
keepAliveMs,
|
|
24
27
|
consumeSseStream,
|
|
25
28
|
}: UIMessageStreamResponseInit & {
|
|
26
29
|
stream: ReadableStream<UIMessageChunk>;
|
|
27
30
|
}): Response {
|
|
28
|
-
let sseStream =
|
|
31
|
+
let sseStream = createSseStreamWithKeepAlive({
|
|
32
|
+
stream: stream.pipeThrough(new JsonToSseTransformStream()),
|
|
33
|
+
keepAliveMs,
|
|
34
|
+
});
|
|
29
35
|
|
|
30
36
|
// when the consumeSseStream is provided, we need to tee the stream
|
|
31
37
|
// and send the second part to the consumeSseStream function
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ServerResponse } from 'node:http';
|
|
2
2
|
import { prepareHeaders } from '../util/prepare-headers';
|
|
3
3
|
import { writeToServerResponse } from '../util/write-to-server-response';
|
|
4
|
+
import { createSseStreamWithKeepAlive } from './create-sse-stream-with-keep-alive';
|
|
4
5
|
import { JsonToSseTransformStream } from './json-to-sse-transform-stream';
|
|
5
6
|
import { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
|
|
6
7
|
import type { UIMessageChunk } from './ui-message-chunks';
|
|
@@ -15,6 +16,7 @@ import type { UIMessageStreamResponseInit } from './ui-message-stream-response-i
|
|
|
15
16
|
* @param options.statusText - The HTTP status text for the response.
|
|
16
17
|
* @param options.headers - Additional HTTP headers to include in the response.
|
|
17
18
|
* @param options.stream - The UI message chunk stream to send.
|
|
19
|
+
* @param options.keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
18
20
|
* @param options.consumeSseStream - Optional callback to consume a copy of the SSE stream independently.
|
|
19
21
|
* @returns A promise that resolves when the stream has been written.
|
|
20
22
|
*/
|
|
@@ -24,12 +26,16 @@ export function pipeUIMessageStreamToResponse({
|
|
|
24
26
|
statusText,
|
|
25
27
|
headers,
|
|
26
28
|
stream,
|
|
29
|
+
keepAliveMs,
|
|
27
30
|
consumeSseStream,
|
|
28
31
|
}: {
|
|
29
32
|
response: ServerResponse;
|
|
30
33
|
stream: ReadableStream<UIMessageChunk>;
|
|
31
34
|
} & UIMessageStreamResponseInit): Promise<void> {
|
|
32
|
-
let sseStream =
|
|
35
|
+
let sseStream = createSseStreamWithKeepAlive({
|
|
36
|
+
stream: stream.pipeThrough(new JsonToSseTransformStream()),
|
|
37
|
+
keepAliveMs,
|
|
38
|
+
});
|
|
33
39
|
|
|
34
40
|
// when the consumeSseStream is provided, we need to tee the stream
|
|
35
41
|
// and send the second part to the consumeSseStream function
|
|
@@ -18,6 +18,11 @@ export type ToUIMessageChunkOptions<
|
|
|
18
18
|
sendSources?: boolean;
|
|
19
19
|
sendStart?: boolean;
|
|
20
20
|
sendFinish?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Formats stream errors, invalid tool calls, and application-executed tool
|
|
23
|
+
* errors. Provider-executed tool execution errors bypass this callback so
|
|
24
|
+
* their original error data survives model-message round trips.
|
|
25
|
+
*/
|
|
21
26
|
onError?: (error: unknown) => string;
|
|
22
27
|
messageMetadata?: InferUIMessageMetadata<UI_MESSAGE>;
|
|
23
28
|
responseMessageId?: string;
|
|
@@ -40,7 +45,7 @@ export function toUIMessageChunk<
|
|
|
40
45
|
sendSources = false,
|
|
41
46
|
sendStart = true,
|
|
42
47
|
sendFinish = true,
|
|
43
|
-
onError = () => 'An error occurred.', //
|
|
48
|
+
onError = () => 'An error occurred.', // masks errors except provider-executed tool execution errors
|
|
44
49
|
messageMetadata,
|
|
45
50
|
responseMessageId,
|
|
46
51
|
}: ToUIMessageChunkOptions<TOOLS, UI_MESSAGE> = {},
|
|
@@ -305,6 +310,9 @@ export function toUIMessageChunk<
|
|
|
305
310
|
case 'tool-error': {
|
|
306
311
|
const dynamic = isDynamic(part);
|
|
307
312
|
|
|
313
|
+
// Preserve provider error codes for model-message round trips and harness
|
|
314
|
+
// runtime error messages. These execution errors intentionally bypass
|
|
315
|
+
// onError; invalid tool calls and stream errors still go through it.
|
|
308
316
|
return {
|
|
309
317
|
type: 'tool-output-error',
|
|
310
318
|
toolCallId: part.toolCallId,
|
|
@@ -26,7 +26,7 @@ export function toUIMessageStream<
|
|
|
26
26
|
sendSources = false,
|
|
27
27
|
sendStart = true,
|
|
28
28
|
sendFinish = true,
|
|
29
|
-
onError = () => 'An error occurred.', //
|
|
29
|
+
onError = () => 'An error occurred.', // masks errors except provider-executed tool execution errors
|
|
30
30
|
messageMetadata,
|
|
31
31
|
originalMessages,
|
|
32
32
|
generateMessageId,
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* Extends the standard `ResponseInit` with additional streaming options.
|
|
4
4
|
*/
|
|
5
5
|
export type UIMessageStreamResponseInit = ResponseInit & {
|
|
6
|
+
/**
|
|
7
|
+
* Optional interval in milliseconds for sending SSE keep-alive comments.
|
|
8
|
+
* When set, an opening comment is sent immediately and additional comments
|
|
9
|
+
* are sent after the stream has been idle for the configured interval.
|
|
10
|
+
*/
|
|
11
|
+
keepAliveMs?: number;
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
14
|
* Optional callback to consume a copy of the SSE stream independently.
|
|
8
15
|
* This is useful for logging, debugging, or processing the stream in parallel.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes an enumerable own property without invoking inherited setters such as
|
|
3
|
+
* `Object.prototype.__proto__`. Keeps the target object's prototype unchanged.
|
|
4
|
+
*/
|
|
5
|
+
export function setOwn<T>(
|
|
6
|
+
object: Record<string, T>,
|
|
7
|
+
key: string,
|
|
8
|
+
value: T,
|
|
9
|
+
): void {
|
|
10
|
+
Object.defineProperty(object, key, {
|
|
11
|
+
value,
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
});
|
|
16
|
+
}
|