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.
Files changed (77) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/index.d.ts +7418 -7268
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +19263 -22545
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +2044 -2004
  7. package/dist/internal/index.d.ts.map +1 -0
  8. package/dist/internal/index.js +3255 -3921
  9. package/dist/internal/index.js.map +1 -1
  10. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/test/index.d.ts +294 -273
  12. package/dist/test/index.d.ts.map +1 -0
  13. package/dist/test/index.js +325 -469
  14. package/dist/test/index.js.map +1 -1
  15. package/docs/02-foundations/03-prompts.mdx +3 -1
  16. package/docs/02-foundations/06-provider-options.mdx +1 -1
  17. package/docs/02-getting-started/06-nodejs.mdx +4 -4
  18. package/docs/02-getting-started/08-tanstack-start.mdx +1 -1
  19. package/docs/03-agents/05-configuring-call-options.mdx +1 -1
  20. package/docs/03-ai-sdk-core/10-generating-structured-data.mdx +1 -1
  21. package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +3 -0
  22. package/docs/03-ai-sdk-core/17-mcp-apps.mdx +5 -5
  23. package/docs/03-ai-sdk-core/37-speech.mdx +1 -1
  24. package/docs/03-ai-sdk-core/42-batch.mdx +1 -1
  25. package/docs/03-ai-sdk-core/60-telemetry.mdx +2 -2
  26. package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +24 -0
  27. package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +12 -12
  28. package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +9 -7
  29. package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +2 -2
  30. package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +7 -9
  31. package/docs/04-ai-sdk-ui/05-completion.mdx +1 -1
  32. package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +2 -2
  33. package/docs/05-ai-sdk-rsc/03-generative-ui-state.mdx +8 -7
  34. package/docs/05-ai-sdk-rsc/06-loading-state.mdx +19 -10
  35. package/docs/05-ai-sdk-rsc/08-error-handling.mdx +2 -0
  36. package/docs/05-ai-sdk-rsc/10-migrating-to-ui.mdx +4 -3
  37. package/docs/06-advanced/02-stopping-streams.mdx +2 -2
  38. package/docs/06-advanced/04-caching.mdx +9 -5
  39. package/docs/06-advanced/05-multiple-streamables.mdx +1 -1
  40. package/docs/06-advanced/07-rendering-ui-with-language-models.mdx +16 -16
  41. package/docs/06-advanced/09-multistep-interfaces.mdx +1 -1
  42. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -4
  43. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +4 -4
  44. package/docs/07-reference/01-ai-sdk-core/14-upload-file.mdx +4 -4
  45. package/docs/07-reference/01-ai-sdk-core/14-upload-skill.mdx +1 -1
  46. package/docs/07-reference/01-ai-sdk-core/15-agent.mdx +1 -1
  47. package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +1 -4
  48. package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +1 -1
  49. package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +1 -1
  50. package/docs/07-reference/01-ai-sdk-core/30-model-message.mdx +2 -2
  51. package/docs/07-reference/01-ai-sdk-core/69-add-tool-input-examples-middleware.mdx +1 -1
  52. package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +1 -1
  53. package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +5 -4
  54. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +3 -2
  55. package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +7 -0
  56. package/docs/07-reference/02-ai-sdk-ui/42-pipe-ui-message-stream-to-response.mdx +7 -0
  57. package/docs/07-reference/02-ai-sdk-ui/50-direct-chat-transport.mdx +2 -2
  58. package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +2 -2
  59. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +1 -1
  60. package/docs/08-migration-guides/25-migration-guide-5-0-data.mdx +4 -5
  61. package/docs/08-migration-guides/26-migration-guide-5-0.mdx +2 -1
  62. package/docs/08-migration-guides/29-migration-guide-4-0.mdx +4 -4
  63. package/docs/08-migration-guides/38-migration-guide-3-2.mdx +1 -1
  64. package/docs/08-migration-guides/39-migration-guide-3-1.mdx +1 -1
  65. package/docs/09-troubleshooting/12-use-chat-an-error-occurred.mdx +8 -6
  66. package/docs/09-troubleshooting/50-react-maximum-update-depth-exceeded.mdx +6 -6
  67. package/docs/09-troubleshooting/70-high-memory-usage-with-images.mdx +1 -1
  68. package/package.json +17 -17
  69. package/src/agent/create-agent-ui-stream-response.ts +3 -0
  70. package/src/agent/pipe-agent-ui-stream-to-response.ts +3 -0
  71. package/src/generate-text/prune-messages.ts +1 -1
  72. package/src/middleware/extract-reasoning-middleware.ts +27 -11
  73. package/src/ui/convert-to-model-messages.ts +18 -6
  74. package/src/ui-message-stream/create-sse-stream-with-keep-alive.ts +89 -0
  75. package/src/ui-message-stream/create-ui-message-stream-response.ts +7 -1
  76. package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +7 -1
  77. 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 `createDataStreamResponse`, you can use the `onError` function when calling `toDataStreamResponse`:
52
+ In case you are using `createUIMessageStream`, you can pass the `onError` function to it:
53
53
 
54
54
  ```tsx
55
- const response = createDataStreamResponse({
56
- // ...
57
- async execute(dataStream) {
55
+ const response = createUIMessageStreamResponse({
56
+ stream: createUIMessageStream({
58
57
  // ...
59
- },
60
- onError: errorHandler,
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, ... } = useChat({
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, ... } = useCompletion({
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
- By default, 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.
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.122",
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.100",
46
- "@ai-sdk/provider": "4.0.19",
47
- "@ai-sdk/provider-utils": "5.0.51"
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.100",
51
- "@ai-sdk/deepseek": "3.0.56",
52
- "@ai-sdk/google": "4.0.85",
53
- "@ai-sdk/groq": "4.0.52",
54
- "@ai-sdk/huggingface": "2.0.59",
55
- "@ai-sdk/moonshotai": "3.0.60",
56
- "@ai-sdk/openai": "4.0.80",
57
- "@ai-sdk/test-server": "2.0.2",
58
- "@ai-sdk/xai": "5.0.12",
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
- "tsup": "^8.5.1",
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 && tsup --tsconfig tsconfig.build.json",
117
- "build:watch": "pnpm clean && tsup --watch --tsconfig tsconfig.build.json",
118
- "clean": "del-cli dist docs *.tsbuildinfo",
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 (reasoningExtractions[chunk.id] == null) {
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.buffer += chunk.delta;
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
- if (options?.ignoreIncompleteToolCalls) {
63
- messages = messages.map(message => ({
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 === 'approval-responded' ||
69
- (part.state === 'output-available' && part.preliminary !== true) ||
70
- part.state === 'output-error' ||
71
- part.state === 'output-denied',
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 = stream.pipeThrough(new JsonToSseTransformStream());
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 = stream.pipeThrough(new JsonToSseTransformStream());
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.