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.
@@ -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
  : {}),
@@ -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();
@@ -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
  | {