@ai-sdk/gateway 4.0.0-beta.22 → 4.0.0-beta.24

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.
@@ -533,12 +533,14 @@ for await (const part of result.fullStream) {
533
533
  }
534
534
  ```
535
535
 
536
- ### Usage Tracking with User and Tags
536
+ ### Custom Reporting
537
537
 
538
- Track usage per end-user and categorize requests with tags:
538
+ Track usage per end-user and categorize requests with tags, then query the data through the reporting API.
539
+
540
+ #### Usage Tracking with User and Tags
539
541
 
540
542
  ```ts
541
- import type { GatewayLanguageModelOptions } from '@ai-sdk/gateway';
543
+ import type { GatewayProviderOptions } from '@ai-sdk/gateway';
542
544
  import { generateText } from 'ai';
543
545
 
544
546
  const { text } = await generateText({
@@ -548,7 +550,7 @@ const { text } = await generateText({
548
550
  gateway: {
549
551
  user: 'user-abc-123', // Track usage for this specific end-user
550
552
  tags: ['document-summary', 'premium-feature'], // Categorize for reporting
551
- } satisfies GatewayLanguageModelOptions,
553
+ } satisfies GatewayProviderOptions,
552
554
  },
553
555
  });
554
556
  ```
@@ -559,6 +561,77 @@ This allows you to:
559
561
  - Filter and analyze spending by feature or use case using tags
560
562
  - Track which users or features are driving the most AI usage
561
563
 
564
+ #### Querying Spend Reports
565
+
566
+ Use the `getSpendReport()` method to query usage data programmatically. The reporting API is only available for Vercel Pro and Enterprise plans. For pricing, see the [Custom Reporting docs](https://vercel.com/docs/ai-gateway/capabilities/custom-reporting).
567
+
568
+ ```ts
569
+ import { gateway } from 'ai';
570
+
571
+ const report = await gateway.getSpendReport({
572
+ startDate: '2026-03-01',
573
+ endDate: '2026-03-25',
574
+ groupBy: 'model',
575
+ });
576
+
577
+ for (const row of report.results) {
578
+ console.log(`${row.model}: $${row.totalCost.toFixed(4)}`);
579
+ }
580
+ ```
581
+
582
+ The `getSpendReport()` method accepts the following parameters:
583
+
584
+ - **startDate** _string_ - Start date in `YYYY-MM-DD` format (inclusive, required)
585
+ - **endDate** _string_ - End date in `YYYY-MM-DD` format (inclusive, required)
586
+ - **groupBy** _string_ - Aggregation dimension: `'day'` (default), `'user'`, `'model'`, `'tag'`, `'provider'`, or `'credential_type'`
587
+ - **datePart** _string_ - Time granularity when `groupBy` is `'day'`: `'day'` or `'hour'`
588
+ - **userId** _string_ - Filter to a specific user
589
+ - **model** _string_ - Filter to a specific model (e.g. `'anthropic/claude-sonnet-4.5'`)
590
+ - **provider** _string_ - Filter to a specific provider (e.g. `'anthropic'`)
591
+ - **credentialType** _string_ - Filter by `'byok'` or `'system'` credentials
592
+ - **tags** _string[]_ - Filter to requests matching these tags
593
+
594
+ Each row in `results` contains a grouping field (matching your `groupBy` choice) and metrics:
595
+
596
+ - **totalCost** _number_ - Total cost in USD
597
+ - **marketCost** _number_ - Market cost in USD
598
+ - **inputTokens** _number_ - Number of input tokens
599
+ - **outputTokens** _number_ - Number of output tokens
600
+ - **cachedInputTokens** _number_ - Number of cached input tokens
601
+ - **cacheCreationInputTokens** _number_ - Number of cache creation input tokens
602
+ - **reasoningTokens** _number_ - Number of reasoning tokens
603
+ - **requestCount** _number_ - Number of requests
604
+
605
+ You can combine tracking and querying to analyze spend by tags you defined:
606
+
607
+ ```ts
608
+ import type { GatewayProviderOptions } from '@ai-sdk/gateway';
609
+ import { gateway, streamText } from 'ai';
610
+
611
+ // 1. Make requests with tags
612
+ const result = streamText({
613
+ model: gateway('anthropic/claude-haiku-4.5'),
614
+ prompt: 'Summarize this quarter's results',
615
+ providerOptions: {
616
+ gateway: {
617
+ tags: ['team:finance', 'feature:summaries'],
618
+ } satisfies GatewayProviderOptions,
619
+ },
620
+ });
621
+
622
+ // 2. Later, query spend filtered by those tags
623
+ const report = await gateway.getSpendReport({
624
+ startDate: '2026-03-01',
625
+ endDate: '2026-03-31',
626
+ groupBy: 'tag',
627
+ tags: ['team:finance'],
628
+ });
629
+
630
+ for (const row of report.results) {
631
+ console.log(`${row.tag}: $${row.totalCost.toFixed(4)} (${row.requestCount} requests)`);
632
+ }
633
+ ```
634
+
562
635
  ## Provider Options
563
636
 
564
637
  The AI Gateway provider accepts provider options that control routing behavior and provider-specific configurations.
@@ -568,7 +641,7 @@ The AI Gateway provider accepts provider options that control routing behavior a
568
641
  You can use the `gateway` key in `providerOptions` to control how AI Gateway routes requests:
569
642
 
570
643
  ```ts
