ai 7.0.158 → 7.0.160
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 +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -9
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/02-foundations/02-providers-and-models.mdx +18 -8
- package/docs/03-agents/04-loop-control.mdx +29 -1
- package/docs/03-ai-sdk-core/60-telemetry.mdx +32 -1
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/16-tool-loop-agent.mdx +2 -2
- package/package.json +4 -4
- package/src/generate-text/generate-text.ts +9 -1
- package/src/generate-text/smooth-stream.ts +1 -1
- package/src/generate-text/steering.ts +56 -3
- package/src/generate-text/stream-text.ts +9 -1
- package/src/ui/process-ui-message-stream.ts +4 -2
package/dist/internal/index.js
CHANGED
|
@@ -122,6 +122,14 @@ Here are the capabilities of popular models:
|
|
|
122
122
|
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.7` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
123
123
|
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.6` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
124
124
|
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.5` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
125
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.20-reasoning` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
126
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.20-non-reasoning` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
127
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4-1-fast-reasoning` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
128
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4-1-fast-non-reasoning` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
129
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4-1` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
130
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4-fast-reasoning` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
131
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4-fast-non-reasoning` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
132
|
+
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-code-fast-1` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
125
133
|
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
126
134
|
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-3` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
127
135
|
| [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-3-mini` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
@@ -193,14 +201,16 @@ Here are the capabilities of popular models:
|
|
|
193
201
|
| [Google](/providers/ai-sdk-providers/google) | `gemini-2.5-flash-lite` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
194
202
|
| [Google](/providers/ai-sdk-providers/google) | `gemini-2.5-flash-lite-preview-06-17` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
195
203
|
| [Google](/providers/ai-sdk-providers/google) | `gemini-2.0-flash` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
196
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `
|
|
197
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `
|
|
198
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `
|
|
199
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `
|
|
200
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `
|
|
201
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-
|
|
202
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-
|
|
203
|
-
| [Mistral](/providers/ai-sdk-providers/mistral) | `
|
|
204
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `labs-leanstral-1-5` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
205
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `magistral-medium-latest` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
206
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `magistral-small-latest` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
207
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `ministral-14b-latest` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
208
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `ministral-8b-latest` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
209
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-large-latest` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
210
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-medium-latest` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
211
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-small-latest` | <Check /> | <Check /> | <Check /> | <Check /> |
|
|
212
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `voxtral-small-latest` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
213
|
+
| [Mistral](/providers/ai-sdk-providers/mistral) | `zai-glm-5-2` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
204
214
|
| [DeepSeek](/providers/ai-sdk-providers/deepseek) | `deepseek-v4-flash` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
205
215
|
| [DeepSeek](/providers/ai-sdk-providers/deepseek) | `deepseek-v4-pro` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
206
216
|
| [Cerebras](/providers/ai-sdk-providers/cerebras) | `gpt-oss-120b` | <Cross /> | <Check /> | <Check /> | <Check /> |
|
|
@@ -12,7 +12,7 @@ You can control both the execution flow and the settings at each step of the age
|
|
|
12
12
|
- A tool call needs approval, or
|
|
13
13
|
- A stop condition is met
|
|
14
14
|
|
|
15
|
-
A [steering message](#steering) that is waiting when the model finishes its answer keeps the loop going for another step.
|
|
15
|
+
A [steering message](#steering) that is waiting when the model finishes its answer keeps the loop going for another step. So does a [queued message](#queueing-messages), unless the answer was cut off.
|
|
16
16
|
|
|
17
17
|
The AI SDK provides built-in loop control through two parameters: `stopWhen` for defining stopping conditions and `prepareStep` for modifying settings (model, tools, messages, and more) between steps.
|
|
18
18
|
|
|
@@ -477,6 +477,34 @@ prepareStep: ({ initialMessages, steps, experimental_steeringMessages }) => ({
|
|
|
477
477
|
}),
|
|
478
478
|
```
|
|
479
479
|
|
|
480
|
+
### Queueing Messages
|
|
481
|
+
|
|
482
|
+
`steer()` changes what the agent is doing right now. Use `queue()` for a follow-up that should wait until the agent has finished its current task:
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
const steering = experimental_createSteering();
|
|
486
|
+
|
|
487
|
+
const resultPromise = agent.generate({
|
|
488
|
+
prompt: 'Refactor the auth module.',
|
|
489
|
+
experimental_steering: steering,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// while the agent is working:
|
|
493
|
+
steering.queue('Then add tests for it.');
|
|
494
|
+
steering.queue('Then update the readme.');
|
|
495
|
+
|
|
496
|
+
const result = await resultPromise;
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
A queued message is added once the model finishes its current task, i.e. when it gives a final answer without tool calls. The loop then continues with the message, so the agent works through the queue one task at a time:
|
|
500
|
+
|
|
501
|
+
- Queued messages are added one per task, in the order they are queued.
|
|
502
|
+
- Waiting steering messages are added first. A queued message waits for the next task that finishes.
|
|
503
|
+
- Messages that are queued before the loop starts wait until the model has answered the prompt.
|
|
504
|
+
- A response that is cut off (finish reason `length` or `content-filter`) does not finish the task, so the loop ends and the queued message is reported as not delivered.
|
|
505
|
+
- Stop conditions still apply, and a queued message that the loop does not reach is reported as not delivered, so you can send it as a new message instead.
|
|
506
|
+
- Queued messages are saved in `experimental_steeringMessages` like steering messages, and `queue()` resolves with the same outcome as `steer()`.
|
|
507
|
+
|
|
480
508
|
### Handling the Outcome
|
|
481
509
|
|
|
482
510
|
`steer()` resolves with the outcome of the message and never rejects:
|
|
@@ -16,7 +16,7 @@ to see providers that offer monitoring and tracing for AI SDK applications.
|
|
|
16
16
|
|
|
17
17
|
### Step 1: Register the OpenTelemetry integration
|
|
18
18
|
|
|
19
|
-
OpenTelemetry
|
|
19
|
+
OpenTelemetry collection requires the `@ai-sdk/otel` package. Install it and register the integration once at application startup:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
pnpm install @ai-sdk/otel
|
|
@@ -513,6 +513,37 @@ The `LegacyOpenTelemetry` integration emits legacy AI SDK-specific spans.
|
|
|
513
513
|
The `OpenTelemetry` emits spans that follow the [OpenTelemetry Semantic Conventions for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/).
|
|
514
514
|
By default, attributes use the `gen_ai.*` prefix. Provider names are mapped to well-known values (e.g. `openai`, `anthropic`, `gcp.vertex_ai`).
|
|
515
515
|
|
|
516
|
+
#### Inference duration metric
|
|
517
|
+
|
|
518
|
+
For `generateText` and `streamText`, `OpenTelemetry` also records the
|
|
519
|
+
[`gen_ai.client.inference.duration`](https://github.com/open-telemetry/semantic-conventions-genai/blob/3fe84604a581b3d50dd5b4bd29383b462d2a5018/docs/gen-ai/client-inference.md#metric-gen_aiclientinferenceduration)
|
|
520
|
+
histogram in seconds, following the development GenAI semantic conventions.
|
|
521
|
+
Configure a global OpenTelemetry `MeterProvider` with a metric reader and exporter
|
|
522
|
+
to collect it. A `TracerProvider` alone does not export metrics.
|
|
523
|
+
|
|
524
|
+
Each language model call contributes one observation, including calls that fail or
|
|
525
|
+
are cancelled. Streaming calls record the full response duration. In a workflow
|
|
526
|
+
with multiple model calls, each call is recorded separately; local tool execution
|
|
527
|
+
time is excluded. Each attempt of a retried model stream contributes its own
|
|
528
|
+
observation, including failed attempts that close without a finish chunk.
|
|
529
|
+
|
|
530
|
+
The histogram includes these attributes:
|
|
531
|
+
|
|
532
|
+
- `gen_ai.operation.name`: `"chat"`
|
|
533
|
+
- `gen_ai.provider.name`: the normalized provider name
|
|
534
|
+
- `gen_ai.request.model`: the requested model ID
|
|
535
|
+
- `gen_ai.response.model`: the response model ID, when available
|
|
536
|
+
- `error.type`: the error name for thrown errors, `"AbortError"` for cancellation,
|
|
537
|
+
or `"_OTHER"` when a call ends with an error finish reason or an unknown error
|
|
538
|
+
|
|
539
|
+
The default bucket boundary advice is `[0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64,
|
|
540
|
+
1.28, 2.56, 5.12, 10.24, 20.48, 40.96, 81.92]` seconds. Your OpenTelemetry SDK
|
|
541
|
+
configuration can override aggregation and bucket boundaries.
|
|
542
|
+
|
|
543
|
+
Setting `telemetry.isEnabled` to `false` disables these metrics for a call.
|
|
544
|
+
Setting `recordInputs` or `recordOutputs` to `false` does not disable duration
|
|
545
|
+
metrics. The `LegacyOpenTelemetry` integration does not emit this histogram.
|
|
546
|
+
|
|
516
547
|
#### generateText / streamText
|
|
517
548
|
|
|
518
549
|
For `generateText` and `streamText`, the integration records 3 types of spans:
|
|
@@ -857,7 +857,7 @@ To see `generateText` in action, check out [these examples](#examples).
|
|
|
857
857
|
type: 'Experimental_Steering',
|
|
858
858
|
isOptional: true,
|
|
859
859
|
description:
|
|
860
|
-
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()`
|
|
860
|
+
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. Messages sent with `steering.queue()` wait until the model finishes its current task and are added one per task. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` and `queue()` resolve with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
|
|
861
861
|
},
|
|
862
862
|
{
|
|
863
863
|
name: 'experimental_download',
|
|
@@ -914,7 +914,7 @@ retain the content their contracts require.
|
|
|
914
914
|
type: 'Experimental_Steering',
|
|
915
915
|
isOptional: true,
|
|
916
916
|
description:
|
|
917
|
-
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()`
|
|
917
|
+
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. Messages sent with `steering.queue()` wait until the model finishes its current task and are added one per task. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` and `queue()` resolve with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
|
|
918
918
|
},
|
|
919
919
|
{
|
|
920
920
|
name: 'experimental_download',
|
|
@@ -768,7 +768,7 @@ const result = await agent.generate({
|
|
|
768
768
|
type: 'Experimental_Steering',
|
|
769
769
|
isOptional: true,
|
|
770
770
|
description:
|
|
771
|
-
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()`
|
|
771
|
+
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. Messages sent with `steering.queue()` wait until the model finishes its current task and are added one per task. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` and `queue()` resolve with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
|
|
772
772
|
},
|
|
773
773
|
{
|
|
774
774
|
name: 'options',
|
|
@@ -891,7 +891,7 @@ for await (const chunk of stream.textStream) {
|
|
|
891
891
|
type: 'Experimental_Steering',
|
|
892
892
|
isOptional: true,
|
|
893
893
|
description:
|
|
894
|
-
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()`
|
|
894
|
+
"Experimental steering object created with `experimental_createSteering()`. Messages sent with `steering.steer()` are added as user messages at the next step boundary, after the current step and its tool calls are done. Messages sent with `steering.queue()` wait until the model finishes its current task and are added one per task. A waiting message keeps the loop going for another step unless a stop condition is met. `steer()` and `queue()` resolve with `{ type: 'delivered', id, message, stepNumber }` or `{ type: 'not-delivered', id, message, reason }`. See [Steering](/docs/agents/loop-control#steering).",
|
|
895
895
|
},
|
|
896
896
|
{
|
|
897
897
|
name: 'options',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.160",
|
|
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,12 +42,12 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.133",
|
|
46
46
|
"@ai-sdk/provider": "4.0.35",
|
|
47
47
|
"@ai-sdk/provider-utils": "5.0.72"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.130",
|
|
51
51
|
"@ai-sdk/deepseek": "3.0.77",
|
|
52
52
|
"@ai-sdk/google": "4.0.109",
|
|
53
53
|
"@ai-sdk/groq": "4.0.74",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@ai-sdk/moonshotai": "3.0.82",
|
|
56
56
|
"@ai-sdk/openai": "4.0.106",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.4",
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
58
|
+
"@ai-sdk/xai": "5.0.36",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -981,7 +981,15 @@ async function generateTextInternal<
|
|
|
981
981
|
clientToolCalls.length === 0 &&
|
|
982
982
|
pendingDeferredToolCalls.size === 0
|
|
983
983
|
) {
|
|
984
|
-
|
|
984
|
+
// queued messages wait for a task that was not cut off:
|
|
985
|
+
const isTruncated =
|
|
986
|
+
steps[steps.length - 1].finishReason === 'length' ||
|
|
987
|
+
steps[steps.length - 1].finishReason === 'content-filter';
|
|
988
|
+
|
|
989
|
+
if (
|
|
990
|
+
!steeringRun?.hasPendingMessages() &&
|
|
991
|
+
(isTruncated || !steeringRun?.promoteQueuedMessage())
|
|
992
|
+
) {
|
|
985
993
|
return false;
|
|
986
994
|
}
|
|
987
995
|
|
|
@@ -79,7 +79,7 @@ export function smoothStream<TOOLS extends ToolSet>({
|
|
|
79
79
|
|
|
80
80
|
if (!buffer.startsWith(match)) {
|
|
81
81
|
throw new Error(
|
|
82
|
-
`Chunking function must return a match that is a prefix of the buffer. Received: "${match}"
|
|
82
|
+
`Chunking function must return a match that is a prefix of the buffer. Received: "${match}" which is not a prefix of buffer "${buffer}"`,
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -81,6 +81,24 @@ export interface Steering {
|
|
|
81
81
|
message: string | UserModelMessage,
|
|
82
82
|
options?: { id?: string },
|
|
83
83
|
): Promise<SteeringOutcome>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Adds a user message to the run once the model has finished its current
|
|
87
|
+
* task, i.e. when it gives a final answer without tool calls. The run then
|
|
88
|
+
* continues with the message unless a stop condition is met. Queued
|
|
89
|
+
* messages are delivered one per task, in order, after any waiting steering
|
|
90
|
+
* messages.
|
|
91
|
+
*
|
|
92
|
+
* @param message - The user message, or its text.
|
|
93
|
+
* @param options.id - The id of the message. Sending a message again with
|
|
94
|
+
* the same id returns the outcome of the first message.
|
|
95
|
+
*
|
|
96
|
+
* @returns The outcome of the message. It never rejects.
|
|
97
|
+
*/
|
|
98
|
+
queue(
|
|
99
|
+
message: string | UserModelMessage,
|
|
100
|
+
options?: { id?: string },
|
|
101
|
+
): Promise<SteeringOutcome>;
|
|
84
102
|
}
|
|
85
103
|
|
|
86
104
|
/**
|
|
@@ -89,6 +107,12 @@ export interface Steering {
|
|
|
89
107
|
export type SteeringRun = {
|
|
90
108
|
hasPendingMessages(): boolean;
|
|
91
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Moves the oldest queued message to the waiting messages for the next
|
|
112
|
+
* step. Returns `false` when no message is queued.
|
|
113
|
+
*/
|
|
114
|
+
promoteQueuedMessage(): boolean;
|
|
115
|
+
|
|
92
116
|
/**
|
|
93
117
|
* Takes the waiting messages for the next step. They are resolved by
|
|
94
118
|
* `resolveTakenMessages` once the prompt of the step is known.
|
|
@@ -116,6 +140,7 @@ type PendingSteer = {
|
|
|
116
140
|
class DefaultSteering implements Steering {
|
|
117
141
|
private state: 'idle' | 'running' | 'ended' = 'idle';
|
|
118
142
|
private pending: Array<PendingSteer> = [];
|
|
143
|
+
private queued: Array<PendingSteer> = [];
|
|
119
144
|
private taken: Array<PendingSteer> = [];
|
|
120
145
|
private outcomes = new Map<string, Promise<SteeringOutcome>>();
|
|
121
146
|
|
|
@@ -123,8 +148,25 @@ class DefaultSteering implements Steering {
|
|
|
123
148
|
message: string | UserModelMessage,
|
|
124
149
|
options?: { id?: string },
|
|
125
150
|
): Promise<SteeringOutcome> {
|
|
126
|
-
|
|
151
|
+
return this.add({ message, id: options?.id, target: this.pending });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
queue(
|
|
155
|
+
message: string | UserModelMessage,
|
|
156
|
+
options?: { id?: string },
|
|
157
|
+
): Promise<SteeringOutcome> {
|
|
158
|
+
return this.add({ message, id: options?.id, target: this.queued });
|
|
159
|
+
}
|
|
127
160
|
|
|
161
|
+
private add({
|
|
162
|
+
message,
|
|
163
|
+
id = generateSteeringId(),
|
|
164
|
+
target,
|
|
165
|
+
}: {
|
|
166
|
+
message: string | UserModelMessage;
|
|
167
|
+
id?: string;
|
|
168
|
+
target: Array<PendingSteer>;
|
|
169
|
+
}): Promise<SteeringOutcome> {
|
|
128
170
|
const existingOutcome = this.outcomes.get(id);
|
|
129
171
|
if (existingOutcome != null) {
|
|
130
172
|
return existingOutcome;
|
|
@@ -144,7 +186,7 @@ class DefaultSteering implements Steering {
|
|
|
144
186
|
reason: 'finished',
|
|
145
187
|
})
|
|
146
188
|
: new Promise<SteeringOutcome>(resolve => {
|
|
147
|
-
|
|
189
|
+
target.push({ id, message: userMessage, resolve });
|
|
148
190
|
});
|
|
149
191
|
|
|
150
192
|
this.outcomes.set(id, outcome);
|
|
@@ -168,6 +210,16 @@ class DefaultSteering implements Steering {
|
|
|
168
210
|
hasPendingMessages: () =>
|
|
169
211
|
this.state === 'running' && this.pending.length > 0,
|
|
170
212
|
|
|
213
|
+
promoteQueuedMessage: () => {
|
|
214
|
+
const next = this.state === 'running' ? this.queued.shift() : undefined;
|
|
215
|
+
if (next == null) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.pending.push(next);
|
|
220
|
+
return true;
|
|
221
|
+
},
|
|
222
|
+
|
|
171
223
|
takeMessages: () => {
|
|
172
224
|
if (this.state !== 'running') {
|
|
173
225
|
return [];
|
|
@@ -207,9 +259,10 @@ class DefaultSteering implements Steering {
|
|
|
207
259
|
return;
|
|
208
260
|
}
|
|
209
261
|
|
|
210
|
-
const notDelivered = [...this.taken, ...this.pending];
|
|
262
|
+
const notDelivered = [...this.taken, ...this.pending, ...this.queued];
|
|
211
263
|
this.taken = [];
|
|
212
264
|
this.pending = [];
|
|
265
|
+
this.queued = [];
|
|
213
266
|
this.state = 'ended';
|
|
214
267
|
|
|
215
268
|
for (const { id, message, resolve } of notDelivered) {
|
|
@@ -3389,7 +3389,15 @@ class DefaultStreamTextResult<
|
|
|
3389
3389
|
clientToolCalls.length === 0 &&
|
|
3390
3390
|
pendingDeferredToolCalls.size === 0
|
|
3391
3391
|
) {
|
|
3392
|
-
|
|
3392
|
+
// queued messages wait for a task that was not cut off:
|
|
3393
|
+
const isTruncated =
|
|
3394
|
+
stepFinishReason === 'length' ||
|
|
3395
|
+
stepFinishReason === 'content-filter';
|
|
3396
|
+
|
|
3397
|
+
if (
|
|
3398
|
+
!steeringRun?.hasPendingMessages() &&
|
|
3399
|
+
(isTruncated || !steeringRun?.promoteQueuedMessage())
|
|
3400
|
+
) {
|
|
3393
3401
|
return false;
|
|
3394
3402
|
}
|
|
3395
3403
|
|
|
@@ -1056,7 +1056,9 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
1056
1056
|
default: {
|
|
1057
1057
|
if (isDataUIMessageChunk(chunk)) {
|
|
1058
1058
|
// validate data chunk if dataPartSchemas is provided
|
|
1059
|
-
|
|
1059
|
+
// (schemas are keyed by the part name without the 'data-' prefix)
|
|
1060
|
+
const dataPartSchema = dataPartSchemas?.[chunk.type.slice(5)];
|
|
1061
|
+
if (dataPartSchema != null) {
|
|
1060
1062
|
const partIdx = state.message.parts.findIndex(
|
|
1061
1063
|
p =>
|
|
1062
1064
|
'id' in p &&
|
|
@@ -1069,7 +1071,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
1069
1071
|
|
|
1070
1072
|
await validateTypes({
|
|
1071
1073
|
value: chunk.data,
|
|
1072
|
-
schema:
|
|
1074
|
+
schema: dataPartSchema,
|
|
1073
1075
|
context: {
|
|
1074
1076
|
field: `message.parts[${actualPartIdx}].data`,
|
|
1075
1077
|
entityName: chunk.type,
|