Repository Public Research preview
1import { KB, resolveModel } from "./knowledge.mjs";23const round = (n) => Math.round(n * 1e6) / 1e6;45/** Cost in USD from token counts. Prices: official pricing page (date in knowledge.json → sources). */6export function estimateCost(input) {7 const other = KB.other_prices_usd_per_mtok[input.model];8 if (other) {9 const usd = ((input.input_tokens ?? 0) * other.input) / 1e6;10 return { model: input.model, requests: input.requests ?? 1, usd_per_request: round(usd), usd_total: round(usd * (input.requests ?? 1)), rates_usd_per_mtok: other, notes: ["Embedding and moderation models bill input tokens only."] };11 }12 const m = resolveModel(input.model);13 const p = m?.pricing_usd_per_mtok;14 if (!p) return { error: `No price is recorded for "${input.model}".` };15 const notes = [];16 const tok = { input: input.input_tokens ?? 0, cached_input: input.cached_input_tokens ?? 0, output: input.output_tokens ?? 0 };17 let inRate = p.input, outRate = p.output, cachedRate = p.cached_input ?? p.input;18 if (input.batch && input.fast) return { error: "Batch and fast mode cannot be combined." };19 const prompt = tok.input + tok.cached_input;20 if (p.long_context && prompt > p.long_context.above_input_tokens) {21 const f = p.long_context.input / p.input;22 inRate = p.long_context.input; outRate = p.long_context.output; cachedRate *= f;23 notes.push(`long-context rates applied (prompt over ${p.long_context.above_input_tokens} tokens)`);24 }25 if (input.batch) {26 if (p.batch_input == null) return { error: `${m.id} has no batch price.` };27 const fi = p.batch_input / p.input, fo = p.batch_output / p.output;28 inRate *= fi; outRate *= fo; cachedRate *= fi; notes.push("batch rates applied");29 }30 if (input.fast) {31 if (p.fast_input == null) return { error: `${m.id} has no fast-mode price.` };32 const fi = p.fast_input / p.input, fo = p.fast_output / p.output;33 inRate *= fi; outRate *= fo; cachedRate *= fi; notes.push("fast-mode (priority) rates applied");34 }35 if (tok.cached_input && p.cached_input == null) notes.push(`${m.id} has no cached-input discount: cached tokens billed as input`);36 const per = { input: (tok.input * inRate) / 1e6, cached_input: (tok.cached_input * cachedRate) / 1e6, output: (tok.output * outRate) / 1e6 };37 const single = per.input + per.cached_input + per.output;38 const n = input.requests ?? 1;39 return {40 model: m.id, requests: n, tokens_per_request: tok, usd_per_request: round(single), usd_total: round(single * n),41 breakdown_usd_per_request: Object.fromEntries(Object.entries(per).map(([k, v]) => [k, round(v)])),42 rates_usd_per_mtok: { input: round(inRate), cached_input: round(cachedRate), output: round(outRate) },43 notes: [...notes, "Reasoning tokens are billed as output tokens.", "Hosted tools (web search, code interpreter, file search) are billed separately.", "Estimate only: verify against response.usage."],44 };45}4647export function compareCost(input, models) {48 return models.map((id) => estimateCost({ ...input, model: id })).filter((x) => !x.error).sort((a, b) => a.usd_total - b.usd_total)49 .map((x) => ({ model: x.model, usd_per_request: x.usd_per_request, usd_total: x.usd_total }));50}51