ai 6.0.38 → 6.0.40
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 +14 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +359 -326
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +359 -326
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +20 -1
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +20 -1
- package/dist/internal/index.mjs.map +1 -1
- package/docs/03-agents/04-loop-control.mdx +44 -0
- package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +7 -0
- package/package.json +1 -1
- package/src/agent/create-agent-ui-stream-response.test.ts +1 -1
- package/src/generate-text/__snapshots__/stream-text.test.ts.snap +52 -4
- package/src/generate-text/stream-text.test.ts +472 -7
- package/src/generate-text/stream-text.ts +4 -0
- package/src/types/usage.ts +24 -0
- package/src/ui/chat.ts +6 -0
- package/src/ui/process-ui-message-stream.test.ts +79 -0
- package/src/ui/process-ui-message-stream.ts +11 -8
- package/src/ui/ui-messages.ts +2 -0
- package/src/ui/validate-ui-messages.ts +2 -0
- package/src/ui-message-stream/ui-message-chunks.ts +5 -0
|
@@ -2229,6 +2229,9 @@ However, the LLM results are expected to be small enough to not cause issues.
|
|
|
2229
2229
|
...(part.providerExecuted != null
|
|
2230
2230
|
? { providerExecuted: part.providerExecuted }
|
|
2231
2231
|
: {}),
|
|
2232
|
+
...(part.providerMetadata != null
|
|
2233
|
+
? { providerMetadata: part.providerMetadata }
|
|
2234
|
+
: {}),
|
|
2232
2235
|
...(dynamic != null ? { dynamic } : {}),
|
|
2233
2236
|
...(part.title != null ? { title: part.title } : {}),
|
|
2234
2237
|
});
|
|
@@ -2371,6 +2374,7 @@ However, the LLM results are expected to be small enough to not cause issues.
|
|
|
2371
2374
|
controller.enqueue({
|
|
2372
2375
|
type: 'finish',
|
|
2373
2376
|
finishReason: part.finishReason,
|
|
2377
|
+
usage: part.totalUsage,
|
|
2374
2378
|
...(messageMetadataValue != null
|
|
2375
2379
|
? { messageMetadata: messageMetadataValue }
|
|
2376
2380
|
: {}),
|
package/src/types/usage.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
JSONObject,
|
|
4
4
|
LanguageModelV3Usage,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
|
+
import { z } from 'zod/v4';
|
|
7
|
+
import { jsonValueSchema } from './json-value';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Represents the number of tokens used in a prompt and completion.
|
|
@@ -77,6 +79,28 @@ export type LanguageModelUsage = {
|
|
|
77
79
|
raw?: JSONObject;
|
|
78
80
|
};
|
|
79
81
|
|
|
82
|
+
// Helper for required properties that can be undefined (key: Type | undefined)
|
|
83
|
+
const numberOrUndefined = z.union([z.number(), z.undefined()]);
|
|
84
|
+
|
|
85
|
+
export const languageModelUsageSchema: z.ZodType<LanguageModelUsage> =
|
|
86
|
+
z.strictObject({
|
|
87
|
+
inputTokens: numberOrUndefined,
|
|
88
|
+
inputTokenDetails: z.strictObject({
|
|
89
|
+
noCacheTokens: numberOrUndefined,
|
|
90
|
+
cacheReadTokens: numberOrUndefined,
|
|
91
|
+
cacheWriteTokens: numberOrUndefined,
|
|
92
|
+
}),
|
|
93
|
+
outputTokens: numberOrUndefined,
|
|
94
|
+
outputTokenDetails: z.strictObject({
|
|
95
|
+
textTokens: numberOrUndefined,
|
|
96
|
+
reasoningTokens: numberOrUndefined,
|
|
97
|
+
}),
|
|
98
|
+
totalTokens: numberOrUndefined,
|
|
99
|
+
reasoningTokens: z.number().optional(),
|
|
100
|
+
cachedInputTokens: z.number().optional(),
|
|
101
|
+
raw: z.record(z.string(), jsonValueSchema.optional()).optional(),
|
|
102
|
+
});
|
|
103
|
+
|
|
80
104
|
/**
|
|
81
105
|
Represents the number of tokens used in an embedding.
|
|
82
106
|
*/
|
package/src/ui/chat.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
InferSchema,
|
|
6
6
|
} from '@ai-sdk/provider-utils';
|
|
7
7
|
import { FinishReason } from '../types/language-model';
|
|
8
|
+
import { LanguageModelUsage } from '../types/usage';
|
|
8
9
|
import { UIMessageChunk } from '../ui-message-stream/ui-message-chunks';
|
|
9
10
|
import { consumeStream } from '../util/consume-stream';
|
|
10
11
|
import { SerialJobExecutor } from '../util/serial-job-executor';
|
|
@@ -124,6 +125,7 @@ export type ChatOnDataCallback<UI_MESSAGE extends UIMessage> = (
|
|
|
124
125
|
* @param isDisconnect Indicates whether the request has been ended by a network error.
|
|
125
126
|
* @param isError Indicates whether the request has been ended by an error.
|
|
126
127
|
* @param finishReason The reason why the generation finished.
|
|
128
|
+
* @param usage Token usage information for the response.
|
|
127
129
|
*/
|
|
128
130
|
export type ChatOnFinishCallback<UI_MESSAGE extends UIMessage> = (options: {
|
|
129
131
|
message: UI_MESSAGE;
|
|
@@ -132,6 +134,7 @@ export type ChatOnFinishCallback<UI_MESSAGE extends UIMessage> = (options: {
|
|
|
132
134
|
isDisconnect: boolean;
|
|
133
135
|
isError: boolean;
|
|
134
136
|
finishReason?: FinishReason;
|
|
137
|
+
usage?: LanguageModelUsage;
|
|
135
138
|
}) => void;
|
|
136
139
|
|
|
137
140
|
export interface ChatInit<UI_MESSAGE extends UIMessage> {
|
|
@@ -691,6 +694,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
691
694
|
isDisconnect,
|
|
692
695
|
isError,
|
|
693
696
|
finishReason: this.activeResponse?.state.finishReason,
|
|
697
|
+
...(this.activeResponse?.state.usage != null && {
|
|
698
|
+
usage: this.activeResponse.state.usage,
|
|
699
|
+
}),
|
|
694
700
|
});
|
|
695
701
|
} catch (err) {
|
|
696
702
|
console.error(err);
|
|
@@ -6171,6 +6171,85 @@ describe('processUIMessageStream', () => {
|
|
|
6171
6171
|
});
|
|
6172
6172
|
});
|
|
6173
6173
|
|
|
6174
|
+
describe('provider metadata during input-streaming', () => {
|
|
6175
|
+
beforeEach(async () => {
|
|
6176
|
+
const stream = createUIMessageStream([
|
|
6177
|
+
{ type: 'start', messageId: 'msg-123' },
|
|
6178
|
+
{ type: 'start-step' },
|
|
6179
|
+
{
|
|
6180
|
+
type: 'tool-input-start',
|
|
6181
|
+
toolCallId: 'tool-call-id',
|
|
6182
|
+
toolName: 'tool-name',
|
|
6183
|
+
providerMetadata: { testProvider: { someKey: 'someValue' } },
|
|
6184
|
+
},
|
|
6185
|
+
{
|
|
6186
|
+
type: 'tool-input-delta',
|
|
6187
|
+
toolCallId: 'tool-call-id',
|
|
6188
|
+
inputTextDelta: '{"query":',
|
|
6189
|
+
},
|
|
6190
|
+
{
|
|
6191
|
+
type: 'tool-input-delta',
|
|
6192
|
+
toolCallId: 'tool-call-id',
|
|
6193
|
+
inputTextDelta: '"test"}',
|
|
6194
|
+
},
|
|
6195
|
+
{
|
|
6196
|
+
type: 'tool-input-available',
|
|
6197
|
+
toolCallId: 'tool-call-id',
|
|
6198
|
+
toolName: 'tool-name',
|
|
6199
|
+
input: { query: 'test' },
|
|
6200
|
+
},
|
|
6201
|
+
{ type: 'finish-step' },
|
|
6202
|
+
{ type: 'finish' },
|
|
6203
|
+
]);
|
|
6204
|
+
|
|
6205
|
+
state = createStreamingUIMessageState({
|
|
6206
|
+
messageId: 'msg-123',
|
|
6207
|
+
lastMessage: undefined,
|
|
6208
|
+
});
|
|
6209
|
+
|
|
6210
|
+
await consumeStream({
|
|
6211
|
+
stream: processUIMessageStream({
|
|
6212
|
+
stream,
|
|
6213
|
+
runUpdateMessageJob,
|
|
6214
|
+
onError: error => {
|
|
6215
|
+
throw error;
|
|
6216
|
+
},
|
|
6217
|
+
}),
|
|
6218
|
+
});
|
|
6219
|
+
});
|
|
6220
|
+
|
|
6221
|
+
it('should propagate providerMetadata during input-streaming state', async () => {
|
|
6222
|
+
// Find the first update after tool-input-start (when state is input-streaming)
|
|
6223
|
+
const inputStreamingUpdate = writeCalls.find(call =>
|
|
6224
|
+
call.message.parts.some(
|
|
6225
|
+
(p: any) =>
|
|
6226
|
+
p.toolCallId === 'tool-call-id' && p.state === 'input-streaming',
|
|
6227
|
+
),
|
|
6228
|
+
);
|
|
6229
|
+
|
|
6230
|
+
expect(inputStreamingUpdate).toBeDefined();
|
|
6231
|
+
const toolPart = inputStreamingUpdate!.message.parts.find(
|
|
6232
|
+
(p: any) => p.toolCallId === 'tool-call-id',
|
|
6233
|
+
) as any;
|
|
6234
|
+
|
|
6235
|
+
// Key assertion: providerMetadata should be available during input-streaming
|
|
6236
|
+
expect(toolPart.callProviderMetadata).toEqual({
|
|
6237
|
+
testProvider: { someKey: 'someValue' },
|
|
6238
|
+
});
|
|
6239
|
+
});
|
|
6240
|
+
|
|
6241
|
+
it('should retain providerMetadata in final input-available state', async () => {
|
|
6242
|
+
const toolPart = state!.message.parts.find(
|
|
6243
|
+
(p: any) => p.toolCallId === 'tool-call-id',
|
|
6244
|
+
) as any;
|
|
6245
|
+
|
|
6246
|
+
expect(toolPart.state).toBe('input-available');
|
|
6247
|
+
expect(toolPart.callProviderMetadata).toEqual({
|
|
6248
|
+
testProvider: { someKey: 'someValue' },
|
|
6249
|
+
});
|
|
6250
|
+
});
|
|
6251
|
+
});
|
|
6252
|
+
|
|
6174
6253
|
describe('tool input error', () => {
|
|
6175
6254
|
beforeEach(async () => {
|
|
6176
6255
|
const stream = createUIMessageStream([
|
|
@@ -2,6 +2,7 @@ import { FlexibleSchema, validateTypes } from '@ai-sdk/provider-utils';
|
|
|
2
2
|
import { UIMessageStreamError } from '../error/ui-message-stream-error';
|
|
3
3
|
import { ProviderMetadata } from '../types';
|
|
4
4
|
import { FinishReason } from '../types/language-model';
|
|
5
|
+
import { LanguageModelUsage } from '../types/usage';
|
|
5
6
|
import {
|
|
6
7
|
DataUIMessageChunk,
|
|
7
8
|
InferUIMessageChunk,
|
|
@@ -44,6 +45,7 @@ export type StreamingUIMessageState<UI_MESSAGE extends UIMessage> = {
|
|
|
44
45
|
}
|
|
45
46
|
>;
|
|
46
47
|
finishReason?: FinishReason;
|
|
48
|
+
usage?: LanguageModelUsage;
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
export function createStreamingUIMessageState<UI_MESSAGE extends UIMessage>({
|
|
@@ -130,6 +132,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
130
132
|
state: 'input-streaming';
|
|
131
133
|
input: unknown;
|
|
132
134
|
providerExecuted?: boolean;
|
|
135
|
+
providerMetadata?: ProviderMetadata;
|
|
133
136
|
}
|
|
134
137
|
| {
|
|
135
138
|
state: 'input-available';
|
|
@@ -177,10 +180,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
177
180
|
anyPart.providerExecuted =
|
|
178
181
|
anyOptions.providerExecuted ?? part.providerExecuted;
|
|
179
182
|
|
|
180
|
-
if (
|
|
181
|
-
anyOptions.providerMetadata != null &&
|
|
182
|
-
part.state === 'input-available'
|
|
183
|
-
) {
|
|
183
|
+
if (anyOptions.providerMetadata != null) {
|
|
184
184
|
part.callProviderMetadata = anyOptions.providerMetadata;
|
|
185
185
|
}
|
|
186
186
|
} else {
|
|
@@ -212,6 +212,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
212
212
|
| {
|
|
213
213
|
state: 'input-streaming';
|
|
214
214
|
input: unknown;
|
|
215
|
+
providerMetadata?: ProviderMetadata;
|
|
215
216
|
}
|
|
216
217
|
| {
|
|
217
218
|
state: 'input-available';
|
|
@@ -256,10 +257,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
256
257
|
anyPart.providerExecuted =
|
|
257
258
|
anyOptions.providerExecuted ?? part.providerExecuted;
|
|
258
259
|
|
|
259
|
-
if (
|
|
260
|
-
anyOptions.providerMetadata != null &&
|
|
261
|
-
part.state === 'input-available'
|
|
262
|
-
) {
|
|
260
|
+
if (anyOptions.providerMetadata != null) {
|
|
263
261
|
part.callProviderMetadata = anyOptions.providerMetadata;
|
|
264
262
|
}
|
|
265
263
|
} else {
|
|
@@ -461,6 +459,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
461
459
|
input: undefined,
|
|
462
460
|
providerExecuted: chunk.providerExecuted,
|
|
463
461
|
title: chunk.title,
|
|
462
|
+
providerMetadata: chunk.providerMetadata,
|
|
464
463
|
});
|
|
465
464
|
} else {
|
|
466
465
|
updateToolPart({
|
|
@@ -470,6 +469,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
470
469
|
input: undefined,
|
|
471
470
|
providerExecuted: chunk.providerExecuted,
|
|
472
471
|
title: chunk.title,
|
|
472
|
+
providerMetadata: chunk.providerMetadata,
|
|
473
473
|
});
|
|
474
474
|
}
|
|
475
475
|
|
|
@@ -688,6 +688,9 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
688
688
|
if (chunk.finishReason != null) {
|
|
689
689
|
state.finishReason = chunk.finishReason;
|
|
690
690
|
}
|
|
691
|
+
if (chunk.usage != null) {
|
|
692
|
+
state.usage = chunk.usage;
|
|
693
|
+
}
|
|
691
694
|
await updateMessageMetadata(chunk.messageMetadata);
|
|
692
695
|
if (chunk.messageMetadata != null) {
|
|
693
696
|
write();
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -233,6 +233,7 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
|
|
|
233
233
|
input: DeepPartial<asUITool<TOOL>['input']> | undefined;
|
|
234
234
|
output?: never;
|
|
235
235
|
errorText?: never;
|
|
236
|
+
callProviderMetadata?: ProviderMetadata;
|
|
236
237
|
approval?: never;
|
|
237
238
|
}
|
|
238
239
|
| {
|
|
@@ -337,6 +338,7 @@ export type DynamicToolUIPart = {
|
|
|
337
338
|
input: unknown | undefined;
|
|
338
339
|
output?: never;
|
|
339
340
|
errorText?: never;
|
|
341
|
+
callProviderMetadata?: ProviderMetadata;
|
|
340
342
|
approval?: never;
|
|
341
343
|
}
|
|
342
344
|
| {
|
|
@@ -78,6 +78,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
78
78
|
state: z.literal('input-streaming'),
|
|
79
79
|
input: z.unknown().optional(),
|
|
80
80
|
providerExecuted: z.boolean().optional(),
|
|
81
|
+
callProviderMetadata: providerMetadataSchema.optional(),
|
|
81
82
|
output: z.never().optional(),
|
|
82
83
|
errorText: z.never().optional(),
|
|
83
84
|
approval: z.never().optional(),
|
|
@@ -185,6 +186,7 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
185
186
|
toolCallId: z.string(),
|
|
186
187
|
state: z.literal('input-streaming'),
|
|
187
188
|
providerExecuted: z.boolean().optional(),
|
|
189
|
+
callProviderMetadata: providerMetadataSchema.optional(),
|
|
188
190
|
input: z.unknown().optional(),
|
|
189
191
|
output: z.never().optional(),
|
|
190
192
|
errorText: z.never().optional(),
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
providerMetadataSchema,
|
|
5
5
|
} from '../types/provider-metadata';
|
|
6
6
|
import { FinishReason } from '../types/language-model';
|
|
7
|
+
import { LanguageModelUsage, languageModelUsageSchema } from '../types/usage';
|
|
7
8
|
import {
|
|
8
9
|
InferUIMessageData,
|
|
9
10
|
InferUIMessageMetadata,
|
|
@@ -41,6 +42,7 @@ export const uiMessageChunkSchema = lazySchema(() =>
|
|
|
41
42
|
toolCallId: z.string(),
|
|
42
43
|
toolName: z.string(),
|
|
43
44
|
providerExecuted: z.boolean().optional(),
|
|
45
|
+
providerMetadata: providerMetadataSchema.optional(),
|
|
44
46
|
dynamic: z.boolean().optional(),
|
|
45
47
|
title: z.string().optional(),
|
|
46
48
|
}),
|
|
@@ -164,6 +166,7 @@ export const uiMessageChunkSchema = lazySchema(() =>
|
|
|
164
166
|
'other',
|
|
165
167
|
] as const satisfies readonly FinishReason[])
|
|
166
168
|
.optional(),
|
|
169
|
+
usage: languageModelUsageSchema.optional(),
|
|
167
170
|
messageMetadata: z.unknown().optional(),
|
|
168
171
|
}),
|
|
169
172
|
z.strictObject({
|
|
@@ -277,6 +280,7 @@ export type UIMessageChunk<
|
|
|
277
280
|
toolCallId: string;
|
|
278
281
|
toolName: string;
|
|
279
282
|
providerExecuted?: boolean;
|
|
283
|
+
providerMetadata?: ProviderMetadata;
|
|
280
284
|
dynamic?: boolean;
|
|
281
285
|
title?: string;
|
|
282
286
|
}
|
|
@@ -321,6 +325,7 @@ export type UIMessageChunk<
|
|
|
321
325
|
| {
|
|
322
326
|
type: 'finish';
|
|
323
327
|
finishReason?: FinishReason;
|
|
328
|
+
usage?: LanguageModelUsage;
|
|
324
329
|
messageMetadata?: METADATA;
|
|
325
330
|
}
|
|
326
331
|
| {
|