ai 7.0.122 → 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 +15 -0
- package/dist/index.d.ts +7418 -7268
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19263 -22545
- 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/03-chatbot-message-persistence.mdx +9 -7
- package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +2 -2
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +7 -9
- 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/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 +4 -4
- 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 +4 -4
- 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/generate-text/prune-messages.ts +1 -1
- package/src/middleware/extract-reasoning-middleware.ts +27 -11
- package/src/ui/convert-to-model-messages.ts +18 -6
- 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/ui-message-stream-response-init.ts +7 -0
|
@@ -49,14 +49,16 @@ return createUIMessageStreamResponse({
|
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
In case you are using `
|
|
52
|
+
In case you are using `createUIMessageStream`, you can pass the `onError` function to it:
|
|
53
53
|
|
|
54
54
|
```tsx
|
|
55
|
-
const response =
|
|
56
|
-
|
|
57
|
-
async execute(dataStream) {
|
|
55
|
+
const response = createUIMessageStreamResponse({
|
|
56
|
+
stream: createUIMessageStream({
|
|
58
57
|
// ...
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
async execute({ writer }) {
|
|
59
|
+
// ...
|
|
60
|
+
},
|
|
61
|
+
onError: errorHandler,
|
|
62
|
+
}),
|
|
61
63
|
});
|
|
62
64
|
```
|
|
@@ -23,17 +23,17 @@ Use the `throttle` option to throttle the UI updates:
|
|
|
23
23
|
### `useChat`
|
|
24
24
|
|
|
25
25
|
```tsx filename="page.tsx" highlight="2-3"
|
|
26
|
-
const { messages
|
|
26
|
+
const { messages } = useChat({
|
|
27
27
|
// Throttle the messages and data updates to 50ms:
|
|
28
|
-
throttle: 50
|
|
29
|
-
})
|
|
28
|
+
throttle: 50,
|
|
29
|
+
});
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
### `useCompletion`
|
|
33
33
|
|
|
34
34
|
```tsx filename="page.tsx" highlight="2-3"
|
|
35
|
-
const { completion
|
|
35
|
+
const { completion } = useCompletion({
|
|
36
36
|
// Throttle the completion and data updates to 50ms:
|
|
37
|
-
throttle: 50
|
|
38
|
-
})
|
|
37
|
+
throttle: 50,
|
|
38
|
+
});
|
|
39
39
|
```
|
|
@@ -17,7 +17,7 @@ This is especially noticeable when using `experimental_download` to process imag
|
|
|
17
17
|
|
|
18
18
|
## Background
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
When enabled with the `include` option, the AI SDK includes the full request messages plus request and response bodies in the step results. When processing images, the request messages and request body can contain base64-encoded image data, which can be very large (a single image can be 1MB+ when base64 encoded). If you process many images and keep references to the results, this data accumulates in memory.
|
|
21
21
|
|
|
22
22
|
For example, processing 100 images of 500KB each would include ~50MB+ of request body data in memory.
|
|
23
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.123",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
46
|
-
"@ai-sdk/provider": "4.0.
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.101",
|
|
46
|
+
"@ai-sdk/provider": "4.0.20",
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.52"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
57
|
-
"@ai-sdk/test-server": "2.0.
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.102",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.57",
|
|
52
|
+
"@ai-sdk/google": "4.0.86",
|
|
53
|
+
"@ai-sdk/groq": "4.0.53",
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.60",
|
|
55
|
+
"@ai-sdk/moonshotai": "3.0.61",
|
|
56
|
+
"@ai-sdk/openai": "4.0.82",
|
|
57
|
+
"@ai-sdk/test-server": "2.0.3",
|
|
58
|
+
"@ai-sdk/xai": "5.0.13",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@types/node": "22.19.19",
|
|
64
64
|
"@vercel/ai-tsconfig": "0.0.0",
|
|
65
65
|
"esbuild": "^0.28.1",
|
|
66
|
-
"
|
|
66
|
+
"tsdown": "^0.23.0",
|
|
67
67
|
"tsx": "4.23.12",
|
|
68
68
|
"typescript": "5.8.3",
|
|
69
69
|
"zod": "3.25.76"
|
|
@@ -113,9 +113,9 @@
|
|
|
113
113
|
"grok"
|
|
114
114
|
],
|
|
115
115
|
"scripts": {
|
|
116
|
-
"build": "pnpm clean &&
|
|
117
|
-
"build:watch": "pnpm clean &&
|
|
118
|
-
"clean": "del-cli
|
|
116
|
+
"build": "pnpm clean && tsdown",
|
|
117
|
+
"build:watch": "pnpm clean && tsdown --watch",
|
|
118
|
+
"clean": "del-cli docs *.tsbuildinfo",
|
|
119
119
|
"type-check": "tsc --build",
|
|
120
120
|
"test": "pnpm test:node && pnpm test:edge",
|
|
121
121
|
"test:update": "pnpm test:node -u",
|
|
@@ -33,6 +33,7 @@ import { createAgentUIStream } from './create-agent-ui-stream';
|
|
|
33
33
|
* @param headers - Additional headers for the response. Optional.
|
|
34
34
|
* @param status - The status code for the response. Optional.
|
|
35
35
|
* @param statusText - The status text for the response. Optional.
|
|
36
|
+
* @param keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
36
37
|
* @param consumeSseStream - Whether to consume the SSE stream. Optional.
|
|
37
38
|
*
|
|
38
39
|
* @returns The response object.
|
|
@@ -47,6 +48,7 @@ export async function createAgentUIStreamResponse<
|
|
|
47
48
|
headers,
|
|
48
49
|
status,
|
|
49
50
|
statusText,
|
|
51
|
+
keepAliveMs,
|
|
50
52
|
consumeSseStream,
|
|
51
53
|
...options
|
|
52
54
|
}: {
|
|
@@ -68,6 +70,7 @@ export async function createAgentUIStreamResponse<
|
|
|
68
70
|
headers,
|
|
69
71
|
status,
|
|
70
72
|
statusText,
|
|
73
|
+
keepAliveMs,
|
|
71
74
|
consumeSseStream,
|
|
72
75
|
stream: await createAgentUIStream(options),
|
|
73
76
|
});
|
|
@@ -35,6 +35,7 @@ import { createAgentUIStream } from './create-agent-ui-stream';
|
|
|
35
35
|
* @param headers - Additional headers for the response. Optional.
|
|
36
36
|
* @param status - The status code for the response. Optional.
|
|
37
37
|
* @param statusText - The status text for the response. Optional.
|
|
38
|
+
* @param keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
38
39
|
* @param consumeSseStream - Whether to consume the SSE stream. Optional.
|
|
39
40
|
*/
|
|
40
41
|
export async function pipeAgentUIStreamToResponse<
|
|
@@ -48,6 +49,7 @@ export async function pipeAgentUIStreamToResponse<
|
|
|
48
49
|
headers,
|
|
49
50
|
status,
|
|
50
51
|
statusText,
|
|
52
|
+
keepAliveMs,
|
|
51
53
|
consumeSseStream,
|
|
52
54
|
...options
|
|
53
55
|
}: {
|
|
@@ -71,6 +73,7 @@ export async function pipeAgentUIStreamToResponse<
|
|
|
71
73
|
headers,
|
|
72
74
|
status,
|
|
73
75
|
statusText,
|
|
76
|
+
keepAliveMs,
|
|
74
77
|
consumeSseStream,
|
|
75
78
|
stream: await createAgentUIStream(options),
|
|
76
79
|
});
|
|
@@ -82,7 +82,7 @@ export function pruneMessages({
|
|
|
82
82
|
const keptToolCallIds: Set<string> = new Set();
|
|
83
83
|
const keptApprovalIds: Set<string> = new Set();
|
|
84
84
|
|
|
85
|
-
if (keepLastMessagesCount != null) {
|
|
85
|
+
if (keepLastMessagesCount != null && keepLastMessagesCount !== 0) {
|
|
86
86
|
for (const message of messages.slice(-keepLastMessagesCount)) {
|
|
87
87
|
if (
|
|
88
88
|
(message.role === 'assistant' || message.role === 'tool') &&
|
|
@@ -116,20 +116,15 @@ export function extractReasoningMiddleware({
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
if (
|
|
120
|
-
chunk.type === 'text-end' &&
|
|
121
|
-
delayedTextStarts[chunk.id] != null
|
|
122
|
-
) {
|
|
123
|
-
controller.enqueue(delayedTextStarts[chunk.id]);
|
|
124
|
-
delete delayedTextStarts[chunk.id];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (chunk.type !== 'text-delta') {
|
|
119
|
+
if (chunk.type !== 'text-delta' && chunk.type !== 'text-end') {
|
|
128
120
|
controller.enqueue(chunk);
|
|
129
121
|
return;
|
|
130
122
|
}
|
|
131
123
|
|
|
132
|
-
if (
|
|
124
|
+
if (
|
|
125
|
+
chunk.type === 'text-delta' &&
|
|
126
|
+
reasoningExtractions[chunk.id] == null
|
|
127
|
+
) {
|
|
133
128
|
reasoningExtractions[chunk.id] = {
|
|
134
129
|
isFirstReasoning: true,
|
|
135
130
|
isFirstText: true,
|
|
@@ -143,7 +138,14 @@ export function extractReasoningMiddleware({
|
|
|
143
138
|
|
|
144
139
|
const activeExtraction = reasoningExtractions[chunk.id];
|
|
145
140
|
|
|
146
|
-
activeExtraction
|
|
141
|
+
if (activeExtraction == null) {
|
|
142
|
+
if (delayedTextStarts[chunk.id] != null) {
|
|
143
|
+
controller.enqueue(delayedTextStarts[chunk.id]);
|
|
144
|
+
delete delayedTextStarts[chunk.id];
|
|
145
|
+
}
|
|
146
|
+
controller.enqueue(chunk);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
147
149
|
|
|
148
150
|
function getReasoningId() {
|
|
149
151
|
return (activeExtraction.reasoningId ??= `reasoning-${reasoningIdCounter++}`);
|
|
@@ -199,6 +201,20 @@ export function extractReasoningMiddleware({
|
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
if (chunk.type === 'text-end') {
|
|
205
|
+
publish(activeExtraction.buffer);
|
|
206
|
+
activeExtraction.buffer = '';
|
|
207
|
+
|
|
208
|
+
if (delayedTextStarts[chunk.id] != null) {
|
|
209
|
+
controller.enqueue(delayedTextStarts[chunk.id]);
|
|
210
|
+
delete delayedTextStarts[chunk.id];
|
|
211
|
+
}
|
|
212
|
+
controller.enqueue(chunk);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
activeExtraction.buffer += chunk.delta;
|
|
217
|
+
|
|
202
218
|
do {
|
|
203
219
|
const nextTag = activeExtraction.isReasoning
|
|
204
220
|
? closingTag
|
|
@@ -59,16 +59,28 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
59
59
|
|
|
60
60
|
warnIfUIMessageHasDeprecatedRawInput(messages);
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// A later user message supersedes unresolved approval requests. Keeping
|
|
63
|
+
// those requests would leave unmatched tool calls at the user boundary.
|
|
64
|
+
const lastUserMessageIndex = messages.reduce(
|
|
65
|
+
(lastIndex, message, index) =>
|
|
66
|
+
message.role === 'user' ? index : lastIndex,
|
|
67
|
+
-1,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (options?.ignoreIncompleteToolCalls || lastUserMessageIndex > 0) {
|
|
71
|
+
messages = messages.map((message, messageIndex) => ({
|
|
64
72
|
...message,
|
|
65
73
|
parts: message.parts.filter(
|
|
66
74
|
part =>
|
|
67
75
|
!isToolUIPart(part) ||
|
|
68
|
-
part.state
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
((part.state !== 'approval-requested' ||
|
|
77
|
+
messageIndex >= lastUserMessageIndex) &&
|
|
78
|
+
(!options?.ignoreIncompleteToolCalls ||
|
|
79
|
+
part.state === 'approval-responded' ||
|
|
80
|
+
(part.state === 'output-available' &&
|
|
81
|
+
part.preliminary !== true) ||
|
|
82
|
+
part.state === 'output-error' ||
|
|
83
|
+
part.state === 'output-denied')),
|
|
72
84
|
),
|
|
73
85
|
}));
|
|
74
86
|
}
|
|
@@ -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
|
|
@@ -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.
|