Files
shiro-neko/src/pricing.ts
T

66 lines
3.0 KiB
TypeScript
Raw Normal View History

export const PRICING_VERIFIED_AT = '2026-09-09';
/**
* Source: hand-entered from provider pricing pages as of PRICING_VERIFIED_AT.
* Anthropic https://www.anthropic.com/pricing, OpenAI https://openai.com/api/pricing,
* DeepSeek https://api-docs.deepseek.com/quick_start/pricing, xAI https://x.ai/api.
* Rates drift; verify before billing. Update PRICING_VERIFIED_AT when changing RATES.
* Displayed in /cost so a stale table is visible.
*/
2026-09-02 17:30:18 +07:00
export type Rate = { inputPerMTok: number; outputPerMTok: number };
/**
* USD per million tokens. Prefix match on the model id, longest first, so
* `claude-sonnet-4-5-20250929` resolves via `claude-sonnet-4-5`. Published rates
* drift, so this is a best-effort estimate rather than a billing source.
*
* Source: vendor pricing pages, checked 2026-09-17. Anthropic (Anthropic API, not
* Batch) and OpenAI listed rates; DeepSeek and Grok per their API pricing. Rates
* are for input, then output. Re-verify before trusting a live spend figure.
2026-09-02 17:30:18 +07:00
*/
const RATES: Record<string, Rate> = {
'claude-opus-4': { inputPerMTok: 15, outputPerMTok: 75 },
'claude-sonnet-4': { inputPerMTok: 3, outputPerMTok: 15 },
'claude-haiku-4': { inputPerMTok: 1, outputPerMTok: 5 },
'claude-3-5-haiku': { inputPerMTok: 0.8, outputPerMTok: 4 },
'gpt-5-mini': { inputPerMTok: 0.25, outputPerMTok: 2 },
'gpt-5-nano': { inputPerMTok: 0.05, outputPerMTok: 0.4 },
'gpt-5': { inputPerMTok: 1.25, outputPerMTok: 10 },
'gpt-4o-mini': { inputPerMTok: 0.15, outputPerMTok: 0.6 },
'gpt-4o': { inputPerMTok: 2.5, outputPerMTok: 10 },
'o4-mini': { inputPerMTok: 1.1, outputPerMTok: 4.4 },
'deepseek-chat': { inputPerMTok: 0.27, outputPerMTok: 1.1 },
'deepseek-reasoner': { inputPerMTok: 0.55, outputPerMTok: 2.19 },
'grok-4': { inputPerMTok: 3, outputPerMTok: 15 },
};
/** Strips a provider prefix such as `anthropic/` that OpenRouter-style ids carry. */
const bare = (modelId: string) => modelId.toLowerCase().split('/').at(-1) ?? modelId.toLowerCase();
export function rateFor(modelId: string): Rate | undefined {
const id = bare(modelId);
const key = Object.keys(RATES)
.filter((k) => id.startsWith(k))
.sort((a, b) => b.length - a.length)[0];
return key ? RATES[key] : undefined;
}
export function costOf(modelId: string, inputTokens: number, outputTokens: number): number | undefined {
const rate = rateFor(modelId);
if (!rate) return undefined;
return (inputTokens / 1_000_000) * rate.inputPerMTok + (outputTokens / 1_000_000) * rate.outputPerMTok;
}
export function formatUsd(amount: number): string {
if (amount === 0) return '$0.00';
if (amount < 0.01) return `$${amount.toFixed(4)}`;
return `$${amount.toFixed(2)}`;
}
/** One-line token and cost summary, omitting the cost when the model is unpriced. */
export function usageLine(modelId: string, inputTokens: number, outputTokens: number): string {
const tokens = `${inputTokens} in / ${outputTokens} out tokens`;
const cost = costOf(modelId, inputTokens, outputTokens);
return cost === undefined ? `${tokens} (${modelId} is unpriced)` : `${tokens} - ${formatUsd(cost)}`;
}