ai 7.0.34 → 7.0.35
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.
- package/CHANGELOG.md +9 -0
- package/dist/index.d.ts +26 -10
- package/dist/index.js +119 -45
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +5 -2
- package/dist/internal/index.js +1 -1
- package/docs/03-ai-sdk-core/25-settings.mdx +15 -2
- package/docs/03-ai-sdk-core/65-devtools.mdx +6 -0
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +42 -0
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +4 -4
- package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +10 -8
- package/docs/07-reference/02-ai-sdk-ui/42-pipe-ui-message-stream-to-response.mdx +6 -1
- package/package.json +2 -2
- package/src/agent/agent.ts +4 -1
- package/src/agent/pipe-agent-ui-stream-to-response.ts +1 -1
- package/src/generate-object/stream-object-result.ts +4 -1
- package/src/generate-object/stream-object.ts +1 -1
- package/src/generate-text/generate-text-events.ts +2 -1
- package/src/generate-text/stream-text-result.ts +5 -2
- package/src/generate-text/stream-text.ts +113 -33
- package/src/prompt/index.ts +1 -0
- package/src/prompt/request-options.ts +20 -2
- package/src/text-stream/pipe-text-stream-to-response.ts +3 -2
- package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +3 -2
- package/src/util/create-stitchable-stream.ts +41 -15
- package/src/util/write-to-server-response.ts +2 -2
package/dist/internal/index.d.ts
CHANGED
|
@@ -421,13 +421,15 @@ type LanguageModelCallOptions = {
|
|
|
421
421
|
* - A number representing milliseconds
|
|
422
422
|
* - An object with `totalMs` property for the total timeout in milliseconds
|
|
423
423
|
* - An object with `stepMs` property for the timeout of each step in milliseconds
|
|
424
|
-
* - An object with `
|
|
424
|
+
* - An object with `firstChunkMs` property for the timeout until the first content chunk of each step (streaming only)
|
|
425
|
+
* - An object with `chunkMs` property for the timeout between content chunks (streaming only)
|
|
425
426
|
* - An object with `toolMs` property for the default timeout for all tool executions
|
|
426
427
|
* - An object with `tools` property for per-tool timeout overrides using `{toolName}Ms` keys
|
|
427
428
|
*/
|
|
428
429
|
type TimeoutConfiguration<TOOLS extends ToolSet> = number | {
|
|
429
430
|
totalMs?: number;
|
|
430
431
|
stepMs?: number;
|
|
432
|
+
firstChunkMs?: number;
|
|
431
433
|
chunkMs?: number;
|
|
432
434
|
toolMs?: number;
|
|
433
435
|
tools?: Partial<Record<`${keyof TOOLS & string}Ms`, number>>;
|
|
@@ -1965,7 +1967,8 @@ type GenerateTextStartEvent<TOOLS extends ToolSet = ToolSet, RUNTIME_CONTEXT ext
|
|
|
1965
1967
|
readonly maxRetries: number;
|
|
1966
1968
|
/**
|
|
1967
1969
|
* Timeout configuration for the generation.
|
|
1968
|
-
* Can be a number (milliseconds) or an object with totalMs, stepMs,
|
|
1970
|
+
* Can be a number (milliseconds) or an object with totalMs, stepMs,
|
|
1971
|
+
* firstChunkMs (streaming only), chunkMs, toolMs, and per-tool overrides via tools.
|
|
1969
1972
|
*/
|
|
1970
1973
|
readonly timeout: TimeoutConfiguration<TOOLS> | undefined;
|
|
1971
1974
|
/** Additional HTTP headers sent with the request. */
|
package/dist/internal/index.js
CHANGED
|
@@ -142,7 +142,8 @@ You can specify the timeout either as a number (milliseconds) or as an object wi
|
|
|
142
142
|
|
|
143
143
|
- `totalMs`: The total timeout for the entire call including all steps.
|
|
144
144
|
- `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
|
-
- `
|
|
145
|
+
- `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.
|
|
146
|
+
- `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.
|
|
146
147
|
- `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.
|
|
147
148
|
- `tools`: Per-tool timeout overrides using `{toolName}Ms` keys (e.g. `weatherMs`, `slowApiMs`). Takes precedence over `toolMs`. Tool names are type-checked for autocomplete.
|
|
148
149
|
|
|
@@ -195,7 +196,19 @@ const result = await generateText({
|
|
|
195
196
|
const result = streamText({
|
|
196
197
|
model: __MODEL__,
|
|
197
198
|
prompt: 'Invent a new holiday and describe its traditions.',
|
|
198
|
-
timeout: { chunkMs: 5000 }, // abort if
|
|
199
|
+
timeout: { chunkMs: 5000 }, // abort if content stalls for 5 seconds
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Example: First-content timeout for streaming (streamText only)
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const result = streamText({
|
|
207
|
+
model: __MODEL__,
|
|
208
|
+
prompt: 'Invent a new holiday and describe its traditions.',
|
|
209
|
+
timeout: {
|
|
210
|
+
firstChunkMs: 10000, // 10 seconds for first content in each step
|
|
211
|
+
},
|
|
199
212
|
});
|
|
200
213
|
```
|
|
201
214
|
|
|
@@ -79,6 +79,12 @@ npx @ai-sdk/devtools@latest
|
|
|
79
79
|
|
|
80
80
|
Open [http://localhost:4983](http://localhost:4983) to view your AI SDK interactions.
|
|
81
81
|
|
|
82
|
+
### Choose a viewer theme
|
|
83
|
+
|
|
84
|
+
The viewer uses the dark theme by default. Use the theme button in the viewer
|
|
85
|
+
header to switch between dark and light themes. Your selection is stored in
|
|
86
|
+
your browser and restored the next time you open the viewer at the same origin.
|
|
87
|
+
|
|
82
88
|
### Monorepo usage
|
|
83
89
|
|
|
84
90
|
If you are using a monorepo setup (e.g. Turborepo, Nx), start DevTools from the same workspace where your AI SDK code runs.
|
|
@@ -192,6 +192,46 @@ const result = await agent.continueStream({ session });
|
|
|
192
192
|
Use `continueStream()` for incremental output, or `continueGenerate()` to drain
|
|
193
193
|
the continued turn and return a `GenerateTextResult`.
|
|
194
194
|
|
|
195
|
+
## Stop After a Harness Step
|
|
196
|
+
|
|
197
|
+
Use `stopWhen` to opt into semantic step boundaries. Predicates run after real
|
|
198
|
+
harness tool steps that can continue into another model step, and receive the
|
|
199
|
+
completed steps from the current invocation. When a predicate matches, the
|
|
200
|
+
returned result finishes while the underlying turn remains unfinished. A
|
|
201
|
+
terminal text-only step instead consumes the turn's `finish` event and finishes
|
|
202
|
+
naturally.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { isStepCount } from 'ai';
|
|
206
|
+
|
|
207
|
+
const steppedAgent = new HarnessAgent({
|
|
208
|
+
harness: claudeCode,
|
|
209
|
+
sandbox: createVercelSandbox({
|
|
210
|
+
runtime: 'node24',
|
|
211
|
+
ports: [4000],
|
|
212
|
+
}),
|
|
213
|
+
stopWhen: isStepCount(1),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const session = await steppedAgent.createSession();
|
|
217
|
+
const result = await steppedAgent.generate({
|
|
218
|
+
session,
|
|
219
|
+
prompt: 'Create a short TODO.md for this repository.',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (session.hasUnfinishedTurn()) {
|
|
223
|
+
const continueFrom = await session.suspendTurn();
|
|
224
|
+
await persistContinuationState({ chatId, continuationState: continueFrom });
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`stopWhen` has no default. When omitted, `HarnessAgent` continues running until
|
|
229
|
+
the turn naturally finishes or pauses for host input, preserving the behavior
|
|
230
|
+
of agents without step control. Pass one predicate or an array; matching any
|
|
231
|
+
predicate finishes the current result slice. Resume a stopped turn with
|
|
232
|
+
`createSession({ continueFrom })` and `continueStream()` or
|
|
233
|
+
`continueGenerate()`.
|
|
234
|
+
|
|
195
235
|
## Prepare the Sandbox
|
|
196
236
|
|
|
197
237
|
Use `sandboxConfig` to prepare the sandbox before the harness starts.
|
|
@@ -325,6 +365,8 @@ console.log(preparation.identity);
|
|
|
325
365
|
- `sandbox`: a `HarnessV1SandboxProvider`.
|
|
326
366
|
- `id`: optional stable agent identifier.
|
|
327
367
|
- `instructions`: instructions applied once to a fresh session.
|
|
368
|
+
- `stopWhen`: condition(s) for finishing a result slice after a completed
|
|
369
|
+
harness tool step that can continue into another model step.
|
|
328
370
|
- `tools`: AI SDK tools executed by the host when the harness calls them.
|
|
329
371
|
- `activeTools`: allowlist of built-in and host-executed tools the harness can
|
|
330
372
|
call.
|
|
@@ -477,10 +477,10 @@ To see `streamText` in action, check out [these examples](#examples).
|
|
|
477
477
|
},
|
|
478
478
|
{
|
|
479
479
|
name: 'timeout',
|
|
480
|
-
type: 'number | { totalMs?: number; stepMs?: number; chunkMs?: number; toolMs?: number; tools?: { [toolName]Ms?: number } }',
|
|
480
|
+
type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number; toolMs?: number; tools?: { [toolName]Ms?: number } }',
|
|
481
481
|
isOptional: true,
|
|
482
482
|
description:
|
|
483
|
-
'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, 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).
|
|
483
|
+
'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.',
|
|
484
484
|
},
|
|
485
485
|
{
|
|
486
486
|
name: 'headers',
|
|
@@ -4020,7 +4020,7 @@ To see `streamText` in action, check out [these examples](#examples).
|
|
|
4020
4020
|
},
|
|
4021
4021
|
{
|
|
4022
4022
|
name: 'pipeUIMessageStreamToResponse',
|
|
4023
|
-
type: '(response: ServerResponse, options?: ResponseInit & UIMessageStreamOptions) => void',
|
|
4023
|
+
type: '(response: ServerResponse, options?: ResponseInit & UIMessageStreamOptions) => Promise<void>',
|
|
4024
4024
|
description:
|
|
4025
4025
|
'Writes UI message stream output to a Node.js response-like object.',
|
|
4026
4026
|
properties: [
|
|
@@ -4051,7 +4051,7 @@ To see `streamText` in action, check out [these examples](#examples).
|
|
|
4051
4051
|
},
|
|
4052
4052
|
{
|
|
4053
4053
|
name: 'pipeTextStreamToResponse',
|
|
4054
|
-
type: '(response: ServerResponse, init?: ResponseInit) => void',
|
|
4054
|
+
type: '(response: ServerResponse, init?: ResponseInit) => Promise<void>',
|
|
4055
4055
|
description:
|
|
4056
4056
|
'Writes text delta output to a Node.js response-like object. It sets a `Content-Type` header to `text/plain; charset=utf-8` and writes each text delta as a separate chunk.',
|
|
4057
4057
|
properties: [
|
|
@@ -246,8 +246,9 @@ To see `ToolLoopAgent` in action, check out [these examples](#examples).
|
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
name: 'timeout',
|
|
249
|
-
type: 'number | { totalMs?: number; stepMs?: number; chunkMs?: number } | undefined',
|
|
250
|
-
description:
|
|
249
|
+
type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number } | undefined',
|
|
250
|
+
description:
|
|
251
|
+
'Timeout configuration for the generation. firstChunkMs and chunkMs are only enforced by stream().',
|
|
251
252
|
},
|
|
252
253
|
{
|
|
253
254
|
name: 'headers',
|
|
@@ -360,8 +361,9 @@ To see `ToolLoopAgent` in action, check out [these examples](#examples).
|
|
|
360
361
|
},
|
|
361
362
|
{
|
|
362
363
|
name: 'timeout',
|
|
363
|
-
type: 'number | { totalMs?: number; stepMs?: number; chunkMs?: number } | undefined',
|
|
364
|
-
description:
|
|
364
|
+
type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number } | undefined',
|
|
365
|
+
description:
|
|
366
|
+
'Timeout configuration for the generation. firstChunkMs and chunkMs are only enforced by stream().',
|
|
365
367
|
},
|
|
366
368
|
{
|
|
367
369
|
name: 'headers',
|
|
@@ -712,10 +714,10 @@ const result = await agent.generate({
|
|
|
712
714
|
},
|
|
713
715
|
{
|
|
714
716
|
name: 'timeout',
|
|
715
|
-
type: 'number | { totalMs?: number; stepMs?: number; chunkMs?: number }',
|
|
717
|
+
type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number }',
|
|
716
718
|
isOptional: true,
|
|
717
719
|
description:
|
|
718
|
-
'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, and/or chunkMs properties.
|
|
720
|
+
'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.',
|
|
719
721
|
},
|
|
720
722
|
{
|
|
721
723
|
name: 'experimental_sandbox',
|
|
@@ -828,10 +830,10 @@ for await (const chunk of stream.textStream) {
|
|
|
828
830
|
},
|
|
829
831
|
{
|
|
830
832
|
name: 'timeout',
|
|
831
|
-
type: 'number | { totalMs?: number; stepMs?: number; chunkMs?: number }',
|
|
833
|
+
type: 'number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number }',
|
|
832
834
|
isOptional: true,
|
|
833
835
|
description:
|
|
834
|
-
'Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, and/or chunkMs properties.
|
|
836
|
+
'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.',
|
|
835
837
|
},
|
|
836
838
|
{
|
|
837
839
|
name: 'experimental_sandbox',
|
|
@@ -17,7 +17,7 @@ The `pipeUIMessageStreamToResponse` function pipes streaming data to a Node.js S
|
|
|
17
17
|
## Example
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
|
-
pipeUIMessageStreamToResponse({
|
|
20
|
+
await pipeUIMessageStreamToResponse({
|
|
21
21
|
response: serverResponse,
|
|
22
22
|
status: 200,
|
|
23
23
|
statusText: 'OK',
|
|
@@ -75,3 +75,8 @@ pipeUIMessageStreamToResponse({
|
|
|
75
75
|
},
|
|
76
76
|
]}
|
|
77
77
|
/>
|
|
78
|
+
|
|
79
|
+
### Returns
|
|
80
|
+
|
|
81
|
+
A `Promise<void>` that resolves when the stream has been written to the response
|
|
82
|
+
and rejects when reading or writing the stream fails.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.35",
|
|
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.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.27",
|
|
46
46
|
"@ai-sdk/provider": "4.0.3",
|
|
47
47
|
"@ai-sdk/provider-utils": "5.0.12"
|
|
48
48
|
},
|
package/src/agent/agent.ts
CHANGED
|
@@ -70,7 +70,10 @@ export type AgentCallParameters<
|
|
|
70
70
|
abortSignal?: AbortSignal;
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* Timeout in milliseconds. Can be specified as a number or as an object
|
|
73
|
+
* Timeout in milliseconds. Can be specified as a number or as an object
|
|
74
|
+
* with total, per-step, first-content, inter-content, and tool timeouts.
|
|
75
|
+
* First-content and inter-content timeouts are only enforced by streaming
|
|
76
|
+
* calls.
|
|
74
77
|
*/
|
|
75
78
|
timeout?: TimeoutConfiguration<TOOLS>;
|
|
76
79
|
|
|
@@ -66,7 +66,7 @@ export async function pipeAgentUIStreamToResponse<
|
|
|
66
66
|
UIMessageStreamOptions<
|
|
67
67
|
UIMessage<MESSAGE_METADATA, never, InferUITools<TOOLS>>
|
|
68
68
|
>): Promise<void> {
|
|
69
|
-
pipeUIMessageStreamToResponse({
|
|
69
|
+
return pipeUIMessageStreamToResponse({
|
|
70
70
|
response,
|
|
71
71
|
headers,
|
|
72
72
|
status,
|
|
@@ -85,7 +85,10 @@ export interface StreamObjectResult<PARTIAL, RESULT, ELEMENT_STREAM> {
|
|
|
85
85
|
* @param response A Node.js response-like object (ServerResponse).
|
|
86
86
|
* @param init Optional headers, status code, and status text.
|
|
87
87
|
*/
|
|
88
|
-
pipeTextStreamToResponse(
|
|
88
|
+
pipeTextStreamToResponse(
|
|
89
|
+
response: ServerResponse,
|
|
90
|
+
init?: ResponseInit,
|
|
91
|
+
): Promise<void>;
|
|
89
92
|
|
|
90
93
|
/**
|
|
91
94
|
* Creates a simple text stream response.
|
|
@@ -56,7 +56,8 @@ export type GenerateTextStartEvent<
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Timeout configuration for the generation.
|
|
59
|
-
* Can be a number (milliseconds) or an object with totalMs, stepMs,
|
|
59
|
+
* Can be a number (milliseconds) or an object with totalMs, stepMs,
|
|
60
|
+
* firstChunkMs (streaming only), chunkMs, toolMs, and per-tool overrides via tools.
|
|
60
61
|
*/
|
|
61
62
|
readonly timeout: TimeoutConfiguration<TOOLS> | undefined;
|
|
62
63
|
|
|
@@ -381,7 +381,7 @@ export interface StreamTextResult<
|
|
|
381
381
|
pipeUIMessageStreamToResponse<UI_MESSAGE extends UIMessage>(
|
|
382
382
|
response: ServerResponse,
|
|
383
383
|
options?: UIMessageStreamResponseInit & UIMessageStreamOptions<UI_MESSAGE>,
|
|
384
|
-
): void
|
|
384
|
+
): Promise<void>;
|
|
385
385
|
|
|
386
386
|
/**
|
|
387
387
|
* Writes text delta output to a Node.js response-like object.
|
|
@@ -395,7 +395,10 @@ export interface StreamTextResult<
|
|
|
395
395
|
* `pipeTextStreamToResponse` helpers from `'ai'` with `result.stream`
|
|
396
396
|
* instead. This method will be removed in the next major release.
|
|
397
397
|
*/
|
|
398
|
-
pipeTextStreamToResponse(
|
|
398
|
+
pipeTextStreamToResponse(
|
|
399
|
+
response: ServerResponse,
|
|
400
|
+
init?: ResponseInit,
|
|
401
|
+
): Promise<void>;
|
|
399
402
|
|
|
400
403
|
/**
|
|
401
404
|
* Converts the result to a streamed response object with a stream data part stream.
|
|
@@ -34,6 +34,7 @@ import { prepareTools } from '../prompt/prepare-tools';
|
|
|
34
34
|
import type { Prompt } from '../prompt/prompt';
|
|
35
35
|
import {
|
|
36
36
|
getChunkTimeoutMs,
|
|
37
|
+
getFirstChunkTimeoutMs,
|
|
37
38
|
getStepTimeoutMs,
|
|
38
39
|
getTotalTimeoutMs,
|
|
39
40
|
type RequestOptions,
|
|
@@ -157,28 +158,29 @@ const originalGenerateCallId = createIdGenerator({
|
|
|
157
158
|
size: 24,
|
|
158
159
|
});
|
|
159
160
|
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
161
|
+
// Chunk types that contain semantic model output. This classification is used
|
|
162
|
+
// for first-content and inter-content timeouts as well as to distinguish empty
|
|
163
|
+
// incomplete streams from incomplete streams with partial results. It is
|
|
164
|
+
// exhaustive so that new chunk types must be classified explicitly.
|
|
163
165
|
const isOutputChunkType = {
|
|
164
166
|
file: true,
|
|
165
|
-
custom:
|
|
166
|
-
source:
|
|
167
|
-
'text-start':
|
|
168
|
-
'text-end':
|
|
167
|
+
custom: false,
|
|
168
|
+
source: false,
|
|
169
|
+
'text-start': false,
|
|
170
|
+
'text-end': false,
|
|
169
171
|
'text-delta': true,
|
|
170
|
-
'reasoning-start':
|
|
171
|
-
'reasoning-end':
|
|
172
|
+
'reasoning-start': false,
|
|
173
|
+
'reasoning-end': false,
|
|
172
174
|
'reasoning-delta': true,
|
|
173
175
|
'reasoning-file': true,
|
|
174
|
-
'tool-input-start':
|
|
175
|
-
'tool-input-end':
|
|
176
|
+
'tool-input-start': false,
|
|
177
|
+
'tool-input-end': false,
|
|
176
178
|
'tool-input-delta': true,
|
|
177
|
-
'tool-approval-request':
|
|
178
|
-
'tool-approval-response':
|
|
179
|
+
'tool-approval-request': false,
|
|
180
|
+
'tool-approval-response': false,
|
|
179
181
|
'tool-call': true,
|
|
180
|
-
'tool-result':
|
|
181
|
-
'tool-error':
|
|
182
|
+
'tool-result': false,
|
|
183
|
+
'tool-error': false,
|
|
182
184
|
'tool-execution-end': false,
|
|
183
185
|
'model-call-start': false,
|
|
184
186
|
'model-call-response-metadata': false,
|
|
@@ -187,6 +189,26 @@ const isOutputChunkType = {
|
|
|
187
189
|
raw: false,
|
|
188
190
|
} as const satisfies Record<ExecuteToolsStreamPart['type'], boolean>;
|
|
189
191
|
|
|
192
|
+
function isOutputChunk(chunk: ExecuteToolsStreamPart): boolean {
|
|
193
|
+
if (!isOutputChunkType[chunk.type]) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
switch (chunk.type) {
|
|
198
|
+
case 'text-delta':
|
|
199
|
+
case 'reasoning-delta':
|
|
200
|
+
return chunk.text.length > 0;
|
|
201
|
+
case 'tool-input-delta':
|
|
202
|
+
return chunk.delta.length > 0;
|
|
203
|
+
case 'file':
|
|
204
|
+
case 'reasoning-file':
|
|
205
|
+
case 'tool-call':
|
|
206
|
+
return true;
|
|
207
|
+
default:
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
190
212
|
export type StreamTextInclude = {
|
|
191
213
|
/**
|
|
192
214
|
* Whether to retain the request body in step results.
|
|
@@ -716,9 +738,12 @@ export function streamText<
|
|
|
716
738
|
}): StreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
|
|
717
739
|
const totalTimeoutMs = getTotalTimeoutMs(timeout);
|
|
718
740
|
const stepTimeoutMs = getStepTimeoutMs(timeout);
|
|
741
|
+
const firstChunkTimeoutMs = getFirstChunkTimeoutMs(timeout);
|
|
719
742
|
const chunkTimeoutMs = getChunkTimeoutMs(timeout);
|
|
720
743
|
const stepAbortController =
|
|
721
744
|
stepTimeoutMs != null ? new AbortController() : undefined;
|
|
745
|
+
const firstChunkAbortController =
|
|
746
|
+
firstChunkTimeoutMs != null ? new AbortController() : undefined;
|
|
722
747
|
const chunkAbortController =
|
|
723
748
|
chunkTimeoutMs != null ? new AbortController() : undefined;
|
|
724
749
|
const resolvedOnStart = onStart ?? experimental_onStart;
|
|
@@ -742,10 +767,13 @@ export function streamText<
|
|
|
742
767
|
abortSignal,
|
|
743
768
|
totalTimeoutMs,
|
|
744
769
|
stepAbortController?.signal,
|
|
770
|
+
firstChunkAbortController?.signal,
|
|
745
771
|
chunkAbortController?.signal,
|
|
746
772
|
),
|
|
747
773
|
stepTimeoutMs,
|
|
748
774
|
stepAbortController,
|
|
775
|
+
firstChunkTimeoutMs,
|
|
776
|
+
firstChunkAbortController,
|
|
749
777
|
chunkTimeoutMs,
|
|
750
778
|
chunkAbortController,
|
|
751
779
|
instructions,
|
|
@@ -930,6 +958,10 @@ class DefaultStreamTextResult<
|
|
|
930
958
|
|
|
931
959
|
private readonly addStream: (
|
|
932
960
|
stream: ReadableStream<TextStreamPart<TOOLS>>,
|
|
961
|
+
callbacks?: {
|
|
962
|
+
onError?: (error: unknown) => void;
|
|
963
|
+
onCancel?: () => void;
|
|
964
|
+
},
|
|
933
965
|
) => void;
|
|
934
966
|
|
|
935
967
|
private readonly closeStream: () => void;
|
|
@@ -951,6 +983,8 @@ class DefaultStreamTextResult<
|
|
|
951
983
|
abortSignal,
|
|
952
984
|
stepTimeoutMs,
|
|
953
985
|
stepAbortController,
|
|
986
|
+
firstChunkTimeoutMs,
|
|
987
|
+
firstChunkAbortController,
|
|
954
988
|
chunkTimeoutMs,
|
|
955
989
|
chunkAbortController,
|
|
956
990
|
instructions,
|
|
@@ -1000,6 +1034,8 @@ class DefaultStreamTextResult<
|
|
|
1000
1034
|
abortSignal: AbortSignal | undefined;
|
|
1001
1035
|
stepTimeoutMs: number | undefined;
|
|
1002
1036
|
stepAbortController: AbortController | undefined;
|
|
1037
|
+
firstChunkTimeoutMs: number | undefined;
|
|
1038
|
+
firstChunkAbortController: AbortController | undefined;
|
|
1003
1039
|
chunkTimeoutMs: number | undefined;
|
|
1004
1040
|
chunkAbortController: AbortController | undefined;
|
|
1005
1041
|
toolsContext: InferToolSetContext<TOOLS>;
|
|
@@ -1771,7 +1807,32 @@ class DefaultStreamTextResult<
|
|
|
1771
1807
|
timeoutMs: stepTimeoutMs,
|
|
1772
1808
|
});
|
|
1773
1809
|
|
|
1774
|
-
//
|
|
1810
|
+
// The first-content timeout is armed when the provider response stream
|
|
1811
|
+
// starts and is cleared by the first semantic output chunk.
|
|
1812
|
+
let firstChunkTimeoutId: ReturnType<typeof setTimeout> | undefined =
|
|
1813
|
+
undefined;
|
|
1814
|
+
|
|
1815
|
+
function startFirstChunkTimeout() {
|
|
1816
|
+
if (abortSignal?.aborted) {
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
firstChunkTimeoutId = setAbortTimeout({
|
|
1821
|
+
abortController: firstChunkAbortController,
|
|
1822
|
+
label: 'First chunk',
|
|
1823
|
+
timeoutMs: firstChunkTimeoutMs,
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function clearFirstChunkTimeout() {
|
|
1828
|
+
if (firstChunkTimeoutId != null) {
|
|
1829
|
+
clearTimeout(firstChunkTimeoutId);
|
|
1830
|
+
firstChunkTimeoutId = undefined;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// Chunk timeout tracking starts after semantic output begins and is
|
|
1835
|
+
// reset only by subsequent semantic output chunks.
|
|
1775
1836
|
let chunkTimeoutId: ReturnType<typeof setTimeout> | undefined =
|
|
1776
1837
|
undefined;
|
|
1777
1838
|
|
|
@@ -1799,12 +1860,24 @@ class DefaultStreamTextResult<
|
|
|
1799
1860
|
}
|
|
1800
1861
|
}
|
|
1801
1862
|
|
|
1863
|
+
function clearStepTimeouts() {
|
|
1864
|
+
clearStepTimeout();
|
|
1865
|
+
clearFirstChunkTimeout();
|
|
1866
|
+
clearChunkTimeout();
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
function cleanupStepTimeouts() {
|
|
1870
|
+
abortSignal?.removeEventListener('abort', cleanupStepTimeouts);
|
|
1871
|
+
clearStepTimeouts();
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1802
1874
|
// The step's stream is registered lazily and consumed long after this
|
|
1803
|
-
// function returns, so
|
|
1804
|
-
//
|
|
1805
|
-
//
|
|
1806
|
-
abortSignal?.addEventListener('abort',
|
|
1807
|
-
|
|
1875
|
+
// function returns, so its timers must stay armed past setup. When the
|
|
1876
|
+
// merged abort signal fires, drop all step-scoped timers so none
|
|
1877
|
+
// outlives the step.
|
|
1878
|
+
abortSignal?.addEventListener('abort', cleanupStepTimeouts, {
|
|
1879
|
+
once: true,
|
|
1880
|
+
});
|
|
1808
1881
|
|
|
1809
1882
|
try {
|
|
1810
1883
|
stepFinish = new DelayedPromise<void>();
|
|
@@ -1967,6 +2040,8 @@ class DefaultStreamTextResult<
|
|
|
1967
2040
|
),
|
|
1968
2041
|
);
|
|
1969
2042
|
|
|
2043
|
+
startFirstChunkTimeout();
|
|
2044
|
+
|
|
1970
2045
|
const streamAfterToolCallbackInvocation =
|
|
1971
2046
|
invokeToolCallbacksFromStream({
|
|
1972
2047
|
stream: languageModelStream,
|
|
@@ -2076,8 +2151,6 @@ class DefaultStreamTextResult<
|
|
|
2076
2151
|
TextStreamPart<TOOLS>
|
|
2077
2152
|
>({
|
|
2078
2153
|
async transform(chunk, controller): Promise<void> {
|
|
2079
|
-
resetChunkTimeout();
|
|
2080
|
-
|
|
2081
2154
|
if (chunk.type === 'model-call-start') {
|
|
2082
2155
|
warnings = chunk.warnings;
|
|
2083
2156
|
return; // stream start chunks are sent immediately and do not count as first chunk
|
|
@@ -2096,8 +2169,14 @@ class DefaultStreamTextResult<
|
|
|
2096
2169
|
|
|
2097
2170
|
const chunkType = chunk.type;
|
|
2098
2171
|
|
|
2099
|
-
if (
|
|
2172
|
+
if (isOutputChunk(chunk)) {
|
|
2173
|
+
if (!hasReceivedOutputChunk) {
|
|
2174
|
+
// Clear before forwarding the first output so a timeout
|
|
2175
|
+
// cannot race with already-visible generated content.
|
|
2176
|
+
clearFirstChunkTimeout();
|
|
2177
|
+
}
|
|
2100
2178
|
hasReceivedOutputChunk = true;
|
|
2179
|
+
resetChunkTimeout();
|
|
2101
2180
|
}
|
|
2102
2181
|
|
|
2103
2182
|
switch (chunkType) {
|
|
@@ -2217,8 +2296,7 @@ class DefaultStreamTextResult<
|
|
|
2217
2296
|
}),
|
|
2218
2297
|
});
|
|
2219
2298
|
|
|
2220
|
-
|
|
2221
|
-
clearChunkTimeout();
|
|
2299
|
+
cleanupStepTimeouts();
|
|
2222
2300
|
self.closeStream();
|
|
2223
2301
|
return;
|
|
2224
2302
|
}
|
|
@@ -2298,9 +2376,8 @@ class DefaultStreamTextResult<
|
|
|
2298
2376
|
}
|
|
2299
2377
|
}
|
|
2300
2378
|
|
|
2301
|
-
// Clear
|
|
2302
|
-
|
|
2303
|
-
clearChunkTimeout();
|
|
2379
|
+
// Clear this step's timeouts before the next step is started.
|
|
2380
|
+
cleanupStepTimeouts();
|
|
2304
2381
|
|
|
2305
2382
|
if (
|
|
2306
2383
|
// Continue if:
|
|
@@ -2345,12 +2422,15 @@ class DefaultStreamTextResult<
|
|
|
2345
2422
|
},
|
|
2346
2423
|
}),
|
|
2347
2424
|
),
|
|
2425
|
+
{
|
|
2426
|
+
onError: cleanupStepTimeouts,
|
|
2427
|
+
onCancel: cleanupStepTimeouts,
|
|
2428
|
+
},
|
|
2348
2429
|
);
|
|
2349
2430
|
} catch (error) {
|
|
2350
2431
|
// Setup failed before the stream was registered, so neither the
|
|
2351
2432
|
// stream's flush nor an abort will clear the timers — clear them here.
|
|
2352
|
-
|
|
2353
|
-
clearChunkTimeout();
|
|
2433
|
+
cleanupStepTimeouts();
|
|
2354
2434
|
throw error;
|
|
2355
2435
|
}
|
|
2356
2436
|
}
|
|
@@ -2678,7 +2758,7 @@ class DefaultStreamTextResult<
|
|
|
2678
2758
|
...init
|
|
2679
2759
|
}: UIMessageStreamResponseInit & UIMessageStreamOptions<UI_MESSAGE> = {},
|
|
2680
2760
|
) {
|
|
2681
|
-
pipeUIMessageStreamToResponse({
|
|
2761
|
+
return pipeUIMessageStreamToResponse({
|
|
2682
2762
|
response,
|
|
2683
2763
|
stream: this.toUIMessageStream({
|
|
2684
2764
|
originalMessages,
|
|
@@ -2696,7 +2776,7 @@ class DefaultStreamTextResult<
|
|
|
2696
2776
|
}
|
|
2697
2777
|
|
|
2698
2778
|
pipeTextStreamToResponse(response: ServerResponse, init?: ResponseInit) {
|
|
2699
|
-
pipeTextStreamToResponse({
|
|
2779
|
+
return pipeTextStreamToResponse({
|
|
2700
2780
|
response,
|
|
2701
2781
|
stream: this.textStream,
|
|
2702
2782
|
...init,
|