ai 7.0.31 → 7.0.33
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 +3 -150
- package/dist/index.js +164 -120
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +45 -1
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +10 -2
- package/docs/03-ai-sdk-harnesses/03-tools.mdx +40 -0
- package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +1 -1
- package/docs/05-ai-sdk-rsc/10-migrating-to-ui.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/33-safe-validate-ui-messages.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/71-has-tool-call.mdx +1 -1
- package/docs/07-reference/02-ai-sdk-ui/50-direct-chat-transport.mdx +1 -1
- package/docs/09-troubleshooting/05-tool-invocation-missing-result.mdx +1 -1
- package/docs/09-troubleshooting/13-repeated-assistant-messages.mdx +2 -2
- package/package.json +3 -3
- package/src/generate-text/generate-text.ts +4 -0
- package/src/prompt/convert-to-language-model-prompt.ts +13 -0
- package/src/ui/chat.ts +2 -2
- package/src/ui/process-ui-message-stream.ts +109 -61
- package/src/ui-message-stream/ui-message-chunks.ts +29 -29
|
@@ -100,6 +100,10 @@ or a message-array `prompt`, `HarnessAgent` takes the latest user message as the
|
|
|
100
100
|
fresh input for the turn. It does not replay the full prior conversation into
|
|
101
101
|
the harness.
|
|
102
102
|
|
|
103
|
+
A trailing tool message is handled differently: tool approval responses and
|
|
104
|
+
client-provided tool results continue the unfinished harness turn that produced
|
|
105
|
+
the corresponding request or tool call.
|
|
106
|
+
|
|
103
107
|
This is different from model calls, where the application usually sends the full
|
|
104
108
|
message history. In chat routes, persist and resume the harness session instead
|
|
105
109
|
of relying on message replay.
|
|
@@ -116,6 +120,8 @@ End every session explicitly:
|
|
|
116
120
|
the turn is unfinished, the resume state includes the continuation state.
|
|
117
121
|
- `session.suspendTurn()` is for advanced active-turn continuation across a
|
|
118
122
|
process boundary.
|
|
123
|
+
- `session.hasUnfinishedTurn()` reports whether the current turn must be
|
|
124
|
+
continued or suspended before the session accepts a new prompt.
|
|
119
125
|
|
|
120
126
|
Use `destroy()` for one-off scripts and tests. Use `detach()` or `stop()` for
|
|
121
127
|
HTTP routes that need multi-turn continuity.
|
|
@@ -165,8 +171,10 @@ For advanced workflows that must hand off an active turn across a process
|
|
|
165
171
|
boundary, suspend the turn and persist the continuation state:
|
|
166
172
|
|
|
167
173
|
```ts
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
if (session.hasUnfinishedTurn()) {
|
|
175
|
+
const continuationState = await session.suspendTurn();
|
|
176
|
+
await persistContinuationState({ chatId, continuationState });
|
|
177
|
+
}
|
|
170
178
|
```
|
|
171
179
|
|
|
172
180
|
When you only have raw continuation state from `suspendTurn()`, resume with
|
|
@@ -91,6 +91,46 @@ const agent = new HarnessAgent({
|
|
|
91
91
|
When the harness calls `weather`, `HarnessAgent` executes the tool in your host
|
|
92
92
|
process, then submits the result back to the harness runtime.
|
|
93
93
|
|
|
94
|
+
## Client-Side Tools
|
|
95
|
+
|
|
96
|
+
Omit `execute` when a browser, user interaction, or another external process
|
|
97
|
+
provides the tool result:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const weather = tool({
|
|
101
|
+
description: 'Get the current temperature for a city.',
|
|
102
|
+
inputSchema: z.object({ city: z.string() }),
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
When the harness calls a tool without `execute`, the returned result slice ends
|
|
107
|
+
after the tool-call step while the underlying turn waits for a result.
|
|
108
|
+
`session.hasUnfinishedTurn()` remains `true`, and `session.suspendTurn()`
|
|
109
|
+
includes the pending tool call in its serializable continuation state.
|
|
110
|
+
|
|
111
|
+
In UI flows, pass the model messages produced after `addToolOutput` to the next
|
|
112
|
+
`stream()` or `generate()` call. `HarnessAgent` extracts the trailing tool
|
|
113
|
+
result and continues the paused turn automatically.
|
|
114
|
+
|
|
115
|
+
For direct agent calls, provide the raw result to `continueStream()` or
|
|
116
|
+
`continueGenerate()`:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const continued = await agent.continueStream({
|
|
120
|
+
session,
|
|
121
|
+
toolResultContinuations: [
|
|
122
|
+
{
|
|
123
|
+
toolCallId,
|
|
124
|
+
output: { city: 'Paris', celsius: 12 },
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Set `isError: true` when the external tool failed. To continue in another
|
|
131
|
+
process, call `suspendTurn()`, recreate the session with `continueFrom`, and
|
|
132
|
+
then pass the result to `continueStream()` or `continueGenerate()`.
|
|
133
|
+
|
|
94
134
|
## Tool Filtering
|
|
95
135
|
|
|
96
136
|
Use `activeTools` or `inactiveTools` on `HarnessAgent` to control which tools the
|
|
@@ -114,7 +114,7 @@ import { dataPartsSchema, metadataSchema } from '@util/schemas';
|
|
|
114
114
|
const tools = {
|
|
115
115
|
weather: tool({
|
|
116
116
|
description: 'Get weather information',
|
|
117
|
-
|
|
117
|
+
inputSchema: z.object({
|
|
118
118
|
location: z.string(),
|
|
119
119
|
units: z.enum(['celsius', 'fahrenheit']),
|
|
120
120
|
}),
|
|
@@ -121,7 +121,7 @@ const result = await generateText({
|
|
|
121
121
|
calculate: calculateTool,
|
|
122
122
|
finalAnswer: {
|
|
123
123
|
description: 'Provide the final answer to the user',
|
|
124
|
-
|
|
124
|
+
inputSchema: z.object({
|
|
125
125
|
answer: z.string(),
|
|
126
126
|
}),
|
|
127
127
|
execute: async ({ answer }) => answer,
|
|
@@ -237,7 +237,7 @@ import { z } from 'zod';
|
|
|
237
237
|
|
|
238
238
|
const weatherTool = tool({
|
|
239
239
|
description: 'Get the current weather',
|
|
240
|
-
|
|
240
|
+
inputSchema: z.object({
|
|
241
241
|
location: z.string().describe('The city and state'),
|
|
242
242
|
}),
|
|
243
243
|
execute: async ({ location }) => {
|
|
@@ -25,7 +25,7 @@ You have two options for handling tool results:
|
|
|
25
25
|
const tools = {
|
|
26
26
|
weather: tool({
|
|
27
27
|
description: 'Get the weather in a location',
|
|
28
|
-
|
|
28
|
+
inputSchema: z.object({
|
|
29
29
|
location: z
|
|
30
30
|
.string()
|
|
31
31
|
.describe('The city and state, e.g. "San Francisco, CA"'),
|
|
@@ -20,7 +20,7 @@ export async function POST(req: Request) {
|
|
|
20
20
|
tools: {
|
|
21
21
|
weather: {
|
|
22
22
|
description: 'Get the weather for a location',
|
|
23
|
-
|
|
23
|
+
inputSchema: z.object({
|
|
24
24
|
location: z.string(),
|
|
25
25
|
}),
|
|
26
26
|
execute: async ({ location }) => {
|
|
@@ -54,7 +54,7 @@ export async function POST(req: Request) {
|
|
|
54
54
|
tools: {
|
|
55
55
|
weather: {
|
|
56
56
|
description: 'Get the weather for a location',
|
|
57
|
-
|
|
57
|
+
inputSchema: z.object({
|
|
58
58
|
location: z.string(),
|
|
59
59
|
}),
|
|
60
60
|
execute: async ({ location }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.33",
|
|
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,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.25",
|
|
46
46
|
"@ai-sdk/provider": "4.0.3",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.12"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -795,6 +795,10 @@ export async function generateText<
|
|
|
795
795
|
const pendingDeferredToolCalls = new Map<string, { toolName: string }>();
|
|
796
796
|
|
|
797
797
|
do {
|
|
798
|
+
if (steps.length > 0) {
|
|
799
|
+
mergedAbortSignal?.throwIfAborted();
|
|
800
|
+
}
|
|
801
|
+
|
|
798
802
|
// Set up step timeout if configured
|
|
799
803
|
const stepTimeoutId = setAbortTimeout({
|
|
800
804
|
abortController: stepAbortController,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
createDefaultDownloadFunction,
|
|
26
26
|
type DownloadFunction,
|
|
27
27
|
} from '../util/download/download-function';
|
|
28
|
+
import { mergeObjects } from '../util/merge-objects';
|
|
28
29
|
import { convertToLanguageModelV4FilePart } from './file-part-data';
|
|
29
30
|
import { logWarnings } from '../logger/log-warnings';
|
|
30
31
|
import type { Warning } from '../types/warning';
|
|
@@ -108,7 +109,19 @@ export async function convertToLanguageModelPrompt({
|
|
|
108
109
|
|
|
109
110
|
const lastCombinedMessage = combinedMessages.at(-1);
|
|
110
111
|
if (lastCombinedMessage?.role === 'tool') {
|
|
112
|
+
const lastContentPart = lastCombinedMessage.content.at(-1);
|
|
113
|
+
if (
|
|
114
|
+
lastContentPart != null &&
|
|
115
|
+
lastCombinedMessage.providerOptions != null
|
|
116
|
+
) {
|
|
117
|
+
lastContentPart.providerOptions = mergeObjects(
|
|
118
|
+
lastCombinedMessage.providerOptions,
|
|
119
|
+
lastContentPart.providerOptions,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
111
123
|
lastCombinedMessage.content.push(...message.content);
|
|
124
|
+
lastCombinedMessage.providerOptions = message.providerOptions;
|
|
112
125
|
} else {
|
|
113
126
|
combinedMessages.push(message);
|
|
114
127
|
}
|
package/src/ui/chat.ts
CHANGED
|
@@ -208,8 +208,8 @@ export interface ChatInit<UI_MESSAGE extends UIMessage> {
|
|
|
208
208
|
* Optional callback function that is invoked when a tool call is received.
|
|
209
209
|
* Intended for automatic client-side tool execution.
|
|
210
210
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
211
|
+
* To add the tool output, call `addToolOutput` without awaiting it inside
|
|
212
|
+
* this callback. The callback's return value is not used.
|
|
213
213
|
*/
|
|
214
214
|
onToolCall?: ChatOnToolCallCallback<UI_MESSAGE>;
|
|
215
215
|
|
|
@@ -104,13 +104,43 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
104
104
|
new TransformStream<UIMessageChunk, InferUIMessageChunk<UI_MESSAGE>>({
|
|
105
105
|
async transform(chunk, controller) {
|
|
106
106
|
await runUpdateMessageJob(async ({ state, write }) => {
|
|
107
|
+
function getCurrentStepParts() {
|
|
108
|
+
const parts = state.message.parts;
|
|
109
|
+
let currentStepStartIndex = parts.length - 1;
|
|
110
|
+
|
|
111
|
+
while (
|
|
112
|
+
currentStepStartIndex >= 0 &&
|
|
113
|
+
parts[currentStepStartIndex].type !== 'step-start'
|
|
114
|
+
) {
|
|
115
|
+
currentStepStartIndex--;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parts.slice(currentStepStartIndex + 1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getCurrentStepToolInvocations() {
|
|
122
|
+
return getCurrentStepParts().filter(isToolUIPart);
|
|
123
|
+
}
|
|
124
|
+
|
|
107
125
|
function getToolInvocation(toolCallId: string) {
|
|
108
|
-
const toolInvocations =
|
|
126
|
+
const toolInvocations = getCurrentStepToolInvocations();
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
let toolInvocation = toolInvocations.find(
|
|
111
129
|
invocation => invocation.toolCallId === toolCallId,
|
|
112
130
|
);
|
|
113
131
|
|
|
132
|
+
if (toolInvocation == null) {
|
|
133
|
+
const parts = state.message.parts;
|
|
134
|
+
|
|
135
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
136
|
+
const part = parts[i];
|
|
137
|
+
if (isToolUIPart(part) && part.toolCallId === toolCallId) {
|
|
138
|
+
toolInvocation = part;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
114
144
|
if (toolInvocation == null) {
|
|
115
145
|
throw new UIMessageStreamError({
|
|
116
146
|
chunkType: 'tool-invocation',
|
|
@@ -177,12 +207,15 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
177
207
|
providerMetadata?: ProviderMetadata;
|
|
178
208
|
}
|
|
179
209
|
),
|
|
210
|
+
existingPart?: ToolUIPart<InferUIMessageTools<UI_MESSAGE>>,
|
|
180
211
|
) {
|
|
181
|
-
const part =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
part
|
|
185
|
-
|
|
212
|
+
const part =
|
|
213
|
+
existingPart ??
|
|
214
|
+
(getCurrentStepParts().find(
|
|
215
|
+
part =>
|
|
216
|
+
isStaticToolUIPart(part) &&
|
|
217
|
+
part.toolCallId === options.toolCallId,
|
|
218
|
+
) as ToolUIPart<InferUIMessageTools<UI_MESSAGE>> | undefined);
|
|
186
219
|
|
|
187
220
|
const anyOptions = options as any;
|
|
188
221
|
const anyPart = part as any;
|
|
@@ -284,12 +317,15 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
284
317
|
providerMetadata?: ProviderMetadata;
|
|
285
318
|
}
|
|
286
319
|
),
|
|
320
|
+
existingPart?: DynamicToolUIPart,
|
|
287
321
|
) {
|
|
288
|
-
const part =
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
part
|
|
292
|
-
|
|
322
|
+
const part =
|
|
323
|
+
existingPart ??
|
|
324
|
+
(getCurrentStepParts().find(
|
|
325
|
+
part =>
|
|
326
|
+
part.type === 'dynamic-tool' &&
|
|
327
|
+
part.toolCallId === options.toolCallId,
|
|
328
|
+
) as DynamicToolUIPart | undefined);
|
|
293
329
|
|
|
294
330
|
const anyOptions = options as any;
|
|
295
331
|
const anyPart = part as any;
|
|
@@ -540,7 +576,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
540
576
|
|
|
541
577
|
case 'tool-input-start': {
|
|
542
578
|
const toolInvocations =
|
|
543
|
-
|
|
579
|
+
getCurrentStepParts().filter(isStaticToolUIPart);
|
|
544
580
|
|
|
545
581
|
// add the partial tool call to the map
|
|
546
582
|
state.partialToolCalls[chunk.toolCallId] = {
|
|
@@ -665,7 +701,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
665
701
|
// When a part already exists for this toolCallId (e.g. from
|
|
666
702
|
// tool-input-start), honour its type so we update in place
|
|
667
703
|
// instead of creating a duplicate with a mismatched type.
|
|
668
|
-
const existingPart =
|
|
704
|
+
const existingPart = getCurrentStepParts()
|
|
669
705
|
.filter(isToolUIPart)
|
|
670
706
|
.find(p => p.toolCallId === chunk.toolCallId);
|
|
671
707
|
const isDynamic =
|
|
@@ -756,31 +792,37 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
756
792
|
const toolInvocation = getToolInvocation(chunk.toolCallId);
|
|
757
793
|
|
|
758
794
|
if (toolInvocation.type === 'dynamic-tool') {
|
|
759
|
-
updateDynamicToolPart(
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
795
|
+
updateDynamicToolPart(
|
|
796
|
+
{
|
|
797
|
+
toolCallId: chunk.toolCallId,
|
|
798
|
+
toolName: toolInvocation.toolName,
|
|
799
|
+
state: 'output-available',
|
|
800
|
+
input: (toolInvocation as any).input,
|
|
801
|
+
output: chunk.output,
|
|
802
|
+
preliminary: chunk.preliminary,
|
|
803
|
+
providerExecuted: chunk.providerExecuted,
|
|
804
|
+
providerMetadata: chunk.providerMetadata,
|
|
805
|
+
title: toolInvocation.title,
|
|
806
|
+
toolMetadata: toolInvocation.toolMetadata,
|
|
807
|
+
},
|
|
808
|
+
toolInvocation,
|
|
809
|
+
);
|
|
771
810
|
} else {
|
|
772
|
-
updateToolPart(
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
811
|
+
updateToolPart(
|
|
812
|
+
{
|
|
813
|
+
toolCallId: chunk.toolCallId,
|
|
814
|
+
toolName: getStaticToolName(toolInvocation),
|
|
815
|
+
state: 'output-available',
|
|
816
|
+
input: (toolInvocation as any).input,
|
|
817
|
+
output: chunk.output,
|
|
818
|
+
providerExecuted: chunk.providerExecuted,
|
|
819
|
+
preliminary: chunk.preliminary,
|
|
820
|
+
providerMetadata: chunk.providerMetadata,
|
|
821
|
+
title: toolInvocation.title,
|
|
822
|
+
toolMetadata: toolInvocation.toolMetadata,
|
|
823
|
+
},
|
|
824
|
+
toolInvocation as ToolUIPart<InferUIMessageTools<UI_MESSAGE>>,
|
|
825
|
+
);
|
|
784
826
|
}
|
|
785
827
|
|
|
786
828
|
write();
|
|
@@ -791,30 +833,36 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
791
833
|
const toolInvocation = getToolInvocation(chunk.toolCallId);
|
|
792
834
|
|
|
793
835
|
if (toolInvocation.type === 'dynamic-tool') {
|
|
794
|
-
updateDynamicToolPart(
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
836
|
+
updateDynamicToolPart(
|
|
837
|
+
{
|
|
838
|
+
toolCallId: chunk.toolCallId,
|
|
839
|
+
toolName: toolInvocation.toolName,
|
|
840
|
+
state: 'output-error',
|
|
841
|
+
input: (toolInvocation as any).input,
|
|
842
|
+
errorText: chunk.errorText,
|
|
843
|
+
providerExecuted: chunk.providerExecuted,
|
|
844
|
+
providerMetadata: chunk.providerMetadata,
|
|
845
|
+
title: toolInvocation.title,
|
|
846
|
+
toolMetadata: toolInvocation.toolMetadata,
|
|
847
|
+
},
|
|
848
|
+
toolInvocation,
|
|
849
|
+
);
|
|
805
850
|
} else {
|
|
806
|
-
updateToolPart(
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
851
|
+
updateToolPart(
|
|
852
|
+
{
|
|
853
|
+
toolCallId: chunk.toolCallId,
|
|
854
|
+
toolName: getStaticToolName(toolInvocation),
|
|
855
|
+
state: 'output-error',
|
|
856
|
+
input: (toolInvocation as any).input,
|
|
857
|
+
rawInput: (toolInvocation as any).rawInput,
|
|
858
|
+
errorText: chunk.errorText,
|
|
859
|
+
providerExecuted: chunk.providerExecuted,
|
|
860
|
+
providerMetadata: chunk.providerMetadata,
|
|
861
|
+
title: toolInvocation.title,
|
|
862
|
+
toolMetadata: toolInvocation.toolMetadata,
|
|
863
|
+
},
|
|
864
|
+
toolInvocation as ToolUIPart<InferUIMessageTools<UI_MESSAGE>>,
|
|
865
|
+
);
|
|
818
866
|
}
|
|
819
867
|
|
|
820
868
|
write();
|