ai 7.0.104 → 7.0.106
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 +31 -0
- package/dist/index.js +95 -33
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +3 -2
- package/dist/internal/index.js +10 -12
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-core/32-evaluation.mdx +26 -7
- package/docs/03-ai-sdk-core/45-provider-management.mdx +4 -4
- package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +9 -9
- package/docs/07-reference/01-ai-sdk-core/40-provider-registry.mdx +3 -2
- package/docs/07-reference/01-ai-sdk-core/42-custom-provider.mdx +7 -5
- package/package.json +11 -11
- package/src/batch/batch.ts +1 -0
- package/src/generate-object/generate-object.ts +1 -0
- package/src/generate-object/stream-object.ts +1 -0
- package/src/generate-text/generate-text.ts +1 -0
- package/src/generate-text/invoke-tool-callbacks-from-stream.ts +1 -1
- package/src/generate-text/prune-messages.ts +13 -0
- package/src/generate-text/stream-language-model-call.ts +1 -0
- package/src/generate-text/stream-text.ts +28 -11
- package/src/middleware/extract-json-middleware.ts +35 -9
- package/src/middleware/simulate-streaming-middleware.ts +7 -1
- package/src/model/resolve-model.ts +2 -4
- package/src/prompt/convert-to-language-model-prompt.ts +4 -2
- package/src/prompt/create-tool-model-output.ts +20 -11
- package/src/ui-message-stream/read-ui-message-stream.ts +13 -4
- package/src/util/download/download-function.ts +5 -2
|
@@ -11,8 +11,8 @@ is one state, not a batch of unrelated inputs.
|
|
|
11
11
|
|
|
12
12
|
This API and the evaluation model specification are experimental and may change
|
|
13
13
|
in patch releases. Pass an evaluation model instance, resolve a model through a
|
|
14
|
-
provider registry, or use a string ID
|
|
15
|
-
|
|
14
|
+
provider registry, or use a string ID. Strings resolve through Vercel AI Gateway
|
|
15
|
+
unless you configure an evaluation-capable default provider.
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
import { experimental_evaluate, type Experimental_EvaluationModel } from 'ai';
|
|
@@ -125,8 +125,26 @@ evaluation models.
|
|
|
125
125
|
|
|
126
126
|
### Default-provider strings
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
String IDs use Vercel AI Gateway by default. Configure Gateway authentication
|
|
129
|
+
with `AI_GATEWAY_API_KEY` or Vercel OIDC, then pass a Gateway model ID:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { experimental_evaluate } from 'ai';
|
|
133
|
+
|
|
134
|
+
const result = await experimental_evaluate({
|
|
135
|
+
model: 'typesafe-ai/jev-latest',
|
|
136
|
+
state: 'I was charged twice. Please refund the extra charge.',
|
|
137
|
+
questions: {
|
|
138
|
+
refund: {
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
instructions: 'Is the customer asking for a refund?',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
To resolve strings through your own provider or aliases, configure the default
|
|
147
|
+
provider once at application startup:
|
|
130
148
|
|
|
131
149
|
```ts
|
|
132
150
|
// Using the registry above, expose an alias through a custom provider:
|
|
@@ -152,9 +170,10 @@ also resolve through this global default. Prefer model instances in aliases to
|
|
|
152
170
|
avoid resolution cycles. Global configuration affects other AI SDK functions
|
|
153
171
|
too; avoid changing it per request in a shared process.
|
|
154
172
|
|
|
155
|
-
|
|
156
|
-
method
|
|
157
|
-
`NoSuchProviderError`; missing models or evaluation
|
|
173
|
+
An explicitly configured default provider must expose an `evaluationModel`
|
|
174
|
+
method; Gateway is used only when no default provider is configured. Missing
|
|
175
|
+
registry providers throw `NoSuchProviderError`; missing models or evaluation
|
|
176
|
+
capabilities throw
|
|
158
177
|
`NoSuchModelError` with `modelType: 'evaluationModel'`. Unsupported model versions
|
|
159
178
|
throw `UnsupportedModelVersionError`, including models returned by registries.
|
|
160
179
|
The stable `ProviderV4` and `ProviderRegistryProvider` interfaces are unchanged;
|
|
@@ -473,8 +473,8 @@ This simplifies provider usage and makes it easier to switch between providers w
|
|
|
473
473
|
|
|
474
474
|
Custom providers accept `evaluationModels` aliases, and registries expose
|
|
475
475
|
`evaluationModel('provider:model')`. These methods return model instances for
|
|
476
|
-
`experimental_evaluate`.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
476
|
+
`experimental_evaluate`. Direct string IDs use Gateway by default, or an
|
|
477
|
+
explicitly configured default provider with an `evaluationModel` method. Registry
|
|
478
|
+
middleware for language and image models does not apply to evaluation.
|
|
479
|
+
See [Evaluation](/docs/ai-sdk-core/evaluation#model-aliases-and-registries)
|
|
480
480
|
for aliases, default-provider configuration, and capability differences.
|
|
@@ -14,15 +14,15 @@ state. See [Evaluation](/docs/ai-sdk-core/evaluation) for examples and semantics
|
|
|
14
14
|
|
|
15
15
|
## Parameters
|
|
16
16
|
|
|
17
|
-
| Parameter | Type | Description
|
|
18
|
-
| ----------------- | ------------------------------------------------- |
|
|
19
|
-
| `model` | `Experimental_EvaluationModel` | Required experimental v4 model instance or a string ID resolved by an explicitly configured evaluation-capable default provider. |
|
|
20
|
-
| `state` | `string \| object \| array` | Required JSON-compatible shared state.
|
|
21
|
-
| `questions` | `Record<string, Experimental_EvaluationQuestion>` | Required nonempty question map.
|
|
22
|
-
| `maxRetries` | `number` | Nonnegative integer; defaults to 2.
|
|
23
|
-
| `abortSignal` | `AbortSignal` | Cancels evaluation.
|
|
24
|
-
| `headers` | `Record<string, string>` | Additional HTTP headers.
|
|
25
|
-
| `providerOptions` | `ProviderOptions` | Provider-specific options.
|
|
17
|
+
| Parameter | Type | Description |
|
|
18
|
+
| ----------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `model` | `Experimental_EvaluationModel` | Required experimental v4 model instance or a string ID resolved by Gateway or an explicitly configured evaluation-capable default provider. |
|
|
20
|
+
| `state` | `string \| object \| array` | Required JSON-compatible shared state. |
|
|
21
|
+
| `questions` | `Record<string, Experimental_EvaluationQuestion>` | Required nonempty question map. |
|
|
22
|
+
| `maxRetries` | `number` | Nonnegative integer; defaults to 2. |
|
|
23
|
+
| `abortSignal` | `AbortSignal` | Cancels evaluation. |
|
|
24
|
+
| `headers` | `Record<string, string>` | Additional HTTP headers. |
|
|
25
|
+
| `providerOptions` | `ProviderOptions` | Provider-specific options. |
|
|
26
26
|
|
|
27
27
|
## Result
|
|
28
28
|
|
|
@@ -312,6 +312,7 @@ retain experimental evaluation access.
|
|
|
312
312
|
Unavailable evaluation capabilities or models throw `NoSuchModelError` with
|
|
313
313
|
`modelType: 'evaluationModel'`; unknown registry providers throw
|
|
314
314
|
`NoSuchProviderError`. These capabilities are structural extensions and are not
|
|
315
|
-
added to the stable `ProviderV4` contract.
|
|
316
|
-
|
|
315
|
+
added to the stable `ProviderV4` contract. Direct evaluation string IDs default
|
|
316
|
+
to Gateway when no default provider is configured.
|
|
317
|
+
See [Evaluation](/docs/ai-sdk-core/evaluation#model-aliases-and-registries)
|
|
317
318
|
for runnable usage patterns.
|
|
@@ -207,13 +207,15 @@ The `customProvider` function returns a `Provider` instance. It has the followin
|
|
|
207
207
|
Pass `evaluationModels: Record<string, Experimental_EvaluationModel>` to define
|
|
208
208
|
aliases for evaluation model instances or string IDs. The returned provider adds
|
|
209
209
|
`evaluationModel(alias): Experimental_EvaluationModelV4`. String aliases resolve
|
|
210
|
-
through an explicitly configured default provider with
|
|
211
|
-
method. Unknown aliases use the fallback provider's evaluation
|
|
212
|
-
available; model failures and unsupported questions do not trigger
|
|
210
|
+
through Gateway by default, or an explicitly configured default provider with
|
|
211
|
+
an `evaluationModel` method. Unknown aliases use the fallback provider's evaluation
|
|
212
|
+
factory when available; model failures and unsupported questions do not trigger
|
|
213
|
+
substitution.
|
|
213
214
|
|
|
214
215
|
Unavailable evaluation capabilities or models throw `NoSuchModelError` with
|
|
215
216
|
`modelType: 'evaluationModel'`; unknown registry providers throw
|
|
216
217
|
`NoSuchProviderError`. These capabilities are structural extensions and are not
|
|
217
|
-
added to the stable `ProviderV4` contract.
|
|
218
|
-
|
|
218
|
+
added to the stable `ProviderV4` contract. Direct evaluation string IDs default
|
|
219
|
+
to Gateway when no default provider is configured.
|
|
220
|
+
See [Evaluation](/docs/ai-sdk-core/evaluation#model-aliases-and-registries)
|
|
219
221
|
for runnable usage patterns.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.106",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.86",
|
|
46
46
|
"@ai-sdk/provider": "4.0.17",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.44"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.87",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.48",
|
|
52
|
+
"@ai-sdk/google": "4.0.75",
|
|
53
|
+
"@ai-sdk/groq": "4.0.45",
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.52",
|
|
55
|
+
"@ai-sdk/moonshotai": "3.0.53",
|
|
56
|
+
"@ai-sdk/openai": "4.0.70",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.1",
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
58
|
+
"@ai-sdk/xai": "5.0.3",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
package/src/batch/batch.ts
CHANGED
|
@@ -80,7 +80,7 @@ export function invokeToolCallbacksFromStream<
|
|
|
80
80
|
|
|
81
81
|
delete ongoingToolCallToolNames[chunk.toolCallId];
|
|
82
82
|
|
|
83
|
-
if (tool?.onInputAvailable != null) {
|
|
83
|
+
if (!chunk.invalid && tool?.onInputAvailable != null) {
|
|
84
84
|
await tool.onInputAvailable({
|
|
85
85
|
input: chunk.input,
|
|
86
86
|
toolCallId: chunk.toolCallId,
|
|
@@ -121,6 +121,7 @@ export function pruneMessages({
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
const approvalIdToToolCallId = new Map<string, string>();
|
|
124
125
|
const approvalIdToToolName = new Map<string, string>();
|
|
125
126
|
for (const message of messages) {
|
|
126
127
|
if (
|
|
@@ -129,6 +130,8 @@ export function pruneMessages({
|
|
|
129
130
|
) {
|
|
130
131
|
for (const part of message.content) {
|
|
131
132
|
if (part.type === 'tool-approval-request') {
|
|
133
|
+
approvalIdToToolCallId.set(part.approvalId, part.toolCallId);
|
|
134
|
+
|
|
132
135
|
const toolName = toolCallIdToToolName.get(part.toolCallId);
|
|
133
136
|
if (toolName != null) {
|
|
134
137
|
approvalIdToToolName.set(part.approvalId, toolName);
|
|
@@ -138,6 +141,16 @@ export function pruneMessages({
|
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
// Approval requests depend on their originating tool call. When an
|
|
145
|
+
// approval response is in the retained messages, trace through its request
|
|
146
|
+
// so pruning does not leave a pending approval without the call to execute.
|
|
147
|
+
for (const approvalId of keptApprovalIds) {
|
|
148
|
+
const toolCallId = approvalIdToToolCallId.get(approvalId);
|
|
149
|
+
if (toolCallId != null) {
|
|
150
|
+
keptToolCallIds.add(toolCallId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
141
154
|
messages = messages.map((message, messageIndex) => {
|
|
142
155
|
if (
|
|
143
156
|
(message.role !== 'assistant' && message.role !== 'tool') ||
|
|
@@ -968,14 +968,16 @@ function createOutputTransformStream<
|
|
|
968
968
|
let text = '';
|
|
969
969
|
let textChunk = '';
|
|
970
970
|
let textProviderMetadata: ProviderMetadata | undefined = undefined;
|
|
971
|
-
let lastPublishedValue =
|
|
971
|
+
let lastPublishedValue: string | undefined = undefined;
|
|
972
|
+
let hasPublishedValue = false;
|
|
972
973
|
|
|
973
|
-
function
|
|
974
|
+
function resetOutputState() {
|
|
974
975
|
firstTextChunkId = undefined;
|
|
975
976
|
text = '';
|
|
976
977
|
textChunk = '';
|
|
977
978
|
textProviderMetadata = undefined;
|
|
978
|
-
lastPublishedValue =
|
|
979
|
+
lastPublishedValue = undefined;
|
|
980
|
+
hasPublishedValue = false;
|
|
979
981
|
}
|
|
980
982
|
|
|
981
983
|
function enqueueChunk({
|
|
@@ -1020,11 +1022,15 @@ function createOutputTransformStream<
|
|
|
1020
1022
|
>({
|
|
1021
1023
|
async transform(chunk, controller) {
|
|
1022
1024
|
if (isStreamRetryBoundaryPart(chunk)) {
|
|
1023
|
-
|
|
1025
|
+
resetOutputState();
|
|
1024
1026
|
controller.enqueue(chunk);
|
|
1025
1027
|
return;
|
|
1026
1028
|
}
|
|
1027
1029
|
|
|
1030
|
+
if (chunk.type === 'start-step') {
|
|
1031
|
+
resetOutputState();
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1028
1034
|
// ensure that we publish the last text chunk before the step finish:
|
|
1029
1035
|
if (chunk.type === 'finish-step' && textChunk.length > 0) {
|
|
1030
1036
|
publishTextChunk({ controller });
|
|
@@ -1096,9 +1102,10 @@ function createOutputTransformStream<
|
|
|
1096
1102
|
typeof result.partial === 'string'
|
|
1097
1103
|
? result.partial
|
|
1098
1104
|
: JSON.stringify(result.partial);
|
|
1099
|
-
if (currentValue !== lastPublishedValue) {
|
|
1105
|
+
if (!hasPublishedValue || currentValue !== lastPublishedValue) {
|
|
1100
1106
|
publishTextChunk({ controller, partialOutput: result.partial });
|
|
1101
1107
|
lastPublishedValue = currentValue;
|
|
1108
|
+
hasPublishedValue = true;
|
|
1102
1109
|
}
|
|
1103
1110
|
}
|
|
1104
1111
|
},
|
|
@@ -1414,6 +1421,8 @@ class DefaultStreamTextResult<
|
|
|
1414
1421
|
const initialResponseMessages: Array<ResponseMessage> = [];
|
|
1415
1422
|
let stepMessagesForNextStep: Array<ModelMessage> | undefined;
|
|
1416
1423
|
let currentStepMessages: Array<ModelMessage> = [];
|
|
1424
|
+
let isAborted = false;
|
|
1425
|
+
let currentStepModel = model;
|
|
1417
1426
|
|
|
1418
1427
|
// provider-assigned text/reasoning part IDs are only unique within a
|
|
1419
1428
|
// single model call (e.g. Anthropic uses the content block index, which
|
|
@@ -1661,8 +1670,8 @@ class DefaultStreamTextResult<
|
|
|
1661
1670
|
new DefaultStepResult({
|
|
1662
1671
|
callId,
|
|
1663
1672
|
stepNumber: recordedSteps.length,
|
|
1664
|
-
provider:
|
|
1665
|
-
modelId:
|
|
1673
|
+
provider: currentStepModel.provider,
|
|
1674
|
+
modelId: currentStepModel.modelId,
|
|
1666
1675
|
runtimeContext,
|
|
1667
1676
|
toolsContext,
|
|
1668
1677
|
content: recordedContent,
|
|
@@ -1691,8 +1700,8 @@ class DefaultStreamTextResult<
|
|
|
1691
1700
|
|
|
1692
1701
|
logWarnings({
|
|
1693
1702
|
warnings: recordedWarnings,
|
|
1694
|
-
provider:
|
|
1695
|
-
model:
|
|
1703
|
+
provider: currentStepModel.provider,
|
|
1704
|
+
model: currentStepModel.modelId,
|
|
1696
1705
|
});
|
|
1697
1706
|
|
|
1698
1707
|
recordedSteps.push(currentStepResult);
|
|
@@ -1743,6 +1752,10 @@ class DefaultStreamTextResult<
|
|
|
1743
1752
|
// aggregate results:
|
|
1744
1753
|
self._steps.resolve(recordedSteps);
|
|
1745
1754
|
|
|
1755
|
+
if (isAborted) {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1746
1759
|
// call onEnd callback:
|
|
1747
1760
|
const finalStep = recordedSteps[recordedSteps.length - 1];
|
|
1748
1761
|
const content = recordedSteps.flatMap(step => step.content);
|
|
@@ -1845,6 +1858,8 @@ class DefaultStreamTextResult<
|
|
|
1845
1858
|
async pull(controller) {
|
|
1846
1859
|
// abort handling:
|
|
1847
1860
|
async function abort() {
|
|
1861
|
+
isAborted = true;
|
|
1862
|
+
|
|
1848
1863
|
await notify({
|
|
1849
1864
|
event: {
|
|
1850
1865
|
callId,
|
|
@@ -1885,6 +1900,7 @@ class DefaultStreamTextResult<
|
|
|
1885
1900
|
if (isAbortError(error) && abortSignal?.aborted) {
|
|
1886
1901
|
await abort();
|
|
1887
1902
|
} else {
|
|
1903
|
+
await telemetryDispatcher.onError?.({ callId, error });
|
|
1888
1904
|
controller.error(error);
|
|
1889
1905
|
}
|
|
1890
1906
|
}
|
|
@@ -2328,6 +2344,7 @@ class DefaultStreamTextResult<
|
|
|
2328
2344
|
const stepModel = resolveLanguageModel(
|
|
2329
2345
|
prepareStepResult?.model ?? model,
|
|
2330
2346
|
);
|
|
2347
|
+
currentStepModel = stepModel;
|
|
2331
2348
|
|
|
2332
2349
|
const stepActiveTools = filterActiveTools({
|
|
2333
2350
|
tools,
|
|
@@ -2752,7 +2769,7 @@ class DefaultStreamTextResult<
|
|
|
2752
2769
|
const createStepResponse = () => ({
|
|
2753
2770
|
id: generateId(),
|
|
2754
2771
|
timestamp: new Date(),
|
|
2755
|
-
modelId:
|
|
2772
|
+
modelId: stepModel.modelId,
|
|
2756
2773
|
});
|
|
2757
2774
|
let stepResponse: {
|
|
2758
2775
|
id: string;
|
|
@@ -3375,7 +3392,7 @@ class DefaultStreamTextResult<
|
|
|
3375
3392
|
InferPartialOutput<OUTPUT>
|
|
3376
3393
|
>({
|
|
3377
3394
|
transform({ partialOutput }, controller) {
|
|
3378
|
-
if (partialOutput
|
|
3395
|
+
if (partialOutput !== undefined) {
|
|
3379
3396
|
controller.enqueue(partialOutput);
|
|
3380
3397
|
}
|
|
3381
3398
|
},
|
|
@@ -19,6 +19,28 @@ function stripMarkdownCodeFenceSuffix(text: string): string {
|
|
|
19
19
|
return text.replace(/\n?```\s*$/, '').trimEnd();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function getPotentialSuffixStart(text: string): number {
|
|
23
|
+
let index = text.length;
|
|
24
|
+
|
|
25
|
+
while (index > 0 && /\s/.test(text[index - 1])) {
|
|
26
|
+
index--;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let backtickCount = 0;
|
|
30
|
+
while (index > 0 && backtickCount < 3 && text[index - 1] === '`') {
|
|
31
|
+
index--;
|
|
32
|
+
backtickCount++;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (backtickCount > 0) {
|
|
36
|
+
while (index > 0 && /\s/.test(text[index - 1])) {
|
|
37
|
+
index--;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return index;
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
/**
|
|
23
45
|
* Middleware that extracts JSON from text content by stripping
|
|
24
46
|
* markdown code fences and other formatting.
|
|
@@ -75,8 +97,6 @@ export function extractJsonMiddleware(options?: {
|
|
|
75
97
|
}
|
|
76
98
|
> = createIdMap();
|
|
77
99
|
|
|
78
|
-
const SUFFIX_BUFFER_SIZE = 12;
|
|
79
|
-
|
|
80
100
|
return {
|
|
81
101
|
stream: stream.pipeThrough(
|
|
82
102
|
new TransformStream<
|
|
@@ -145,13 +165,19 @@ export function extractJsonMiddleware(options?: {
|
|
|
145
165
|
}
|
|
146
166
|
}
|
|
147
167
|
|
|
148
|
-
// Stream content
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
168
|
+
// Stream content while retaining anything that could still
|
|
169
|
+
// become trailing whitespace or a markdown fence suffix.
|
|
170
|
+
if (block.phase === 'streaming') {
|
|
171
|
+
const potentialSuffixStart = getPotentialSuffixStart(
|
|
172
|
+
block.buffer,
|
|
173
|
+
);
|
|
174
|
+
const toStream = block.buffer.slice(0, potentialSuffixStart);
|
|
175
|
+
block.buffer = block.buffer.slice(potentialSuffixStart);
|
|
176
|
+
|
|
177
|
+
if (toStream.length === 0) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
155
181
|
controller.enqueue({
|
|
156
182
|
type: 'text-delta',
|
|
157
183
|
id: chunk.id,
|
|
@@ -25,7 +25,13 @@ export function simulateStreamingMiddleware(): LanguageModelMiddleware {
|
|
|
25
25
|
switch (part.type) {
|
|
26
26
|
case 'text': {
|
|
27
27
|
if (part.text.length > 0) {
|
|
28
|
-
controller.enqueue({
|
|
28
|
+
controller.enqueue({
|
|
29
|
+
type: 'text-start',
|
|
30
|
+
id: String(id),
|
|
31
|
+
...(part.providerMetadata != null
|
|
32
|
+
? { providerMetadata: part.providerMetadata }
|
|
33
|
+
: {}),
|
|
34
|
+
});
|
|
29
35
|
controller.enqueue({
|
|
30
36
|
type: 'text-delta',
|
|
31
37
|
id: String(id),
|
|
@@ -222,10 +222,8 @@ export function resolveEvaluationModel(
|
|
|
222
222
|
): EvaluationModelV4 {
|
|
223
223
|
if (typeof model === 'string') {
|
|
224
224
|
// Use the original provider so experimental methods and their receiver survive.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
| EvaluationProvider
|
|
228
|
-
| undefined;
|
|
225
|
+
const provider = (globalThis.AI_SDK_DEFAULT_PROVIDER ??
|
|
226
|
+
gateway) as EvaluationProvider;
|
|
229
227
|
|
|
230
228
|
if (typeof provider?.evaluationModel !== 'function') {
|
|
231
229
|
throw new NoSuchModelError({
|
|
@@ -36,7 +36,8 @@ import { MissingToolResultsError } from '../error/missing-tool-result-error';
|
|
|
36
36
|
export async function convertToLanguageModelPrompt({
|
|
37
37
|
prompt,
|
|
38
38
|
supportedUrls,
|
|
39
|
-
download
|
|
39
|
+
download,
|
|
40
|
+
abortSignal,
|
|
40
41
|
// `provider` is only needed here to convert legacy tool output types via `mapToolResultOutput`.
|
|
41
42
|
// TODO: remove in v8 when "file-id" and "image-file-id" types are removed
|
|
42
43
|
provider,
|
|
@@ -44,11 +45,12 @@ export async function convertToLanguageModelPrompt({
|
|
|
44
45
|
prompt: StandardizedPrompt;
|
|
45
46
|
supportedUrls: Record<string, RegExp[]>;
|
|
46
47
|
download: DownloadFunction | undefined;
|
|
48
|
+
abortSignal?: AbortSignal;
|
|
47
49
|
provider?: string;
|
|
48
50
|
}): Promise<LanguageModelV4Prompt> {
|
|
49
51
|
const downloadedAssets = await downloadAssets(
|
|
50
52
|
prompt.messages,
|
|
51
|
-
download,
|
|
53
|
+
download ?? createDefaultDownloadFunction(undefined, abortSignal),
|
|
52
54
|
supportedUrls,
|
|
53
55
|
);
|
|
54
56
|
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { getErrorMessage, type JSONValue } from '@ai-sdk/provider';
|
|
2
|
-
import {
|
|
3
|
-
parseJSON,
|
|
4
|
-
type Tool,
|
|
5
|
-
type ToolResultOutput,
|
|
6
|
-
} from '@ai-sdk/provider-utils';
|
|
2
|
+
import type { Tool, ToolResultOutput } from '@ai-sdk/provider-utils';
|
|
7
3
|
|
|
8
4
|
export async function createToolModelOutput({
|
|
9
5
|
toolCallId,
|
|
@@ -21,7 +17,7 @@ export async function createToolModelOutput({
|
|
|
21
17
|
if (errorMode === 'text') {
|
|
22
18
|
return { type: 'error-text', value: getErrorMessage(output) };
|
|
23
19
|
} else if (errorMode === 'json') {
|
|
24
|
-
return { type: 'error-json', value:
|
|
20
|
+
return { type: 'error-json', value: toJSONValue(output) };
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
if (tool?.toModelOutput) {
|
|
@@ -30,16 +26,29 @@ export async function createToolModelOutput({
|
|
|
30
26
|
|
|
31
27
|
return typeof output === 'string'
|
|
32
28
|
? { type: 'text', value: output }
|
|
33
|
-
: { type: 'json', value:
|
|
29
|
+
: { type: 'json', value: toJSONValue(output) };
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Normalizes an in-process tool output to a plain JSON value
|
|
34
|
+
* (applies `toJSON`, converts `Date`, drops `undefined`, etc.)
|
|
35
|
+
* by round-tripping it through `JSON.stringify`.
|
|
36
|
+
*
|
|
37
|
+
* The parsed text is produced by `JSON.stringify` from a value that is
|
|
38
|
+
* already materialized in this process, so it is not untrusted input.
|
|
39
|
+
* `JSON.parse` is used deliberately instead of the secure parser from
|
|
40
|
+
* `@ai-sdk/provider-utils`: the secure parser rejects own `__proto__` and
|
|
41
|
+
* `constructor.prototype` keys, which are valid data in tool outputs
|
|
42
|
+
* (e.g. rows from an external API). `JSON.parse` defines `__proto__` as an
|
|
43
|
+
* own data property and never modifies the prototype chain, so preserving
|
|
44
|
+
* these keys here is safe. Do not reuse this pattern for text that comes
|
|
45
|
+
* from outside the process; use `parseJSON` / `safeParseJSON` instead.
|
|
46
|
+
*/
|
|
47
|
+
function toJSONValue(value: unknown): JSONValue {
|
|
37
48
|
if (value === undefined) {
|
|
38
49
|
return null;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
const serialized = JSON.stringify(value);
|
|
42
|
-
return serialized === undefined
|
|
43
|
-
? null
|
|
44
|
-
: await parseJSON({ text: serialized });
|
|
53
|
+
return serialized === undefined ? null : JSON.parse(serialized);
|
|
45
54
|
}
|
|
@@ -65,11 +65,17 @@ export function readUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
65
65
|
}): AsyncIterableStream<UI_MESSAGE> {
|
|
66
66
|
let controller: ReadableStreamDefaultController<UI_MESSAGE> | undefined;
|
|
67
67
|
let hasErrored = false;
|
|
68
|
+
let isCancelled = false;
|
|
69
|
+
const abortController = new AbortController();
|
|
68
70
|
|
|
69
71
|
const outputStream = new ReadableStream<UI_MESSAGE>({
|
|
70
72
|
start(controllerParam) {
|
|
71
73
|
controller = controllerParam;
|
|
72
74
|
},
|
|
75
|
+
cancel() {
|
|
76
|
+
isCancelled = true;
|
|
77
|
+
abortController.abort();
|
|
78
|
+
},
|
|
73
79
|
});
|
|
74
80
|
|
|
75
81
|
const state = createStreamingUIMessageState<UI_MESSAGE>({
|
|
@@ -98,17 +104,20 @@ export function readUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
98
104
|
return job({
|
|
99
105
|
state,
|
|
100
106
|
write: () => {
|
|
101
|
-
|
|
107
|
+
if (!isCancelled) {
|
|
108
|
+
controller?.enqueue(createUIMessageSnapshot(state.message));
|
|
109
|
+
}
|
|
102
110
|
},
|
|
103
111
|
});
|
|
104
112
|
},
|
|
105
113
|
onError: handleError,
|
|
106
114
|
}),
|
|
107
115
|
onError: handleError,
|
|
116
|
+
abortSignal: abortController.signal,
|
|
108
117
|
}).finally(() => {
|
|
109
|
-
// Only close if no error occurred.
|
|
110
|
-
//
|
|
111
|
-
if (!hasErrored) {
|
|
118
|
+
// Only close if no error or cancellation occurred. Both erroring and
|
|
119
|
+
// cancelling a stream close its controller.
|
|
120
|
+
if (!hasErrored && !isCancelled) {
|
|
112
121
|
controller?.close();
|
|
113
122
|
}
|
|
114
123
|
});
|
|
@@ -34,12 +34,15 @@ export type DownloadFunction = (
|
|
|
34
34
|
* Downloads the file if it is not supported by the model.
|
|
35
35
|
*/
|
|
36
36
|
export const createDefaultDownloadFunction =
|
|
37
|
-
(
|
|
37
|
+
(
|
|
38
|
+
download: typeof originalDownload = originalDownload,
|
|
39
|
+
abortSignal?: AbortSignal,
|
|
40
|
+
): DownloadFunction =>
|
|
38
41
|
requestedDownloads =>
|
|
39
42
|
Promise.all(
|
|
40
43
|
requestedDownloads.map(async requestedDownload =>
|
|
41
44
|
requestedDownload.isUrlSupportedByModel
|
|
42
45
|
? null
|
|
43
|
-
: await download(requestedDownload),
|
|
46
|
+
: await download({ ...requestedDownload, abortSignal }),
|
|
44
47
|
),
|
|
45
48
|
);
|