571
- import type { GatewayLanguageModelOptions } from '@ai-sdk/gateway';
644
+ import type { GatewayProviderOptions } from '@ai-sdk/gateway';
572
645
  import { generateText } from 'ai';
573
646
 
574
647
  const { text } = await generateText({
@@ -578,7 +651,7 @@ const { text } = await generateText({
578
651
  gateway: {
579
652
  order: ['vertex', 'anthropic'], // Try Vertex AI first, then Anthropic
580
653
  only: ['vertex', 'anthropic'], // Only use these providers
581
- } satisfies GatewayLanguageModelOptions,
654
+ } satisfies GatewayProviderOptions,
582
655
  },
583
656
  });
584
657
  ```
@@ -642,7 +715,7 @@ The following gateway provider options are available:
642
715
  You can combine these options to have fine-grained control over routing and tracking:
643
716
 
644
717
  ```ts
645
- import type { GatewayLanguageModelOptions } from '@ai-sdk/gateway';
718
+ import type { GatewayProviderOptions } from '@ai-sdk/gateway';
646
719
  import { generateText } from 'ai';
647
720
 
648
721
  const { text } = await generateText({
@@ -652,7 +725,7 @@ const { text } = await generateText({
652
725
  gateway: {
653
726
  order: ['vertex'], // Prefer Vertex AI
654
727
  only: ['anthropic', 'vertex'], // Only allow these providers
655
- } satisfies GatewayLanguageModelOptions,
728
+ } satisfies GatewayProviderOptions,
656
729
  },
657
730
  });
658
731
  ```
@@ -662,7 +735,7 @@ const { text } = await generateText({
662
735
  The `models` option enables automatic fallback to alternative models when the primary model fails:
663
736
 
664
737
  ```ts
665
- import type { GatewayLanguageModelOptions } from '@ai-sdk/gateway';
738
+ import type { GatewayProviderOptions } from '@ai-sdk/gateway';
666
739
  import { generateText } from 'ai';
667
740
 
