SPB Git

Repository Public Research preview

cost.mjs 2.7 KB 43 lines javascript

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
Files main
Menu

Theme