ai 7.0.107 → 7.0.109
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 +23 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.js +242 -59
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +38 -3
- package/dist/internal/index.js +201 -39
- package/dist/internal/index.js.map +1 -1
- package/docs/00-introduction/index.mdx +1 -1
- package/docs/02-getting-started/03-nextjs-pages-router.mdx +1 -1
- package/docs/02-getting-started/05-nuxt.mdx +1 -1
- package/docs/03-ai-sdk-core/16-mcp-tools.mdx +31 -0
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +7 -0
- package/docs/03-ai-sdk-harnesses/03-tools.mdx +46 -0
- package/docs/04-ai-sdk-ui/02-chatbot.mdx +26 -11
- package/docs/04-ai-sdk-ui/05-completion.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +7 -0
- package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +1 -1
- package/package.json +6 -6
- package/src/generate-text/generate-text.ts +2 -1
- package/src/generate-text/parse-tool-call.ts +64 -12
- package/src/generate-text/stream-language-model-call.ts +32 -21
- package/src/generate-text/tool-call-repair-function.ts +2 -0
- package/src/generate-text/tools-context-parameter.ts +13 -7
- package/src/middleware/extract-reasoning-middleware.ts +29 -14
- package/src/model/as-language-model-v4.ts +166 -6
- package/src/ui/call-completion-api.ts +20 -7
|
@@ -26,7 +26,7 @@ For example, here’s how you can generate text with various models using the AI
|
|
|
26
26
|
The AI SDK has these primary surfaces:
|
|
27
27
|
|
|
28
28
|
- **[AI SDK Core](/docs/ai-sdk-core):** A unified API for generating text, structured objects, tool calls, and building agents with LLMs.
|
|
29
|
-
- **[AI SDK UI](/docs/ai-sdk-ui):** A set of framework-agnostic hooks for quickly building chat and generative user
|
|
29
|
+
- **[AI SDK UI](/docs/ai-sdk-ui):** A set of framework-agnostic hooks for quickly building chat and generative user interfaces.
|
|
30
30
|
- **[AI SDK Harnesses](/docs/ai-sdk-harnesses):** A uniform API for running established agent harnesses with `HarnessAgent`.
|
|
31
31
|
|
|
32
32
|
## Model Providers
|
|
@@ -153,7 +153,7 @@ Pick the approach that best matches how you want to manage providers across your
|
|
|
153
153
|
|
|
154
154
|
## Wire up the UI
|
|
155
155
|
|
|
156
|
-
Now that you have an API route that can query an LLM, it's time to
|
|
156
|
+
Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [ UI ](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).
|
|
157
157
|
|
|
158
158
|
Update your root page (`pages/index.tsx`) with the following code to show a list of chat messages and provide a user message input:
|
|
159
159
|
|
|
@@ -154,7 +154,7 @@ model: openai('gpt-5.1');
|
|
|
154
154
|
|
|
155
155
|
## Wire up the UI
|
|
156
156
|
|
|
157
|
-
Now that you have an API route that can query an LLM, it's time to
|
|
157
|
+
Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [ UI ](/docs/ai-sdk-ui/overview) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat).
|
|
158
158
|
|
|
159
159
|
Update your root page (`pages/index.vue`) with the following code to show a list of chat messages and provide a user message input:
|
|
160
160
|
|
|
@@ -236,6 +236,37 @@ client infers `native` for loopback, localhost, and custom-scheme redirects,
|
|
|
236
236
|
and `web` for remote HTTP(S) redirects. You can override this by setting
|
|
237
237
|
`application_type` in the provider's `clientMetadata`.
|
|
238
238
|
|
|
239
|
+
### Dynamic Client Credential Recovery
|
|
240
|
+
|
|
241
|
+
To let the SDK replace invalid credentials created through dynamic client
|
|
242
|
+
registration, implement `isClientInformationDynamicallyRegistered` on the
|
|
243
|
+
OAuth client provider. Return `true` only when the current client information
|
|
244
|
+
came from a dynamic registration response. Persist this provenance alongside
|
|
245
|
+
the credentials when the provider is recreated between requests.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const myOAuthClientProvider = {
|
|
249
|
+
// ...other OAuthClientProvider methods
|
|
250
|
+
|
|
251
|
+
async saveClientInformation(clientInformation) {
|
|
252
|
+
await saveClientRecord({
|
|
253
|
+
clientInformation,
|
|
254
|
+
dynamicallyRegistered: true,
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
async isClientInformationDynamicallyRegistered() {
|
|
259
|
+
return (await loadClientRecord())?.dynamicallyRegistered === true;
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
When this method is omitted or returns `false`, client information is treated
|
|
265
|
+
as pre-registered and is preserved after `invalid_client` and
|
|
266
|
+
`unauthorized_client` errors. The SDK does not infer provenance from fields such
|
|
267
|
+
as `redirect_uris`, because pre-registered clients can contain the same
|
|
268
|
+
metadata as dynamically registered clients.
|
|
269
|
+
|
|
239
270
|
### Retrying Transient Tool Failures
|
|
240
271
|
|
|
241
272
|
MCP tool calls can fail for transient transport reasons, such as rate limits,
|
|
@@ -362,6 +362,8 @@ When you only have raw continuation state from `suspendTurn()`, resume with
|
|
|
362
362
|
const session = await agent.createSession({
|
|
363
363
|
sessionId: chatId,
|
|
364
364
|
continueFrom: continuationState,
|
|
365
|
+
// Rebind this when the suspended turn used host-only tool context:
|
|
366
|
+
toolsContext,
|
|
365
367
|
});
|
|
366
368
|
|
|
367
369
|
const result = await agent.continueStream({ session });
|
|
@@ -370,6 +372,11 @@ const result = await agent.continueStream({ session });
|
|
|
370
372
|
Use `continueStream()` for incremental output, or `continueGenerate()` to drain
|
|
371
373
|
the continued turn and return a `GenerateTextResult`.
|
|
372
374
|
|
|
375
|
+
`toolsContext` is intentionally not stored in continuation state because it can
|
|
376
|
+
contain credentials or non-serializable host objects. When a suspended turn
|
|
377
|
+
used static or `prepareCall`-derived tool context, pass the same per-tool map to
|
|
378
|
+
`createSession` to rebind it in the process that continues the turn.
|
|
379
|
+
|
|
373
380
|
## Stop After a Harness Step
|
|
374
381
|
|
|
375
382
|
Use `stopWhen` to opt into semantic step boundaries. Predicates run after real
|
|
@@ -93,6 +93,52 @@ const agent = new HarnessAgent({
|
|
|
93
93
|
When the harness calls `weather`, `HarnessAgent` executes the tool in your host
|
|
94
94
|
process, then submits the result back to the harness runtime.
|
|
95
95
|
|
|
96
|
+
Host-executed tools can declare a `contextSchema` and receive turn-scoped
|
|
97
|
+
context through `toolsContext`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const lookupAccount = tool({
|
|
101
|
+
inputSchema: z.object({}),
|
|
102
|
+
contextSchema: z.object({ userId: z.string() }),
|
|
103
|
+
execute: async (_, { context }) => {
|
|
104
|
+
return loadAccount(context.userId);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const agent = new HarnessAgent({
|
|
109
|
+
harness: claudeCode,
|
|
110
|
+
tools: { lookupAccount },
|
|
111
|
+
toolsContext: {
|
|
112
|
+
lookupAccount: { userId: 'user-123' },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use `prepareCall` to replace `toolsContext` when the value depends on custom
|
|
118
|
+
call options. The type of `toolsContext` follows the tool set: it is rejected
|
|
119
|
+
when no tool declares a context schema, required when a tool requires a
|
|
120
|
+
context object, and optional when every context object is optional.
|
|
121
|
+
`HarnessAgent` validates each entry against the tool's `contextSchema` before
|
|
122
|
+
execution and exposes the configured map in step results and lifecycle
|
|
123
|
+
callbacks.
|
|
124
|
+
|
|
125
|
+
Tool context remains host-only and is not serialized into suspended-turn state.
|
|
126
|
+
When recreating a session for an unfinished turn, rebind it explicitly:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const session = await agent.createSession({
|
|
130
|
+
sessionId,
|
|
131
|
+
continueFrom,
|
|
132
|
+
toolsContext: {
|
|
133
|
+
lookupAccount: { userId: 'user-123' },
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Missing or invalid context fails validation before the host tool executes. The
|
|
139
|
+
validation error returned to the harness runtime is intentionally generic so
|
|
140
|
+
host-only context values and schema details are not disclosed to the model.
|
|
141
|
+
|
|
96
142
|
## Client-Side Tools
|
|
97
143
|
|
|
98
144
|
Omit `execute` when a browser, user interaction, or another external process
|
|
@@ -323,17 +323,32 @@ When the user clicks the "Regenerate" button, the AI provider will regenerate th
|
|
|
323
323
|
|
|
324
324
|
### Throttling UI Updates
|
|
325
325
|
|
|
326
|
-
<Note>This feature is currently only available for React.</Note>
|
|
327
|
-
|
|
328
326
|
By default, the `useChat` hook will trigger a render every time a new chunk is received.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
327
|
+
React and Vue applications can throttle reactive message updates with the `throttle` option.
|
|
328
|
+
Stream processing and event callbacks remain immediate, and the latest messages are published before the chat enters a terminal `ready` or `error` status.
|
|
329
|
+
|
|
330
|
+
<Tabs items={['React', 'Vue']}>
|
|
331
|
+
<Tab>
|
|
332
|
+
```tsx filename="page.tsx" highlight="2-3"
|
|
333
|
+
const { messages, ... } = useChat({
|
|
334
|
+
// Throttle reactive message updates to 50ms:
|
|
335
|
+
throttle: 50,
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
</Tab>
|
|
339
|
+
<Tab>
|
|
340
|
+
```vue filename="pages/index.vue" highlight="4-6"
|
|
341
|
+
<script setup lang="ts">
|
|
342
|
+
import { useChat } from '@ai-sdk/vue';
|
|
343
|
+
|
|
344
|
+
const { messages } = useChat({
|
|
345
|
+
throttle: 50,
|
|
346
|
+
});
|
|
347
|
+
</script>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
</Tab>
|
|
351
|
+
</Tabs>
|
|
337
352
|
|
|
338
353
|
## Event Callbacks
|
|
339
354
|
|
|
@@ -369,7 +384,7 @@ It's worth noting that you can abort the processing by throwing an error in the
|
|
|
369
384
|
|
|
370
385
|
### Custom headers, body, and credentials
|
|
371
386
|
|
|
372
|
-
By default, the `useChat` hook sends
|
|
387
|
+
By default, the `useChat` hook sends an HTTP POST request to the `/api/chat` endpoint with the message list as the request body. You can customize the request in two ways:
|
|
373
388
|
|
|
374
389
|
#### Hook-Level Configuration (Applied to all requests)
|
|
375
390
|
|
|
@@ -169,7 +169,7 @@ const { ... } = useCompletion({
|
|
|
169
169
|
|
|
170
170
|
## Configure Request Options
|
|
171
171
|
|
|
172
|
-
By default, the `useCompletion` hook sends
|
|
172
|
+
By default, the `useCompletion` hook sends an HTTP POST request to the `/api/completion` endpoint with the prompt as part of the request body. You can customize the request by passing additional options to the `useCompletion` hook:
|
|
173
173
|
|
|
174
174
|
```tsx
|
|
175
175
|
const { messages, input, handleInputChange, handleSubmit } = useCompletion({
|
|
@@ -929,6 +929,13 @@ To see `generateText` in action, check out [these examples](#examples).
|
|
|
929
929
|
description:
|
|
930
930
|
'The error that occurred while parsing the tool call.',
|
|
931
931
|
},
|
|
932
|
+
{
|
|
933
|
+
name: 'abortSignal',
|
|
934
|
+
type: 'AbortSignal',
|
|
935
|
+
isOptional: true,
|
|
936
|
+
description:
|
|
937
|
+
'An optional signal for cancelling the tool call repair.',
|
|
938
|
+
},
|
|
932
939
|
],
|
|
933
940
|
},
|
|
934
941
|
],
|
|
@@ -980,6 +980,13 @@ To see `streamText` in action, check out [these examples](#examples).
|
|
|
980
980
|
description:
|
|
981
981
|
'The error that occurred while parsing the tool call.',
|
|
982
982
|
},
|
|
983
|
+
{
|
|
984
|
+
name: 'abortSignal',
|
|
985
|
+
type: 'AbortSignal',
|
|
986
|
+
isOptional: true,
|
|
987
|
+
description:
|
|
988
|
+
'An optional signal for cancelling the tool call repair.',
|
|
989
|
+
},
|
|
983
990
|
],
|
|
984
991
|
},
|
|
985
992
|
],
|
|
@@ -338,7 +338,7 @@ Allows you to easily create a conversational user interface for your chatbot app
|
|
|
338
338
|
type: 'number',
|
|
339
339
|
isOptional: true,
|
|
340
340
|
description:
|
|
341
|
-
'Custom throttle wait in
|
|
341
|
+
'React and Vue only. Custom throttle wait time in milliseconds for reactive chat message updates. Positive values reduce UI update frequency without delaying stream processing or callbacks, and the latest messages are published before a ready or error status. Default is undefined, which disables throttling.',
|
|
342
342
|
},
|
|
343
343
|
{
|
|
344
344
|
name: 'resume',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.109",
|
|
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.88",
|
|
46
46
|
"@ai-sdk/provider": "4.0.17",
|
|
47
47
|
"@ai-sdk/provider-utils": "5.0.45"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.90",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.50",
|
|
52
52
|
"@ai-sdk/google": "4.0.76",
|
|
53
53
|
"@ai-sdk/groq": "4.0.46",
|
|
54
54
|
"@ai-sdk/huggingface": "2.0.53",
|
|
55
55
|
"@ai-sdk/moonshotai": "3.0.54",
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
56
|
+
"@ai-sdk/openai": "4.0.72",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.1",
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
58
|
+
"@ai-sdk/xai": "5.0.5",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -1079,11 +1079,12 @@ export async function generateText<
|
|
|
1079
1079
|
.map(toolCall =>
|
|
1080
1080
|
parseToolCall({
|
|
1081
1081
|
toolCall,
|
|
1082
|
-
tools:
|
|
1082
|
+
tools: stepModelTools as TOOLS,
|
|
1083
1083
|
repairToolCall,
|
|
1084
1084
|
refineToolInput,
|
|
1085
1085
|
instructions: stepInstructions,
|
|
1086
1086
|
messages: stepMessages,
|
|
1087
|
+
abortSignal: mergedAbortSignal,
|
|
1087
1088
|
}),
|
|
1088
1089
|
),
|
|
1089
1090
|
);
|
|
@@ -23,6 +23,7 @@ export async function parseToolCall<TOOLS extends ToolSet>({
|
|
|
23
23
|
refineToolInput,
|
|
24
24
|
messages,
|
|
25
25
|
instructions,
|
|
26
|
+
abortSignal,
|
|
26
27
|
}: {
|
|
27
28
|
toolCall: LanguageModelV4ToolCall;
|
|
28
29
|
tools: TOOLS | undefined;
|
|
@@ -30,6 +31,7 @@ export async function parseToolCall<TOOLS extends ToolSet>({
|
|
|
30
31
|
refineToolInput?: ToolInputRefinement<TOOLS> | undefined;
|
|
31
32
|
instructions: Instructions | undefined;
|
|
32
33
|
messages: ModelMessage[];
|
|
34
|
+
abortSignal?: AbortSignal;
|
|
33
35
|
}): Promise<TypedToolCall<TOOLS>> {
|
|
34
36
|
try {
|
|
35
37
|
if (tools == null) {
|
|
@@ -63,19 +65,25 @@ export async function parseToolCall<TOOLS extends ToolSet>({
|
|
|
63
65
|
let repairedToolCall: LanguageModelV4ToolCall | null = null;
|
|
64
66
|
|
|
65
67
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
abortSignal?.throwIfAborted();
|
|
69
|
+
repairedToolCall = await waitForPromiseWithAbortSignal({
|
|
70
|
+
promise: repairToolCall({
|
|
71
|
+
toolCall,
|
|
72
|
+
tools,
|
|
73
|
+
inputSchema: async ({ toolName }) => {
|
|
74
|
+
const inputSchema = getOwn(tools, toolName)?.inputSchema;
|
|
75
|
+
return await asSchema(inputSchema).jsonSchema;
|
|
76
|
+
},
|
|
77
|
+
instructions,
|
|
78
|
+
system: instructions,
|
|
79
|
+
messages,
|
|
80
|
+
error,
|
|
81
|
+
abortSignal,
|
|
82
|
+
}),
|
|
83
|
+
abortSignal,
|
|
77
84
|
});
|
|
78
85
|
} catch (repairError) {
|
|
86
|
+
abortSignal?.throwIfAborted();
|
|
79
87
|
throw new ToolCallRepairError({
|
|
80
88
|
cause: repairError,
|
|
81
89
|
originalError: error,
|
|
@@ -87,12 +95,18 @@ export async function parseToolCall<TOOLS extends ToolSet>({
|
|
|
87
95
|
throw error;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
const parsedRepairedToolCall = await refineParsedToolCallInput({
|
|
91
99
|
toolCall: await doParseToolCall({ toolCall: repairedToolCall, tools }),
|
|
92
100
|
refineToolInput,
|
|
93
101
|
});
|
|
102
|
+
|
|
103
|
+
abortSignal?.throwIfAborted();
|
|
104
|
+
|
|
105
|
+
return parsedRepairedToolCall;
|
|
94
106
|
}
|
|
95
107
|
} catch (error) {
|
|
108
|
+
abortSignal?.throwIfAborted();
|
|
109
|
+
|
|
96
110
|
// use parsed input when possible
|
|
97
111
|
const parsedInput = await safeParseJSON({ text: toolCall.input });
|
|
98
112
|
const input = parsedInput.success ? parsedInput.value : toolCall.input;
|
|
@@ -115,6 +129,44 @@ export async function parseToolCall<TOOLS extends ToolSet>({
|
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
|
|
132
|
+
async function waitForPromiseWithAbortSignal<T>({
|
|
133
|
+
promise,
|
|
134
|
+
abortSignal,
|
|
135
|
+
}: {
|
|
136
|
+
promise: PromiseLike<T>;
|
|
137
|
+
abortSignal: AbortSignal | undefined;
|
|
138
|
+
}): Promise<T> {
|
|
139
|
+
if (abortSignal == null) {
|
|
140
|
+
return await promise;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return await new Promise<T>((resolve, reject) => {
|
|
144
|
+
const cleanup = () => {
|
|
145
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
146
|
+
};
|
|
147
|
+
const onAbort = () => {
|
|
148
|
+
cleanup();
|
|
149
|
+
reject(abortSignal.reason);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
Promise.resolve(promise)
|
|
153
|
+
.then(value => {
|
|
154
|
+
cleanup();
|
|
155
|
+
resolve(value);
|
|
156
|
+
})
|
|
157
|
+
.catch(error => {
|
|
158
|
+
cleanup();
|
|
159
|
+
reject(error);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
163
|
+
|
|
164
|
+
if (abortSignal.aborted) {
|
|
165
|
+
onAbort();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
118
170
|
async function refineParsedToolCallInput<TOOLS extends ToolSet>({
|
|
119
171
|
toolCall,
|
|
120
172
|
refineToolInput,
|
|
@@ -373,6 +373,7 @@ export async function streamLanguageModelCall<
|
|
|
373
373
|
messages: standardizedPrompt.messages,
|
|
374
374
|
repairToolCall,
|
|
375
375
|
refineToolInput,
|
|
376
|
+
abortSignal,
|
|
376
377
|
callId: effectiveCallId,
|
|
377
378
|
provider: resolvedModel.provider,
|
|
378
379
|
modelId: resolvedModel.modelId,
|
|
@@ -400,6 +401,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
400
401
|
messages,
|
|
401
402
|
repairToolCall,
|
|
402
403
|
refineToolInput,
|
|
404
|
+
abortSignal,
|
|
403
405
|
callId,
|
|
404
406
|
provider,
|
|
405
407
|
modelId,
|
|
@@ -414,6 +416,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
414
416
|
messages: ModelMessage[];
|
|
415
417
|
repairToolCall: ToolCallRepairFunction<TOOLS> | undefined;
|
|
416
418
|
refineToolInput: ToolInputRefinement<TOOLS> | undefined;
|
|
419
|
+
abortSignal: AbortSignal | undefined;
|
|
417
420
|
callId: string;
|
|
418
421
|
provider: string;
|
|
419
422
|
modelId: string;
|
|
@@ -637,40 +640,47 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
637
640
|
callbacks: onLanguageModelCallEnd,
|
|
638
641
|
});
|
|
639
642
|
|
|
640
|
-
// Preserve the completed model call's usage, metadata, and
|
|
641
|
-
// performance even when response validation below surfaces a
|
|
642
|
-
// semantic error.
|
|
643
|
-
controller.enqueue({
|
|
644
|
-
type: 'model-call-end',
|
|
645
|
-
finishReason: chunk.finishReason.unified,
|
|
646
|
-
rawFinishReason: chunk.finishReason.raw,
|
|
647
|
-
usage,
|
|
648
|
-
providerMetadata: chunk.providerMetadata,
|
|
649
|
-
performance,
|
|
650
|
-
});
|
|
651
|
-
|
|
652
643
|
const enforcedToolChoice =
|
|
653
644
|
toolChoice.type === 'required' || toolChoice.type === 'tool'
|
|
654
645
|
? toolChoice
|
|
655
646
|
: undefined;
|
|
656
647
|
|
|
657
|
-
|
|
648
|
+
const toolChoiceViolationError =
|
|
658
649
|
enforcedToolChoice != null &&
|
|
659
650
|
![...toolCallsByToolCallId.values()].some(
|
|
660
651
|
toolCall =>
|
|
661
652
|
enforcedToolChoice.type === 'required' ||
|
|
662
653
|
toolCall.toolName === enforcedToolChoice.toolName,
|
|
663
654
|
)
|
|
664
|
-
|
|
655
|
+
? new ToolChoiceViolationError({
|
|
656
|
+
toolChoice: enforcedToolChoice,
|
|
657
|
+
finishReason: chunk.finishReason.unified,
|
|
658
|
+
provider,
|
|
659
|
+
modelId,
|
|
660
|
+
content: rawModelCallContent,
|
|
661
|
+
})
|
|
662
|
+
: undefined;
|
|
663
|
+
|
|
664
|
+
// Preserve the completed model call's usage, metadata, and
|
|
665
|
+
// performance even when response validation below surfaces a
|
|
666
|
+
// semantic error. Prevent invalid tool calls from being executed
|
|
667
|
+
// when the model-call-end event reaches the tool executor.
|
|
668
|
+
controller.enqueue({
|
|
669
|
+
type: 'model-call-end',
|
|
670
|
+
finishReason:
|
|
671
|
+
toolChoiceViolationError == null
|
|
672
|
+
? chunk.finishReason.unified
|
|
673
|
+
: 'error',
|
|
674
|
+
rawFinishReason: chunk.finishReason.raw,
|
|
675
|
+
usage,
|
|
676
|
+
providerMetadata: chunk.providerMetadata,
|
|
677
|
+
performance,
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
if (toolChoiceViolationError != null) {
|
|
665
681
|
controller.enqueue({
|
|
666
682
|
type: 'error',
|
|
667
|
-
error:
|
|
668
|
-
toolChoice: enforcedToolChoice,
|
|
669
|
-
finishReason: chunk.finishReason.unified,
|
|
670
|
-
provider,
|
|
671
|
-
modelId,
|
|
672
|
-
content: rawModelCallContent,
|
|
673
|
-
}),
|
|
683
|
+
error: toolChoiceViolationError,
|
|
674
684
|
});
|
|
675
685
|
break;
|
|
676
686
|
}
|
|
@@ -688,6 +698,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
688
698
|
refineToolInput,
|
|
689
699
|
instructions,
|
|
690
700
|
messages,
|
|
701
|
+
abortSignal,
|
|
691
702
|
});
|
|
692
703
|
|
|
693
704
|
toolCallsByToolCallId.set(toolCall.toolCallId, toolCall);
|
|
@@ -17,6 +17,7 @@ import type { ToolSet } from '@ai-sdk/provider-utils';
|
|
|
17
17
|
* @param options.tools - The tools that are available.
|
|
18
18
|
* @param options.inputSchema - A function that returns the JSON Schema for a tool.
|
|
19
19
|
* @param options.error - The error that occurred while parsing the tool call.
|
|
20
|
+
* @param options.abortSignal - An optional signal for cancelling the repair.
|
|
20
21
|
*/
|
|
21
22
|
export type ToolCallRepairFunction<TOOLS extends ToolSet> = (options: {
|
|
22
23
|
instructions: Instructions | undefined;
|
|
@@ -29,4 +30,5 @@ export type ToolCallRepairFunction<TOOLS extends ToolSet> = (options: {
|
|
|
29
30
|
tools: TOOLS;
|
|
30
31
|
inputSchema: (options: { toolName: string }) => PromiseLike<JSONSchema7>;
|
|
31
32
|
error: NoSuchToolError | InvalidToolInputError;
|
|
33
|
+
abortSignal?: AbortSignal;
|
|
32
34
|
}) => Promise<LanguageModelV4ToolCall | null>;
|
|
@@ -10,13 +10,19 @@ import type {
|
|
|
10
10
|
type IsEmptyObject<OBJECT> = keyof OBJECT extends never ? true : false;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Makes the toolsContext setting optional, required, or unavailable based on
|
|
14
|
+
* the tool set.
|
|
15
|
+
*/
|
|
16
|
+
export type ToolsContextSettings<TOOLS extends ToolSet> =
|
|
17
|
+
IsEmptyObject<InferToolSetContext<TOOLS>> extends true
|
|
18
|
+
? { toolsContext?: never }
|
|
19
|
+
: HasRequiredKey<InferToolSetContext<TOOLS>> extends true
|
|
20
|
+
? { toolsContext: InferToolSetContext<TOOLS> }
|
|
21
|
+
: { toolsContext?: InferToolSetContext<TOOLS> };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper type for request options that include both tools and their context.
|
|
15
25
|
*/
|
|
16
26
|
export type ToolsContextParameter<TOOLS extends ToolSet> = {
|
|
17
27
|
tools?: TOOLS;
|
|
18
|
-
} &
|
|
19
|
-
? { toolsContext?: never }
|
|
20
|
-
: HasRequiredKey<InferToolSetContext<TOOLS>> extends true
|
|
21
|
-
? { toolsContext: InferToolSetContext<TOOLS> }
|
|
22
|
-
: { toolsContext?: InferToolSetContext<TOOLS> });
|
|
28
|
+
} & ToolsContextSettings<TOOLS>;
|
|
@@ -90,12 +90,17 @@ export function extractReasoningMiddleware({
|
|
|
90
90
|
afterSwitch: boolean;
|
|
91
91
|
isReasoning: boolean;
|
|
92
92
|
buffer: string;
|
|
93
|
-
|
|
93
|
+
reasoningId: string | undefined;
|
|
94
94
|
textId: string;
|
|
95
95
|
}
|
|
96
96
|
> = createIdMap();
|
|
97
97
|
|
|
98
|
-
let
|
|
98
|
+
let reasoningIdCounter = 0;
|
|
99
|
+
|
|
100
|
+
const delayedTextStarts: Record<
|
|
101
|
+
string,
|
|
102
|
+
Extract<LanguageModelV4StreamPart, { type: 'text-start' }>
|
|
103
|
+
> = createIdMap();
|
|
99
104
|
|
|
100
105
|
return {
|
|
101
106
|
stream: stream.pipeThrough(
|
|
@@ -107,13 +112,16 @@ export function extractReasoningMiddleware({
|
|
|
107
112
|
// do not send `text-start` before `reasoning-start`
|
|
108
113
|
// https://github.com/vercel/ai/issues/7774
|
|
109
114
|
if (chunk.type === 'text-start') {
|
|
110
|
-
|
|
115
|
+
delayedTextStarts[chunk.id] = chunk;
|
|
111
116
|
return;
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
if (
|
|
120
|
+
chunk.type === 'text-end' &&
|
|
121
|
+
delayedTextStarts[chunk.id] != null
|
|
122
|
+
) {
|
|
123
|
+
controller.enqueue(delayedTextStarts[chunk.id]);
|
|
124
|
+
delete delayedTextStarts[chunk.id];
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
if (chunk.type !== 'text-delta') {
|
|
@@ -128,7 +136,7 @@ export function extractReasoningMiddleware({
|
|
|
128
136
|
afterSwitch: false,
|
|
129
137
|
isReasoning: startWithReasoning,
|
|
130
138
|
buffer: '',
|
|
131
|
-
|
|
139
|
+
reasoningId: undefined,
|
|
132
140
|
textId: chunk.id,
|
|
133
141
|
};
|
|
134
142
|
}
|
|
@@ -137,6 +145,10 @@ export function extractReasoningMiddleware({
|
|
|
137
145
|
|
|
138
146
|
activeExtraction.buffer += chunk.delta;
|
|
139
147
|
|
|
148
|
+
function getReasoningId() {
|
|
149
|
+
return (activeExtraction.reasoningId ??= `reasoning-${reasoningIdCounter++}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
140
152
|
function publish(text: string) {
|
|
141
153
|
if (text.length > 0) {
|
|
142
154
|
const prefix =
|
|
@@ -154,7 +166,7 @@ export function extractReasoningMiddleware({
|
|
|
154
166
|
) {
|
|
155
167
|
controller.enqueue({
|
|
156
168
|
type: 'reasoning-start',
|
|
157
|
-
id:
|
|
169
|
+
id: getReasoningId(),
|
|
158
170
|
});
|
|
159
171
|
}
|
|
160
172
|
|
|
@@ -162,12 +174,14 @@ export function extractReasoningMiddleware({
|
|
|
162
174
|
controller.enqueue({
|
|
163
175
|
type: 'reasoning-delta',
|
|
164
176
|
delta: prefix + text,
|
|
165
|
-
id:
|
|
177
|
+
id: getReasoningId(),
|
|
166
178
|
});
|
|
167
179
|
} else {
|
|
168
|
-
if (
|
|
169
|
-
controller.enqueue(
|
|
170
|
-
|
|
180
|
+
if (delayedTextStarts[activeExtraction.textId] != null) {
|
|
181
|
+
controller.enqueue(
|
|
182
|
+
delayedTextStarts[activeExtraction.textId],
|
|
183
|
+
);
|
|
184
|
+
delete delayedTextStarts[activeExtraction.textId];
|
|
171
185
|
}
|
|
172
186
|
controller.enqueue({
|
|
173
187
|
type: 'text-delta',
|
|
@@ -221,15 +235,16 @@ export function extractReasoningMiddleware({
|
|
|
221
235
|
if (activeExtraction.isFirstReasoning) {
|
|
222
236
|
controller.enqueue({
|
|
223
237
|
type: 'reasoning-start',
|
|
224
|
-
id:
|
|
238
|
+
id: getReasoningId(),
|
|
225
239
|
});
|
|
226
240
|
}
|
|
227
241
|
|
|
228
242
|
// reasoning part finished:
|
|
229
243
|
controller.enqueue({
|
|
230
244
|
type: 'reasoning-end',
|
|
231
|
-
id:
|
|
245
|
+
id: getReasoningId(),
|
|
232
246
|
});
|
|
247
|
+
activeExtraction.reasoningId = undefined;
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
activeExtraction.isReasoning = !activeExtraction.isReasoning;
|