668
741
  const { text } = await generateText({
@@ -671,7 +744,7 @@ const { text } = await generateText({
671
744
  providerOptions: {
672
745
  gateway: {
673
746
  models: ['openai/gpt-5-nano', 'gemini-2.0-flash'], // Fallback models
674
- } satisfies GatewayLanguageModelOptions,
747
+ } satisfies GatewayProviderOptions,
675
748
  },
676
749
  });
677
750
 
@@ -689,7 +762,7 @@ that have zero data retention policies. When `zeroDataRetention` is `false` or n
689
762
  specified, there is no enforcement of restricting routing.
690
763
 
691
764
  ```ts
692
- import type { GatewayLanguageModelOptions } from '@ai-sdk/gateway';
765
+ import type { GatewayProviderOptions } from '@ai-sdk/gateway';
693
766
  import { generateText } from 'ai';
694
767
 
695
768
  const { text } = await generateText({
@@ -698,7 +771,7 @@ const { text } = await generateText({
698
771
  providerOptions: {
699
772
  gateway: {
700
773
  zeroDataRetention: true,
701
- } satisfies GatewayLanguageModelOptions,
774
+ } satisfies GatewayProviderOptions,
702
775
  },
703
776
  });
704
777
  ```
@@ -709,7 +782,7 @@ When using provider-specific options through AI Gateway, use the actual provider
709
782
 
710
783
  ```ts
711
784
  import type { AnthropicLanguageModelOptions } from '@ai-sdk/anthropic';
712
- import type { GatewayLanguageModelOptions } from '@ai-sdk/gateway';
785
+ import type { GatewayProviderOptions } from '@ai-sdk/gateway';
713
786
  import { generateText } from 'ai';
714
787
 
715
788
  const { text } = await generateText({
@@ -718,7 +791,7 @@ const { text } = await generateText({
718
791
  providerOptions: {
719
792
  gateway: {
720
793
  order: ['vertex', 'anthropic'],
721
- } satisfies GatewayLanguageModelOptions,
794
+ } satisfies GatewayProviderOptions,
722
795
  anthropic: {
723
796
  thinking: { type: 'enabled', budgetTokens: 12000 },
724
797
  } satisfies AnthropicLanguageModelOptions,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ai-sdk/gateway",
3
3
  "private": false,
4
- "version": "4.0.0-beta.22",
4
+ "version": "4.0.0-beta.24",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -2,7 +2,7 @@ import { InferSchema, lazySchema, zodSchema } from '@ai-sdk/provider-utils';
2
2
  import { z } from 'zod/v4';
3
3
 
4
4
  // https://vercel.com/docs/ai-gateway/provider-options
5
- const gatewayLanguageModelOptions = lazySchema(() =>
5
+ const gatewayProviderOptions = lazySchema(() =>
6
6
  zodSchema(
7
7
  z.object({
8
8
  /**
@@ -75,6 +75,4 @@ const gatewayLanguageModelOptions = lazySchema(() =>
75
75
  ),
76
76
  );
77
77
 
78
- export type GatewayLanguageModelOptions = InferSchema<
79
- typeof gatewayLanguageModelOptions
80
- >;
78
+ export type GatewayProviderOptions = InferSchema<typeof gatewayProviderOptions>;
@@ -13,6 +13,11 @@ import {
13
13
  type GatewayFetchMetadataResponse,
14
14
  type GatewayCreditsResponse,
15
15
  } from './gateway-fetch-metadata';
16
+ import {
17
+ GatewaySpendReport,
18
+ type GatewaySpendReportParams,
19
+ type GatewaySpendReportResponse,
20
+ } from './gateway-spend-report';
16
21
  import { GatewayLanguageModel } from './gateway-language-model';
17
22
  import { GatewayEmbeddingModel } from './gateway-embedding-model';
18
23
  import { GatewayImageModel } from './gateway-image-model';
@@ -56,6 +61,14 @@ export interface GatewayProvider extends ProviderV4 {
56
61
  */
57
62
  getCredits(): Promise<GatewayCreditsResponse>;
58
63
 
64
+ /**
65
+ * Returns a spend report with cost, token, and request count data,
66
+ * aggregated by the specified dimension.
67
+ */
68
+ getSpendReport(
69
+ params: GatewaySpendReportParams,
70
+ ): Promise<GatewaySpendReportResponse>;
71
+
59
72
  /**
60
73
  * Creates a model for generating text embeddings.
61
74
  */
@@ -253,6 +266,21 @@ export function createGatewayProvider(
253
266
  });
254
267
  };
255
268
 
269
+ const getSpendReport = async (params: GatewaySpendReportParams) => {
270
+ return new GatewaySpendReport({
271
+ baseURL,
272
+ headers: getHeaders,
273
+ fetch: options.fetch,
274
+ })
275
+ .getSpendReport(params)
276
+ .catch(async (error: unknown) => {
277
+ throw await asGatewayError(
278
+ error,
279
+ await parseAuthMethod(await getHeaders()),
280
+ );
281
+ });
282
+ };
283
+
256
284
  const provider = function (modelId: GatewayModelId) {
257
285
  if (new.target) {
258
286
  throw new Error(
@@ -266,6 +294,7 @@ export function createGatewayProvider(
266
294
  provider.specificationVersion = 'v4' as const;
267
295
  provider.getAvailableModels = getAvailableModels;
268
296
  provider.getCredits = getCredits;
297
+ provider.getSpendReport = getSpendReport;
269
298
  provider.imageModel = (modelId: GatewayImageModelId) => {
270
299
  return new GatewayImageModel(modelId, {
271
300
  provider: 'gateway',
@@ -0,0 +1,191 @@
1
+ import {
2
+ createJsonErrorResponseHandler,
3
+ createJsonResponseHandler,
4
+ getFromApi,
5
+ lazySchema,
6
+ resolve,
7
+ zodSchema,
8
+ } from '@ai-sdk/provider-utils';
9
+ import { z } from 'zod/v4';
10
+ import { asGatewayError } from './errors';
11
+ import type { GatewayConfig } from './gateway-config';
12
+
13
+ export interface GatewaySpendReportParams {
14
+ /** Start date in YYYY-MM-DD format (inclusive) */
15
+ startDate: string;
16
+ /** End date in YYYY-MM-DD format (inclusive) */
17
+ endDate: string;
18
+ /** Primary aggregation dimension. Defaults to 'day'. */
19
+ groupBy?: 'day' | 'user' | 'model' | 'tag' | 'provider' | 'credential_type';
20
+ /** Time granularity when groupBy is 'day'. */
21
+ datePart?: 'day' | 'hour';
22
+ /** Filter to a specific user's spend. */
23
+ userId?: string;
24
+ /** Filter to a specific model (e.g. 'anthropic/claude-sonnet-4.5'). */
25
+ model?: string;
26
+ /** Filter to a specific provider (e.g. 'anthropic'). */
27
+ provider?: string;
28
+ /** Filter to BYOK or system credentials. */
29
+ credentialType?: 'byok' | 'system';
30
+ /** Filter to requests with these tags. */
31
+ tags?: string[];
32
+ }
33
+
34
+ export interface GatewaySpendReportRow {
35
+ /** Date string (present when groupBy is 'day') */
36
+ day?: string;
37
+ /** Hour timestamp (present when groupBy is 'day' and datePart is 'hour') */
38
+ hour?: string;
39
+ /** User identifier (present when groupBy is 'user') */
40
+ user?: string;
41
+ /** Model identifier (present when groupBy is 'model') */
42
+ model?: string;
43
+ /** Tag value (present when groupBy is 'tag') */
44
+ tag?: string;
45
+ /** Provider name (present when groupBy is 'provider') */
46
+ provider?: string;
47
+ /** Credential type (present when groupBy is 'credential_type') */
48
+ credentialType?: 'byok' | 'system';
49
+
50
+ /** Total cost in USD */
51
+ totalCost: number;
52
+ /** Market cost in USD */
53
+ marketCost?: number;
54
+ /** Number of input tokens */
55
+ inputTokens?: number;
56
+ /** Number of output tokens */
57
+ outputTokens?: number;
58
+ /** Number of cached input tokens */
59
+ cachedInputTokens?: number;
60
+ /** Number of cache creation input tokens */
61
+ cacheCreationInputTokens?: number;
62
+ /** Number of reasoning tokens */
63
+ reasoningTokens?: number;
64
+ /** Number of requests */
65
+ requestCount?: number;
66
+ }
67
+
68
+ export interface GatewaySpendReportResponse {
69
+ results: GatewaySpendReportRow[];
70
+ }
71
+
72
+ export class GatewaySpendReport {
73
+ constructor(private readonly config: GatewayConfig) {}
74
+
75
+ async getSpendReport(
76
+ params: GatewaySpendReportParams,
77
+ ): Promise<GatewaySpendReportResponse> {
78
+ try {
79
+ const baseUrl = new URL(this.config.baseURL);
80
+
81
+ const searchParams = new URLSearchParams();
82
+ searchParams.set('start_date', params.startDate);
83
+ searchParams.set('end_date', params.endDate);
84
+
85
+ if (params.groupBy) {
86
+ searchParams.set('group_by', params.groupBy);
87
+ }
88
+ if (params.datePart) {
89
+ searchParams.set('date_part', params.datePart);
90
+ }
91
+ if (params.userId) {
92
+ searchParams.set('user_id', params.userId);
93
+ }
94
+ if (params.model) {
95
+ searchParams.set('model', params.model);
96
+ }
97
+ if (params.provider) {
98
+ searchParams.set('provider', params.provider);
99
+ }
100
+ if (params.credentialType) {
101
+ searchParams.set('credential_type', params.credentialType);
102
+ }
103
+ if (params.tags && params.tags.length > 0) {
104
+ searchParams.set('tags', params.tags.join(','));
105
+ }
106
+
107
+ const { value } = await getFromApi({
108
+ url: `${baseUrl.origin}/v1/report?${searchParams.toString()}`,
109
+ headers: await resolve(this.config.headers()),
110
+ successfulResponseHandler: createJsonResponseHandler(
111
+ gatewaySpendReportResponseSchema,
112
+ ),
113
+ failedResponseHandler: createJsonErrorResponseHandler({
114
+ errorSchema: z.any(),
115
+ errorToMessage: data => data,
116
+ }),
117
+ fetch: this.config.fetch,
118
+ });
119
+
120
+ return value;
121
+ } catch (error) {
122
+ throw await asGatewayError(error);
123
+ }
124
+ }
125
+ }
126
+
127
+ const gatewaySpendReportResponseSchema = lazySchema(() =>
128
+ zodSchema(
129
+ z.object({
130
+ results: z.array(
131
+ z
132
+ .object({
133
+ day: z.string().optional(),
134
+ hour: z.string().optional(),
135
+ user: z.string().optional(),
136
+ model: z.string().optional(),
137
+ tag: z.string().optional(),
138
+ provider: z.string().optional(),
139
+ credential_type: z.enum(['byok', 'system']).optional(),
140
+ total_cost: z.number(),
141
+ market_cost: z.number().optional(),
142
+ input_tokens: z.number().optional(),
143
+ output_tokens: z.number().optional(),
144
+ cached_input_tokens: z.number().optional(),
145
+ cache_creation_input_tokens: z.number().optional(),
146
+ reasoning_tokens: z.number().optional(),
147
+ request_count: z.number().optional(),
148
+ })
149
+ .transform(
150
+ ({
151
+ credential_type,
152
+ total_cost,
153
+ market_cost,
154
+ input_tokens,
155
+ output_tokens,
156
+ cached_input_tokens,
157
+ cache_creation_input_tokens,
158
+ reasoning_tokens,
159
+ request_count,
160
+ ...rest
161
+ }) => ({
162
+ ...rest,
163
+ ...(credential_type !== undefined
164
+ ? { credentialType: credential_type }
165
+ : {}),
166
+ totalCost: total_cost,
167
+ ...(market_cost !== undefined ? { marketCost: market_cost } : {}),
168
+ ...(input_tokens !== undefined
169
+ ? { inputTokens: input_tokens }
170
+ : {}),
171
+ ...(output_tokens !== undefined
172
+ ? { outputTokens: output_tokens }
173
+ : {}),
174
+ ...(cached_input_tokens !== undefined
175
+ ? { cachedInputTokens: cached_input_tokens }
176
+ : {}),
177
+ ...(cache_creation_input_tokens !== undefined
178
+ ? { cacheCreationInputTokens: cache_creation_input_tokens }
179
+ : {}),
180
+ ...(reasoning_tokens !== undefined
181
+ ? { reasoningTokens: reasoning_tokens }
182
+ : {}),
183
+ ...(request_count !== undefined
184
+ ? { requestCount: request_count }
185
+ : {}),
186
+ }),
187
+ ),
188
+ ),
189
+ }),
190
+ ),
191
+ );
package/src/index.ts CHANGED
@@ -5,6 +5,11 @@ export type {
5
5
  GatewayLanguageModelSpecification,
6
6
  } from './gateway-model-entry';
7
7
  export type { GatewayCreditsResponse } from './gateway-fetch-metadata';
8
+ export type {
9
+ GatewaySpendReportParams,
10
+ GatewaySpendReportRow,
11
+ GatewaySpendReportResponse,
12
+ } from './gateway-spend-report';
8
13
  export type { GatewayLanguageModelEntry as GatewayModelEntry } from './gateway-model-entry';
9
14
  export {
10
15
  createGatewayProvider,
@@ -16,9 +21,9 @@ export type {
16
21
  GatewayProviderSettings,
17
22
  } from './gateway-provider';
18
23
  export type {
19
- GatewayLanguageModelOptions,
20
- /** @deprecated Use `GatewayLanguageModelOptions` instead. */
21
- GatewayLanguageModelOptions as GatewayProviderOptions,
24
+ GatewayProviderOptions,
25
+ /** @deprecated Use `GatewayProviderOptions` instead. */
26
+ GatewayProviderOptions as GatewayLanguageModelOptions,
22
27
  } from './gateway-provider-options';
23
28
  export {
24
29
  GatewayError,