@ai-sdk/gateway 4.0.0-beta.23 → 4.0.0-beta.25
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 +12 -0
- package/dist/index.d.mts +110 -1
- package/dist/index.d.ts +110 -1
- package/dist/index.js +346 -131
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +405 -176
- package/dist/index.mjs.map +1 -1
- package/docs/00-ai-gateway.mdx +148 -3
- package/package.json +1 -1
- package/src/gateway-generation-info.ts +147 -0
- package/src/gateway-provider.ts +58 -0
- package/src/gateway-spend-report.ts +191 -0
- package/src/index.ts +9 -0
package/docs/00-ai-gateway.mdx
CHANGED
|
@@ -238,6 +238,86 @@ The `getCredits()` method returns your team's credit information based on the au
|
|
|
238
238
|
- **balance** _number_ - Your team's current available credit balance
|
|
239
239
|
- **total_used** _number_ - Total credits consumed by your team
|
|
240
240
|
|
|
241
|
+
## Generation Lookup
|
|
242
|
+
|
|
243
|
+
Look up detailed information about a specific generation by its ID, including cost, token usage, latency, and provider details. Generation IDs are available in `providerMetadata.gateway.generationId` on both `generateText` and `streamText` responses.
|
|
244
|
+
|
|
245
|
+
When streaming, the generation ID is injected on the first content chunk, so you can capture it early in the stream without waiting for completion. This is especially useful in cases where a network interruption or mid-stream error could prevent you from receiving the final response — since the gateway records the final status server-side, you can use the generation ID to look up the results (including cost, token usage, and finish reason) later via `getGenerationInfo()`.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { gateway, generateText } from 'ai';
|
|
249
|
+
|
|
250
|
+
// Make a request
|
|
251
|
+
const result = await generateText({
|
|
252
|
+
model: gateway('anthropic/claude-sonnet-4'),
|
|
253
|
+
prompt: 'Explain quantum entanglement briefly',
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Get the generation ID from provider metadata
|
|
257
|
+
const generationId = result.providerMetadata?.gateway?.generationId;
|
|
258
|
+
|
|
259
|
+
// Look up detailed generation info
|
|
260
|
+
const generation = await gateway.getGenerationInfo({ id: generationId });
|
|
261
|
+
|
|
262
|
+
console.log(`Model: ${generation.model}`);
|
|
263
|
+
console.log(`Cost: $${generation.totalCost.toFixed(6)}`);
|
|
264
|
+
console.log(`Latency: ${generation.latency}ms`);
|
|
265
|
+
console.log(`Prompt tokens: ${generation.promptTokens}`);
|
|
266
|
+
console.log(`Completion tokens: ${generation.completionTokens}`);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
With `streamText`, you can capture the generation ID from the first chunk via `fullStream`:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { gateway, streamText } from 'ai';
|
|
273
|
+
|
|
274
|
+
const result = streamText({
|
|
275
|
+
model: gateway('anthropic/claude-sonnet-4'),
|
|
276
|
+
prompt: 'Explain quantum entanglement briefly',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
let generationId: string | undefined;
|
|
280
|
+
|
|
281
|
+
for await (const part of result.fullStream) {
|
|
282
|
+
if (!generationId && part.providerMetadata?.gateway?.generationId) {
|
|
283
|
+
generationId = part.providerMetadata.gateway.generationId as string;
|
|
284
|
+
console.log(`Generation ID (early): ${generationId}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Look up cost and usage after the stream completes
|
|
289
|
+
if (generationId) {
|
|
290
|
+
const generation = await gateway.getGenerationInfo({ id: generationId });
|
|
291
|
+
console.log(`Cost: $${generation.totalCost.toFixed(6)}`);
|
|
292
|
+
console.log(`Finish reason: ${generation.finishReason}`);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The `getGenerationInfo()` method accepts:
|
|
297
|
+
|
|
298
|
+
- **id** _string_ - The generation ID to look up (format: `gen_<ulid>`, required)
|
|
299
|
+
|
|
300
|
+
It returns a `GatewayGenerationInfo` object with the following fields:
|
|
301
|
+
|
|
302
|
+
- **id** _string_ - The generation ID
|
|
303
|
+
- **totalCost** _number_ - Total cost in USD
|
|
304
|
+
- **upstreamInferenceCost** _number_ - Upstream inference cost in USD (relevant for BYOK)
|
|
305
|
+
- **usage** _number_ - Usage cost in USD (same as totalCost)
|
|
306
|
+
- **createdAt** _string_ - ISO 8601 timestamp when the generation was created
|
|
307
|
+
- **model** _string_ - Model identifier used
|
|
308
|
+
- **isByok** _boolean_ - Whether Bring Your Own Key credentials were used
|
|
309
|
+
- **providerName** _string_ - The provider that served this generation
|
|
310
|
+
- **streamed** _boolean_ - Whether streaming was used
|
|
311
|
+
- **finishReason** _string_ - Finish reason (e.g. `'stop'`)
|
|
312
|
+
- **latency** _number_ - Time to first token in milliseconds
|
|
313
|
+
- **generationTime** _number_ - Total generation time in milliseconds
|
|
314
|
+
- **promptTokens** _number_ - Number of prompt tokens
|
|
315
|
+
- **completionTokens** _number_ - Number of completion tokens
|
|
316
|
+
- **reasoningTokens** _number_ - Reasoning tokens used (if applicable)
|
|
317
|
+
- **cachedTokens** _number_ - Cached tokens used (if applicable)
|
|
318
|
+
- **cacheCreationTokens** _number_ - Cache creation input tokens
|
|
319
|
+
- **billableWebSearchCalls** _number_ - Number of billable web search calls
|
|
320
|
+
|
|
241
321
|
## Examples
|
|
242
322
|
|
|
243
323
|
### Basic Text Generation
|
|
@@ -561,11 +641,76 @@ This allows you to:
|
|
|
561
641
|
- Filter and analyze spending by feature or use case using tags
|
|
562
642
|
- Track which users or features are driving the most AI usage
|
|
563
643
|
|
|
564
|
-
####
|
|
644
|
+
#### Querying Spend Reports
|
|
565
645
|
|
|
566
|
-
|
|
646
|
+
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
647
|
|
|
568
|
-
|
|
648
|
+
```ts
|
|
649
|
+
import { gateway } from 'ai';
|
|
650
|
+
|
|
651
|
+
const report = await gateway.getSpendReport({
|
|
652
|
+
startDate: '2026-03-01',
|
|
653
|
+
endDate: '2026-03-25',
|
|
654
|
+
groupBy: 'model',
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
for (const row of report.results) {
|
|
658
|
+
console.log(`${row.model}: $${row.totalCost.toFixed(4)}`);
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
The `getSpendReport()` method accepts the following parameters:
|
|
663
|
+
|
|
664
|
+
- **startDate** _string_ - Start date in `YYYY-MM-DD` format (inclusive, required)
|
|
665
|
+
- **endDate** _string_ - End date in `YYYY-MM-DD` format (inclusive, required)
|
|
666
|
+
- **groupBy** _string_ - Aggregation dimension: `'day'` (default), `'user'`, `'model'`, `'tag'`, `'provider'`, or `'credential_type'`
|
|
667
|
+
- **datePart** _string_ - Time granularity when `groupBy` is `'day'`: `'day'` or `'hour'`
|
|
668
|
+
- **userId** _string_ - Filter to a specific user
|
|
669
|
+
- **model** _string_ - Filter to a specific model (e.g. `'anthropic/claude-sonnet-4.5'`)
|
|
670
|
+
- **provider** _string_ - Filter to a specific provider (e.g. `'anthropic'`)
|
|
671
|
+
- **credentialType** _string_ - Filter by `'byok'` or `'system'` credentials
|
|
672
|
+
- **tags** _string[]_ - Filter to requests matching these tags
|
|
673
|
+
|
|
674
|
+
Each row in `results` contains a grouping field (matching your `groupBy` choice) and metrics:
|
|
675
|
+
|
|
676
|
+
- **totalCost** _number_ - Total cost in USD
|
|
677
|
+
- **marketCost** _number_ - Market cost in USD
|
|
678
|
+
- **inputTokens** _number_ - Number of input tokens
|
|
679
|
+
- **outputTokens** _number_ - Number of output tokens
|
|
680
|
+
- **cachedInputTokens** _number_ - Number of cached input tokens
|
|
681
|
+
- **cacheCreationInputTokens** _number_ - Number of cache creation input tokens
|
|
682
|
+
- **reasoningTokens** _number_ - Number of reasoning tokens
|
|
683
|
+
- **requestCount** _number_ - Number of requests
|
|
684
|
+
|
|
685
|
+
You can combine tracking and querying to analyze spend by tags you defined:
|
|
686
|
+
|
|
687
|
+
```ts
|
|
688
|
+
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
|
|
689
|
+
import { gateway, streamText } from 'ai';
|
|
690
|
+
|
|
691
|
+
// 1. Make requests with tags
|
|
692
|
+
const result = streamText({
|
|
693
|
+
model: gateway('anthropic/claude-haiku-4.5'),
|
|
694
|
+
prompt: 'Summarize this quarter's results',
|
|
695
|
+
providerOptions: {
|
|
696
|
+
gateway: {
|
|
697
|
+
tags: ['team:finance', 'feature:summaries'],
|
|
698
|
+
} satisfies GatewayProviderOptions,
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// 2. Later, query spend filtered by those tags
|
|
703
|
+
const report = await gateway.getSpendReport({
|
|
704
|
+
startDate: '2026-03-01',
|
|
705
|
+
endDate: '2026-03-31',
|
|
706
|
+
groupBy: 'tag',
|
|
707
|
+
tags: ['team:finance'],
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
for (const row of report.results) {
|
|
711
|
+
console.log(`${row.tag}: $${row.totalCost.toFixed(4)} (${row.requestCount} requests)`);
|
|
712
|
+
}
|
|
713
|
+
```
|
|
569
714
|
|
|
570
715
|
## Provider Options
|
|
571
716
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,147 @@
|
|
|
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 GatewayGenerationInfoParams {
|
|
14
|
+
/** The generation ID to look up (format: gen_<ulid>) */
|
|
15
|
+
id: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GatewayGenerationInfo {
|
|
19
|
+
/** The generation ID */
|
|
20
|
+
id: string;
|
|
21
|
+
/** Total cost in USD */
|
|
22
|
+
totalCost: number;
|
|
23
|
+
/** Upstream inference cost in USD (BYOK only) */
|
|
24
|
+
upstreamInferenceCost: number;
|
|
25
|
+
/** Usage cost in USD (same as totalCost) */
|
|
26
|
+
usage: number;
|
|
27
|
+
/** ISO 8601 timestamp when the generation was created */
|
|
28
|
+
createdAt: string;
|
|
29
|
+
/** Model identifier */
|
|
30
|
+
model: string;
|
|
31
|
+
/** Whether BYOK credentials were used */
|
|
32
|
+
isByok: boolean;
|
|
33
|
+
/** Provider that served this generation */
|
|
34
|
+
providerName: string;
|
|
35
|
+
/** Whether streaming was used */
|
|
36
|
+
streamed: boolean;
|
|
37
|
+
/** Finish reason (e.g. 'stop') */
|
|
38
|
+
finishReason: string;
|
|
39
|
+
/** Time to first token in milliseconds */
|
|
40
|
+
latency: number;
|
|
41
|
+
/** Total generation time in milliseconds */
|
|
42
|
+
generationTime: number;
|
|
43
|
+
/** Number of prompt tokens */
|
|
44
|
+
promptTokens: number;
|
|
45
|
+
/** Number of completion tokens */
|
|
46
|
+
completionTokens: number;
|
|
47
|
+
/** Reasoning tokens used */
|
|
48
|
+
reasoningTokens: number;
|
|
49
|
+
/** Cached tokens used */
|
|
50
|
+
cachedTokens: number;
|
|
51
|
+
/** Cache creation input tokens */
|
|
52
|
+
cacheCreationTokens: number;
|
|
53
|
+
/** Billable web search calls */
|
|
54
|
+
billableWebSearchCalls: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class GatewayGenerationInfoFetcher {
|
|
58
|
+
constructor(private readonly config: GatewayConfig) {}
|
|
59
|
+
|
|
60
|
+
async getGenerationInfo(
|
|
61
|
+
params: GatewayGenerationInfoParams,
|
|
62
|
+
): Promise<GatewayGenerationInfo> {
|
|
63
|
+
try {
|
|
64
|
+
const baseUrl = new URL(this.config.baseURL);
|
|
65
|
+
|
|
66
|
+
const { value } = await getFromApi({
|
|
67
|
+
url: `${baseUrl.origin}/v1/generation?id=${encodeURIComponent(params.id)}`,
|
|
68
|
+
headers: await resolve(this.config.headers()),
|
|
69
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
70
|
+
gatewayGenerationInfoResponseSchema,
|
|
71
|
+
),
|
|
72
|
+
failedResponseHandler: createJsonErrorResponseHandler({
|
|
73
|
+
errorSchema: z.any(),
|
|
74
|
+
errorToMessage: data => data,
|
|
75
|
+
}),
|
|
76
|
+
fetch: this.config.fetch,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return value;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw await asGatewayError(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const gatewayGenerationInfoResponseSchema = lazySchema(() =>
|
|
87
|
+
zodSchema(
|
|
88
|
+
z
|
|
89
|
+
.object({
|
|
90
|
+
data: z
|
|
91
|
+
.object({
|
|
92
|
+
id: z.string(),
|
|
93
|
+
total_cost: z.number(),
|
|
94
|
+
upstream_inference_cost: z.number(),
|
|
95
|
+
usage: z.number(),
|
|
96
|
+
created_at: z.string(),
|
|
97
|
+
model: z.string(),
|
|
98
|
+
is_byok: z.boolean(),
|
|
99
|
+
provider_name: z.string(),
|
|
100
|
+
streamed: z.boolean(),
|
|
101
|
+
finish_reason: z.string(),
|
|
102
|
+
latency: z.number(),
|
|
103
|
+
generation_time: z.number(),
|
|
104
|
+
native_tokens_prompt: z.number(),
|
|
105
|
+
native_tokens_completion: z.number(),
|
|
106
|
+
native_tokens_reasoning: z.number(),
|
|
107
|
+
native_tokens_cached: z.number(),
|
|
108
|
+
native_tokens_cache_creation: z.number(),
|
|
109
|
+
billable_web_search_calls: z.number(),
|
|
110
|
+
})
|
|
111
|
+
.transform(
|
|
112
|
+
({
|
|
113
|
+
total_cost,
|
|
114
|
+
upstream_inference_cost,
|
|
115
|
+
created_at,
|
|
116
|
+
is_byok,
|
|
117
|
+
provider_name,
|
|
118
|
+
finish_reason,
|
|
119
|
+
generation_time,
|
|
120
|
+
native_tokens_prompt,
|
|
121
|
+
native_tokens_completion,
|
|
122
|
+
native_tokens_reasoning,
|
|
123
|
+
native_tokens_cached,
|
|
124
|
+
native_tokens_cache_creation,
|
|
125
|
+
billable_web_search_calls,
|
|
126
|
+
...rest
|
|
127
|
+
}) => ({
|
|
128
|
+
...rest,
|
|
129
|
+
totalCost: total_cost,
|
|
130
|
+
upstreamInferenceCost: upstream_inference_cost,
|
|
131
|
+
createdAt: created_at,
|
|
132
|
+
isByok: is_byok,
|
|
133
|
+
providerName: provider_name,
|
|
134
|
+
finishReason: finish_reason,
|
|
135
|
+
generationTime: generation_time,
|
|
136
|
+
promptTokens: native_tokens_prompt,
|
|
137
|
+
completionTokens: native_tokens_completion,
|
|
138
|
+
reasoningTokens: native_tokens_reasoning,
|
|
139
|
+
cachedTokens: native_tokens_cached,
|
|
140
|
+
cacheCreationTokens: native_tokens_cache_creation,
|
|
141
|
+
billableWebSearchCalls: billable_web_search_calls,
|
|
142
|
+
}),
|
|
143
|
+
),
|
|
144
|
+
})
|
|
145
|
+
.transform(({ data }) => data),
|
|
146
|
+
),
|
|
147
|
+
);
|
package/src/gateway-provider.ts
CHANGED
|
@@ -13,6 +13,16 @@ 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';
|
|
21
|
+
import {
|
|
22
|
+
GatewayGenerationInfoFetcher,
|
|
23
|
+
type GatewayGenerationInfoParams,
|
|
24
|
+
type GatewayGenerationInfo,
|
|
25
|
+
} from './gateway-generation-info';
|
|
16
26
|
import { GatewayLanguageModel } from './gateway-language-model';
|
|
17
27
|
import { GatewayEmbeddingModel } from './gateway-embedding-model';
|
|
18
28
|
import { GatewayImageModel } from './gateway-image-model';
|
|
@@ -56,6 +66,22 @@ export interface GatewayProvider extends ProviderV4 {
|
|
|
56
66
|
*/
|
|
57
67
|
getCredits(): Promise<GatewayCreditsResponse>;
|
|
58
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Returns a spend report with cost, token, and request count data,
|
|
71
|
+
* aggregated by the specified dimension.
|
|
72
|
+
*/
|
|
73
|
+
getSpendReport(
|
|
74
|
+
params: GatewaySpendReportParams,
|
|
75
|
+
): Promise<GatewaySpendReportResponse>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns detailed information about a specific generation by its ID,
|
|
79
|
+
* including cost, token usage, latency, and provider details.
|
|
80
|
+
*/
|
|
81
|
+
getGenerationInfo(
|
|
82
|
+
params: GatewayGenerationInfoParams,
|
|
83
|
+
): Promise<GatewayGenerationInfo>;
|
|
84
|
+
|
|
59
85
|
/**
|
|
60
86
|
* Creates a model for generating text embeddings.
|
|
61
87
|
*/
|
|
@@ -253,6 +279,36 @@ export function createGatewayProvider(
|
|
|
253
279
|
});
|
|
254
280
|
};
|
|
255
281
|
|
|
282
|
+
const getSpendReport = async (params: GatewaySpendReportParams) => {
|
|
283
|
+
return new GatewaySpendReport({
|
|
284
|
+
baseURL,
|
|
285
|
+
headers: getHeaders,
|
|
286
|
+
fetch: options.fetch,
|
|
287
|
+
})
|
|
288
|
+
.getSpendReport(params)
|
|
289
|
+
.catch(async (error: unknown) => {
|
|
290
|
+
throw await asGatewayError(
|
|
291
|
+
error,
|
|
292
|
+
await parseAuthMethod(await getHeaders()),
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const getGenerationInfo = async (params: GatewayGenerationInfoParams) => {
|
|
298
|
+
return new GatewayGenerationInfoFetcher({
|
|
299
|
+
baseURL,
|
|
300
|
+
headers: getHeaders,
|
|
301
|
+
fetch: options.fetch,
|
|
302
|
+
})
|
|
303
|
+
.getGenerationInfo(params)
|
|
304
|
+
.catch(async (error: unknown) => {
|
|
305
|
+
throw await asGatewayError(
|
|
306
|
+
error,
|
|
307
|
+
await parseAuthMethod(await getHeaders()),
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
|
|
256
312
|
const provider = function (modelId: GatewayModelId) {
|
|
257
313
|
if (new.target) {
|
|
258
314
|
throw new Error(
|
|
@@ -266,6 +322,8 @@ export function createGatewayProvider(
|
|
|
266
322
|
provider.specificationVersion = 'v4' as const;
|
|
267
323
|
provider.getAvailableModels = getAvailableModels;
|
|
268
324
|
provider.getCredits = getCredits;
|
|
325
|
+
provider.getSpendReport = getSpendReport;
|
|
326
|
+
provider.getGenerationInfo = getGenerationInfo;
|
|
269
327
|
provider.imageModel = (modelId: GatewayImageModelId) => {
|
|
270
328
|
return new GatewayImageModel(modelId, {
|
|
271
329
|
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,15 @@ 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';
|
|
13
|
+
export type {
|
|
14
|
+
GatewayGenerationInfoParams,
|
|
15
|
+
GatewayGenerationInfo,
|
|
16
|
+
} from './gateway-generation-info';
|
|
8
17
|
export type { GatewayLanguageModelEntry as GatewayModelEntry } from './gateway-model-entry';
|
|
9
18
|
export {
|
|
10
19
|
createGatewayProvider,
|