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
@@ -208,7 +208,7 @@ To see `streamUI` in action, check out [these examples](#examples).
208
208
  {
209
209
  name: 'role',
210
210
  type: "'tool'",
211
- description: 'The role for the assistant message.',
211
+ description: 'The role for the tool message.',
212
212
  },
213
213
  {
214
214
  name: 'content',
@@ -364,7 +364,7 @@ To see `streamUI` in action, check out [these examples](#examples).
364
364
  {
365
365
  name: 'generate',
366
366
  isOptional: true,
367
- type: '(async (parameters) => ReactNode) | AsyncGenerator<ReactNode, ReactNode, void>',
367
+ type: '(parameters) => ReactNode | Promise<ReactNode> | Generator<ReactNode, ReactNode, void> | AsyncGenerator<ReactNode, ReactNode, void>',
368
368
  description:
369
369
  'A function or a generator function that is called with the arguments from the tool call and yields React nodes as the UI.',
370
370
  },
@@ -1022,7 +1022,7 @@ const result = await agent.stream({
1022
1022
  messages,
1023
1023
 
1024
1024
  // Per-call callbacks (both fire)
1025
- onStepEnd({ usage }) {
1025
+ async onStepEnd({ usage }) {
1026
1026
  await trackUsage(usage);
1027
1027
  },
1028
1028
 
@@ -127,6 +127,7 @@ type V4FilePart = Extract<V4Part, { type: 'file' }>;
127
127
  // Type guards
128
128
  function isV4Message(msg: V4Message | MyUIMessage): msg is V4Message {
129
129
  return (
130
+ msg?.parts == null ||
130
131
  'toolInvocations' in msg ||
131
132
  (msg?.parts?.some(p => p.type === 'tool-invocation') ?? false) ||
132
133
  msg?.role === 'data' ||
@@ -465,9 +466,8 @@ export async function POST(req: Request) {
465
466
  message: convertV5MessageToV4(message), // convert to v4
466
467
  });
467
468
 
468
- // Load previous messages (already in v5 format)
469
- const previousMessages = await loadChat(chatId);
470
- const messages = [...previousMessages, message];
469
+ // Load all messages, including the one just saved (already in v5 format)
470
+ const messages = await loadChat(chatId);
471
471
 
472
472
  const result = streamText({
473
473
  model: __MODEL__,
@@ -827,8 +827,7 @@ export async function POST(req: Request) {
827
827
  message,
828
828
  });
829
829
 
830
- const previousMessages = await loadChat(chatId);
831
- const messages = [...previousMessages, message];
830
+ const messages = await loadChat(chatId);
832
831
 
833
832
  const result = streamText({
834
833
  model: __MODEL__,
@@ -723,7 +723,8 @@ const runtimeTool = dynamicTool({
723
723
  inputSchema: z.object({}),
724
724
  execute: async input => {
725
725
  // Input and output are typed as 'unknown'
726
- return { result: `Processed: ${input.query}` };
726
+ const { query } = input as { query: string };
727
+ return { result: `Processed: ${query}` };
727
728
  },
728
729
  });
729
730
  ```
@@ -27,8 +27,8 @@ description: Learn how to upgrade AI SDK 3.4 to 4.0.
27
27
  You need to update the following packages to the following versions in your `package.json` file(s):
28
28
 
29
29
  - `ai` package: `4.0.*`
30
- - `ai-sdk@provider-utils` package: `2.0.*`
31
- - `ai-sdk/*` packages: `1.0.*` (other `@ai-sdk` packages)
30
+ - `@ai-sdk/provider-utils` package: `2.0.*`
31
+ - `@ai-sdk/*` packages: `1.0.*` (other `@ai-sdk/*` packages)
32
32
 
33
33
  ## Codemods
34
34
 
@@ -440,7 +440,7 @@ const result = streamText({
440
440
 
441
441
  result.toDataStream();
442
442
  result.pipeDataStreamToResponse(response);
443
- result.toUIMessageStreamResponse();
443
+ result.toDataStreamResponse();
444
444
  ```
445
445
 
446
446
  ### Renamed "formatStreamPart" to "formatDataStreamPart"
@@ -518,7 +518,7 @@ import { Provider } from 'ai';
518
518
  providers](/docs/reference/ai-sdk-core/provider-registry#setup) instead.
519
519
  </Note>
520
520
 
521
- #### Removed `experimental_​createModelRegistry` function
521
+ #### Removed `experimental_createModelRegistry` function
522
522
 
523
523
  The `experimental_createModelRegistry` function has been removed.
524
524
  Please use the `experimental_createProviderRegistry` function instead.
@@ -573,7 +573,7 @@ const result = await streamText({
573
573
  // ...
574
574
  });
575
575
 
576
- result.toUIMessageStreamResponse(response, {
576
+ result.pipeDataStreamToResponse(response, {
577
577
  init: {
578
578
  headers: {
579
579
  'X-Custom-Header': 'value',
@@ -588,7 +588,7 @@ const result = streamText({
588
588
  // ...
589
589
  });
590
590
 
591
- result.toUIMessageStreamResponse(response, {
591
+ result.pipeDataStreamToResponse(response, {
592
592
  headers: {
593
593
  'X-Custom-Header': 'value',
594
594
  },
@@ -621,7 +621,7 @@ const { text, response } = await generateText({
621
621
  const responseMessages = response.messages;
622
622
  ```
623
623
 
624
- ### Removed `experimental_​continuationSteps` option
624
+ ### Removed `experimental_continuationSteps` option
625
625
 
626
626
  The `experimental_continuationSteps` option has been removed from the `generateText` function.
627
627
  Please use the `experimental_continueSteps` option instead.
@@ -710,7 +710,7 @@ const warnings = await result.warnings;
710
710
  code manually.
711
711
  </Note>
712
712
 
713
- The `simulateReadableStream` function from `ai/test` has been renamed to `chunks`.
713
+ The `values` option of the `simulateReadableStream` function from `ai/test` has been renamed to `chunks`.
714
714
 
715
715
  ```ts filename="AI SDK 3.4"
716
716
  import { simulateReadableStream } from 'ai/test';
@@ -965,7 +965,7 @@ return AssistantResponse(
965
965
  );
966
966
  ```
967
967
 
968
- #### Removed `experimental_​AssistantResponse` export
968
+ #### Removed `experimental_AssistantResponse` export
969
969
 
970
970
  <Note type="warning">
971
971
  There is no codemod available for this change. Please review and update your
@@ -18,7 +18,7 @@ introducing significant improvements and new features across the AI SDK and its
18
18
 
19
19
  ## New Features
20
20
 
21
- ### Open Telemetry Support
21
+ ### OpenTelemetry Support
22
22
 
23
23
  - Added experimental [OpenTelemetry support](/docs/ai-sdk-core/telemetry) for all [AI SDK Core functions](/docs/ai-sdk-core/overview#ai-sdk-core-functions), enabling better observability and tracing capabilities.
24
24
 
@@ -23,7 +23,7 @@ This guide will help you upgrade to AI SDK 3.2:
23
23
 
24
24
  To update to AI SDK version 3.2, run the following command using your preferred package manager:
25
25
 
26
- <Snippet text="pnpm add ai@latest" />
26
+ <Snippet text="pnpm add ai@3.2" />
27
27
 
28
28
  ## Removed Functionality
29
29
 
@@ -80,7 +80,7 @@ export async function POST(req: Request) {
80
80
  messages,
81
81
  });
82
82
 
83
- return result.toUIMessageStreamResponse();
83
+ return result.toDataStreamResponse();
84
84
  }
85
85
  ```
86
86
 
@@ -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.120",
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.98",
46
- "@ai-sdk/provider": "4.0.19",
47
- "@ai-sdk/provider-utils": "5.0.50"
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.99",
51
- "@ai-sdk/deepseek": "3.0.55",
52
- "@ai-sdk/google": "4.0.84",
53
- "@ai-sdk/groq": "4.0.51",
54
- "@ai-sdk/huggingface": "2.0.58",
55
- "@ai-sdk/moonshotai": "3.0.59",
56
- "@ai-sdk/openai": "4.0.79",
57
- "@ai-sdk/test-server": "2.0.2",
58
- "@ai-sdk/xai": "5.0.11",
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
  });
@@ -14,6 +14,8 @@ import type { TelemetryOptions } from '../telemetry/telemetry-options';
14
14
  import type { Embedding, EmbeddingModel, ProviderMetadata } from '../types';
15
15
  import type { Warning } from '../types/warning';
16
16
  import type { Callback } from '../util/callback';
17
+ import { getOwn } from '../util/get-own';
18
+ import { setOwn } from '../util/set-own';
17
19
  import { notify } from '../util/notify';
18
20
  import { prepareRetries } from '../util/prepare-retries';
19
21
  import { splitArray } from '../util/split-array';
@@ -427,10 +429,10 @@ export async function embedMany<RUNTIME_CONTEXT extends Context = Context>({
427
429
  for (const [providerName, metadata] of Object.entries(
428
430
  result.providerMetadata,
429
431
  )) {
430
- providerMetadata[providerName] = {
431
- ...providerMetadata[providerName],
432
+ setOwn(providerMetadata, providerName, {
433
+ ...getOwn(providerMetadata, providerName),
432
434
  ...metadata,
433
- };
435
+ });
434
436
  }
435
437
  }
436
438
  }
@@ -25,6 +25,8 @@ import type { ImageModel } from '../types/image-model';
25
25
  import type { ImageModelResponseMetadata } from '../types/image-model-response-metadata';
26
26
  import { addImageModelUsage, type ImageModelUsage } from '../types/usage';
27
27
  import type { Warning } from '../types/warning';
28
+ import { getOwn } from '../util/get-own';
29
+ import { setOwn } from '../util/set-own';
28
30
  import { prepareRetries } from '../util/prepare-retries';
29
31
  import { RetryError } from '../util/retry-error';
30
32
  import { VERSION } from '../version';
@@ -324,8 +326,11 @@ export async function generateImage({
324
326
  .images;
325
327
  }
326
328
  } else {
327
- providerMetadata[providerName] ??= { images: [] };
328
- providerMetadata[providerName].images.push(...metadata.images);
329
+ const currentEntry = getOwn(providerMetadata, providerName) ?? {
330
+ images: [],
331
+ };
332
+ currentEntry.images.push(...metadata.images);
333
+ setOwn(providerMetadata, providerName, currentEntry);
329
334
  }
330
335
  }
331
336
  }
@@ -395,7 +400,7 @@ export function getImageProviderMetadata(
395
400
  const value = metadata.images?.[imageIndex];
396
401
 
397
402
  if (isJSONObject(value) && !Array.isArray(value)) {
398
- (imageMetadata ??= {})[providerName] = value;
403
+ setOwn((imageMetadata ??= {}), providerName, value);
399
404
  }
400
405
  }
401
406
 
@@ -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') &&
@@ -159,10 +159,20 @@ export function smoothStream<TOOLS extends ToolSet>({
159
159
  return;
160
160
  }
161
161
 
162
- // Flush buffer when type or id changes
162
+ if (chunk.text.length === 0 && chunk.providerMetadata != null) {
163
+ flushBuffer(controller);
164
+ controller.enqueue(chunk);
165
+ return;
166
+ }
167
+
168
+ // Flush at metadata boundaries because one output part cannot preserve
169
+ // metadata from multiple input deltas.
163
170
  if (
164
- (chunk.type !== type || chunk.id !== id) &&
165
- (buffer.length > 0 || providerMetadata != null)
171
+ buffer.length > 0 &&
172
+ (chunk.type !== type ||
173
+ chunk.id !== id ||
174
+ providerMetadata != null ||
175
+ chunk.providerMetadata != null)
166
176
  ) {
167
177
  flushBuffer(controller);
168
178
  }
@@ -170,20 +180,25 @@ export function smoothStream<TOOLS extends ToolSet>({
170
180
  buffer += chunk.text;
171
181
  id = chunk.id;
172
182
  type = chunk.type;
173
-
174
- // Preserve providerMetadata (e.g., Anthropic thinking signatures)
175
- if (chunk.providerMetadata != null) {
176
- providerMetadata = chunk.providerMetadata;
177
- }
183
+ providerMetadata = chunk.providerMetadata;
178
184
 
179
185
  let match;
180
186
 
181
187
  while ((match = detectChunk(buffer)) != null) {
182
- controller.enqueue({ type, text: match, id });
188
+ controller.enqueue({
189
+ type,
190
+ text: match,
191
+ id,
192
+ ...(providerMetadata != null ? { providerMetadata } : {}),
193
+ });
183
194
  buffer = buffer.slice(match.length);
184
195
 
185
196
  await delay(isDocumentHidden() ? null : delayInMs);
186
197
  }
198
+
199
+ if (buffer.length === 0) {
200
+ providerMetadata = undefined;
201
+ }
187
202
  },
188
203
  });
189
204
  };
@@ -104,6 +104,11 @@ export type UIMessageStreamOptions<UI_MESSAGE extends UIMessage> = {
104
104
  /**
105
105
  * Process an error, e.g. to log it. Default to `() => 'An error occurred.'`.
106
106
  *
107
+ * Tool execution errors marked `providerExecuted: true` bypass this callback.
108
+ * Their strings pass through unchanged; other values are JSON-stringified to
109
+ * preserve provider error data for model-message round trips and harness
110
+ * runtime error messages. Stream errors and invalid tool calls still use it.
111
+ *
107
112
  * @returns error message to include in the data stream.
108
113
  */
109
114
  onError?: (error: unknown) => string;
@@ -29,6 +29,8 @@ import { resolveVideoModel } from '../model/resolve-model';
29
29
  import type { VideoModel } from '../types/video-model';
30
30
  import type { VideoModelResponseMetadata } from '../types/video-model-response-metadata';
31
31
  import type { Warning } from '../types/warning';
32
+ import { getOwn } from '../util/get-own';
33
+ import { setOwn } from '../util/set-own';
32
34
  import { createDownload } from '../util/download/create-download';
33
35
  import { prepareRetries } from '../util/prepare-retries';
34
36
  import { VERSION } from '../version';
@@ -697,17 +699,17 @@ function mergeProviderMetadata(
697
699
  source: SharedV4ProviderMetadata,
698
700
  ): void {
699
701
  for (const [providerName, metadataValue] of Object.entries(source)) {
700
- const existingMetadata = target[providerName];
702
+ const existingMetadata = getOwn(target, providerName);
701
703
  if (
702
704
  existingMetadata != null &&
703
705
  typeof existingMetadata === 'object' &&
704
706
  metadataValue != null &&
705
707
  typeof metadataValue === 'object'
706
708
  ) {
707
- target[providerName] = {
709
+ setOwn(target, providerName, {
708
710
  ...existingMetadata,
709
711
  ...metadataValue,
710
- };
712
+ });
711
713
 
712
714
  if (
713
715
  'videos' in existingMetadata &&
@@ -721,7 +723,7 @@ function mergeProviderMetadata(
721
723
  ];
722
724
  }
723
725
  } else {
724
- target[providerName] = metadataValue;
726
+ setOwn(target, providerName, metadataValue);
725
727
  }
726
728
  }
727
729
  }
@@ -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
@@ -213,7 +213,7 @@ class DefaultProviderRegistry<
213
213
  keyof PROVIDERS,
214
214
  ProviderV4 & ProviderWithOptionalVideoModel & EvaluationProvider
215
215
  >
216
- > = {};
216
+ > = Object.create(null);
217
217
  private separator: SEPARATOR;
218
218
  private languageModelMiddleware?:
219
219
  | LanguageModelMiddleware
package/src/ui/chat.ts CHANGED
@@ -818,6 +818,14 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
818
818
  responseMessageIndex === -1
819
819
  ? lastMessage
820
820
  : this.state.messages[responseMessageIndex];
821
+ const resumableResponseMessage =
822
+ trigger === 'resume-stream' &&
823
+ responseMessage?.role === 'assistant' &&
824
+ responseMessage.parts.some(
825
+ part => isToolUIPart(part) && part.state === 'input-streaming',
826
+ )
827
+ ? this.state.snapshot(responseMessage)
828
+ : undefined;
821
829
  const usesEarlierAssistantMessage =
822
830
  responseMessageIndex !== -1 &&
823
831
  responseMessageIndex < this.state.messages.length - 1 &&
@@ -835,10 +843,11 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
835
843
  ? this.resumableStreamState
836
844
  : createStreamingUIMessageState({
837
845
  lastMessage:
838
- trigger === 'resume-stream' ||
839
- trigger === 'regenerate-message'
840
- ? undefined
841
- : this.state.snapshot(responseMessage),
846
+ trigger === 'resume-stream'
847
+ ? resumableResponseMessage
848
+ : trigger === 'regenerate-message'
849
+ ? undefined
850
+ : this.state.snapshot(responseMessage),
842
851
  messageId: this.generateId(),
843
852
  }),
844
853
  abortController,
@@ -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
  }