@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.
@@ -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;
@@ -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,3 @@
1
+ import { Usage, ModelPrice, ModelInfo } from './types.js';
2
+ export declare function calcPrice(usage: Usage, modelPrice: ModelPrice): number;
3
+ export declare function getActiveModelPrice(model: ModelInfo, timestamp: Date): ModelPrice;
@@ -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
+ }
@@ -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
+ }