token-costs 1.0.0
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/README.md +87 -0
- package/dist/npm/client.d.ts +135 -0
- package/dist/npm/client.d.ts.map +1 -0
- package/dist/npm/client.js +312 -0
- package/dist/npm/client.js.map +1 -0
- package/dist/npm/index.d.ts +25 -0
- package/dist/npm/index.d.ts.map +1 -0
- package/dist/npm/index.js +25 -0
- package/dist/npm/index.js.map +1 -0
- package/dist/npm/types.d.ts +146 -0
- package/dist/npm/types.d.ts.map +1 -0
- package/dist/npm/types.js +5 -0
- package/dist/npm/types.js.map +1 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Token Prices
|
|
2
|
+
|
|
3
|
+
Daily-updated LLM pricing data for OpenAI, Anthropic, Google, and OpenRouter.
|
|
4
|
+
|
|
5
|
+
## What Is This?
|
|
6
|
+
|
|
7
|
+
An npm package and JSON API that gives you up-to-date token pricing for major LLM providers. Stop hardcoding prices or manually checking pricing pages.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { getModelPricing, calculateCost } from 'token-costs';
|
|
11
|
+
|
|
12
|
+
// Get pricing info for a model
|
|
13
|
+
const pricing = await getModelPricing('openai', 'gpt-4o');
|
|
14
|
+
// { input: 2.5, output: 10, context: 128000 }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// Or calculate cost directly (fetches pricing automatically)
|
|
19
|
+
const cost = await calculateCost('anthropic', 'claude-sonnet-4', {
|
|
20
|
+
inputTokens: 1500,
|
|
21
|
+
outputTokens: 800,
|
|
22
|
+
});
|
|
23
|
+
// { totalCost: 0.0165, ... }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or fetch directly without dependencies:
|
|
27
|
+
```javascript
|
|
28
|
+
const data = await fetch('https://mikkotikkanen.github.io/token-costs/api/v1/openai.json')
|
|
29
|
+
.then(r => r.json());
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Daily updates** - Crawled automatically at 00:01 UTC
|
|
35
|
+
- **4 providers** - OpenAI, Anthropic, Google, OpenRouter
|
|
36
|
+
- **Zero dependencies** - npm package has no runtime dependencies
|
|
37
|
+
- **TypeScript** - Full type definitions included
|
|
38
|
+
- **Caching** - Fetches once per day, caches automatically
|
|
39
|
+
- **Stale detection** - Know when data might be outdated
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install token-costs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
Full usage guide, API reference, and data formats: **[mikkotikkanen.github.io/token-costs](https://mikkotikkanen.github.io/token-costs)**
|
|
50
|
+
|
|
51
|
+
## What's Included
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
token-costs/
|
|
55
|
+
├── PricingClient # Main client class with caching
|
|
56
|
+
├── getModelPricing() # Quick lookup function
|
|
57
|
+
├── calculateCost() # Cost calculation helper
|
|
58
|
+
└── TypeScript types # Full type definitions
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**API Endpoints** (JSON):
|
|
62
|
+
- `api/v1/openai.json`
|
|
63
|
+
- `api/v1/anthropic.json`
|
|
64
|
+
- `api/v1/google.json`
|
|
65
|
+
- `api/v1/openrouter.json`
|
|
66
|
+
|
|
67
|
+
## Contributing
|
|
68
|
+
|
|
69
|
+
Found incorrect pricing? Want to add a provider? Contributions welcome!
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/mikkotikkanen/token-costs
|
|
73
|
+
cd token-costs
|
|
74
|
+
npm install
|
|
75
|
+
npm run build
|
|
76
|
+
npm test
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
See [AGENTS.md](AGENTS.md) for development details.
|
|
80
|
+
|
|
81
|
+
## For LLM Providers
|
|
82
|
+
|
|
83
|
+
We'd prefer not to scrape. Consider publishing `/llm_prices.json` on your website - a simple standard format that tools can fetch directly. See the [full proposal](https://mikkotikkanen.github.io/token-costs/#proposal) on the documentation site.
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Prices Client
|
|
3
|
+
* Fetches and caches provider pricing data with daily refresh
|
|
4
|
+
*/
|
|
5
|
+
import type { Provider, ProviderFile, ModelPricing, PricingClientOptions, PriceLookupResult, CostResult } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Error thrown when a clock mismatch is detected
|
|
8
|
+
*/
|
|
9
|
+
export declare class ClockMismatchError extends Error {
|
|
10
|
+
readonly clientDate: string;
|
|
11
|
+
readonly dataDate: string;
|
|
12
|
+
readonly daysDiff: number;
|
|
13
|
+
constructor(clientDate: string, dataDate: string, daysDiff: number);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Token Prices Client
|
|
17
|
+
*
|
|
18
|
+
* Provides access to LLM pricing data with automatic caching and daily refresh.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { PricingClient } from 'token-prices';
|
|
23
|
+
*
|
|
24
|
+
* const client = new PricingClient();
|
|
25
|
+
*
|
|
26
|
+
* // Get pricing for a model
|
|
27
|
+
* const pricing = await client.getModelPricing('openai', 'gpt-4o');
|
|
28
|
+
* console.log(pricing.input, pricing.output);
|
|
29
|
+
*
|
|
30
|
+
* // Calculate cost
|
|
31
|
+
* const cost = await client.calculateCost('anthropic', 'claude-sonnet-4', {
|
|
32
|
+
* inputTokens: 1000,
|
|
33
|
+
* outputTokens: 500,
|
|
34
|
+
* });
|
|
35
|
+
* console.log(cost.totalCost);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare class PricingClient {
|
|
39
|
+
private baseUrl;
|
|
40
|
+
private fetchFn;
|
|
41
|
+
private timeOffsetMs;
|
|
42
|
+
private cache;
|
|
43
|
+
private externalCache?;
|
|
44
|
+
constructor(options?: PricingClientOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Get today's date according to this client (with offset applied)
|
|
47
|
+
*/
|
|
48
|
+
private getToday;
|
|
49
|
+
/**
|
|
50
|
+
* Get cache key for external cache
|
|
51
|
+
*/
|
|
52
|
+
private getCacheKey;
|
|
53
|
+
/**
|
|
54
|
+
* Try to load from external cache
|
|
55
|
+
*/
|
|
56
|
+
private loadFromExternalCache;
|
|
57
|
+
/**
|
|
58
|
+
* Save to external cache
|
|
59
|
+
*/
|
|
60
|
+
private saveToExternalCache;
|
|
61
|
+
/**
|
|
62
|
+
* Fetch provider data, using cache if available and fresh
|
|
63
|
+
*/
|
|
64
|
+
private fetchProvider;
|
|
65
|
+
/**
|
|
66
|
+
* Get the effective pricing data, handling the dual-date fallback
|
|
67
|
+
*/
|
|
68
|
+
private getEffectiveData;
|
|
69
|
+
/**
|
|
70
|
+
* Get pricing for a specific model
|
|
71
|
+
*
|
|
72
|
+
* @param provider - The provider (openai, anthropic, google, openrouter)
|
|
73
|
+
* @param modelId - The model identifier
|
|
74
|
+
* @returns The pricing data for the model
|
|
75
|
+
* @throws Error if model is not found
|
|
76
|
+
*/
|
|
77
|
+
getModelPricing(provider: Provider, modelId: string): Promise<PriceLookupResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Get pricing for a model, returning null if not found
|
|
80
|
+
*/
|
|
81
|
+
getModelPricingOrNull(provider: Provider, modelId: string): Promise<PriceLookupResult | null>;
|
|
82
|
+
/**
|
|
83
|
+
* Get all models for a provider
|
|
84
|
+
*/
|
|
85
|
+
getProviderModels(provider: Provider): Promise<Record<string, ModelPricing>>;
|
|
86
|
+
/**
|
|
87
|
+
* List all model IDs for a provider
|
|
88
|
+
*/
|
|
89
|
+
listModels(provider: Provider): Promise<string[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Calculate cost for a given number of tokens
|
|
92
|
+
*
|
|
93
|
+
* @param provider - The provider
|
|
94
|
+
* @param modelId - The model identifier
|
|
95
|
+
* @param tokens - Token counts
|
|
96
|
+
* @param tokens.inputTokens - Number of input tokens
|
|
97
|
+
* @param tokens.outputTokens - Number of output tokens
|
|
98
|
+
* @param tokens.cachedInputTokens - Number of cached input tokens (optional)
|
|
99
|
+
*/
|
|
100
|
+
calculateCost(provider: Provider, modelId: string, tokens: {
|
|
101
|
+
inputTokens: number;
|
|
102
|
+
outputTokens: number;
|
|
103
|
+
cachedInputTokens?: number;
|
|
104
|
+
}): Promise<CostResult>;
|
|
105
|
+
/**
|
|
106
|
+
* Get the raw provider file (includes both current and previous data)
|
|
107
|
+
*/
|
|
108
|
+
getRawProviderData(provider: Provider): Promise<ProviderFile>;
|
|
109
|
+
/**
|
|
110
|
+
* Get the date of the currently cached data for a provider
|
|
111
|
+
* Returns null if no data is cached
|
|
112
|
+
*/
|
|
113
|
+
getCachedDate(provider: Provider): string | null;
|
|
114
|
+
/**
|
|
115
|
+
* Clear the cache for a specific provider or all providers
|
|
116
|
+
*/
|
|
117
|
+
clearCache(provider?: Provider): void;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the default client instance
|
|
121
|
+
*/
|
|
122
|
+
export declare function getDefaultClient(): PricingClient;
|
|
123
|
+
/**
|
|
124
|
+
* Convenience function to get model pricing using default client
|
|
125
|
+
*/
|
|
126
|
+
export declare function getModelPricing(provider: Provider, modelId: string): Promise<PriceLookupResult>;
|
|
127
|
+
/**
|
|
128
|
+
* Convenience function to calculate cost using default client
|
|
129
|
+
*/
|
|
130
|
+
export declare function calculateCost(provider: Provider, modelId: string, tokens: {
|
|
131
|
+
inputTokens: number;
|
|
132
|
+
outputTokens: number;
|
|
133
|
+
cachedInputTokens?: number;
|
|
134
|
+
}): Promise<CostResult>;
|
|
135
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/npm/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EAEZ,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACX,MAAM,YAAY,CAAC;AA4BpB;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,UAAU,EAAE,MAAM;aAClB,QAAQ,EAAE,MAAM;aAChB,QAAQ,EAAE,MAAM;gBAFhB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CASnC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,aAAa,CAAC,CAAwC;gBAElD,OAAO,GAAE,oBAAyB;IAO9C;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;YACW,qBAAqB;IAcnC;;OAEG;YACW,mBAAmB;IAUjC;;OAEG;YACW,aAAa;IAsD3B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAMxB;;;;;;;OAOG;IACG,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyBtF;;OAEG;IACG,qBAAqB,CACzB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAQpC;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAKlF;;OAEG;IACG,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAKvD;;;;;;;;;OASG;IACG,aAAa,CACjB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;QACN,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,GACA,OAAO,CAAC,UAAU,CAAC;IAqCtB;;OAEG;IACG,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC;IAInE;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IAKhD;;OAEG;IACH,UAAU,CAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI;CAOtC;AAOD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IACN,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GACA,OAAO,CAAC,UAAU,CAAC,CAErB"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Prices Client
|
|
3
|
+
* Fetches and caches provider pricing data with daily refresh
|
|
4
|
+
*/
|
|
5
|
+
// Default URL serves from GitHub Pages when configured, falls back to raw.githubusercontent.com
|
|
6
|
+
// Users can override with their own GitHub Pages URL via baseUrl option
|
|
7
|
+
const DEFAULT_BASE_URL = 'https://raw.githubusercontent.com/mikkotikkanen/token-prices/main/docs/api/v1';
|
|
8
|
+
/**
|
|
9
|
+
* Get UTC date as YYYY-MM-DD with optional offset
|
|
10
|
+
*/
|
|
11
|
+
function getUtcDate(offsetMs = 0) {
|
|
12
|
+
return new Date(Date.now() + offsetMs).toISOString().split('T')[0];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Calculate days difference between two YYYY-MM-DD dates
|
|
16
|
+
*/
|
|
17
|
+
function daysDifference(dateA, dateB) {
|
|
18
|
+
const a = new Date(dateA + 'T00:00:00Z').getTime();
|
|
19
|
+
const b = new Date(dateB + 'T00:00:00Z').getTime();
|
|
20
|
+
return Math.floor((a - b) / (24 * 60 * 60 * 1000));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Error thrown when a clock mismatch is detected
|
|
24
|
+
*/
|
|
25
|
+
export class ClockMismatchError extends Error {
|
|
26
|
+
clientDate;
|
|
27
|
+
dataDate;
|
|
28
|
+
daysDiff;
|
|
29
|
+
constructor(clientDate, dataDate, daysDiff) {
|
|
30
|
+
super(`Clock mismatch detected: client thinks it's ${clientDate} but latest data is from ${dataDate} ` +
|
|
31
|
+
`(${daysDiff} days difference). This may indicate your server clock is wrong. ` +
|
|
32
|
+
`Use the timeOffsetMs option to adjust, or check your system clock.`);
|
|
33
|
+
this.clientDate = clientDate;
|
|
34
|
+
this.dataDate = dataDate;
|
|
35
|
+
this.daysDiff = daysDiff;
|
|
36
|
+
this.name = 'ClockMismatchError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Token Prices Client
|
|
41
|
+
*
|
|
42
|
+
* Provides access to LLM pricing data with automatic caching and daily refresh.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { PricingClient } from 'token-prices';
|
|
47
|
+
*
|
|
48
|
+
* const client = new PricingClient();
|
|
49
|
+
*
|
|
50
|
+
* // Get pricing for a model
|
|
51
|
+
* const pricing = await client.getModelPricing('openai', 'gpt-4o');
|
|
52
|
+
* console.log(pricing.input, pricing.output);
|
|
53
|
+
*
|
|
54
|
+
* // Calculate cost
|
|
55
|
+
* const cost = await client.calculateCost('anthropic', 'claude-sonnet-4', {
|
|
56
|
+
* inputTokens: 1000,
|
|
57
|
+
* outputTokens: 500,
|
|
58
|
+
* });
|
|
59
|
+
* console.log(cost.totalCost);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export class PricingClient {
|
|
63
|
+
baseUrl;
|
|
64
|
+
fetchFn;
|
|
65
|
+
timeOffsetMs;
|
|
66
|
+
cache = new Map();
|
|
67
|
+
externalCache;
|
|
68
|
+
constructor(options = {}) {
|
|
69
|
+
this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
70
|
+
this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
71
|
+
this.timeOffsetMs = options.timeOffsetMs ?? 0;
|
|
72
|
+
this.externalCache = options.externalCache;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get today's date according to this client (with offset applied)
|
|
76
|
+
*/
|
|
77
|
+
getToday() {
|
|
78
|
+
return getUtcDate(this.timeOffsetMs);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get cache key for external cache
|
|
82
|
+
*/
|
|
83
|
+
getCacheKey(provider) {
|
|
84
|
+
return `token-prices:${provider}`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Try to load from external cache
|
|
88
|
+
*/
|
|
89
|
+
async loadFromExternalCache(provider) {
|
|
90
|
+
if (!this.externalCache)
|
|
91
|
+
return null;
|
|
92
|
+
try {
|
|
93
|
+
const raw = await this.externalCache.get(this.getCacheKey(provider));
|
|
94
|
+
if (raw) {
|
|
95
|
+
return JSON.parse(raw);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// External cache failed, continue without it
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Save to external cache
|
|
105
|
+
*/
|
|
106
|
+
async saveToExternalCache(provider, entry) {
|
|
107
|
+
if (!this.externalCache)
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
await this.externalCache.set(this.getCacheKey(provider), JSON.stringify(entry));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// External cache failed, continue without it
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Fetch provider data, using cache if available and fresh
|
|
118
|
+
*/
|
|
119
|
+
async fetchProvider(provider) {
|
|
120
|
+
const today = this.getToday();
|
|
121
|
+
// Check in-memory cache first
|
|
122
|
+
let cached = this.cache.get(provider);
|
|
123
|
+
// If no in-memory cache, try external cache
|
|
124
|
+
if (!cached && this.externalCache) {
|
|
125
|
+
const external = await this.loadFromExternalCache(provider);
|
|
126
|
+
if (external) {
|
|
127
|
+
cached = external;
|
|
128
|
+
// Populate in-memory cache from external
|
|
129
|
+
this.cache.set(provider, external);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// If we have cached data from today, use it (don't fetch again)
|
|
133
|
+
if (cached && cached.fetchedDate === today) {
|
|
134
|
+
return cached.data;
|
|
135
|
+
}
|
|
136
|
+
// Try to fetch fresh data
|
|
137
|
+
const url = `${this.baseUrl}/${provider}.json`;
|
|
138
|
+
const response = await this.fetchFn(url);
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
// If fetch fails but we have cached data, use it
|
|
141
|
+
if (cached) {
|
|
142
|
+
return cached.data;
|
|
143
|
+
}
|
|
144
|
+
throw new Error(`Failed to fetch pricing data for ${provider}: ${response.status}`);
|
|
145
|
+
}
|
|
146
|
+
const data = (await response.json());
|
|
147
|
+
const daysDiff = daysDifference(today, data.current.date);
|
|
148
|
+
// Check for clock mismatch: data from future means our clock is behind
|
|
149
|
+
if (daysDiff < 0) {
|
|
150
|
+
throw new ClockMismatchError(today, data.current.date, daysDiff);
|
|
151
|
+
}
|
|
152
|
+
// Check for clock mismatch: data more than 1 day behind means our clock is ahead
|
|
153
|
+
if (daysDiff > 1) {
|
|
154
|
+
throw new ClockMismatchError(today, data.current.date, daysDiff);
|
|
155
|
+
}
|
|
156
|
+
// Cache the data with today's date so we don't fetch again today
|
|
157
|
+
const entry = { data, fetchedDate: today };
|
|
158
|
+
this.cache.set(provider, entry);
|
|
159
|
+
await this.saveToExternalCache(provider, entry);
|
|
160
|
+
return data;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get the effective pricing data, handling the dual-date fallback
|
|
164
|
+
*/
|
|
165
|
+
getEffectiveData(file) {
|
|
166
|
+
// Always use current - the dual-date structure is for consumers
|
|
167
|
+
// who want to detect if data is stale and handle it themselves
|
|
168
|
+
return file.current;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get pricing for a specific model
|
|
172
|
+
*
|
|
173
|
+
* @param provider - The provider (openai, anthropic, google, openrouter)
|
|
174
|
+
* @param modelId - The model identifier
|
|
175
|
+
* @returns The pricing data for the model
|
|
176
|
+
* @throws Error if model is not found
|
|
177
|
+
*/
|
|
178
|
+
async getModelPricing(provider, modelId) {
|
|
179
|
+
const file = await this.fetchProvider(provider);
|
|
180
|
+
const data = this.getEffectiveData(file);
|
|
181
|
+
const pricing = data.models[modelId];
|
|
182
|
+
if (!pricing) {
|
|
183
|
+
const available = Object.keys(data.models).join(', ');
|
|
184
|
+
throw new Error(`Model '${modelId}' not found for provider '${provider}'. Available: ${available}`);
|
|
185
|
+
}
|
|
186
|
+
// Data is stale if today's date (with offset) is newer than the data date
|
|
187
|
+
const today = this.getToday();
|
|
188
|
+
const stale = data.date < today;
|
|
189
|
+
return {
|
|
190
|
+
provider,
|
|
191
|
+
modelId,
|
|
192
|
+
pricing,
|
|
193
|
+
date: data.date,
|
|
194
|
+
stale,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get pricing for a model, returning null if not found
|
|
199
|
+
*/
|
|
200
|
+
async getModelPricingOrNull(provider, modelId) {
|
|
201
|
+
try {
|
|
202
|
+
return await this.getModelPricing(provider, modelId);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get all models for a provider
|
|
210
|
+
*/
|
|
211
|
+
async getProviderModels(provider) {
|
|
212
|
+
const file = await this.fetchProvider(provider);
|
|
213
|
+
return this.getEffectiveData(file).models;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* List all model IDs for a provider
|
|
217
|
+
*/
|
|
218
|
+
async listModels(provider) {
|
|
219
|
+
const models = await this.getProviderModels(provider);
|
|
220
|
+
return Object.keys(models);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Calculate cost for a given number of tokens
|
|
224
|
+
*
|
|
225
|
+
* @param provider - The provider
|
|
226
|
+
* @param modelId - The model identifier
|
|
227
|
+
* @param tokens - Token counts
|
|
228
|
+
* @param tokens.inputTokens - Number of input tokens
|
|
229
|
+
* @param tokens.outputTokens - Number of output tokens
|
|
230
|
+
* @param tokens.cachedInputTokens - Number of cached input tokens (optional)
|
|
231
|
+
*/
|
|
232
|
+
async calculateCost(provider, modelId, tokens) {
|
|
233
|
+
const { pricing, date, stale } = await this.getModelPricing(provider, modelId);
|
|
234
|
+
// Validate this is a text model with token pricing
|
|
235
|
+
if (pricing.input === undefined || pricing.output === undefined) {
|
|
236
|
+
throw new Error(`Model '${modelId}' does not have token-based pricing. ` +
|
|
237
|
+
`Use image/audio/video pricing fields instead.`);
|
|
238
|
+
}
|
|
239
|
+
const { inputTokens, outputTokens, cachedInputTokens = 0 } = tokens;
|
|
240
|
+
// Calculate costs (prices are per million tokens)
|
|
241
|
+
const regularInputTokens = inputTokens - cachedInputTokens;
|
|
242
|
+
const usedCachedPricing = cachedInputTokens > 0 && pricing.cached !== undefined;
|
|
243
|
+
let inputCost = (regularInputTokens / 1_000_000) * pricing.input;
|
|
244
|
+
if (usedCachedPricing && pricing.cached !== undefined) {
|
|
245
|
+
inputCost += (cachedInputTokens / 1_000_000) * pricing.cached;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// No cached pricing available, use regular input price
|
|
249
|
+
inputCost += (cachedInputTokens / 1_000_000) * pricing.input;
|
|
250
|
+
}
|
|
251
|
+
const outputCost = (outputTokens / 1_000_000) * pricing.output;
|
|
252
|
+
return {
|
|
253
|
+
inputCost,
|
|
254
|
+
outputCost,
|
|
255
|
+
totalCost: inputCost + outputCost,
|
|
256
|
+
usedCachedPricing,
|
|
257
|
+
date,
|
|
258
|
+
stale,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Get the raw provider file (includes both current and previous data)
|
|
263
|
+
*/
|
|
264
|
+
async getRawProviderData(provider) {
|
|
265
|
+
return this.fetchProvider(provider);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get the date of the currently cached data for a provider
|
|
269
|
+
* Returns null if no data is cached
|
|
270
|
+
*/
|
|
271
|
+
getCachedDate(provider) {
|
|
272
|
+
const cached = this.cache.get(provider);
|
|
273
|
+
return cached?.data.current.date ?? null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Clear the cache for a specific provider or all providers
|
|
277
|
+
*/
|
|
278
|
+
clearCache(provider) {
|
|
279
|
+
if (provider) {
|
|
280
|
+
this.cache.delete(provider);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
this.cache.clear();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Default client instance for convenience
|
|
289
|
+
*/
|
|
290
|
+
let defaultClient = null;
|
|
291
|
+
/**
|
|
292
|
+
* Get the default client instance
|
|
293
|
+
*/
|
|
294
|
+
export function getDefaultClient() {
|
|
295
|
+
if (!defaultClient) {
|
|
296
|
+
defaultClient = new PricingClient();
|
|
297
|
+
}
|
|
298
|
+
return defaultClient;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Convenience function to get model pricing using default client
|
|
302
|
+
*/
|
|
303
|
+
export async function getModelPricing(provider, modelId) {
|
|
304
|
+
return getDefaultClient().getModelPricing(provider, modelId);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Convenience function to calculate cost using default client
|
|
308
|
+
*/
|
|
309
|
+
export async function calculateCost(provider, modelId, tokens) {
|
|
310
|
+
return getDefaultClient().calculateCost(provider, modelId, tokens);
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/npm/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Prices - Daily LLM pricing data
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { getModelPricing, calculateCost } from 'token-prices';
|
|
7
|
+
*
|
|
8
|
+
* // Get pricing for a model
|
|
9
|
+
* const result = await getModelPricing('openai', 'gpt-4o');
|
|
10
|
+
* console.log(`Input: $${result.pricing.input}/M tokens`);
|
|
11
|
+
* console.log(`Output: $${result.pricing.output}/M tokens`);
|
|
12
|
+
*
|
|
13
|
+
* // Calculate cost for an API call
|
|
14
|
+
* const cost = await calculateCost('anthropic', 'claude-sonnet-4', {
|
|
15
|
+
* inputTokens: 1500,
|
|
16
|
+
* outputTokens: 800,
|
|
17
|
+
* });
|
|
18
|
+
* console.log(`Total cost: $${cost.totalCost.toFixed(6)}`);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
export type { ModelPricing, ProviderData, ProviderFile, Provider, PricingClientOptions, PriceLookupResult, CostResult, } from './types.js';
|
|
24
|
+
export { PricingClient, ClockMismatchError, getDefaultClient, getModelPricing, calculateCost, } from './client.js';
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/npm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Prices - Daily LLM pricing data
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { getModelPricing, calculateCost } from 'token-prices';
|
|
7
|
+
*
|
|
8
|
+
* // Get pricing for a model
|
|
9
|
+
* const result = await getModelPricing('openai', 'gpt-4o');
|
|
10
|
+
* console.log(`Input: $${result.pricing.input}/M tokens`);
|
|
11
|
+
* console.log(`Output: $${result.pricing.output}/M tokens`);
|
|
12
|
+
*
|
|
13
|
+
* // Calculate cost for an API call
|
|
14
|
+
* const cost = await calculateCost('anthropic', 'claude-sonnet-4', {
|
|
15
|
+
* inputTokens: 1500,
|
|
16
|
+
* outputTokens: 800,
|
|
17
|
+
* });
|
|
18
|
+
* console.log(`Total cost: $${cost.totalCost.toFixed(6)}`);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
// Re-export client
|
|
24
|
+
export { PricingClient, ClockMismatchError, getDefaultClient, getModelPricing, calculateCost, } from './client.js';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/npm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAaH,mBAAmB;AACnB,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Prices NPM Module Types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Variant pricing for resolution/quality dependent models
|
|
6
|
+
*/
|
|
7
|
+
export interface VariantPricing {
|
|
8
|
+
/** Input cost for this variant */
|
|
9
|
+
input?: number;
|
|
10
|
+
/** Output cost for this variant */
|
|
11
|
+
output?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Pricing data for a single model
|
|
15
|
+
*/
|
|
16
|
+
export interface ModelPricing {
|
|
17
|
+
/** Price per 1M input tokens in USD */
|
|
18
|
+
input?: number;
|
|
19
|
+
/** Price per 1M output tokens in USD */
|
|
20
|
+
output?: number;
|
|
21
|
+
/** Price per 1M cached input tokens in USD (if supported) */
|
|
22
|
+
cached?: number;
|
|
23
|
+
/** Context window size in tokens */
|
|
24
|
+
context?: number;
|
|
25
|
+
/** Maximum output tokens */
|
|
26
|
+
maxOutput?: number;
|
|
27
|
+
/** Image pricing by resolution/quality (per image) */
|
|
28
|
+
image?: Record<string, VariantPricing>;
|
|
29
|
+
/** Audio pricing by quality (per minute) */
|
|
30
|
+
audio?: Record<string, VariantPricing>;
|
|
31
|
+
/** Video pricing by resolution (per second) */
|
|
32
|
+
video?: Record<string, VariantPricing>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Provider pricing data for a single date
|
|
36
|
+
*/
|
|
37
|
+
export interface ProviderData {
|
|
38
|
+
/** ISO date string (YYYY-MM-DD) */
|
|
39
|
+
date: string;
|
|
40
|
+
/** Model pricing map: modelId -> pricing */
|
|
41
|
+
models: Record<string, ModelPricing>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Dual-date structure for handling update timing
|
|
45
|
+
* When fetching on a new day, if current.date hasn't updated yet,
|
|
46
|
+
* the previous data is still valid to use
|
|
47
|
+
*/
|
|
48
|
+
export interface ProviderFile {
|
|
49
|
+
/** Current/latest pricing data */
|
|
50
|
+
current: ProviderData;
|
|
51
|
+
/** Previous day's data (for fallback during updates) */
|
|
52
|
+
previous?: ProviderData;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Supported providers
|
|
56
|
+
*/
|
|
57
|
+
export type Provider = 'openai' | 'anthropic' | 'google' | 'openrouter';
|
|
58
|
+
/**
|
|
59
|
+
* Options for the pricing client
|
|
60
|
+
*/
|
|
61
|
+
export interface PricingClientOptions {
|
|
62
|
+
/**
|
|
63
|
+
* Base URL for fetching pricing data
|
|
64
|
+
* @default 'https://raw.githubusercontent.com/mikkotikkanen/token-prices/main/docs/api/v1'
|
|
65
|
+
*/
|
|
66
|
+
baseUrl?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Custom fetch function (for testing or special environments)
|
|
69
|
+
*/
|
|
70
|
+
fetch?: typeof globalThis.fetch;
|
|
71
|
+
/**
|
|
72
|
+
* Time offset in milliseconds to adjust the client's "today" calculation.
|
|
73
|
+
* Use this if the server clock is known to be off.
|
|
74
|
+
* Positive values move time forward, negative values move it back.
|
|
75
|
+
* @example
|
|
76
|
+
* // Server clock is 2 hours behind UTC
|
|
77
|
+
* new PricingClient({ timeOffsetMs: 2 * 60 * 60 * 1000 })
|
|
78
|
+
*/
|
|
79
|
+
timeOffsetMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* External cache for persisting fetch timestamps across restarts/instances.
|
|
82
|
+
* If not provided, uses in-memory cache (lost on restart).
|
|
83
|
+
*
|
|
84
|
+
* This prevents hammering GitHub when:
|
|
85
|
+
* - Running in serverless (cold starts)
|
|
86
|
+
* - Multiple server instances
|
|
87
|
+
* - Server restarts
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Using a simple file-based cache
|
|
91
|
+
* const cacheFile = './price-cache.json';
|
|
92
|
+
* new PricingClient({
|
|
93
|
+
* externalCache: {
|
|
94
|
+
* get: async (key) => {
|
|
95
|
+
* try {
|
|
96
|
+
* const data = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
97
|
+
* return data[key];
|
|
98
|
+
* } catch { return undefined; }
|
|
99
|
+
* },
|
|
100
|
+
* set: async (key, value) => {
|
|
101
|
+
* let data = {};
|
|
102
|
+
* try { data = JSON.parse(fs.readFileSync(cacheFile, 'utf8')); } catch {}
|
|
103
|
+
* data[key] = value;
|
|
104
|
+
* fs.writeFileSync(cacheFile, JSON.stringify(data));
|
|
105
|
+
* }
|
|
106
|
+
* }
|
|
107
|
+
* });
|
|
108
|
+
*/
|
|
109
|
+
externalCache?: {
|
|
110
|
+
get: (key: string) => Promise<string | undefined> | string | undefined;
|
|
111
|
+
set: (key: string, value: string) => Promise<void> | void;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Result of a price lookup
|
|
116
|
+
*/
|
|
117
|
+
export interface PriceLookupResult {
|
|
118
|
+
/** The provider */
|
|
119
|
+
provider: Provider;
|
|
120
|
+
/** The model ID */
|
|
121
|
+
modelId: string;
|
|
122
|
+
/** The pricing data */
|
|
123
|
+
pricing: ModelPricing;
|
|
124
|
+
/** The date of the pricing data (YYYY-MM-DD) */
|
|
125
|
+
date: string;
|
|
126
|
+
/** True if the data is from a previous day (new data not yet available) */
|
|
127
|
+
stale: boolean;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Calculate cost result
|
|
131
|
+
*/
|
|
132
|
+
export interface CostResult {
|
|
133
|
+
/** Cost for input tokens in USD */
|
|
134
|
+
inputCost: number;
|
|
135
|
+
/** Cost for output tokens in USD */
|
|
136
|
+
outputCost: number;
|
|
137
|
+
/** Total cost in USD */
|
|
138
|
+
totalCost: number;
|
|
139
|
+
/** Whether cached pricing was used for input */
|
|
140
|
+
usedCachedPricing: boolean;
|
|
141
|
+
/** The date of the pricing data used (YYYY-MM-DD) */
|
|
142
|
+
date: string;
|
|
143
|
+
/** True if the pricing data is from a previous day */
|
|
144
|
+
stale: boolean;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/npm/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,aAAa,CAAC,EAAE;QACd,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC;QACvE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC3D,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mBAAmB;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,mBAAmB;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,OAAO,EAAE,YAAY,CAAC;IACtB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,KAAK,EAAE,OAAO,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/npm/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "token-costs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Daily LLM token pricing data with automatic updates",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/npm/index.js",
|
|
7
|
+
"types": "dist/npm/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/npm/index.js",
|
|
11
|
+
"types": "./dist/npm/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/npm/**/*"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"dev": "npm run build && npm run crawl:all",
|
|
20
|
+
"dev:openai": "npm run build && npm run crawl:openai",
|
|
21
|
+
"dev:anthropic": "npm run build && npm run crawl:anthropic",
|
|
22
|
+
"dev:google": "npm run build && npm run crawl:google",
|
|
23
|
+
"dev:openrouter": "npm run build && npm run crawl:openrouter",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:coverage": "vitest run --coverage",
|
|
27
|
+
"crawl:openai": "node --experimental-vm-modules dist/crawlers/openai/index.js",
|
|
28
|
+
"crawl:anthropic": "node --experimental-vm-modules dist/crawlers/anthropic/index.js",
|
|
29
|
+
"crawl:google": "node --experimental-vm-modules dist/crawlers/google/index.js",
|
|
30
|
+
"crawl:openrouter": "node --experimental-vm-modules dist/crawlers/openrouter/index.js",
|
|
31
|
+
"crawl:all": "npm run crawl:openai && npm run crawl:anthropic && npm run crawl:google && npm run crawl:openrouter",
|
|
32
|
+
"show:openai": "node -e \"const fs=require('fs');const d=JSON.parse(fs.readFileSync('./history/prices/openai.json','utf8'));console.log(JSON.stringify(d,null,2));\"",
|
|
33
|
+
"show:anthropic": "node -e \"const fs=require('fs');const d=JSON.parse(fs.readFileSync('./history/prices/anthropic.json','utf8'));console.log(JSON.stringify(d,null,2));\"",
|
|
34
|
+
"show:google": "node -e \"const fs=require('fs');const d=JSON.parse(fs.readFileSync('./history/prices/google.json','utf8'));console.log(JSON.stringify(d,null,2));\"",
|
|
35
|
+
"show:openrouter": "node -e \"const fs=require('fs');const d=JSON.parse(fs.readFileSync('./history/prices/openrouter.json','utf8'));console.log(JSON.stringify(d,null,2));\"",
|
|
36
|
+
"test:local": "npm run build && node --experimental-vm-modules dist/test-local.js",
|
|
37
|
+
"test:local:openai": "npm run build && node --experimental-vm-modules dist/test-local.js openai",
|
|
38
|
+
"test:local:anthropic": "npm run build && node --experimental-vm-modules dist/test-local.js anthropic",
|
|
39
|
+
"test:local:google": "npm run build && node --experimental-vm-modules dist/test-local.js google",
|
|
40
|
+
"test:local:openrouter": "npm run build && node --experimental-vm-modules dist/test-local.js openrouter",
|
|
41
|
+
"test:local:show": "npm run build && node --experimental-vm-modules dist/test-local.js --show",
|
|
42
|
+
"generate:npm": "npm run build && node dist/generate-npm-files.js",
|
|
43
|
+
"prepublishOnly": "npm run build && npm test"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/mikkotikkanen/token-costs.git"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://mikkotikkanen.github.io/token-costs",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/mikkotikkanen/token-costs/issues"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"llm",
|
|
55
|
+
"pricing",
|
|
56
|
+
"openai",
|
|
57
|
+
"anthropic",
|
|
58
|
+
"google",
|
|
59
|
+
"openrouter",
|
|
60
|
+
"gpt",
|
|
61
|
+
"claude",
|
|
62
|
+
"gemini",
|
|
63
|
+
"tokens",
|
|
64
|
+
"cost"
|
|
65
|
+
],
|
|
66
|
+
"author": "Mikko Tikkanen",
|
|
67
|
+
"license": "MIT",
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/node": "^20.10.0",
|
|
70
|
+
"cheerio": "^1.0.0-rc.12",
|
|
71
|
+
"node-fetch": "^3.3.2",
|
|
72
|
+
"typescript": "^5.3.0",
|
|
73
|
+
"vitest": "^1.0.0"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=20.0.0"
|
|
77
|
+
}
|
|
78
|
+
}
|