Repository Public Research preview
1import { resolveModel } from "./knowledge.mjs";23const round = (n) => Math.round(n * 1e6) / 1e6;45/** Cost in USD from token counts. Prices: official pricing page, read 2026-11-09 (knowledge.json). */6export function estimateCost(input) {7 const m = resolveModel(input.model);8 if (!m?.pricing_usd_per_mtok) return { error: `No price is recorded for "${input.model}".` };9 let p = m.pricing_usd_per_mtok;10 const notes = [];11 const tok = { input: input.input_tokens ?? 0, output: input.output_tokens ?? 0, cache_read: input.cache_read_input_tokens ?? 0, cache_write_5m: input.cache_write_5m_tokens ?? 0, cache_write_1h: input.cache_write_1h_tokens ?? 0 };12 const promptTokens = tok.input + tok.cache_read + tok.cache_write_5m + tok.cache_write_1h;13 if (p.long_prompt && promptTokens > p.long_prompt.threshold_input_tokens) {14 const [i, w5, w1, r, o] = p.long_prompt.prices;15 p = { ...p, input: i, cache_write_5m: w5, cache_write_1h: w1, cache_read: r, output: o, batch_input: i / 2, batch_output: o / 2 };16 notes.push(`long-prompt rates applied (prompt over ${m.pricing_usd_per_mtok.long_prompt.threshold_input_tokens} tokens)`);17 }18 let inRate = p.input, outRate = p.output, mult = 1;19 if (input.fast) {20 if (!p.fast) return { error: `${m.id} has no fast-mode price.` };21 if (input.batch) return { error: "Fast mode cannot be combined with batch." };22 mult = p.fast.input / p.input; inRate = p.fast.input; outRate = p.fast.output; notes.push("fast-mode rates applied");23 }24 if (input.batch) { mult = 0.5; inRate = p.batch_input; outRate = p.batch_output; notes.push("batch discount (50%) applied"); }25 const per = {26 input: (tok.input * inRate) / 1e6, output: (tok.output * outRate) / 1e6, cache_read: (tok.cache_read * p.cache_read * mult) / 1e6,27 cache_write_5m: (tok.cache_write_5m * p.cache_write_5m * mult) / 1e6, cache_write_1h: (tok.cache_write_1h * p.cache_write_1h * mult) / 1e6,28 };29 const single = Object.values(per).reduce((a, b) => a + b, 0);30 const n = input.requests ?? 1;31 return {32 model: m.id, requests: n, tokens_per_request: tok, usd_per_request: round(single), usd_total: round(single * n),33 breakdown_usd_per_request: Object.fromEntries(Object.entries(per).map(([k, v]) => [k, round(v)])),34 rates_usd_per_mtok: { input: inRate, output: outRate, cache_read: p.cache_read * mult },35 notes: [...notes, "Thinking tokens are billed as output tokens.", "Estimate only: verify against response.usage."],36 };37}3839export function compareCost(input, models) {40 return models.map((id) => estimateCost({ ...input, model: id })).filter((x) => !x.error).sort((a, b) => a.usd_total - b.usd_total)41 .map((x) => ({ model: x.model, usd_per_request: x.usd_per_request, usd_total: x.usd_total }));42}43