@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
1,136 lines (1,135 loc) • 47 kB
JavaScript
import { getManifestForProvider, resolveManifestEntryExact, } from "../models/manifestRegistry.js";
import { getCatalogJsonEntries } from "../providers/catalog/loader.js";
/**
* Per-model pricing for the JSON-catalog providers, derived from
* models.catalog[*].pricingPerMTok (dollars-per-million → dollars-per-token,
* matching this file's unit convention). `_default` is the rate of the
* provider's own models.default model — mirroring what every hand-written
* catalog-provider block already did pre-migration (e.g. xai's `_default`
* was always grok-3's rate, xai's default model).
*
* `pricingPerMTok.cachedInput`, where present, maps to this file's
* `cacheRead` field — same semantics (price for a cached-read hit), just a
* different field name in the two shapes.
*
* A provider contributes no block here when its catalog carries zero
* `pricingPerMTok` data anywhere — fireworks (a fully refreshed model
* roster with no sourced pricing yet) and cloudflare (bills per "neuron",
* not per token — there's no USD-per-token figure to source). Both stay
* entirely hand-written below with their pre-existing symbolic/estimated
* rates, since the JSON has nothing to derive. Mistral's catalog likewise
* has no pricingPerMTok data, and its hand block below uses coarse model
* names ("mistral-large", "codestral", …) that don't match the JSON's real
* ids ("mistral-large-latest", "codestral-latest", …) — it stays
* hand-written too.
*/
const CATALOG_PRICING = Object.fromEntries(getCatalogJsonEntries().flatMap((entry) => {
const priced = Object.fromEntries(Object.entries(entry.models.catalog).flatMap(([modelId, spec]) => spec.pricingPerMTok
? [
[
modelId,
{
input: spec.pricingPerMTok.input / 1_000_000,
output: spec.pricingPerMTok.output / 1_000_000,
...(spec.pricingPerMTok.cachedInput !== undefined
? { cacheRead: spec.pricingPerMTok.cachedInput / 1_000_000 }
: {}),
},
],
]
: []));
if (Object.keys(priced).length === 0) {
return [];
}
const defaultRate = entry.models.catalog[entry.models.default]?.pricingPerMTok;
return [
[
entry.id,
{
...(defaultRate
? {
_default: {
input: defaultRate.input / 1_000_000,
output: defaultRate.output / 1_000_000,
// Without this, a request falling back to `_default`
// bills cached tokens at the full input rate.
...(defaultRate.cachedInput !== undefined
? { cacheRead: defaultRate.cachedInput / 1_000_000 }
: {}),
},
}
: {}),
...priced,
},
],
];
}));
/**
* Per-token pricing data (USD per token). Updated Feb 2026.
* Sources:
* - Anthropic: https://www.anthropic.com/pricing
* - OpenAI: https://openai.com/api/pricing
* - Google: https://ai.google.dev/pricing
*
* Note: Not all supported providers have pricing data. Missing providers
* (Bedrock, Azure, Mistral, etc.) will return 0 from calculateCost().
*/
const PRICING = {
// Anthropic (direct API) — updated March 2026
anthropic: {
// Claude 5 family. Rates from platform.claude.com/docs/en/about-claude/pricing
// (checked 2026-09-23). A 5-minute cache write is 1.25x base input and a
// 1-hour one 2x, on every Claude model below. The cache
// hit multiplier is 0.1x for most models but 0.05x on Opus 5.5 and 0.025x on
// Fable/Mythos 5.1, so those three are listed in full rather than left to the
// longest-prefix fallback — which would silently quote the 5.0 family's rates.
"claude-fable-5-1": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 0.25 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-mythos-5-1": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 0.25 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-opus-5-5": {
input: 4.0 / 1_000_000,
output: 20.0 / 1_000_000,
cacheRead: 0.2 / 1_000_000,
cacheCreation: 5.0 / 1_000_000,
// 1h TTL = 2x base input (platform.claude.com/docs/en/about-claude/pricing, read 2026-09-29).
cacheCreation1h: 8.0 / 1_000_000,
},
// Sonnet 5.5 bills exactly as Sonnet 5 does, cache rates included.
"claude-sonnet-5-5": {
input: 2.0 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.2 / 1_000_000,
cacheCreation: 2.5 / 1_000_000,
// 1h TTL = 2x base input (platform.claude.com/docs/en/about-claude/pricing, read 2026-09-29).
cacheCreation1h: 4.0 / 1_000_000,
},
"claude-fable-5": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 1.0 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-mythos-5": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 1.0 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-opus-5": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
"claude-opus-4-8": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
"claude-opus-4-7": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
// Sonnet 5's $2/$10 launch pricing became the standard price; the
// previously scheduled 2026-09-01 rise to $3/$15 was cancelled.
"claude-sonnet-5": {
input: 2.0 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.2 / 1_000_000,
cacheCreation: 2.5 / 1_000_000,
// 1h TTL = 2x base input (platform.claude.com/docs/en/about-claude/pricing).
cacheCreation1h: 4.0 / 1_000_000,
},
// Claude 4.6 family
"claude-opus-4-6": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
"claude-sonnet-4-6": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
// Claude 4.5 family
"claude-sonnet-4-5-20250929": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
// Undated aliases for the same models. Clients report the bare name far
// more often than the dated one, and without these the longest-prefix
// match lands on the previous generation ("claude-sonnet-4"), which both
// reports as an inferred rate and would silently drift if the two
// generations ever diverge in price.
"claude-sonnet-4-5": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
"claude-opus-4-5": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
"claude-haiku-4-5-20251001": {
input: 1.0 / 1_000_000,
output: 5.0 / 1_000_000,
cacheRead: 0.1 / 1_000_000,
cacheCreation: 1.25 / 1_000_000,
cacheCreation1h: 2.0 / 1_000_000,
},
"claude-haiku-4-5": {
input: 1.0 / 1_000_000,
output: 5.0 / 1_000_000,
cacheRead: 0.1 / 1_000_000,
cacheCreation: 1.25 / 1_000_000,
cacheCreation1h: 2.0 / 1_000_000,
},
// Claude 4.0/4.1 family
"claude-opus-4-1": {
input: 15.0 / 1_000_000,
output: 75.0 / 1_000_000,
cacheRead: 1.5 / 1_000_000,
cacheCreation: 18.75 / 1_000_000,
cacheCreation1h: 30.0 / 1_000_000,
},
"claude-opus-4": {
input: 15.0 / 1_000_000,
output: 75.0 / 1_000_000,
cacheRead: 1.5 / 1_000_000,
cacheCreation: 18.75 / 1_000_000,
cacheCreation1h: 30.0 / 1_000_000,
},
"claude-sonnet-4": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
// Claude 3.x family
"claude-3-7-sonnet": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
"claude-3-5-sonnet": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
"claude-3-5-haiku": {
input: 0.8 / 1_000_000,
output: 4.0 / 1_000_000,
cacheRead: 0.08 / 1_000_000,
cacheCreation: 1.0 / 1_000_000,
cacheCreation1h: 1.6 / 1_000_000,
},
"claude-3-opus": {
input: 15.0 / 1_000_000,
output: 75.0 / 1_000_000,
cacheRead: 1.5 / 1_000_000,
cacheCreation: 18.75 / 1_000_000,
cacheCreation1h: 30.0 / 1_000_000,
},
"claude-3-sonnet": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
"claude-3-haiku": {
input: 0.25 / 1_000_000,
output: 1.25 / 1_000_000,
cacheRead: 0.025 / 1_000_000,
cacheCreation: 0.3125 / 1_000_000,
cacheCreation1h: 0.5 / 1_000_000,
},
},
// Google Vertex AI — Claude models on Vertex (same pricing, @ date suffix)
vertex: {
// A 1-hour cache write is 2x base input, as on the direct API.
// Claude 5 family. Absent here until now, so a Vertex leg serving any of
// them resolved to no rate at all and booked the turn at $0 — on the one
// provider in the chain that bills in real currency.
// `isVertexAnthropicModel` admits every `claude-*` id, so the whole
// family needs an entry, not only the ones the chain names today.
"claude-fable-5-1": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 0.25 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-mythos-5-1": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 0.25 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-fable-5": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 1.0 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-mythos-5": {
input: 10.0 / 1_000_000,
output: 50.0 / 1_000_000,
cacheRead: 1.0 / 1_000_000,
cacheCreation: 12.5 / 1_000_000,
cacheCreation1h: 20.0 / 1_000_000,
},
"claude-opus-5-5": {
input: 4.0 / 1_000_000,
output: 20.0 / 1_000_000,
cacheRead: 0.2 / 1_000_000,
cacheCreation: 5.0 / 1_000_000,
// 1h TTL = 2x base input (platform.claude.com/docs/en/about-claude/pricing, read 2026-09-29).
cacheCreation1h: 8.0 / 1_000_000,
},
// Sonnet 5.5 bills exactly as Sonnet 5 does, cache rates included.
"claude-sonnet-5-5": {
input: 2.0 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.2 / 1_000_000,
cacheCreation: 2.5 / 1_000_000,
// 1h TTL = 2x base input (platform.claude.com/docs/en/about-claude/pricing, read 2026-09-29).
cacheCreation1h: 4.0 / 1_000_000,
},
"claude-opus-5": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
"claude-sonnet-5": {
input: 2.0 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.2 / 1_000_000,
cacheCreation: 2.5 / 1_000_000,
// 1h TTL = 2x base input (platform.claude.com/docs/en/about-claude/pricing).
cacheCreation1h: 4.0 / 1_000_000,
},
"claude-sonnet-4-6": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
"claude-opus-4-6": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
"claude-sonnet-4-5": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
"claude-opus-4-5": {
input: 5.0 / 1_000_000,
output: 25.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
cacheCreation1h: 10.0 / 1_000_000,
},
"claude-haiku-4-5": {
input: 1.0 / 1_000_000,
output: 5.0 / 1_000_000,
cacheRead: 0.1 / 1_000_000,
cacheCreation: 1.25 / 1_000_000,
cacheCreation1h: 2.0 / 1_000_000,
},
"claude-3-5-haiku": {
input: 0.8 / 1_000_000,
output: 4.0 / 1_000_000,
cacheRead: 0.08 / 1_000_000,
cacheCreation: 1.0 / 1_000_000,
cacheCreation1h: 1.6 / 1_000_000,
},
"claude-3-5-sonnet": {
input: 3.0 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.3 / 1_000_000,
cacheCreation: 3.75 / 1_000_000,
cacheCreation1h: 6.0 / 1_000_000,
},
},
// OpenAI — updated March 2026
openai: {
// GPT-5.6 family (Sol/Terra/Luna). Rates reflect OpenAI's 2026-07-30 cut,
// which reduced Luna by 80% and Terra by 20%; Sol was unchanged. Many
// third-party tables still carry the pre-cut numbers.
// List rates. Note that some resellers advertise sol at 50% off
// ($2.50/$15.00/$0.25); those are promotional and expire, so the table
// carries list price and cache read stays the documented 0.1x of input.
"gpt-5.6-sol": {
input: 5.0 / 1_000_000,
output: 30.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
cacheCreation: 6.25 / 1_000_000,
},
"gpt-5.6-terra": {
input: 2.0 / 1_000_000,
output: 12.0 / 1_000_000,
cacheRead: 0.2 / 1_000_000,
cacheCreation: 2.5 / 1_000_000,
},
"gpt-5.6-luna": {
input: 0.2 / 1_000_000,
output: 1.2 / 1_000_000,
cacheRead: 0.02 / 1_000_000,
cacheCreation: 0.25 / 1_000_000,
},
"gpt-5.5": {
input: 5.0 / 1_000_000,
output: 30.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
},
// GPT-5.x family
// cacheRead = 0.25x input (cached input tokens; no separate cacheCreation).
"gpt-5.4": {
input: 2.5 / 1_000_000,
output: 15.0 / 1_000_000,
cacheRead: 0.625 / 1_000_000,
},
"gpt-5.2": {
input: 1.75 / 1_000_000,
output: 14.0 / 1_000_000,
cacheRead: 0.4375 / 1_000_000,
},
"gpt-5.1": {
input: 0.625 / 1_000_000,
output: 5.0 / 1_000_000,
cacheRead: 0.15625 / 1_000_000,
},
"gpt-5.1-codex": {
input: 1.25 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.3125 / 1_000_000,
},
"gpt-5": {
input: 1.25 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.3125 / 1_000_000,
},
"gpt-5-mini": {
input: 0.25 / 1_000_000,
output: 2.0 / 1_000_000,
cacheRead: 0.0625 / 1_000_000,
},
"gpt-5-nano": {
input: 0.05 / 1_000_000,
output: 0.4 / 1_000_000,
cacheRead: 0.0125 / 1_000_000,
},
// GPT-4.1 family
"gpt-4.1": {
input: 2.0 / 1_000_000,
output: 8.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
},
"gpt-4.1-mini": {
input: 0.4 / 1_000_000,
output: 1.6 / 1_000_000,
cacheRead: 0.1 / 1_000_000,
},
"gpt-4.1-nano": {
input: 0.1 / 1_000_000,
output: 0.4 / 1_000_000,
cacheRead: 0.025 / 1_000_000,
},
// GPT-4o family
"gpt-4o": {
input: 2.5 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.625 / 1_000_000,
},
"gpt-4o-mini": {
input: 0.15 / 1_000_000,
output: 0.6 / 1_000_000,
cacheRead: 0.0375 / 1_000_000,
},
// o-series reasoning
o3: {
input: 2.0 / 1_000_000,
output: 8.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
},
"o3-mini": {
input: 1.1 / 1_000_000,
output: 4.4 / 1_000_000,
cacheRead: 0.275 / 1_000_000,
},
"o4-mini": {
input: 1.1 / 1_000_000,
output: 4.4 / 1_000_000,
cacheRead: 0.275 / 1_000_000,
},
o1: {
input: 15.0 / 1_000_000,
output: 60.0 / 1_000_000,
cacheRead: 3.75 / 1_000_000,
},
"o1-mini": {
input: 0.55 / 1_000_000,
output: 2.2 / 1_000_000,
cacheRead: 0.1375 / 1_000_000,
},
// Legacy
"gpt-4-turbo": { input: 10.0 / 1_000_000, output: 30.0 / 1_000_000 },
"gpt-4": { input: 30.0 / 1_000_000, output: 60.0 / 1_000_000 },
"gpt-3.5-turbo": { input: 0.5 / 1_000_000, output: 1.0 / 1_000_000 },
},
// Google (Gemini) — updated March 2026
google: {
// Gemini 3.1 family (all require -preview suffix)
// cacheRead = 0.25x input (cached content tokens; explicit-cache storage is
// billed separately by TTL/time, so no per-token cacheCreation rate here).
"gemini-3.1-pro-preview": {
input: 2.0 / 1_000_000,
output: 12.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
},
"gemini-3.1-flash-lite-preview": {
input: 0.25 / 1_000_000,
output: 1.5 / 1_000_000,
cacheRead: 0.0625 / 1_000_000,
},
"gemini-3.1-flash-image-preview": {
input: 0.5 / 1_000_000,
output: 3.0 / 1_000_000,
cacheRead: 0.125 / 1_000_000,
},
"gemini-3.1-pro-preview-customtools": {
input: 2.0 / 1_000_000,
output: 12.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
},
// Gemini 3 family
"gemini-3-flash-preview": {
input: 0.5 / 1_000_000,
output: 3.0 / 1_000_000,
cacheRead: 0.125 / 1_000_000,
},
"gemini-3-pro-image-preview": {
input: 2.0 / 1_000_000,
output: 12.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
},
/** @deprecated SHUT DOWN March 9, 2026. Migrate to gemini-3.1-pro-preview. */
"gemini-3-pro-preview": {
input: 2.0 / 1_000_000,
output: 12.0 / 1_000_000,
cacheRead: 0.5 / 1_000_000,
},
// Gemini 2.5 family
"gemini-2.5-flash": {
input: 0.3 / 1_000_000,
output: 2.5 / 1_000_000,
cacheRead: 0.075 / 1_000_000,
},
"gemini-2.5-pro": {
input: 1.25 / 1_000_000,
output: 10.0 / 1_000_000,
cacheRead: 0.3125 / 1_000_000,
},
"gemini-2.5-flash-lite": {
input: 0.1 / 1_000_000,
output: 0.4 / 1_000_000,
cacheRead: 0.025 / 1_000_000,
},
// Gemini 2.0 family (deprecated June 2026)
"gemini-2.0-flash": {
input: 0.15 / 1_000_000,
output: 0.6 / 1_000_000,
cacheRead: 0.0375 / 1_000_000,
},
"gemini-2.0-flash-lite": {
input: 0.075 / 1_000_000,
output: 0.3 / 1_000_000,
cacheRead: 0.01875 / 1_000_000,
},
// Gemini 1.5 family
"gemini-1.5-pro": {
input: 1.25 / 1_000_000,
output: 5.0 / 1_000_000,
cacheRead: 0.3125 / 1_000_000,
},
"gemini-1.5-flash": {
input: 0.075 / 1_000_000,
output: 0.3 / 1_000_000,
cacheRead: 0.01875 / 1_000_000,
},
},
// Mistral AI
mistral: {
"mistral-large": { input: 2.0 / 1_000_000, output: 6.0 / 1_000_000 },
"mistral-medium": { input: 2.7 / 1_000_000, output: 8.1 / 1_000_000 },
"mistral-small": { input: 0.2 / 1_000_000, output: 0.6 / 1_000_000 },
codestral: { input: 0.3 / 1_000_000, output: 0.9 / 1_000_000 },
"open-mistral-nemo": {
input: 0.15 / 1_000_000,
output: 0.15 / 1_000_000,
},
},
deepseek: {
"deepseek-chat": {
input: 0.27 / 1_000_000,
output: 1.1 / 1_000_000,
cacheRead: 0.07 / 1_000_000,
},
"deepseek-reasoner": {
input: 0.55 / 1_000_000,
output: 2.19 / 1_000_000,
cacheRead: 0.14 / 1_000_000,
},
},
"nvidia-nim": {
// Source: NVIDIA build.nvidia.com listed rates for openai/gpt-oss-20b,
// read 2026-09-06. NIM prices and models both rot — this PR exists
// because the default model was decommissioned — so re-check against
// https://build.nvidia.com/openai/gpt-oss-20b rather than trusting this.
"openai/gpt-oss-20b": {
input: 0.05 / 1_000_000,
output: 0.2 / 1_000_000,
},
"meta/llama-3.3-70b-instruct": {
input: 0.4 / 1_000_000,
output: 0.4 / 1_000_000,
},
"meta/llama-3.1-405b-instruct": {
input: 1.79 / 1_000_000,
output: 1.79 / 1_000_000,
},
"meta/llama-3.1-70b-instruct": {
input: 0.4 / 1_000_000,
output: 0.4 / 1_000_000,
},
"meta/llama-3.2-90b-vision-instruct": {
input: 0.5 / 1_000_000,
output: 0.5 / 1_000_000,
},
"nvidia/llama-3.3-nemotron-super-49b-v1": {
input: 0.3 / 1_000_000,
output: 0.3 / 1_000_000,
},
"deepseek-ai/deepseek-r1": {
input: 0.55 / 1_000_000,
output: 2.19 / 1_000_000,
},
"mistralai/mixtral-8x22b-instruct-v0.1": {
input: 0.6 / 1_000_000,
output: 0.6 / 1_000_000,
},
"mistralai/mixtral-8x7b-instruct-v0.1": {
input: 0.24 / 1_000_000,
output: 0.24 / 1_000_000,
},
"microsoft/phi-4": { input: 0.07 / 1_000_000, output: 0.07 / 1_000_000 },
"google/gemma-3-27b-it": {
input: 0.07 / 1_000_000,
output: 0.07 / 1_000_000,
},
},
"lm-studio": {
// Local inference — there is no upstream USD price. Reporting a fabricated
// symbolic rate here misstated spend in analytics/spans, so the rate is
// explicitly zero. `calculateCost()` returns 0 for zero rates and the CLI
// / span renderers already treat 0 as "no billable cost" (no $ shown).
_default: { input: 0, output: 0 },
},
llamacpp: {
_default: { input: 0, output: 0 },
},
// xai, groq, cerebras, sambanova — derived from the JSON catalog, see
// CATALOG_PRICING above.
...CATALOG_PRICING,
cohere: {
_default: { input: 2.5 / 1_000_000, output: 10.0 / 1_000_000 },
"command-r-plus": { input: 2.5 / 1_000_000, output: 10.0 / 1_000_000 },
"command-r": { input: 0.15 / 1_000_000, output: 0.6 / 1_000_000 },
"command-r7b-12-2024": {
input: 0.0375 / 1_000_000,
output: 0.15 / 1_000_000,
},
},
// together-ai — derived from the JSON catalog, see CATALOG_PRICING above.
fireworks: {
_default: { input: 0.9 / 1_000_000, output: 0.9 / 1_000_000 },
"accounts/fireworks/models/llama-v3p1-70b-instruct": {
input: 0.9 / 1_000_000,
output: 0.9 / 1_000_000,
},
"accounts/fireworks/models/llama-v3p1-405b-instruct": {
input: 3.0 / 1_000_000,
output: 3.0 / 1_000_000,
},
"accounts/fireworks/models/llama-v3p1-8b-instruct": {
input: 0.2 / 1_000_000,
output: 0.2 / 1_000_000,
},
"accounts/fireworks/models/llama-v3p3-70b-instruct": {
input: 0.9 / 1_000_000,
output: 0.9 / 1_000_000,
},
"accounts/fireworks/models/mixtral-8x22b-instruct": {
input: 1.2 / 1_000_000,
output: 1.2 / 1_000_000,
},
"accounts/fireworks/models/qwen2p5-72b-instruct": {
input: 0.9 / 1_000_000,
output: 0.9 / 1_000_000,
},
"accounts/fireworks/models/qwen2p5-coder-32b-instruct": {
input: 0.9 / 1_000_000,
output: 0.9 / 1_000_000,
},
"accounts/fireworks/models/deepseek-v3": {
input: 0.75 / 1_000_000,
output: 3.0 / 1_000_000,
},
},
// perplexity — derived from the JSON catalog, see CATALOG_PRICING above.
cloudflare: {
// Cloudflare bills per "neuron"; symbolic per-token rate so cost
// attribution dashboards have non-zero values.
_default: { input: 0.011 / 1_000_000, output: 0.011 / 1_000_000 },
},
replicate: {
// Replicate bills per compute-second, not per-token. Symbolic rate.
_default: { input: 0.5 / 1_000_000, output: 1.5 / 1_000_000 },
},
voyage: {
// Voyage bills per million input tokens — output dimension is the
// embedding vector, not generated tokens. We charge to input only.
_default: { input: 0.18 / 1_000_000, output: 0 },
// Voyage 4 family — docs.voyageai.com/docs/embeddings pricing table
"voyage-4": { input: 0.06 / 1_000_000, output: 0 },
"voyage-4-large": { input: 0.12 / 1_000_000, output: 0 },
"voyage-4-lite": { input: 0.02 / 1_000_000, output: 0 },
"voyage-code-4": { input: 0.12 / 1_000_000, output: 0 },
"voyage-3.5": { input: 0.06 / 1_000_000, output: 0 },
"voyage-3.5-lite": { input: 0.02 / 1_000_000, output: 0 },
"voyage-3-large": { input: 0.18 / 1_000_000, output: 0 },
"voyage-code-3": { input: 0.18 / 1_000_000, output: 0 },
"voyage-finance-2": { input: 0.12 / 1_000_000, output: 0 },
"voyage-law-2": { input: 0.12 / 1_000_000, output: 0 },
"voyage-multilingual-2": { input: 0.12 / 1_000_000, output: 0 },
},
jina: {
_default: { input: 0.02 / 1_000_000, output: 0 },
"jina-embeddings-v3": { input: 0.02 / 1_000_000, output: 0 },
"jina-reranker-v2-base-multilingual": {
input: 0.05 / 1_000_000,
output: 0,
},
},
typesafe: {
// TypeSafe bills per million INPUT tokens only. Output is REPORTED but
// not billed: measured live, a decision returns roughly 17 output tokens
// per question (1 question → 21, 20 questions → 354), and the rate on
// them is zero. So `output: 0` is a price, not a token count — do not
// "correct" it by reading a non-zero `usage.output_tokens` off a
// response. A three-question decision costs ~$0.000013 in total.
// Rates from typesafe.ai/pricing (checked 2026-09-20).
_default: { input: 0.042 / 1_000_000, output: 0 },
"jev-latest": { input: 0.042 / 1_000_000, output: 0 },
},
"perplexity-decider": {
// The Decisions API bills $0.04 per million INPUT tokens (image tokens
// included) and charges nothing for output or per request. `output: 0` is
// a price, not a token count, so do not "correct" it from
// `usage.output_tokens`. Rate from the API reference (checked 2026-10-02).
_default: { input: 0.04 / 1_000_000, output: 0 },
"pplx-decider-v1-27b": { input: 0.04 / 1_000_000, output: 0 },
},
"cloudflare-clef": {
// Workers AI bills Clef per million INPUT tokens ($0.24 for clef, $0.09 for
// clef-flash); no output price is listed. `output: 0` is a price, not a token
// count, so do not "correct" it from `usage.output_tokens`. The `cf-ai-neurons`
// response header was checked against the published neuron rates on
// 2026-10-03 and matched them exactly (21,818 and 8,182 neurons per million
// input tokens). Rates from Cloudflare's Workers AI pricing page. An unknown
// model name is priced as clef, the dearer of the two.
_default: { input: 0.24 / 1_000_000, output: 0 },
clef: { input: 0.24 / 1_000_000, output: 0 },
"clef-flash": { input: 0.09 / 1_000_000, output: 0 },
},
stability: {
// Stability AI bills per image; symbolic per-token rate.
_default: { input: 0, output: 0.04 / 1_000 },
},
ideogram: {
_default: { input: 0, output: 0.08 / 1_000 },
},
recraft: {
_default: { input: 0, output: 0.04 / 1_000 },
},
};
/**
* Derives the catalog-provider rows of PROVIDER_ALIASES from each entry's
* own `id` and `aliases` — the same stripped-lowercase key format the hand
* rows below use ("together-ai" -> "togetherai", "workers-ai" -> "workersai").
*/
function buildCatalogProviderAliases() {
return Object.fromEntries(getCatalogJsonEntries().flatMap((entry) => {
const stripped = (s) => s.toLowerCase().replace(/[^a-z]/g, "");
return [
[stripped(entry.id), entry.id],
...entry.aliases.map((alias) => [stripped(alias), entry.id]),
];
}));
}
/**
* Map of normalized provider aliases to canonical PRICING keys.
* After stripping non-alpha characters, e.g. "google-ai" becomes "googleai".
*/
const PROVIDER_ALIASES = {
googleai: "google",
googleaistudio: "google",
googlevertex: "vertex",
anthropic: "anthropic",
openai: "openai",
vertex: "vertex",
google: "google",
azure: "openai",
azureopenai: "openai",
bedrock: "anthropic",
amazonbedrock: "anthropic",
litellm: "__cross_provider__",
openrouter: "__cross_provider__",
openaicompatible: "__cross_provider__",
deepseek: "deepseek",
nvidianim: "nvidia-nim",
nim: "nvidia-nim",
nvidia: "nvidia-nim",
lmstudio: "lm-studio",
llamacpp: "llamacpp",
cohere: "cohere",
replicate: "replicate",
voyage: "voyage",
voyageai: "voyage",
jina: "jina",
jinaai: "jina",
stability: "stability",
stabilityai: "stability",
sd: "stability",
ideogram: "ideogram",
recraft: "recraft",
// 9 JSON-catalog providers (cerebras, cloudflare, fireworks, groq,
// mistral, perplexity, sambanova, together-ai, xai), derived from each
// entry's id/aliases above.
...buildCatalogProviderAliases(),
// Not in mistral.json's `aliases` array (only "grok", "together",
// "pplx", "workers-ai"/"cf-ai" are real catalog aliases) — a pre-existing
// lenient alias kept explicitly since it can't be derived from the catalog.
mistralai: "mistral",
};
/**
* Look up per-token rates for a provider/model combination.
* Normalises the provider name via aliases, then tries an exact model match
* followed by a longest-prefix match so that e.g. "gpt-4o-2024-08-06"
* resolves to the "gpt-4o" entry without a false hit on "gpt-4".
*
* @returns The rate entry, or undefined when the combination is unknown.
*/
/**
* Whether the tail left over after a longest-prefix match is only a version or
* date stamp — e.g. "claude-sonnet-4-5-20250929-v1:0" against the table key
* "claude-sonnet-4-5". That is the *same* model carrying a release suffix, so
* its rate is quoted, not inferred.
*
* Deliberately narrow: "gpt-5.6-sol" against "gpt-5" leaves ".6-sol", which is
* a different model generation and stays flagged as inferred.
*/
const VERSION_SUFFIX_RE = /^[-@](\d{8}|v\d+(:\d+)?|latest)([-@:](\d{8}|v\d+(:\d+)?))*$/;
function isVersionOnlySuffix(model, key) {
if (!model.startsWith(key) || model === key) {
return false;
}
return VERSION_SUFFIX_RE.test(model.slice(key.length));
}
/**
* The manifest's canonical id for `model` when the manifest names it by that
* id or by one of its declared aliases — deliberately excludes
* resolveManifestEntryExact's longest-prefix fallback. See the call site in
* findRates for why the prefix path is unsafe to use for pricing precedence.
*/
function manifestCanonicalId(provider, model) {
const manifest = getManifestForProvider(provider);
if (!manifest) {
return undefined;
}
if (Object.prototype.hasOwnProperty.call(manifest.models, model)) {
return model;
}
return Object.entries(manifest.models).find(([, entry]) => entry.aliases.includes(model))?.[0];
}
/**
* A provider table's entry for `modelKey`: the exact key, else the longest
* key `modelKey` starts with (never the synthetic "_default"). `exact` is
* true for a literal key or a version-only suffix of one.
*/
function findTableEntry(providerPricing, modelKey) {
if (providerPricing[modelKey]) {
return { rates: providerPricing[modelKey], exact: true };
}
const sortedKeys = Object.keys(providerPricing)
.filter((k) => k !== "_default")
.sort((a, b) => b.length - a.length);
const key = sortedKeys.find((k) => modelKey.startsWith(k));
return key
? { rates: providerPricing[key], exact: isVersionOnlySuffix(modelKey, key) }
: undefined;
}
function findRates(provider, model,
/**
* Set to true when the rates came from a literal table key rather than a
* prefix/fallback match. Threaded as an out-param so there is exactly one
* lookup implementation — a second hand-written copy drifted from this one
* and mislabelled every Bedrock and Vertex-Gemini hit.
*/
matchKind) {
const stripped = provider.toLowerCase().replace(/[^a-z]/g, "");
const normalizedProvider = PROVIDER_ALIASES[stripped] ?? stripped;
// Proxy providers (LiteLLM, OpenRouter): search all known providers for a model match
if (normalizedProvider === "__cross_provider__") {
for (const providerPricing of Object.values(PRICING)) {
// Exact match
if (providerPricing[model]) {
if (matchKind) {
matchKind.exact = true;
}
return providerPricing[model];
}
const sortedKeys = Object.keys(providerPricing).sort((a, b) => b.length - a.length);
// model is a prefix of a known key (e.g. "claude-sonnet-4-5" matches "claude-sonnet-4-5-20250929")
const reverseKey = sortedKeys.find((k) => k.startsWith(model));
if (reverseKey) {
return providerPricing[reverseKey];
}
// Known key is a prefix of model (e.g. "gpt-4o" matches "gpt-4o-2024-08-06")
const forwardKey = sortedKeys.find((k) => model.startsWith(k));
if (forwardKey) {
return providerPricing[forwardKey];
}
}
return undefined;
}
const providerPricing = PRICING[normalizedProvider] || PRICING[provider];
if (!providerPricing) {
return undefined;
}
// Bedrock model IDs carry region/vendor prefixes ("us.anthropic.claude-…",
// "global.anthropic.claude-…", "anthropic.claude-…") or arrive as full
// inference-profile ARNs ("arn:aws:bedrock:…:inference-profile/us.anthropic.
// claude-…" — the form this repo's own setup guides recommend). None of
// those match the bare pricing keys ("claude-…"), so every Bedrock call
// priced to $0. Strip everything up to and including the vendor prefix; the
// trailing "-v1:0" version suffix is absorbed by the longest-prefix match
// below. Non-Anthropic Bedrock models (meta./amazon./mistral.) don't match
// and fall through unchanged, exactly as before.
const modelKey = stripped === "bedrock" || stripped === "amazonbedrock"
? model.replace(/^.*\banthropic\./, "")
: model;
// Manifest-backed exact/alias match takes precedence over legacy PRICING
// (see Task 8 of the model metadata consolidation plan). Deliberately
// gated to a NAMED match only (manifest's own id or a declared alias) —
// never resolveManifestEntryExact's longest-prefix fallback. The
// manifest's model set is coarser-grained than PRICING's for several
// live ids today (e.g. openai's manifest names only "gpt-5" while
// PRICING.openai carries distinct exact entries for
// gpt-5.1/5.4/5.5/5.6-sol/terra/luna); letting the manifest's prefix
// match run first would silently steal a more specific PRICING entry's
// rate — confirmed: resolveManifestEntryExact("openai", "gpt-5.4")
// prefix-matches onto the "gpt-5" entry, $1.25/$10 instead of the
// correct $2.5/$15. Everything the manifest doesn't name exactly or
// alias — including a named entry with no pricingPerMTok, e.g.
// claude-sonnet-5 — falls straight through to PRICING's unchanged
// exact+prefix match below, which is exactly "legacy PRICING as the
// fallback for entries the manifest lacks."
const canonicalId = manifestCanonicalId(normalizedProvider, modelKey);
if (canonicalId !== undefined) {
const manifestEntry = resolveManifestEntryExact(normalizedProvider, modelKey);
if (manifestEntry?.pricingPerMTok) {
if (matchKind) {
matchKind.exact = true;
}
// The manifest carries no 1-hour cache-write rate; the table entry for
// the same model does. Without it a 1h write bills at the 5m rate.
const cacheCreation1h = (findTableEntry(providerPricing, modelKey) ??
findTableEntry(providerPricing, canonicalId))?.rates.cacheCreation1h;
return {
input: manifestEntry.pricingPerMTok.input / 1_000_000,
output: manifestEntry.pricingPerMTok.output / 1_000_000,
cacheRead: manifestEntry.pricingPerMTok.cacheRead !== undefined
? manifestEntry.pricingPerMTok.cacheRead / 1_000_000
: undefined,
cacheCreation: manifestEntry.pricingPerMTok.cacheWrite !== undefined
? manifestEntry.pricingPerMTok.cacheWrite / 1_000_000
: undefined,
...(cacheCreation1h !== undefined ? { cacheCreation1h } : {}),
};
}
}
// Exact match, else longest-prefix match
const tableEntry = findTableEntry(providerPricing, modelKey);
if (tableEntry) {
if (matchKind && tableEntry.exact) {
matchKind.exact = true;
}
return tableEntry.rates;
}
// Fallback: Vertex hosts both Claude and Gemini models.
// If no match found under "vertex", try "google" pricing for Gemini models.
// (Run BEFORE the provider-level _default fallback so that Vertex Gemini
// requests get the more specific Google rates rather than a generic Vertex
// _default if one is ever added.)
if (normalizedProvider === "vertex" && model.startsWith("gemini")) {
const googlePricing = PRICING["google"];
if (googlePricing) {
if (googlePricing[model]) {
if (matchKind) {
matchKind.exact = true;
}
return googlePricing[model];
}
const googleKeys = Object.keys(googlePricing).sort((a, b) => b.length - a.length);
const googleKey = googleKeys.find((k) => model.startsWith(k));
if (googleKey) {
return googlePricing[googleKey];
}
}
}
// Provider-level fallback: when a pricing table only has _default (or has
// no entry matching the specific model), use _default. This is mainly for
// local/symbolic providers (lm-studio, llamacpp) that don't enumerate per-
// model pricing.
if (providerPricing["_default"]) {
return providerPricing["_default"];
}
return undefined;
}
/**
* Calculate the dollar cost of a generate/stream call based on token usage.
* Returns 0 if the provider/model combination is not in the pricing table.
*/
export function calculateCost(provider, model, usage) {
const rates = findRates(provider, model);
if (!rates) {
return 0;
}
let cost = 0;
cost += (usage.input || 0) * rates.input;
cost += (usage.output || 0) * rates.output;
if (usage.cacheReadTokens) {
// Price cache reads at the discounted cacheRead rate when one exists;
// otherwise fall back to the full input rate. The overlapping extractor
// (tokenUtils) moves cached tokens out of `input` into `cacheReadTokens`
// for OpenAI/Gemini-family providers, so without this fallback a provider
// that reports cached tokens but has no cacheRead rate would bill those
// tokens at $0 (silent undercharge). Falling back to the input rate keeps
// total cost identical to pre-split billing for unpriced providers, while
// priced providers still get the cheaper cacheRead rate.
cost += usage.cacheReadTokens * (rates.cacheRead ?? rates.input);
}
if (usage.cacheCreationTokens) {
// `cacheCreation1hTokens` is a subset of `cacheCreationTokens` (the 1h-TTL
// share of the total cache-write count), not additive with it — Anthropic
// reports the 1h breakdown inside the same total
// (`cache_creation.ephemeral_1h_input_tokens` within
// `cache_creation_input_tokens`). Clamp to [0, total] so an inconsistent
// or negative count can never bill more tokens than were written, or
// subtract cost.
const oneHourTokens = Math.max(0, Math.min(usage.cacheCreation1hTokens ?? 0, usage.cacheCreationTokens));
const fiveMinTokens = usage.cacheCreationTokens - oneHourTokens;
// Like cache reads, disjoint cache writes must not silently become free
// when a model has no separate creation rate. Preserve explicit zero rates.
cost += fiveMinTokens * (rates.cacheCreation ?? rates.input);
// A model without a `cacheCreation1h` rate keeps today's pricing exactly:
// it falls back through the 5-minute rate to the input rate, the same
// chain the 5-minute share already uses, rather than going unpriced.
cost +=
oneHourTokens *
(rates.cacheCreation1h ?? rates.cacheCreation ?? rates.input);
}
return Math.round(cost * 1_000_000) / 1_000_000; // Round to 6 decimal places
}
/**
* Check if pricing is available for a provider/model combination.
* Checks the rate table directly instead of computing a cost,
* so even very cheap models (e.g. gemini-1.5-flash) are detected correctly.
*
* Zero-rate entries (the local-provider `_default` for lm-studio / llamacpp)
* count as "no pricing" — those providers explicitly don't have an upstream
* USD price, and any caller gated by `hasPricing()` should treat them as
* non-billable rather than zero-cost-billable.
*/
/**
* Whether a model's rates came from an exact table entry or were inferred.
*
* `findRates` falls back to a longest-prefix match, so an unlisted model can
* silently inherit a listed one's rates — e.g. "gpt-5.6-sol" matches the
* "gpt-5" entry and is billed at its price. That is a guess, not a quote, and
* a caller reporting spend needs to be able to say which it had.
*/
export function isExactPricingMatch(provider, model) {
const matchKind = { exact: false };
const rates = findRates(provider, model, matchKind);
return rates !== undefined && matchKind.exact;
}
export function hasPricing(provider, model) {
const rates = findRates(provider, model);
if (!rates) {
return false;
}
return (rates.input > 0 ||
rates.output > 0 ||
(rates.cacheRead ?? 0) > 0 ||
(rates.cacheCreation ?? 0) > 0);
}