ai 7.0.41 → 7.0.42

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.
@@ -129,6 +129,10 @@ const result = await agent.generate({
129
129
  });
130
130
  ```
131
131
 
132
+ Model call settings returned from `prepareStep`, such as `temperature`, apply
133
+ only to the current step. Later steps use the agent's top-level setting unless
134
+ they return another override.
135
+
132
136
  For the full model, including sensitive context filtering and where each context
133
137
  value is available, see [Runtime and Tool
134
138
  Context](/docs/ai-sdk-core/runtime-and-tool-context).
@@ -180,6 +180,46 @@ const result = await agent.generate({
180
180
  });
181
181
  ```
182
182
 
183
+ ### Model Call Settings
184
+
185
+ Override provider-agnostic model call settings for an individual step. This can
186
+ be useful when tool-calling steps need more deterministic sampling than the
187
+ final response:
188
+
189
+ ```ts
190
+ import { ToolLoopAgent } from 'ai';
191
+ __PROVIDER_IMPORT__;
192
+
193
+ const agent = new ToolLoopAgent({
194
+ model: __MODEL__,
195
+ temperature: 0.7,
196
+ tools: {
197
+ // your tools
198
+ },
199
+ prepareStep: async ({ stepNumber }) => {
200
+ if (stepNumber === 0) {
201
+ return {
202
+ temperature: 0,
203
+ maxOutputTokens: 300,
204
+ };
205
+ }
206
+
207
+ return {};
208
+ },
209
+ });
210
+
211
+ const result = await agent.generate({
212
+ prompt: '...',
213
+ });
214
+ ```
215
+
216
+ `prepareStep` can override `maxOutputTokens`, `temperature`, `topP`, `topK`,
217
+ `presencePenalty`, `frequencyPenalty`, `stopSequences`, `seed`, and
218
+ `reasoning`. These overrides apply only to the current step. When a setting is
219
+ omitted or `undefined`, the top-level value is used for that step. Defined
220
+ falsy values such as `temperature: 0`, `seed: 0`, and an empty
221
+ `stopSequences` array are preserved.
222
+
183
223
  ### Context Management
184
224
 
185
225
  Long-running agents can accumulate large tool results, reasoning parts, and assistant messages. Use `prepareStep` to mutate the message state that will be used by later steps. This is useful for compaction, and you decide when compaction should happen.
