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.
Files changed (95) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/index.d.ts +7423 -7247
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +19263 -22470
  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/02-chatbot.mdx +9 -0
  29. package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +17 -7
  30. package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +12 -5
  31. package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +17 -10
  32. package/docs/04-ai-sdk-ui/05-completion.mdx +1 -1
  33. package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +2 -2
  34. package/docs/05-ai-sdk-rsc/03-generative-ui-state.mdx +8 -7
  35. package/docs/05-ai-sdk-rsc/04-multistep-interfaces.mdx +1 -1
  36. package/docs/05-ai-sdk-rsc/06-loading-state.mdx +19 -10
  37. package/docs/05-ai-sdk-rsc/08-error-handling.mdx +2 -0
  38. package/docs/05-ai-sdk-rsc/10-migrating-to-ui.mdx +4 -3
  39. package/docs/06-advanced/02-stopping-streams.mdx +2 -2
  40. package/docs/06-advanced/04-caching.mdx +9 -5
  41. package/docs/06-advanced/05-multiple-streamables.mdx +1 -1
  42. package/docs/06-advanced/07-rendering-ui-with-language-models.mdx +16 -16
  43. package/docs/06-advanced/09-multistep-interfaces.mdx +1 -1
  44. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -4
  45. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +5 -5
  46. package/docs/07-reference/01-ai-sdk-core/14-upload-file.mdx +4 -4
  47. package/docs/07-reference/01-ai-sdk-core/14-upload-skill.mdx +1 -1
  48. package/docs/07-reference/01-ai-sdk-core/15-agent.mdx +1 -1
  49. package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +1 -4
  50. package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +1 -1
  51. package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +1 -1
  52. package/docs/07-reference/01-ai-sdk-core/30-model-message.mdx +2 -2
  53. package/docs/07-reference/01-ai-sdk-core/69-add-tool-input-examples-middleware.mdx +1 -1
  54. package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +1 -1
  55. package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +5 -4
  56. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +3 -2
  57. package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +7 -0
  58. package/docs/07-reference/02-ai-sdk-ui/42-pipe-ui-message-stream-to-response.mdx +7 -0
  59. package/docs/07-reference/02-ai-sdk-ui/50-direct-chat-transport.mdx +2 -2
  60. package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +2 -2
  61. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +1 -1
  62. package/docs/08-migration-guides/25-migration-guide-5-0-data.mdx +4 -5
  63. package/docs/08-migration-guides/26-migration-guide-5-0.mdx +2 -1
  64. package/docs/08-migration-guides/29-migration-guide-4-0.mdx +9 -9
  65. package/docs/08-migration-guides/37-migration-guide-3-3.mdx +1 -1
  66. package/docs/08-migration-guides/38-migration-guide-3-2.mdx +1 -1
  67. package/docs/08-migration-guides/39-migration-guide-3-1.mdx +1 -1
  68. package/docs/09-troubleshooting/12-use-chat-an-error-occurred.mdx +8 -6
  69. package/docs/09-troubleshooting/50-react-maximum-update-depth-exceeded.mdx +6 -6
  70. package/docs/09-troubleshooting/70-high-memory-usage-with-images.mdx +1 -1
  71. package/package.json +17 -17
  72. package/src/agent/create-agent-ui-stream-response.ts +3 -0
  73. package/src/agent/pipe-agent-ui-stream-to-response.ts +3 -0
  74. package/src/embed/embed-many.ts +5 -3
  75. package/src/generate-image/generate-image.ts +8 -3
  76. package/src/generate-text/prune-messages.ts +1 -1
  77. package/src/generate-text/smooth-stream.ts +24 -9
  78. package/src/generate-text/stream-text-result.ts +5 -0
  79. package/src/generate-video/generate-video.ts +6 -4
  80. package/src/middleware/extract-reasoning-middleware.ts +27 -11
  81. package/src/registry/provider-registry.ts +1 -1
  82. package/src/ui/chat.ts +13 -4
  83. package/src/ui/convert-to-model-messages.ts +18 -6
  84. package/src/ui/http-chat-transport.ts +19 -3
  85. package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +15 -3
  86. package/src/ui/process-ui-message-stream.ts +51 -14
  87. package/src/ui/ui-messages.ts +14 -0
  88. package/src/ui/validate-ui-messages.ts +2 -0
  89. package/src/ui-message-stream/create-sse-stream-with-keep-alive.ts +89 -0
  90. package/src/ui-message-stream/create-ui-message-stream-response.ts +7 -1
  91. package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +7 -1
  92. package/src/ui-message-stream/to-ui-message-chunk.ts +9 -1
  93. package/src/ui-message-stream/to-ui-message-stream.ts +1 -1
  94. package/src/ui-message-stream/ui-message-stream-response-init.ts +7 -0
  95. 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
- const api =
251
- preparedRequest?.api ??
252
- appendPathToUrl(this.api, `/${options.chatId}/stream`);
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 lastStepToolInvocations = message.parts
27
- .slice(lastStepStartIndex + 1)
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: createIdMap(),
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 ?? anyPart.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
  });
@@ -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 = 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
@@ -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.', // prevent leaking server error details to the client by default
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.', // prevent leaking server error details to the client by default
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
+ }