@pydantic/genai-prices 0.0.6
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 +203 -0
- package/dist/__tests__/calcPrice.test.d.ts +1 -0
- package/dist/async/calcPriceAsync.d.ts +8 -0
- package/dist/async/calcPriceAsync.js +22 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +149 -0
- package/dist/data.d.ts +2 -0
- package/dist/data.js +10414 -0
- package/dist/dataLoader.d.ts +18 -0
- package/dist/dataLoader.js +146 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.umd.cjs +10697 -0
- package/dist/matcher.d.ts +3 -0
- package/dist/matcher.js +37 -0
- package/dist/priceCalc.d.ts +3 -0
- package/dist/priceCalc.js +68 -0
- package/dist/sync/calcPriceSync.d.ts +8 -0
- package/dist/sync/calcPriceSync.js +22 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Provider, ModelInfo } from './types.js';
|
|
2
|
+
export declare function matchProvider(providers: Provider[], modelRef: string, providerId?: string, providerApiUrl?: string): Provider | undefined;
|
|
3
|
+
export declare function matchModel(models: ModelInfo[], modelRef: string): ModelInfo | undefined;
|
package/dist/matcher.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function matchLogic(logic, text) {
|
|
2
|
+
if ('or' in logic) {
|
|
3
|
+
return logic.or.some((clause) => matchLogic(clause, text));
|
|
4
|
+
}
|
|
5
|
+
if ('and' in logic) {
|
|
6
|
+
return logic.and.every((clause) => matchLogic(clause, text));
|
|
7
|
+
}
|
|
8
|
+
if ('equals' in logic) {
|
|
9
|
+
return text === logic.equals;
|
|
10
|
+
}
|
|
11
|
+
if ('starts_with' in logic) {
|
|
12
|
+
return text.startsWith(logic.starts_with);
|
|
13
|
+
}
|
|
14
|
+
if ('ends_with' in logic) {
|
|
15
|
+
return text.endsWith(logic.ends_with);
|
|
16
|
+
}
|
|
17
|
+
if ('contains' in logic) {
|
|
18
|
+
return text.includes(logic.contains);
|
|
19
|
+
}
|
|
20
|
+
if ('regex' in logic) {
|
|
21
|
+
return new RegExp(logic.regex).test(text);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
export function matchProvider(providers, modelRef, providerId, providerApiUrl) {
|
|
26
|
+
if (providerId) {
|
|
27
|
+
return providers.find((p) => p.id === providerId);
|
|
28
|
+
}
|
|
29
|
+
if (providerApiUrl) {
|
|
30
|
+
return providers.find((p) => new RegExp(p.api_pattern).test(providerApiUrl));
|
|
31
|
+
}
|
|
32
|
+
// Try model_match logic
|
|
33
|
+
return providers.find((p) => p.model_match && matchLogic(p.model_match, modelRef));
|
|
34
|
+
}
|
|
35
|
+
export function matchModel(models, modelRef) {
|
|
36
|
+
return models.find((m) => matchLogic(m.match, modelRef));
|
|
37
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function calcTieredPrice(tiered, tokens) {
|
|
2
|
+
if (tokens <= 0)
|
|
3
|
+
return 0;
|
|
4
|
+
let price = 0;
|
|
5
|
+
// Sort tiers by start ascending
|
|
6
|
+
const tiers = [...tiered.tiers].sort((a, b) => a.start - b.start);
|
|
7
|
+
// Base price for tokens up to the first tier start
|
|
8
|
+
const firstTierStart = tiers[0]?.start ?? tokens;
|
|
9
|
+
const baseTokens = Math.min(tokens, firstTierStart);
|
|
10
|
+
price += (baseTokens * tiered.base) / 1_000_000;
|
|
11
|
+
// Price for each tier
|
|
12
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
13
|
+
const tier = tiers[i];
|
|
14
|
+
const nextTierStart = tiers[i + 1]?.start ?? Infinity;
|
|
15
|
+
// Tokens in this tier: from tier.start up to nextTierStart or tokens
|
|
16
|
+
const tierTokenCount = Math.max(0, Math.min(tokens, nextTierStart) - tier.start);
|
|
17
|
+
if (tierTokenCount > 0) {
|
|
18
|
+
price += (tierTokenCount * tier.price) / 1_000_000;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return price;
|
|
22
|
+
}
|
|
23
|
+
function calcMtokPrice(price, tokens, _field) {
|
|
24
|
+
if (price === undefined || tokens === undefined)
|
|
25
|
+
return 0;
|
|
26
|
+
if (typeof price === 'number') {
|
|
27
|
+
return (price * tokens) / 1_000_000;
|
|
28
|
+
}
|
|
29
|
+
return calcTieredPrice(price, tokens);
|
|
30
|
+
}
|
|
31
|
+
export function calcPrice(usage, modelPrice) {
|
|
32
|
+
let price = 0;
|
|
33
|
+
price += calcMtokPrice(modelPrice.input_mtok, usage.input_tokens, 'input_mtok');
|
|
34
|
+
price += calcMtokPrice(modelPrice.cache_write_mtok, usage.cache_write_tokens, 'cache_write_mtok');
|
|
35
|
+
price += calcMtokPrice(modelPrice.cache_read_mtok, usage.cache_read_tokens, 'cache_read_mtok');
|
|
36
|
+
price += calcMtokPrice(modelPrice.output_mtok, usage.output_tokens, 'output_mtok');
|
|
37
|
+
price += calcMtokPrice(modelPrice.input_audio_mtok, usage.input_audio_tokens, 'input_audio_mtok');
|
|
38
|
+
price += calcMtokPrice(modelPrice.cache_audio_read_mtok, usage.cache_audio_read_tokens, 'cache_audio_read_mtok');
|
|
39
|
+
price += calcMtokPrice(modelPrice.output_audio_mtok, usage.output_audio_tokens, 'output_audio_mtok');
|
|
40
|
+
if (modelPrice.requests_kcount !== undefined) {
|
|
41
|
+
price += modelPrice.requests_kcount * ((usage.requests ?? 1) / 1000);
|
|
42
|
+
}
|
|
43
|
+
return price;
|
|
44
|
+
}
|
|
45
|
+
export function getActiveModelPrice(model, timestamp) {
|
|
46
|
+
if (!Array.isArray(model.prices)) {
|
|
47
|
+
return model.prices;
|
|
48
|
+
}
|
|
49
|
+
// Conditional prices: last active wins
|
|
50
|
+
for (let i = model.prices.length - 1; i >= 0; i--) {
|
|
51
|
+
const cond = model.prices[i];
|
|
52
|
+
if (!cond.constraint)
|
|
53
|
+
return cond.prices;
|
|
54
|
+
if (cond.constraint.type === 'start_date') {
|
|
55
|
+
if (timestamp >= new Date(cond.constraint.start_date)) {
|
|
56
|
+
return cond.prices;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (cond.constraint.type === 'time_of_date') {
|
|
60
|
+
const t = timestamp.toTimeString().slice(0, 8);
|
|
61
|
+
if (t >= cond.constraint.start_time && t < cond.constraint.end_time) {
|
|
62
|
+
return cond.prices;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Fallback to first
|
|
67
|
+
return model.prices[0].prices;
|
|
68
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Usage, PriceCalculationResult } from '../types.js';
|
|
2
|
+
export type { Usage, PriceCalculation, PriceCalculationResult } from '../types.js';
|
|
3
|
+
export interface CalcPriceOptions {
|
|
4
|
+
providerId?: string;
|
|
5
|
+
providerApiUrl?: string;
|
|
6
|
+
timestamp?: Date;
|
|
7
|
+
}
|
|
8
|
+
export declare function calcPriceSync(usage: Usage, modelRef: string, options?: CalcPriceOptions): PriceCalculationResult;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getProvidersSync } from '../dataLoader.js';
|
|
2
|
+
import { matchProvider, matchModel } from '../matcher.js';
|
|
3
|
+
import { calcPrice as calcModelPrice, getActiveModelPrice } from '../priceCalc.js';
|
|
4
|
+
export function calcPriceSync(usage, modelRef, options = {}) {
|
|
5
|
+
const providers = getProvidersSync();
|
|
6
|
+
const provider = matchProvider(providers, modelRef, options.providerId, options.providerApiUrl);
|
|
7
|
+
if (!provider)
|
|
8
|
+
return null;
|
|
9
|
+
const model = matchModel(provider.models, modelRef);
|
|
10
|
+
if (!model)
|
|
11
|
+
return null;
|
|
12
|
+
const timestamp = options.timestamp || new Date();
|
|
13
|
+
const model_price = getActiveModelPrice(model, timestamp);
|
|
14
|
+
const price = calcModelPrice(usage, model_price);
|
|
15
|
+
return {
|
|
16
|
+
price,
|
|
17
|
+
provider,
|
|
18
|
+
model,
|
|
19
|
+
model_price,
|
|
20
|
+
auto_update_timestamp: undefined,
|
|
21
|
+
};
|
|
22
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export interface Usage {
|
|
2
|
+
input_tokens?: number;
|
|
3
|
+
cache_write_tokens?: number;
|
|
4
|
+
cache_read_tokens?: number;
|
|
5
|
+
output_tokens?: number;
|
|
6
|
+
input_audio_tokens?: number;
|
|
7
|
+
cache_audio_read_tokens?: number;
|
|
8
|
+
output_audio_tokens?: number;
|
|
9
|
+
requests?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Tier {
|
|
12
|
+
start: number;
|
|
13
|
+
price: number;
|
|
14
|
+
}
|
|
15
|
+
export interface TieredPrices {
|
|
16
|
+
base: number;
|
|
17
|
+
tiers: Tier[];
|
|
18
|
+
}
|
|
19
|
+
export interface ModelPrice {
|
|
20
|
+
input_mtok?: number | TieredPrices;
|
|
21
|
+
cache_write_mtok?: number | TieredPrices;
|
|
22
|
+
cache_read_mtok?: number | TieredPrices;
|
|
23
|
+
output_mtok?: number | TieredPrices;
|
|
24
|
+
input_audio_mtok?: number | TieredPrices;
|
|
25
|
+
cache_audio_read_mtok?: number | TieredPrices;
|
|
26
|
+
output_audio_mtok?: number | TieredPrices;
|
|
27
|
+
requests_kcount?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ConditionalPrice {
|
|
30
|
+
constraint?: StartDateConstraint | TimeOfDateConstraint;
|
|
31
|
+
prices: ModelPrice;
|
|
32
|
+
}
|
|
33
|
+
export interface StartDateConstraint {
|
|
34
|
+
start_date: string;
|
|
35
|
+
type: 'start_date';
|
|
36
|
+
}
|
|
37
|
+
export interface TimeOfDateConstraint {
|
|
38
|
+
start_time: string;
|
|
39
|
+
end_time: string;
|
|
40
|
+
type: 'time_of_date';
|
|
41
|
+
}
|
|
42
|
+
export type MatchLogic = {
|
|
43
|
+
starts_with: string;
|
|
44
|
+
} | {
|
|
45
|
+
ends_with: string;
|
|
46
|
+
} | {
|
|
47
|
+
contains: string;
|
|
48
|
+
} | {
|
|
49
|
+
equals: string;
|
|
50
|
+
} | {
|
|
51
|
+
regex: string;
|
|
52
|
+
} | {
|
|
53
|
+
or: MatchLogic[];
|
|
54
|
+
} | {
|
|
55
|
+
and: MatchLogic[];
|
|
56
|
+
};
|
|
57
|
+
export interface ModelInfo {
|
|
58
|
+
id: string;
|
|
59
|
+
match: MatchLogic;
|
|
60
|
+
name?: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
context_window?: number;
|
|
63
|
+
price_comments?: string;
|
|
64
|
+
prices: ModelPrice | ConditionalPrice[];
|
|
65
|
+
}
|
|
66
|
+
export interface Provider {
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
api_pattern: string;
|
|
70
|
+
pricing_urls?: string[];
|
|
71
|
+
description?: string;
|
|
72
|
+
price_comments?: string;
|
|
73
|
+
model_match?: MatchLogic;
|
|
74
|
+
models: ModelInfo[];
|
|
75
|
+
}
|
|
76
|
+
export interface PriceCalculation {
|
|
77
|
+
price: number;
|
|
78
|
+
provider: Provider;
|
|
79
|
+
model: ModelInfo;
|
|
80
|
+
model_price: ModelPrice;
|
|
81
|
+
auto_update_timestamp?: string;
|
|
82
|
+
}
|
|
83
|
+
export type PriceCalculationResult = PriceCalculation | null;
|
|
84
|
+
export type PriceDataStorage = {
|
|
85
|
+
get: () => Promise<string | null>;
|
|
86
|
+
set: (data: string) => Promise<void>;
|
|
87
|
+
get_last_modified?: () => Promise<number | null>;
|
|
88
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pydantic/genai-prices",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"description": "Calculate prices for calling LLM inference APIs (JS/TS version of genai-prices)",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"genai-prices": "dist/cli.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "vite build && npm run build:cli",
|
|
29
|
+
"build:cli": "tsc src/cli.ts --outDir dist --module nodenext --esModuleInterop --skipLibCheck",
|
|
30
|
+
"postbuild": "if ! head -n1 dist/cli.js | grep -q '^#!/usr/bin/env node$'; then echo '#!/usr/bin/env node' | cat - dist/cli.js > dist/cli.tmp && mv dist/cli.tmp dist/cli.js; fi && chmod +x dist/cli.js",
|
|
31
|
+
"dev": "vite build --watch",
|
|
32
|
+
"typecheck": "tsc",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:ui": "vitest --ui",
|
|
35
|
+
"prepare": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/pydantic/genai-prices"
|
|
40
|
+
},
|
|
41
|
+
"author": "Pydantic Team",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"node-fetch": "^3.3.2",
|
|
45
|
+
"yargs": "^17.7.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@changesets/cli": "^2.29.5",
|
|
49
|
+
"@types/node": "^20.0.0",
|
|
50
|
+
"@types/yargs": "^17.0.24",
|
|
51
|
+
"@vitest/ui": "^3.2.4",
|
|
52
|
+
"typescript": "^5.0.0",
|
|
53
|
+
"vite": "^5.1.3",
|
|
54
|
+
"vite-plugin-dts": "^4.5.3",
|
|
55
|
+
"vitest": "^3.2.4"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20"
|
|
59
|
+
},
|
|
60
|
+
"browser": {
|
|
61
|
+
"node-fetch": false
|
|
62
|
+
},
|
|
63
|
+
"type": "module",
|
|
64
|
+
"optionalDependencies": {
|
|
65
|
+
"@rollup/rollup-linux-x64-gnu": "^4.9.5"
|
|
66
|
+
}
|
|
67
|
+
}
|