ai 7.0.135 → 7.0.137

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.
@@ -606,9 +606,9 @@ type LanguageModelCallOptions = {
606
606
  * Timeout configuration for API calls. Can be specified as:
607
607
  * - A number representing milliseconds
608
608
  * - An object with `totalMs` property for the total timeout in milliseconds
609
- * - An object with `stepMs` property for the timeout of each step in milliseconds
609
+ * - An object with `stepMs` property for the timeout of each complete step, including model generation, retries, and local tool execution, in milliseconds
610
610
  * - An object with `firstChunkMs` property for the timeout until the first content chunk of each step (streaming only)
611
- * - An object with `chunkMs` property for the timeout between content chunks (streaming only)
611
+ * - An object with `chunkMs` property for the timeout between meaningful model output chunks, starting with the first output and ending when the model response ends (streaming only)
612
612
  * - An object with `toolMs` property for the default timeout for all tool executions
613
613
  * - An object with `tools` property for per-tool timeout overrides using `{toolName}Ms` keys
614
614
  */
@@ -111,7 +111,7 @@ function asAsyncIterableStream(stream) {
111
111
  }
112
112
  //#endregion
113
113
  //#region src/version.ts
114
- const VERSION = "7.0.135";
114
+ const VERSION = "7.0.137";
115
115
  //#endregion
116
116
  //#region src/util/download/download.ts
117
117
  /**
@@ -400,7 +400,7 @@ You can also combine multiple conditions in an array or create custom conditions
400
400
  results.
401
401
  </Note>
402
402
 
403
- By default, when you use `generateText` or `streamText`, it triggers a single generation. This works well for many use cases where you can rely on the model's training data to generate a response. However, when you provide tools, the model now has the choice to either generate a normal text response, or generate a tool call. If the model generates a tool call, its generation is complete and that step is finished.
403
+ By default, when you use `generateText` or `streamText`, it triggers a single generation. This works well for many use cases where you can rely on the model's training data to generate a response. However, when you provide tools, the model now has the choice to either generate a normal text response, or generate a tool call. A step includes one model generation and the execution of any local tools requested by that generation. If the model generates a tool call, the model generation finishes first; the step finishes after its local tool execution completes.
404
404
 
405
405
  You may want the model to generate text after the tool has been executed, either to summarize the tool results in the context of the users query. In many cases, you may also want the model to use multiple tools in a single response. This is where multi-step calls come in.
406
406
 
@@ -19,8 +19,12 @@ endpoint receives the events.
19
19
  ## Choose who manages subscriptions
20
20
 
21
21
  Use `experimental_events: { adapter }` when a backend manages subscriptions,
22
- webhook delivery and renewal. The adapter exposes managed watch handles and
23
- requires no AI SDK event store. See [managed subscriptions](/docs/reference/ai-sdk-core/mcp-events#managed-subscriptions).
22
+ webhook delivery and renewal. Supply an adapter whose
23
+ `createAdapter({ transport })` receives the MCP transport type and URL
24
+ (or just the type for custom transports) and returns bound
25
+ managed watch operations. No AI SDK event store is required. A backend SDK helper can
26
+ return this adapter directly, so callers do not need an inline `createAdapter`
27
+ wrapper. See [managed subscriptions](/docs/reference/ai-sdk-core/mcp-events#managed-subscriptions).
24
28
 
25
29
  Use `experimental_events: { store }` when your application directly registers
26
30
  MCP webhooks, persists their secrets and schedules renewal. The following flow
@@ -142,12 +142,14 @@ This is a convenience parameter that creates an abort signal internally. It can
142
142
  You can specify the timeout either as a number (milliseconds) or as an object with the following properties:
143
143
 
144
144
  - `totalMs`: The total timeout for the entire call including all steps.
145
- - `stepMs`: The timeout for each individual step (LLM call). This is useful for multi-step generations where you want to limit the time spent on each step independently.
145
+ - `stepMs`: The timeout for each complete step, including model generation, retries, and local tool execution. The budget is cumulative across provider attempts within the step.
146
146
  - `firstChunkMs`: The timeout until the first content-bearing output of each step (streaming only). Text deltas, reasoning deltas, tool-input deltas, generated files, and tool calls satisfy the timeout. Response metadata, stream starts, empty deltas, raw chunks, and transport activity do not satisfy or reset it.
147
- - `chunkMs`: The timeout between content-bearing output chunks after output has started (streaming only). Non-content chunks do not reset it. This is useful for detecting streams that stall after generation begins.
147
+ - `chunkMs`: The timeout between meaningful model output chunks after output has started (streaming only). It starts with the first meaningful output; it does not limit the wait for that first output. Non-content chunks do not reset it. This is useful for detecting streams that stall after generation begins.
148
148
  - `toolMs`: The default timeout for all tool executions. If a tool takes longer, it aborts and returns a tool-error so the model can respond or retry.
149
149
  - `tools`: Per-tool timeout overrides using `{toolName}Ms` keys (e.g. `weatherMs`, `slowApiMs`). Takes precedence over `toolMs`. Tool names are type-checked for autocomplete.
150
150
 
151
+ The `firstChunkMs` and `chunkMs` timers stop when the model response finishes, errors, closes, or is cancelled. Each provider retry or later model call gets fresh output timeout budgets. Retry backoff and local tool execution are outside these budgets. Provider-executed tools remain within the model response. The `stepMs` and `totalMs` budgets remain active across retries and local tool execution.
152
+
151
153
  #### Example: 5 second timeout (number format)
152
154
 
153
155
  ```ts
@@ -82,8 +82,9 @@ from `ai`. A part has one of these shapes:
82
82
  - JSON: `{ type: 'json', value: JSONValue }`.
83
83
 
84
84
  Language-model decision adapters accept image files only. The underlying model
85
- must support the file data form. Gateway and TypeSafe AI accept text and JSON
86
- state only.
85
+ must support the file data form. Gateway forwards image files to the routed
86
+ decision model, which must support image input. TypeSafe AI accepts text and
87
+ JSON state only.
87
88
 
88
89
  Arrays passed directly as `state` contain parts. To send a JSON array, put it
89
90
  inside an object (`state: { records }`) or a JSON part
@@ -479,7 +479,7 @@ To see `generateText` in action, check out [these examples](#examples).
479
479
  type: 'number | { totalMs?: number; stepMs?: number; toolMs?: number; tools?: Partial<Record<`${toolName}Ms`, number>> }',
480
480
  isOptional: true,
481
481
  description:
482
- 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, toolMs, and/or tools properties. totalMs sets the total timeout for the entire call. stepMs sets the timeout for each individual step (LLM call). toolMs sets the default timeout for all tool executions. tools sets per-tool timeout overrides using the pattern {toolName}Ms (e.g. weatherMs, slowApiMs) that take precedence over toolMs - tool names are type-checked for autocomplete. If a tool takes longer than its timeout, it aborts and returns a tool-error so the model can respond or retry. Can be used alongside abortSignal.',
482
+ 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, toolMs, and/or tools properties. totalMs sets the total timeout for the entire call. stepMs sets the timeout for each complete step, including model generation, retries, and local tool execution. toolMs sets the default timeout for all tool executions. tools sets per-tool timeout overrides using the pattern {toolName}Ms (e.g. weatherMs, slowApiMs) that take precedence over toolMs - tool names are type-checked for autocomplete. If a tool takes longer than its timeout, it aborts and returns a tool-error so the model can respond or retry. Can be used alongside abortSignal.',
483
483
  },
484
484
  {
485
485
  name: 'headers',
@@ -1251,7 +1251,7 @@ To see `generateText` in action, check out [these examples](#examples).
1251
1251
  type: '(event: GenerateTextStepStartEvent) => PromiseLike<void> | void',
1252
1252
  isOptional: true,
1253
1253
  description:
1254
- 'Callback that is called when a step (LLM call) begins, before the provider is called. Errors thrown in this callback are silently caught and do not break the generation flow.',
1254
+ 'Callback that is called when a step begins, before the provider is called. Errors thrown in this callback are silently caught and do not break the generation flow.',
1255
1255
  properties: [
1256
1256
  {
1257
1257
  type: 'GenerateTextStepStartEvent',
@@ -487,7 +487,7 @@ To see `streamText` in action, check out [these examples](#examples).
487
487
  type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number; toolMs?: number; tools?: Partial<Record<`${toolName}Ms`, number>> }',
488
488
  isOptional: true,
489
489
  description:
490
- 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, chunkMs, toolMs, and/or tools properties. totalMs sets the total timeout for the entire call. stepMs sets the timeout for each individual step (LLM call). firstChunkMs sets the timeout until the first content-bearing output in each step; metadata, stream starts, empty deltas, raw chunks, and transport activity do not satisfy it. chunkMs sets the timeout between content-bearing output chunks after output has started; non-content chunks do not reset it. toolMs sets the default timeout for all tool executions. tools sets per-tool timeout overrides using the pattern {toolName}Ms (e.g. weatherMs, slowApiMs) that take precedence over toolMs - tool names are type-checked for autocomplete. If a tool takes longer than its timeout, it aborts and returns a tool-error so the model can respond or retry. Can be used alongside abortSignal.',
490
+ 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, chunkMs, toolMs, and/or tools properties. totalMs sets the total timeout for the entire call. stepMs sets the timeout for each complete step, including model generation, retries, and local tool execution. firstChunkMs sets the timeout until the first content-bearing output in each step; metadata, stream starts, empty deltas, raw chunks, and transport activity do not satisfy it. chunkMs starts with the first meaningful model output and limits gaps between subsequent meaningful model output chunks. Both output timers stop when the model response finishes and start fresh for each retry or later model call. Local tool results, including preliminary results, do not reset them; local tool execution is outside their scope. Provider-executed tools remain within the model response. toolMs sets the default timeout for all tool executions. tools sets per-tool timeout overrides using the pattern {toolName}Ms (e.g. weatherMs, slowApiMs) that take precedence over toolMs - tool names are type-checked for autocomplete. If a tool takes longer than its timeout, it aborts and returns a tool-error so the model can respond or retry. Can be used alongside abortSignal.',
491
491
  },
492
492
  {
493
493
  name: 'headers',
@@ -2265,7 +2265,7 @@ To see `streamText` in action, check out [these examples](#examples).
2265
2265
  type: '(event: GenerateTextStepStartEvent) => PromiseLike<void> | void',
2266
2266
  isOptional: true,
2267
2267
  description:
2268
- 'Callback that is called when a step (LLM call) begins, before the provider is called. Errors thrown in this callback are silently caught and do not break the generation flow.',
2268
+ 'Callback that is called when a step begins, before the provider is called. Errors thrown in this callback are silently caught and do not break the generation flow.',
2269
2269
  properties: [
2270
2270
  {
2271
2271
  type: 'GenerateTextStepStartEvent',
@@ -306,7 +306,7 @@ To see `ToolLoopAgent` in action, check out [these examples](#examples).
306
306
  type: 'GenerateTextOnStepStartCallback',
307
307
  isOptional: true,
308
308
  description:
309
- 'Callback that is called when a step (LLM call) begins, before the provider is called. Each step represents a single LLM invocation. If also specified in `generate()` or `stream()`, both callbacks are called (constructor first).',
309
+ 'Callback that is called when a step begins, before the provider is called. Each step includes a model generation and any associated local tool execution; provider retries remain within the same step. If also specified in `generate()` or `stream()`, both callbacks are called (constructor first).',
310
310
  properties: [
311
311
  {
312
312
  type: 'GenerateTextStepStartEvent',
@@ -726,7 +726,7 @@ const result = await agent.generate({
726
726
  type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number }',
727
727
  isOptional: true,
728
728
  description:
729
- 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, and/or chunkMs properties. firstChunkMs and chunkMs have no effect on generate(); they are streaming-only and are enforced by stream(). Can be used alongside abortSignal.',
729
+ 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, and/or chunkMs properties. stepMs limits each complete step, including model generation, retries, and local tool execution. firstChunkMs and chunkMs have no effect on generate(); they are streaming-only and are enforced by stream(). Can be used alongside abortSignal.',
730
730
  },
731
731
  {
732
732
  name: 'experimental_sandbox',
@@ -754,7 +754,7 @@ const result = await agent.generate({
754
754
  type: 'GenerateTextOnStepStartCallback',
755
755
  isOptional: true,
756
756
  description:
757
- 'Callback that is called when a step (LLM call) begins, before the provider is called. If also specified in the constructor, both callbacks are called (constructor first).',
757
+ 'Callback that is called when a step begins, before the provider is called. If also specified in the constructor, both callbacks are called (constructor first).',
758
758
  },
759
759
  {
760
760
  name: 'onToolExecutionStart',
@@ -842,7 +842,7 @@ for await (const chunk of stream.textStream) {
842
842
  type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number }',
843
843
  isOptional: true,
844
844
  description:
845
- 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, and/or chunkMs properties. firstChunkMs limits the wait for the first content-bearing output in each model-call step. chunkMs limits gaps between later content-bearing output chunks. Can be used alongside abortSignal.',
845
+ 'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, and/or chunkMs properties. stepMs limits each complete step, including model generation, retries, and local tool execution. firstChunkMs limits the wait for the first content-bearing output in each model-call step. chunkMs starts with the first meaningful model output and limits gaps between subsequent meaningful model output chunks. Both output timers stop when the model response finishes and start fresh for each retry or later model call; they exclude retry backoff and local tool execution. Tool results, including preliminary results, do not reset them. Provider-executed tools remain within the model response. Can be used alongside abortSignal.',
846
846
  },
847
847
  {
848
848
  name: 'experimental_sandbox',
@@ -877,7 +877,7 @@ for await (const chunk of stream.textStream) {
877
877
  type: 'GenerateTextOnStepStartCallback',
878
878
  isOptional: true,
879
879
  description:
880
- 'Callback that is called when a step (LLM call) begins, before the provider is called. If also specified in the constructor, both callbacks are called (constructor first).',
880
+ 'Callback that is called when a step begins, before the provider is called. If also specified in the constructor, both callbacks are called (constructor first).',
881
881
  },
882
882
  {
883
883
  name: 'onToolExecutionStart',
@@ -59,10 +59,10 @@ experimental webhook delivery, see the [MCP Events guide](/docs/ai-sdk-core/mcp-
59
59
  },
60
60
  {
61
61
  name: 'adapter',
62
- type: 'Experimental_MCPEventsAdapter',
62
+ type: 'Experimental_MCPEventAdapter',
63
63
  isOptional: true,
64
64
  description:
65
- 'Delegates subscription management to a backend. Cannot be combined with store or validateArguments. No local event store or refresh loop is required.',
65
+ "Required in managed mode. Creates bound subscription operations once before transport startup, receiving transport metadata: { type, url } for HTTP/SSE or { type: 'custom' } for custom transports. Cannot be combined with store or validateArguments. No local event store or refresh loop is required.",
66
66
  },
67
67
  {
68
68
  name: 'validateArguments',
@@ -99,9 +99,11 @@ for the definition fields. Pagination is explicit; this method does not fetch al
99
99
  ## Managed Subscriptions
100
100
 
101
101
  Configure `experimental_events: { adapter }` to delegate subscription lifecycle
102
- operations to a backend. The adapter implements `Experimental_MCPEventsAdapter`
103
- from `@ai-sdk/mcp`; it is bound to the authorized account and destination by your
104
- application or integration package.
102
+ operations to a backend. The `adapter` configuration requires an
103
+ `Experimental_MCPEventAdapter` from `@ai-sdk/mcp`. Its
104
+ `createAdapter({ transport })` method returns `Experimental_MCPEventOperations` bound
105
+ to the authorized account, endpoint and destination by your application or
106
+ integration package.
105
107
 
106
108
  ```ts
107
109
  import { createMCPClient } from '@ai-sdk/mcp';
@@ -143,7 +145,123 @@ try {
143
145
  Use the same authorized account for discovery and subscription management.
144
146
  Never derive account identity, destination or authorization from model arguments.
145
147
 
146
- ### Adapter Contract
148
+ ### Using an Adapter Helper
149
+
150
+ A backend SDK or application helper can return `Experimental_MCPEventAdapter`
151
+ directly. Pass the helper's result as `adapter`; you do not need another object
152
+ wrapper or an inline `createAdapter` callback:
153
+
154
+ ```ts
155
+ import { createMCPClient } from '@ai-sdk/mcp';
156
+ import { createBackendEventsAdapter } from './my-events-adapter';
157
+
158
+ const client = await createMCPClient({
159
+ transport: { type: 'http', url: 'https://service.example/mcp' },
160
+ experimental_events: {
161
+ adapter: createBackendEventsAdapter({
162
+ accountId: authenticatedUser.id,
163
+ destination: { path: '/hooks' },
164
+ }),
165
+ },
166
+ });
167
+ ```
168
+
169
+ The helper holds the authorized account and delivery configuration. AI SDK supplies
170
+ the transport type and URL to its `createAdapter` method internally, so the application
171
+ configures that URL only once. When a backend SDK exports this helper, import it
172
+ from that SDK instead of an application module. Use the same authorized account
173
+ for the MCP transport and the backend's subscription operations.
174
+
175
+ ### Implementing an Adapter Helper
176
+
177
+ Backend integration authors implement `createAdapter` inside their helper:
178
+
179
+ ```ts filename="my-events-adapter.ts"
180
+ import type { Experimental_MCPEventAdapter } from '@ai-sdk/mcp';
181
+ import { createBackendEventOperations } from './my-events-backend';
182
+
183
+ type BackendEventsConfig = {
184
+ accountId: string;
185
+ destination: { path: string };
186
+ mcpUrl?: string;
187
+ };
188
+
189
+ export function createBackendEventsAdapter(
190
+ config: BackendEventsConfig,
191
+ ): Experimental_MCPEventAdapter {
192
+ return {
193
+ createAdapter({ transport }) {
194
+ return createBackendEventOperations({
195
+ ...config,
196
+ mcpUrl:
197
+ config.mcpUrl ??
198
+ (transport.type === 'custom' ? undefined : transport.url),
199
+ });
200
+ },
201
+ };
202
+ }
203
+ ```
204
+
205
+ These helpers and `BackendEventsConfig` are illustrative application or backend
206
+ SDK code, not AI SDK exports. `createBackendEventOperations` returns
207
+ `Experimental_MCPEventOperations` bound to the authorized account, endpoint
208
+ and destination. The helper above lets an explicit `mcpUrl` override the transport
209
+ URL for backend operations; it does not change the MCP client's transport or
210
+ catalog discovery. The backend still authorizes the selected endpoint.
211
+
212
+ The AI SDK adapter contract is:
213
+
214
+ ```ts
215
+ interface MCPEventAdapter {
216
+ createAdapter(context: {
217
+ transport: { type: 'http' | 'sse'; url: string } | { type: 'custom' };
218
+ }): MCPEventOperations;
219
+ }
220
+ ```
221
+
222
+ The interface is exported as `Experimental_MCPEventAdapter`. The client
223
+ calls `createAdapter` once, before starting the transport, and uses the returned
224
+ operations for managed subscriptions. The return value must be an object with
225
+ callable `subscribe`, `getSubscription`, `listSubscriptions`, and `unsubscribe`
226
+ methods. Nullish values, missing or non-callable methods, and Promise/thenable
227
+ results reject `createMCPClient` with `MCPClientError` before the transport starts.
228
+ Errors thrown by `createAdapter` also reject initialization without starting the
229
+ transport. Bound operations are no longer accepted directly in
230
+ `experimental_events.adapter`. To migrate existing subscription operations, wrap them explicitly:
231
+
232
+ ```ts
233
+ experimental_events: {
234
+ adapter: {
235
+ createAdapter: () => existingOperations,
236
+ },
237
+ }
238
+ ```
239
+
240
+ Only reuse bound operations when their account, endpoint and destination are correct
241
+ for every client using them. The former subscription operations interface is now
242
+ exported as `Experimental_MCPEventOperations`; `Experimental_MCPEventAdapter`
243
+ names the configured integration with `createAdapter`.
244
+
245
+ For HTTP and SSE transport configurations, the context contains a fresh
246
+ `transport: { type, url }` object. `url` is the configured string, including its
247
+ path and query, not a resolved redirect target. Custom `MCPTransport` instances
248
+ receive `transport: { type: 'custom' }`, with no `url` property; arbitrary
249
+ properties on the instance are not inspected.
250
+
251
+ This is a description of the transport, not the full configuration or live
252
+ instance. Headers, auth providers, fetch functions, session callbacks and
253
+ `start`/`send`/`close` methods are not passed to the adapter. The `transport`
254
+ grouping leaves room for other client context without mixing it with transport
255
+ metadata. No negotiated protocol version is supplied before initialization.
256
+
257
+ Return separately bound operations for each client instead of mutating shared
258
+ endpoint state. Adapters may prefer their own explicit endpoint override or
259
+ resolve a configured endpoint when `transport.type` is `'custom'`. That policy belongs to
260
+ the adapter; it does not change the client's transport or catalog discovery.
261
+ The managed backend must still authorize an endpoint before sending credentials
262
+ there. Receiving the transport URL does not grant that authorization.
263
+
264
+ ### Managed Operations Contract
147
265
 
148
266
  ```ts
149
267
  import type { JSONObject } from '@ai-sdk/provider';
@@ -184,7 +302,7 @@ These types are exported as `Experimental_ManagedSubscription` and
184
302
  a positive `ttlMs`, an ISO deadline in `expiresAt`, or `expiresAt: null` to
185
303
  monitor until stopped. The backend validates these values and enforces its limits.
186
304
 
187
- `Experimental_MCPEventsAdapter` has four operations:
305
+ `Experimental_MCPEventOperations` has four operations:
188
306
 
189
307
  | Operation | Input | Result |
190
308
  | ------------------- | ------------------------------------------------- | ---------------------------------- |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "7.0.135",
3
+ "version": "7.0.137",
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,7 +42,7 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@ai-sdk/gateway": "4.0.109",
45
+ "@ai-sdk/gateway": "4.0.110",
46
46
  "@ai-sdk/provider": "4.0.26",
47
47
  "@ai-sdk/provider-utils": "5.0.58"
48
48
  },
@@ -2275,6 +2275,9 @@ class DefaultStreamTextResult<
2275
2275
  if (chunkTimeoutId != null) {
2276
2276
  clearTimeout(chunkTimeoutId);
2277
2277
  }
2278
+ if (abortSignal?.aborted) {
2279
+ return;
2280
+ }
2278
2281
  chunkTimeoutId = setAbortTimeout({
2279
2282
  abortController: chunkAbortController,
2280
2283
  label: 'Chunk',
@@ -2289,6 +2292,11 @@ class DefaultStreamTextResult<
2289
2292
  }
2290
2293
  }
2291
2294
 
2295
+ function clearModelOutputTimeouts() {
2296
+ clearFirstChunkTimeout();
2297
+ clearChunkTimeout();
2298
+ }
2299
+
2292
2300
  function clearStepTimeout() {
2293
2301
  if (stepTimeoutId != null) {
2294
2302
  clearTimeout(stepTimeoutId);
@@ -2297,8 +2305,7 @@ class DefaultStreamTextResult<
2297
2305
 
2298
2306
  function clearStepTimeouts() {
2299
2307
  clearStepTimeout();
2300
- clearFirstChunkTimeout();
2301
- clearChunkTimeout();
2308
+ clearModelOutputTimeouts();
2302
2309
  }
2303
2310
 
2304
2311
  function cleanupStepTimeouts() {
@@ -2509,7 +2516,6 @@ class DefaultStreamTextResult<
2509
2516
  let automaticStreamRetryCount = 0;
2510
2517
  let callbackStreamRetryCount = 0;
2511
2518
  let bufferedAttemptParts: LanguageModelStreamPart<TOOLS>[] = [];
2512
- const outputChunksHandledBeforeBuffering = new WeakSet<object>();
2513
2519
  const openTextParts = new Set<string>();
2514
2520
  const openReasoningParts = new Set<string>();
2515
2521
  let enqueueStreamRetryAttemptBoundary = false;
@@ -2561,7 +2567,16 @@ class DefaultStreamTextResult<
2561
2567
  };
2562
2568
 
2563
2569
  while (true) {
2564
- const { done, value } = await languageModelStreamReader.read();
2570
+ let result: ReadableStreamReadResult<
2571
+ LanguageModelStreamPart<TOOLS>
2572
+ >;
2573
+ try {
2574
+ result = await languageModelStreamReader.read();
2575
+ } catch (error) {
2576
+ clearModelOutputTimeouts();
2577
+ throw error;
2578
+ }
2579
+ const { done, value } = result;
2565
2580
 
2566
2581
  if (enqueueStreamRetryAttemptBoundary) {
2567
2582
  controller.enqueue(
@@ -2576,6 +2591,7 @@ class DefaultStreamTextResult<
2576
2591
  }
2577
2592
 
2578
2593
  if (done) {
2594
+ clearModelOutputTimeouts();
2579
2595
  flushBufferedAttemptParts();
2580
2596
  controller.close();
2581
2597
  return;
@@ -2592,22 +2608,24 @@ class DefaultStreamTextResult<
2592
2608
  value.type === 'tool-error';
2593
2609
 
2594
2610
  if (value.type === 'model-call-end') {
2611
+ clearModelOutputTimeouts();
2595
2612
  flushBufferedAttemptParts();
2596
2613
  enqueueAttemptPart(value);
2597
2614
  return;
2598
2615
  }
2599
2616
 
2617
+ // Observe model output before buffering and tool processing.
2618
+ // Replaying buffered parts must not re-arm finished timers.
2619
+ if (isOutputChunk(value)) {
2620
+ clearFirstChunkTimeout();
2621
+ resetChunkTimeout();
2622
+ }
2623
+
2600
2624
  if (
2601
2625
  shouldBufferToolParts &&
2602
2626
  value.type !== 'error' &&
2603
2627
  (isToolPart || bufferedAttemptParts.length > 0)
2604
2628
  ) {
2605
- if (isOutputChunk(value)) {
2606
- clearFirstChunkTimeout();
2607
- resetChunkTimeout();
2608
- outputChunksHandledBeforeBuffering.add(value);
2609
- }
2610
-
2611
2629
  bufferedAttemptParts.push(value);
2612
2630
  continue;
2613
2631
  }
@@ -2617,6 +2635,7 @@ class DefaultStreamTextResult<
2617
2635
  return;
2618
2636
  }
2619
2637
 
2638
+ clearModelOutputTimeouts();
2620
2639
  await notify({
2621
2640
  event: { chunk: value },
2622
2641
  callbacks: onChunk,
@@ -2677,10 +2696,12 @@ class DefaultStreamTextResult<
2677
2696
  response = retryLanguageModelCall.response;
2678
2697
  languageModelStreamReader =
2679
2698
  retryLanguageModelCall.stream.getReader();
2699
+ startFirstChunkTimeout();
2680
2700
  enqueueStreamRetryAttemptBoundary = true;
2681
2701
  }
2682
2702
  },
2683
2703
  cancel(reason) {
2704
+ clearModelOutputTimeouts();
2684
2705
  return languageModelStreamReader.cancel(reason);
2685
2706
  },
2686
2707
  });
@@ -2857,21 +2878,7 @@ class DefaultStreamTextResult<
2857
2878
  const chunkType = chunk.type;
2858
2879
 
2859
2880
  if (isOutputChunk(chunk)) {
2860
- const timeoutHandledBeforeBuffering =
2861
- outputChunksHandledBeforeBuffering.has(chunk);
2862
-
2863
- if (
2864
- !hasReceivedOutputChunk &&
2865
- !timeoutHandledBeforeBuffering
2866
- ) {
2867
- // Clear before forwarding the first output so a timeout
2868
- // cannot race with already-visible generated content.
2869
- clearFirstChunkTimeout();
2870
- }
2871
2881
  hasReceivedOutputChunk = true;
2872
- if (!timeoutHandledBeforeBuffering) {
2873
- resetChunkTimeout();
2874
- }
2875
2882
  }
2876
2883
 
2877
2884
  switch (chunkType) {
@@ -4,9 +4,9 @@ import type { ToolSet } from '@ai-sdk/provider-utils';
4
4
  * Timeout configuration for API calls. Can be specified as:
5
5
  * - A number representing milliseconds
6
6
  * - An object with `totalMs` property for the total timeout in milliseconds
7
- * - An object with `stepMs` property for the timeout of each step in milliseconds
7
+ * - An object with `stepMs` property for the timeout of each complete step, including model generation, retries, and local tool execution, in milliseconds
8
8
  * - An object with `firstChunkMs` property for the timeout until the first content chunk of each step (streaming only)
9
- * - An object with `chunkMs` property for the timeout between content chunks (streaming only)
9
+ * - An object with `chunkMs` property for the timeout between meaningful model output chunks, starting with the first output and ending when the model response ends (streaming only)
10
10
  * - An object with `toolMs` property for the default timeout for all tool executions
11
11
  * - An object with `tools` property for per-tool timeout overrides using `{toolName}Ms` keys
12
12
  */
@@ -95,6 +95,7 @@ export function readUIMessageStream<UI_MESSAGE extends UIMessage>({
95
95
  consumeStream({
96
96
  stream: processUIMessageStream({
97
97
  stream,
98
+ resetStateOnMessageIdChange: true,
98
99
  runUpdateMessageJob(
99
100
  job: (options: {
100
101
  state: StreamingUIMessageState<UI_MESSAGE>;