@@ -304,6 +304,7 @@ try {
304
304
  | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#streaming-transcription-models) | `scribe_v2_realtime` |
305
305
  | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3-turbo` |
306
306
  | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3` |
307
+ | [Mistral](/providers/ai-sdk-providers/mistral#transcription-models) | `voxtral-mini-latest` |
307
308
  | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `whisper-1` |
308
309
  | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-transcribe` |
309
310
  | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-mini-transcribe` |
@@ -105,10 +105,56 @@ the workspace.
105
105
  DevTools captures the following information from your AI SDK calls:
106
106
 
107
107
  - **Input parameters and prompts**: View the complete input sent to your LLM
108
- - **Output content and tool calls**: Inspect generated text and tool invocations
108
+ - **Output content and tool calls**: Inspect generated text, tool invocations, and tool results
109
+ - **Media previews**: View images, audio, and video included in prompts, tool inputs, and tool outputs
109
110
  - **Token usage and timing**: Monitor resource consumption and performance
110
111
  - **Raw provider data**: Access provider request and response payloads when body retention is enabled
111
112
 
113
+ ### Media previews
114
+
115
+ DevTools recognizes current `file` content parts as well as the deprecated
116
+ `image-*`, `file-*`, and `media` tool-result aliases. Inline image, audio, and
117
+ video data is previewed directly, while the captured JSON shape and metadata
118
+ remain available for inspection. The viewer displays at most 8 previews per
119
+ value, traverses at most 12 nested levels, and embeds inline previews up to 5
120
+ MiB. Longer JSON strings are truncated in the viewer to avoid duplicating large
121
+ base64 payloads. Binary values in recognized media-bearing fields are persisted
122
+ as base64 so they remain previewable; unrelated binary values retain their
123
+ normal JSON representation.
124
+
125
+ For example, a tool can return media through `toModelOutput`:
126
+
127
+ ```ts
128
+ import { tool } from 'ai';
129
+ import { z } from 'zod';
130
+
131
+ const captureScreenshot = tool({
132
+ inputSchema: z.object({}),
133
+ execute: async () => ({
134
+ base64: await captureScreenshotAsBase64(),
135
+ }),
136
+ toModelOutput: ({ output }) => ({
137
+ type: 'content',
138
+ value: [
139
+ {
140
+ type: 'file',
141
+ filename: 'screenshot.png',
142
+ mediaType: 'image/png',
143
+ data: { type: 'data', data: output.base64 },
144
+ },
145
+ ],
146
+ }),
147
+ });
148
+ ```
149
+
150
+ Remote `http` and `https` media is not loaded automatically. Select **Load
151
+ preview** in the viewer to fetch it with anonymous CORS and no referrer.
152
+ Cross-origin browser credentials are omitted, but same-origin browser
153
+ credentials may still be included by the browser. URLs containing embedded
154
+ usernames or passwords are rejected. Unsupported media, provider references,
155
+ malformed values, unsafe URL schemes, and inline values over the preview limit
156
+ retain their JSON and metadata fallback without an embedded preview.
157
+
112
158
  Telemetry is enabled automatically, but AI SDK 7 excludes raw request and response bodies from step results by default. To make them available to DevTools for `generateText`, enable body retention on the call:
113
159
 
114
160
  ```ts highlight="4-7"
@@ -27,6 +27,10 @@ When the SDK fetches a URL taken from a provider response, it:
27
27
  are rejected too.
28
28
  - **Re-validates every redirect hop** — a URL that passes but then redirects to
29
29
  an internal address is blocked; the redirect is never followed blindly.
30
+ - **Validates DNS at connection time on Node.js** — every resolved address is
31
+ checked, and the socket is pinned to the validated DNS result so DNS
32
+ rebinding cannot introduce a different address between validation and
33
+ connection.
30
34
  - **Strips risky request headers** — proxy-forwarding, cloud-metadata, and
31
35
  cookie headers are removed before the request.
32
36
  - **Drops credentials across origins** — caller headers (`Authorization`,
@@ -43,28 +47,17 @@ custom `baseURL` pointing at a self-hosted or `localhost` deployment) are
43
47
  exempt from these checks — they target exactly the host you told the SDK to
44
48
  talk to. Any redirect off that origin is still validated.
45
49
 
46
- ## Limitation: DNS resolution and DNS rebinding
50
+ ## DNS validation across runtimes
47
51
 
48
- The built-in guard inspects the URL **as a string**. It deliberately does
49
- **not resolve DNS**, so two attacks remain out of scope at this layer:
52
+ On Node.js, the default validated download fetch uses `node:dns` and an
53
+ `undici` connector hook to validate every resolved address at connection time.
54
+ The connector uses those exact results, closing both hostname-to-private-IP and
55
+ DNS-rebinding bypasses.
50
56
 
51
- 1. **Hostname that resolves to a private IP** a literal host that looks public
52
- but whose DNS record points at an internal address.
53
- 2. **DNS rebinding** a host that resolves to a public IP when validated and a
54
- private IP a moment later when the socket actually connects (a
55
- time-of-check/time-of-use window).
56
-
57
- ### Why this isn't built in
58
-
59
- Closing these requires resolving DNS and pinning the resolved IP **at connect
60
- time** — Node-only capabilities (`node:dns`, a custom `undici` dispatcher). The
61
- SDK's provider utilities are **cross-runtime**: they run on the edge, in the
62
- browser, and on Bun/Deno, with no Node-only dependencies, so those APIs aren't
63
- available there. The threat is also specifically a **server-side** one — on the
64
- edge and in the browser, outbound `fetch` cannot reach a host's internal network
65
- or metadata endpoint in the first place. So connect-time IP pinning is only
66
- meaningful, and only available, on a Node server — which is exactly where you
67
- can add it yourself.
57
+ If you inject or globally replace `fetch`, it is responsible for equivalent DNS
58
+ validation and connection pinning. Other runtimes do not expose Node's
59
+ DNS/socket hooks, so server deployments on those runtimes should restrict
60
+ network egress to private, loopback, link-local, and cloud-metadata ranges.
68
61
 
69
62
  ## Hardening your deployment
70
63
 
@@ -77,9 +70,10 @@ Deny your server's network egress to `169.254.0.0/16`, RFC-1918 ranges, and
77
70
  loopback. This is the most robust control and is independent of application
78
71
  code.
79
72
 
80
- ### 2. Inject a hardened `fetch`
73
+ ### 2. Harden an injected `fetch`
81
74
 
82
- Every provider accepts a custom `fetch`. On Node, back it with an `undici`
75
+ The Node.js default is already pinned. If you inject or globally replace
76
+ `fetch`, back it with an `undici`
83
77
  `Agent` whose `connect.lookup` validates the resolved IP and lets the socket
84
78
  connect only to a safe address — closing both the hostname-to-private and the
85
79
  DNS-rebinding windows:
@@ -113,5 +107,5 @@ import { createFal } from '@ai-sdk/fal';
113
107
  const fal = createFal({ fetch: safeFetch });
114
108
  ```
115
109
 
116
- The SDK's built-in validation and your connect-time pinning are complementary —
117
- keep both.
110
+ The SDK's URL validation and your custom fetch's connect-time pinning are
111
+ complementary — keep both.
@@ -597,7 +597,7 @@ To see `generateText` in action, check out [these examples](#examples).
597
597
  type: '(options: PrepareStepOptions) => PrepareStepResult<TOOLS> | Promise<PrepareStepResult<TOOLS>>',
598
598
  isOptional: true,
599
599
  description:
600
- 'Optional function that you can use to provide different settings for a step. You can modify the model, tool choices, active tools, instructions, input messages, and experimental sandbox for each step.',
600
+ 'Optional function that you can use to provide different settings for a step. You can modify the model, model call settings, tool choices, active tools, instructions, input messages, and experimental sandbox for each step.',
601
601
  properties: [
602
602
  {
603
603
  type: 'PrepareStepFunction<TOOLS>',
@@ -682,6 +682,69 @@ To see `generateText` in action, check out [these examples](#examples).
682
682
  description:
683
683
  'Optionally override which LanguageModel instance is used for this step.',
684
684
  },
685
+ {
686
+ name: 'maxOutputTokens',
687
+ type: 'number',
688
+ isOptional: true,
689
+ description:
690
+ 'Maximum number of tokens to generate for this step. Uses the top-level value when omitted or undefined.',
691
+ },
692
+ {
693
+ name: 'temperature',
694
+ type: 'number',
695
+ isOptional: true,
696
+ description:
697
+ 'Temperature for this step. Uses the top-level value when omitted or undefined.',
698
+ },
699
+ {
700
+ name: 'topP',
701
+ type: 'number',
702
+ isOptional: true,
703
+ description:
704
+ 'Nucleus sampling value for this step. Uses the top-level value when omitted or undefined.',
705
+ },
706
+ {
707
+ name: 'topK',
708
+ type: 'number',
709
+ isOptional: true,
710
+ description:
711
+ 'Top-K sampling value for this step. Uses the top-level value when omitted or undefined.',
712
+ },
713
+ {
714
+ name: 'presencePenalty',
715
+ type: 'number',
716
+ isOptional: true,
717
+ description:
718
+ 'Presence penalty for this step. Uses the top-level value when omitted or undefined.',
719
+ },
720
+ {
721
+ name: 'frequencyPenalty',
722
+ type: 'number',
723
+ isOptional: true,
724
+ description:
725
+ 'Frequency penalty for this step. Uses the top-level value when omitted or undefined.',
726
+ },
727
+ {
728
+ name: 'stopSequences',
729
+ type: 'string[]',
730
+ isOptional: true,
731
+ description:
732
+ 'Stop sequences for this step. Uses the top-level value when omitted or undefined.',
733
+ },
734
+ {
735
+ name: 'seed',
736
+ type: 'number',
737
+ isOptional: true,
738
+ description:
739
+ 'Random sampling seed for this step. Uses the top-level value when omitted or undefined.',
740
+ },
741
+ {
742
+ name: 'reasoning',
743
+ type: 'LanguageModelV4CallOptions["reasoning"]',
744
+ isOptional: true,
745
+ description:
746
+ 'Reasoning effort for this step. Uses the top-level value when omitted or undefined.',
747
+ },
685
748
  {
686
749
  name: 'toolChoice',
687
750
  type: 'ToolChoice<TOOLS>',
@@ -2362,7 +2425,8 @@ To see `generateText` in action, check out [these examples](#examples).
2362
2425
  {
2363
2426
  name: 'text',
2364
2427
  type: 'string',
2365
- description: 'The generated text by the model.',
2428
+ description:
2429
+ 'The concatenation of all text parts generated in the final step. It is an empty string if the final step contains no text parts. Inspect `finalStep.content` to distinguish that case.',
2366
2430
  },
2367
2431
  {
2368
2432
  name: 'reasoning',
@@ -2795,7 +2859,8 @@ To see `generateText` in action, check out [these examples](#examples).
2795
2859
  {
2796
2860
  name: 'text',
2797
2861
  type: 'string',
2798
- description: 'The generated text.',
2862
+ description:
2863
+ 'The concatenation of all text parts generated in this step. It is an empty string if the step contains no text parts.',
2799
2864
  },
2800
2865
  {
2801
2866
  name: 'reasoning',
@@ -641,7 +641,7 @@ To see `streamText` in action, check out [these examples](#examples).
641
641
  type: '(options: PrepareStepOptions) => PrepareStepResult<TOOLS> | Promise<PrepareStepResult<TOOLS>>',
642
642
  isOptional: true,
643
643
  description:
644
- 'Optional function that you can use to provide different settings for a step. You can modify the model, tool choices, active tools, instructions, input messages, and experimental sandbox for each step.',
644
+ 'Optional function that you can use to provide different settings for a step. You can modify the model, model call settings, tool choices, active tools, instructions, input messages, and experimental sandbox for each step.',
645
645
  properties: [
646
646
  {
647
647
  type: 'PrepareStepFunction<TOOLS>',
@@ -726,6 +726,69 @@ To see `streamText` in action, check out [these examples](#examples).
726
726
  description:
727
727
  'Optionally override which LanguageModel instance is used for this step.',
728
728
  },
729
+ {
730
+ name: 'maxOutputTokens',
731
+ type: 'number',
732
+ isOptional: true,
733
+ description:
734
+ 'Maximum number of tokens to generate for this step. Uses the top-level value when omitted or undefined.',
735
+ },
736
+ {
737
+ name: 'temperature',
738
+ type: 'number',
739
+ isOptional: true,
740
+ description:
741
+ 'Temperature for this step. Uses the top-level value when omitted or undefined.',
742
+ },
743
+ {
744
+ name: 'topP',
745
+ type: 'number',
746
+ isOptional: true,
747
+ description:
748
+ 'Nucleus sampling value for this step. Uses the top-level value when omitted or undefined.',
749
+ },
750
+ {
751
+ name: 'topK',
752
+ type: 'number',
753
+ isOptional: true,
754
+ description:
755
+ 'Top-K sampling value for this step. Uses the top-level value when omitted or undefined.',
756
+ },
757
+ {
758
+ name: 'presencePenalty',
759
+ type: 'number',
760
+ isOptional: true,
761
+ description:
762
+ 'Presence penalty for this step. Uses the top-level value when omitted or undefined.',
763
+ },
764
+ {
765
+ name: 'frequencyPenalty',
766
+ type: 'number',
767
+ isOptional: true,
768
+ description:
769
+ 'Frequency penalty for this step. Uses the top-level value when omitted or undefined.',
770
+ },
771
+ {
772
+ name: 'stopSequences',
773
+ type: 'string[]',
774
+ isOptional: true,
775
+ description:
776
+ 'Stop sequences for this step. Uses the top-level value when omitted or undefined.',
777
+ },
778
+ {
779
+ name: 'seed',
780
+ type: 'number',
781
+ isOptional: true,
782
+ description:
783
+ 'Random sampling seed for this step. Uses the top-level value when omitted or undefined.',
784
+ },
785
+ {
786
+ name: 'reasoning',
787
+ type: 'LanguageModelV4CallOptions["reasoning"]',
788
+ isOptional: true,
789
+ description:
790
+ 'Reasoning effort for this step. Uses the top-level value when omitted or undefined.',
791
+ },
729
792
  {
730
793
  name: 'toolChoice',
731
794
  type: 'ToolChoice<TOOLS>',
@@ -123,7 +123,7 @@ To see `ToolLoopAgent` in action, check out [these examples](#examples).
123
123
  type: 'PrepareStepFunction',
124
124
  isOptional: true,
125
125
  description:
126
- 'Optional function to mutate step settings or inject state for each agent step.',
126
+ 'Optional function to mutate step settings or inject state for each agent step, including per-step model call settings such as temperature, maxOutputTokens, sampling controls, penalties, stop sequences, seed, and reasoning. Model call setting overrides apply only to the current step.',
127
127
  },
128
128
  {
129
129
  name: 'include',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.41",
3
+ "version": "7.0.42",
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,9 +42,9 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.31",
45
+ "@ai-sdk/gateway": "4.0.32",
46
46
  "@ai-sdk/provider": "4.0.4",
47
- "@ai-sdk/provider-utils": "5.0.14"
47
+ "@ai-sdk/provider-utils": "5.0.15"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@edge-runtime/vm": "^5.0.0",
@@ -40,7 +40,9 @@ export interface GenerateTextResult<
40
40
  readonly content: Array<ContentPart<TOOLS>>;
41
41
 
42
42
  /**
43
- * The text that was generated in the final step.
43
+ * The concatenation of all text parts generated in the final step.
44
+ * It is an empty string if the final step contains no text parts.
45
+ * Inspect `finalStep.content` to distinguish that case.
44
46
  */
45
47
  readonly text: string;
46
48
 
@@ -84,6 +84,7 @@ import { text, type Output } from './output';
84
84
  import type { InferCompleteOutput } from './output-utils';
85
85
  import { parseToolCall } from './parse-tool-call';
86
86
  import type { PrepareStepFunction } from './prepare-step';
87
+ import { prepareStepCallSettings } from './prepare-step-call-settings';
87
88
  import { convertToReasoningOutputs } from './reasoning-output';
88
89
  import { resolveToolApproval } from './resolve-tool-approval';
89
90
  import type { ResponseMessage } from './response-message';
@@ -888,6 +889,11 @@ export async function generateText<
888
889
  prepareStepResult?.providerOptions,
889
890
  );
890
891
 
892
+ const stepCallSettings = prepareStepCallSettings({
893
+ callSettings,
894
+ stepSettings: prepareStepResult,
895
+ });
896
+
891
897
  await notify({
892
898
  event: {
893
899
  callId,
@@ -921,7 +927,7 @@ export async function generateText<
921
927
  instructions: stepInstructions,
922
928
  messages: stepMessages,
923
929
  tools: stepTools,
924
- ...callSettings,
930
+ ...stepCallSettings,
925
931
  };
926
932
  const languageModelCallStartEvent = {
927
933
  callId,
@@ -951,7 +957,7 @@ export async function generateText<
951
957
  ...languageModelCallStartEvent,
952
958
  execute: async () =>
953
959
  await stepModel.doGenerate({
954
- ...callSettings,
960
+ ...stepCallSettings,
955
961
  tools: stepTools,
956
962
  toolChoice: stepToolChoice,
957
963
  responseFormat: await output?.responseFormat,
@@ -0,0 +1,31 @@
1
+ import type { LanguageModelCallOptions } from '../prompt/language-model-call-options';
2
+ import { prepareLanguageModelCallOptions } from '../prompt/prepare-language-model-call-options';
3
+
4
+ /**
5
+ * Resolves model call settings for a single step.
6
+ *
7
+ * Undefined step settings intentionally fall back to the outer call settings,
8
+ * while defined falsy values such as `temperature: 0` and `seed: 0` are kept.
9
+ */
10
+ export function prepareStepCallSettings({
11
+ callSettings,
12
+ stepSettings,
13
+ }: {
14
+ callSettings: LanguageModelCallOptions;
15
+ stepSettings: LanguageModelCallOptions | undefined;
16
+ }): LanguageModelCallOptions {
17
+ return prepareLanguageModelCallOptions({
18
+ maxOutputTokens:
19
+ stepSettings?.maxOutputTokens ?? callSettings.maxOutputTokens,
20
+ temperature: stepSettings?.temperature ?? callSettings.temperature,
21
+ topP: stepSettings?.topP ?? callSettings.topP,
22
+ topK: stepSettings?.topK ?? callSettings.topK,
23
+ presencePenalty:
24
+ stepSettings?.presencePenalty ?? callSettings.presencePenalty,
25
+ frequencyPenalty:
26
+ stepSettings?.frequencyPenalty ?? callSettings.frequencyPenalty,
27
+ stopSequences: stepSettings?.stopSequences ?? callSettings.stopSequences,
28
+ seed: stepSettings?.seed ?? callSettings.seed,
29
+ reasoning: stepSettings?.reasoning ?? callSettings.reasoning,
30
+ });
31
+ }
@@ -7,6 +7,7 @@ import type {
7
7
  ToolSet,
8
8
  } from '@ai-sdk/provider-utils';
9
9
  import type { Instructions } from '../prompt';
10
+ import type { LanguageModelCallOptions } from '../prompt/language-model-call-options';
10
11
  import type { LanguageModel, ToolChoice } from '../types/language-model';
11
12
  import type { ActiveTools } from './active-tools';
12
13
  import type { ResponseMessage } from './response-message';
@@ -95,13 +96,17 @@ export type PrepareStepFunction<
95
96
 
96
97
  /**
97
98
  * The result type returned by a {@link PrepareStepFunction},
98
- * allowing per-step overrides of model, tools, instructions, or messages.
99
+ * allowing per-step overrides of model call settings, model, tools,
100
+ * instructions, or messages.
101
+ *
102
+ * Model call setting overrides apply only to the current step. Undefined
103
+ * settings fall back to the outer call settings.
99
104
  */
100
105
  export type PrepareStepResult<
101
106
  TOOLS extends ToolSet,
102
107
  RUNTIME_CONTEXT extends Context = Context,
103
108
  > =
104
- | {
109
+ | ({
105
110
  /**
106
111
  * Optionally override which LanguageModel instance is used for this step.
107
112
  */
@@ -175,5 +180,5 @@ export type PrepareStepResult<
175
180
  * container IDs for Anthropic's code execution.
176
181
  */
177
182
  providerOptions?: ProviderOptions;
178
- }
183
+ } & LanguageModelCallOptions)
179
184
  | undefined;
@@ -176,7 +176,8 @@ export type StepResult<
176
176
  readonly content: Array<ContentPart<TOOLS>>;
177
177
 
178
178
  /**
179
- * The generated text. Can be an empty string if the model has not generated any text.
179
+ * The concatenation of all text parts generated in this step.
180
+ * It is an empty string if the step contains no text parts.
180
181
  */
181
182
  readonly text: string;
182
183
 
@@ -113,6 +113,7 @@ import type {
113
113
  InferPartialOutput,
114
114
  } from './output-utils';
115
115
  import type { PrepareStepFunction } from './prepare-step';
116
+ import { prepareStepCallSettings } from './prepare-step-call-settings';
116
117
  import { convertToReasoningOutputs } from './reasoning-output';
117
118
  import type { ResponseMessage } from './response-message';
118
119
  import { createRestrictedTelemetryDispatcher } from './restricted-telemetry-dispatcher';
@@ -915,6 +916,11 @@ function createOutputTransformStream<
915
916
  textChunk += chunk.text;
916
917
  textProviderMetadata = chunk.providerMetadata ?? textProviderMetadata;
917
918
 
919
+ if (chunk.text.length === 0 && chunk.providerMetadata != null) {
920
+ controller.enqueue({ part: chunk, partialOutput: undefined });
921
+ return;
922
+ }
923
+
918
924
  // only publish if partial json can be parsed:
919
925
  const result = await output.parsePartialOutput({ text });
920
926
 
@@ -1963,6 +1969,11 @@ class DefaultStreamTextResult<
1963
1969
  prepareStepResult?.providerOptions,
1964
1970
  );
1965
1971
 
1972
+ const stepCallSettings = prepareStepCallSettings({
1973
+ callSettings,
1974
+ stepSettings: prepareStepResult,
1975
+ });
1976
+
1966
1977
  const stepStartTimestampMs = now();
1967
1978
 
1968
1979
  const { retry } = prepareRetries({ maxRetries, abortSignal });
@@ -2035,7 +2046,7 @@ class DefaultStreamTextResult<
2035
2046
  _internal: {
2036
2047
  now,
2037
2048
  },
2038
- ...callSettings,
2049
+ ...stepCallSettings,
2039
2050
  }),
2040
2051
  ),
2041
2052
  );
@@ -2198,7 +2209,10 @@ class DefaultStreamTextResult<
2198
2209
  }
2199
2210
 
2200
2211
  case 'text-delta': {
2201
- if (chunk.text.length > 0) {
2212
+ if (
2213
+ chunk.text.length > 0 ||
2214
+ chunk.providerMetadata != null
2215
+ ) {
2202
2216
  controller.enqueue(chunk);
2203
2217
  }
2204
2218
  break;