diff --git a/src/free-models.constants.ts b/src/free-models.constants.ts index 2468809f..e1ecadd3 100644 --- a/src/free-models.constants.ts +++ b/src/free-models.constants.ts @@ -26,7 +26,9 @@ export const FREE_DEFAULT_MODEL = 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning /** Free models in preference order, best first. See free-models.ts. */ export const FREE_PREFERENCE_ORDER: readonly string[] = [ - 'nvidia/nemotron-3-nano-30b', // 5/5 healthy, fastest + // nvidia/nemotron-3-nano-30b led this list until the gateway delisted it on + // 2026-09-08 (NVIDIA deprovisioned it). It is gone from /api/v1/models, so + // the chain-safe default leads again. FREE_DEFAULT_MODEL, // the floor this never drops below 'poolside/laguna-xs-2.1', // different provider 'cohere/north-mini-code', // different provider; needs max_tokens >= 1200 diff --git a/src/free-models.ts b/src/free-models.ts index dd79c475..ba6d2372 100644 --- a/src/free-models.ts +++ b/src/free-models.ts @@ -117,7 +117,15 @@ export { FREE_DEFAULT_MODEL, FREE_POOL_CONTEXT_FLOOR, FREE_PREFERENCE_ORDER }; * chain alive through an NVIDIA-wide outage instead of it being four names * for one upstream. * + * 2026-10-20: nemotron-3-nano-30b, the Base leader in the table above, was + * delisted on 2026-09-08 and has left this list (see LEGACY_FREE_MODEL_IDS). + * The measurements stay as the record of why it led. + * * Not listed, and why: + * - nvidia/muse-glimmer-30b — joined the free pool 2026-10-02 and has not + * been measured on /v1/messages, the bar every rung above had to clear. + * A reasoning model that spends a small max_tokens budget entirely on + * reasoning (gateway probe). Reachable by explicit pin until measured. * - nvidia/nemotron-3.5-lightning — LATENCY, and only latency. Measured * 10.9s / 12.2s / 22.5s for a one-sentence answer at max_tokens 800, * against ~0.8-3s for the rest of the pool. Its name is a lie. (An @@ -213,12 +221,16 @@ export const QUARANTINED_FREE_MODELS: readonly string[] = [ ]; /** - * Free ids Franklin used to name. The gateway still answers for all of them - * (via substitution), but they are gone from /api/v1/models. Kept so old - * session-cost records still price at $0 and so stale aliases can be remapped - * rather than handed back to the user as-is. + * Free ids Franklin used to name. All are gone from /api/v1/models; the + * gateway answers for most of them by substitution. Kept DELIBERATELY: this + * list is what lets isFreeModelId() report an old session's model as $0 and + * keep a stale pin on the free side of every paid-approval gate. It is never + * routed to — FREE_PREFERENCE_ORDER is the routing list. */ export const LEGACY_FREE_MODEL_IDS: readonly string[] = [ + // Delisted 2026-09-08 (NVIDIA deprovisioned it; gateway marks it + // unavailable and redirects to nano-omni). Led the free chain until then. + 'nvidia/nemotron-3-nano-30b', 'nvidia/nemotron-nano-9b-v2', 'nvidia/mistral-nemotron', 'nvidia/nemotron-nano-12b-v2-vl', @@ -228,17 +240,18 @@ export const LEGACY_FREE_MODEL_IDS: readonly string[] = [ ]; /** - * Every free id in the live Base catalog as of 2026-08-30, mapped to its - * catalogued context window. Used for metadata display; NOT for sizing a - * request — use FREE_POOL_CONTEXT_FLOOR for that. + * Every free id (billing_mode "free") in the live catalog as of 2026-10-20 — + * identical on Base and Solana — mapped to its catalogued context window. + * Used for metadata display and as the cold-cache answer to isFreeModelId(); + * NOT for sizing a request — use FREE_POOL_CONTEXT_FLOOR for that. */ export const FREE_MODEL_CONTEXT_WINDOWS: Readonly> = { 'nvidia/nemotron-3-ultra-550b': 1_000_000, 'nvidia/nemotron-3.5-lightning': 1_000_000, 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning': 256_000, 'cohere/north-mini-code': 256_000, - 'nvidia/nemotron-3-nano-30b': 131_072, 'poolside/laguna-xs-2.1': 131_072, + 'nvidia/muse-glimmer-30b': 131_072, 'nvidia/llama-3.2-11b-vision': 128_000, }; diff --git a/src/pricing.ts b/src/pricing.ts index 73ccc56c..e12fd139 100644 --- a/src/pricing.ts +++ b/src/pricing.ts @@ -10,13 +10,13 @@ const LEGACY_PRICING: Record nemotron-3-nano-omni 'nvidia/step-3.7-flash': { input: 0, output: 0 }, // -> nemotron-3.5-lightning 'nvidia/nemotron-3-super-120b-a12b-free': { input: 0, output: 0 }, // pool backing id, never listed + 'nvidia/nemotron-3-nano-30b': { input: 0, output: 0 }, // delisted 2026-09-08 (NVIDIA deprovisioned it) // Retired free models (kept at 0 for legacy session-cost records; gateway no // longer serves these — do NOT route to them). 'nvidia/qwen3-next-80b-a3b-instruct': { input: 0, output: 0 }, // NVIDIA EOL 410, 2026-07-27 @@ -74,6 +75,7 @@ const LEGACY_PRICING: Record grok-4.3", repriced 2026-09-07/09). The old per-id list + // prices here overstated grok-3 / grok-4-0709 by 2.4x/6x and understated + // the fast family, so they now carry what the gateway actually charges. + 'xai/grok-4-fast': { input: 0.2, output: 0.5 }, // not in the gateway catalog — historical only + 'xai/grok-4-fast-reasoning': { input: 1.25, output: 2.5 }, // served as grok-4.3 + 'xai/grok-4-1-fast': { input: 0.2, output: 0.5 }, // not in the gateway catalog — historical only + 'xai/grok-4-1-fast-reasoning': { input: 1.25, output: 2.5 }, // served as grok-4.3 // The two non-reasoning ids sit in the shared Router's SIMPLE chains and - // were charged today, yet had no row here — Franklin reported them as $0 + // were charged, yet once had no row here — Franklin reported them as $0 // and the portfolio scorer, which treats an unpriced id as infinitely - // expensive, could never pick them over a priced rung. Priced with their - // reasoning siblings (xAI bills the pair identically). - 'xai/grok-4-1-fast-non-reasoning': { input: 0.2, output: 0.5 }, // (probed) - 'xai/grok-4-fast-non-reasoning': { input: 0.2, output: 0.5 }, // (probed) - 'xai/grok-4-0709': { input: 3.0, output: 15.0 }, // (probed) gateway lists $3/$15 (was mispriced here at $0.2/$1.5) - 'xai/grok-3-mini': { input: 0.3, output: 0.5 }, // (probed) - 'xai/grok-2-vision': { input: 2.0, output: 10.0 }, - 'xai/grok-3': { input: 3.0, output: 15.0 }, // (probed) + // expensive, could never pick them over a priced rung. + 'xai/grok-4-1-fast-non-reasoning': { input: 1.25, output: 2.5 }, // served as grok-4.3 + 'xai/grok-4-fast-non-reasoning': { input: 1.25, output: 2.5 }, // served as grok-4.3 + 'xai/grok-4-0709': { input: 1.25, output: 2.5 }, // served as grok-4.3 (was $3/$15) + 'xai/grok-3-mini': { input: 1.25, output: 2.5 }, // served as grok-4.3 + 'xai/grok-2-vision': { input: 1.25, output: 2.5 }, // served as grok-4.3 + 'xai/grok-3': { input: 1.25, output: 2.5 }, // served as grok-4.3 (was $3/$15) 'minimax/minimax-m2.5': { input: 0.3, output: 1.2 }, // K2.x (probed): the bare `kimi`/`k2.*` shortcuts resolve to K3, the // explicit ids still answer. 'moonshot/kimi-k2.7': { input: 0.95, output: 4.0 }, 'moonshot/kimi-k2.6': { input: 0.95, output: 4.0 }, 'moonshot/kimi-k2.5': { input: 0.6, output: 3.0 }, - 'nvidia/kimi-k2.5': { input: 0.55, output: 2.5 }, + 'nvidia/kimi-k2.5': { input: 0.6, output: 3.0 }, // gateway list (falls back to moonshot/kimi-k2.5, same price) 'zai/glm-5.1-turbo': { input: 1.2, output: 4.0 }, // was a client alias for zai/glm-5-turbo }; diff --git a/src/ui/model-picker.ts b/src/ui/model-picker.ts index 0aac6ff3..c9ff4d18 100644 --- a/src/ui/model-picker.ts +++ b/src/ui/model-picker.ts @@ -53,6 +53,9 @@ const LEGACY_SHORTCUTS: Record = { 'gpt-6-astra': 'openai/gpt-6-astra', 'gpt-6-sol': 'openai/gpt-6-sol', 'gpt-6-luna': 'openai/gpt-6-luna', + // GPT-6.1 Sol (live /api/v1/models 2026-10-20, Base + Solana): same $2/$10 + // as GPT-6 Sol with a newer knowledge cutoff. Explicit pin only. + 'gpt-6.1-sol': 'openai/gpt-6.1-sol', 'gpt-5.1': 'openai/gpt-5.1', 'gpt-5.6-sol': 'openai/gpt-5.6-sol', 'gpt-5.6-terra': 'openai/gpt-5.6-terra', @@ -117,6 +120,10 @@ const LEGACY_SHORTCUTS: Record = { 'gemini-3.5-flash': 'google/gemini-3.5-flash', 'gemini-2.5-flash': 'google/gemini-2.5-flash', 'gemini-3-flash-preview': 'google/gemini-3-flash-preview', + // Gemini 3.8 Flash (live 2026-10-20, both chains) — same $0.75/$3.75 as 3.6. + // Explicit pins only for now; `flash` is NOT moved in a catalog sync. + 'gemini-3.8': 'google/gemini-3.8-flash', + 'gemini-3.8-flash': 'google/gemini-3.8-flash', // Flash Lite — thinking-mode Gemini for high-throughput work. `flash-lite` // follows the newest (3.5); 3.1 is cheaper still and stays explicit. 'flash-lite': 'google/gemini-3.5-flash-lite', @@ -137,14 +144,14 @@ const LEGACY_SHORTCUTS: Record = { 'grok-4.6': 'xai/grok-4.6', 'grok-4.3': 'xai/grok-4.3', 'grok-build': 'xai/grok-build-0.1', - // grok-3 / grok-4-0709 / the grok-4-1-fast pair are hidden from - // /v1/models but still served and charged (probed 2026-08-29) — explicit - // pins keep resolving to the real ids. Note grok-3 and grok-4-0709 bill at - // $3/$15; `grok` (4.5) is the cheaper flagship. - 'grok-3': 'xai/grok-3', - 'grok-4': 'xai/grok-4-0709', - 'grok-fast': 'xai/grok-4-1-fast-reasoning', - 'grok-4.1': 'xai/grok-4-1-fast-reasoning', + // grok-3 / grok-4-0709 / the grok-4-1-fast pair are retired at xAI. The + // gateway pins every one of them to grok-4.3 and bills grok-4.3's rate + // (blockrun model-maps.ts, "Retired -> grok-4.3"), so these pins name what + // actually answers. `grok-4` is the generation alias and follows `grok`. + 'grok-3': 'xai/grok-4.3', + 'grok-4': 'xai/grok-4.5', + 'grok-fast': 'xai/grok-4.3', + 'grok-4.1': 'xai/grok-4.3', // DeepSeek — paid SKUs route through deepseek/* (gateway aliases serve V4 // Flash modes upstream); free tier routes through nvidia/*. deepseek: 'deepseek/deepseek-chat', // V4 Flash Chat (paid, $0.20/$0.40) @@ -153,6 +160,11 @@ const LEGACY_SHORTCUTS: Record = { 'deepseek-v4-pro': 'deepseek/deepseek-v4-pro', 'dsv4-pro': 'deepseek/deepseek-v4-pro', 'v4-pro': 'deepseek/deepseek-v4-pro', + // V4 Flash Vision (experimental, live 2026-10-20): the paid DeepSeek SKU + // that takes images, $0.30/$1.20. Names match the shared catalog policy. + 'deepseek-v4-flash-vision-exp': 'deepseek/deepseek-v4-flash-vision-exp', + 'deepseek-vision': 'deepseek/deepseek-v4-flash-vision-exp', + 'v4-flash-vision': 'deepseek/deepseek-v4-flash-vision-exp', // The free nvidia/deepseek-v4-flash SKU was EOL'd by the gateway (410). // Point the deepseek-free aliases at the current free default so muscle // memory keeps working without handing back a dead id. @@ -195,12 +207,17 @@ const LEGACY_SHORTCUTS: Record = { omni: FREE_DEFAULT_MODEL, 'free-omni': FREE_DEFAULT_MODEL, 'nano-omni': FREE_DEFAULT_MODEL, - // Base-only free ids. They 400 on Solana, which is a clear error rather than - // a silent wrong model, so the shortcuts stay honest and resolve to the id - // the user typed. - 'nano-30b': 'nvidia/nemotron-3-nano-30b', + // nemotron-3-nano-30b was delisted by the gateway on 2026-09-08 (NVIDIA + // deprovisioned it), so `nano-30b` joins the retired-free-id pattern. + 'nano-30b': FREE_DEFAULT_MODEL, + // Explicit pins to other live free ids (Base + Solana, 2026-10-20) — they + // resolve to the id the user typed, never to the default. laguna: 'poolside/laguna-xs-2.1', 'ultra-550b': 'nvidia/nemotron-3-ultra-550b', + // Meta's Muse Glimmer 30B, free on NVIDIA (gateway 2026-10-02). A reasoning + // model: give it a real max_tokens budget or it answers blank. + 'muse-glimmer': 'nvidia/muse-glimmer-30b', + 'muse-glimmer-30b': 'nvidia/muse-glimmer-30b', // Maverick left the gateway catalog on/before 2026-07-14 and the free pool // silently substitutes for it, so pointing users at it would promise a model // they don't get. Resolve to the current free default instead. @@ -219,6 +236,13 @@ const LEGACY_SHORTCUTS: Record = { 'qwen-max': 'qwen/qwen3.7-max', 'qwen3.7-max': 'qwen/qwen3.7-max', 'qwen-3.7-max': 'qwen/qwen3.7-max', + // Qwen 3.8 (live 2026-10-20, both chains). Max is $2/$6, Flash $0.15/$0.47 + // and takes images. Explicit pins — `qwen-max` / `qwen-flash` stay on 3.7. + 'qwen3.8-max': 'qwen/qwen3.8-max', + 'qwen-3.8-max': 'qwen/qwen3.8-max', + 'qwen3.8-flash': 'qwen/qwen3.8-flash', + 'qwen-3.8-flash': 'qwen/qwen3.8-flash', + 'qwen-vision': 'qwen/qwen3.8-flash', // Plus and Flash round out the paid Qwen line — both 1M context with // reasoning. Flash at $0.03/$0.13 is the cheapest paid model on the gateway. 'qwen-plus': 'qwen/qwen3.7-plus', @@ -248,11 +272,18 @@ const LEGACY_SHORTCUTS: Record = { 'glm-turbo': 'zai/glm-5-turbo', 'glm5': 'zai/glm-5.2', // Tencent + Xiaomi joined the gateway in 2026-08 — cheap reasoning at long - // context, below the frontier tier. - hy3: 'tencent/hy3', - tencent: 'tencent/hy3', + // context, below the frontier tier. tencent/hy3 was retired 2026-09-24 (the + // gateway now redirects it to qwen3.7-flash); Hy4 Preview is Tencent's live + // model, so `hy3` / `tencent` follow it the way the K2.x pins follow K3. + hy3: 'tencent/hy4-preview', + tencent: 'tencent/hy4-preview', + hy4: 'tencent/hy4-preview', + 'hy4-preview': 'tencent/hy4-preview', mimo: 'xiaomi/mimo-v2.5-pro', 'mimo-v2.5-pro': 'xiaomi/mimo-v2.5-pro', + // MiMo V2.5 (non-Pro): cheaper and the vision-capable one ($0.14/$0.28). + 'mimo-v2.5': 'xiaomi/mimo-v2.5', + 'mimo-vision': 'xiaomi/mimo-v2.5', xiaomi: 'xiaomi/mimo-v2.5-pro', kimi: 'moonshot/kimi-k3', k3: 'moonshot/kimi-k3', @@ -263,6 +294,16 @@ const LEGACY_SHORTCUTS: Record = { 'k2.6': 'moonshot/kimi-k3', 'kimi-k2.5': 'moonshot/kimi-k3', 'k2.5': 'moonshot/kimi-k3', + // Mistral Large 4 (live 2026-10-20, both chains): $0.68/$2.09, reasoning + + // vision. Bare `mistral` is safe to take — `mistral-small` / `mistral-nemotron` + // are the free aliases and stay free. + mistral: 'mistral/mistral-large-4', + 'mistral-large': 'mistral/mistral-large-4', + 'mistral-large-4': 'mistral/mistral-large-4', + // Poolside Laguna S 2.1 — the PAID sibling of the free laguna-xs ($0.09/$0.18). + // `laguna` stays on the free XS model: a free alias never moves to a paid one. + 'laguna-s': 'poolside/laguna-s-2.1', + 'laguna-s-2.1': 'poolside/laguna-s-2.1', }; /** @@ -343,6 +384,9 @@ const PROVIDER_ORDER = [ 'deepseek', 'tencent', 'xiaomi', + 'mistral', + 'poolside', + 'cohere', 'nvidia', ]; @@ -358,6 +402,9 @@ const PROVIDER_LABELS: Record = { deepseek: 'DeepSeek', tencent: 'Tencent / Hunyuan', xiaomi: 'Xiaomi / MiMo', + mistral: 'Mistral', + poolside: 'Poolside / Laguna', + cohere: 'Cohere', nvidia: 'Free / NVIDIA', }; diff --git a/test/free-model-matrix.mjs b/test/free-model-matrix.mjs index 20e3d72b..8dd765eb 100644 --- a/test/free-model-matrix.mjs +++ b/test/free-model-matrix.mjs @@ -3,7 +3,7 @@ * * Run: * npm run test:free-models - * FREE_MODEL_MATRIX=nvidia/nemotron-3-ultra-550b,nvidia/nemotron-3-nano-30b npm run test:free-models + * FREE_MODEL_MATRIX=nvidia/nemotron-3-ultra-550b,nvidia/muse-glimmer-30b npm run test:free-models * FREE_MODEL_MATRIX=all npm run test:free-models * FREE_MODEL_MATRIX_PROBES=echo npm run test:free-models * @@ -35,7 +35,7 @@ const requestedModels = (process.env.FREE_MODEL_MATRIX || '') const defaultMatrixModels = new Set([ 'nvidia/nemotron-3-ultra-550b', - 'nvidia/nemotron-3-nano-30b', + 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', 'poolside/laguna-xs-2.1', ]); diff --git a/test/local.mjs b/test/local.mjs index 3475c309..8a0075c2 100644 --- a/test/local.mjs +++ b/test/local.mjs @@ -478,7 +478,7 @@ test('proxy server handles OPTIONS and local model switching without backend cal nemotron: FREE_DEFAULT, devstral: FREE_DEFAULT, 'nano-9b': FREE_DEFAULT, - 'nano-30b': 'nvidia/nemotron-3-nano-30b', + 'nano-30b': FREE_DEFAULT, // delisted 2026-09-08 — retired-free-id pattern laguna: 'poolside/laguna-xs-2.1', 'ultra-550b': 'nvidia/nemotron-3-ultra-550b', }; @@ -6124,9 +6124,10 @@ test('free model catalog: picker, shortcuts, pricing, and weak-model guard stay 'nano-vl': FREE_DEFAULT_MODEL, 'free-vision': FREE_DEFAULT_MODEL, omni: FREE_DEFAULT_MODEL, - 'nano-30b': 'nvidia/nemotron-3-nano-30b', + 'nano-30b': FREE_DEFAULT_MODEL, // delisted 2026-09-08 — retired-free-id pattern laguna: 'poolside/laguna-xs-2.1', 'ultra-550b': 'nvidia/nemotron-3-ultra-550b', + 'muse-glimmer': 'nvidia/muse-glimmer-30b', }; for (const [shortcut, expectedModel] of Object.entries(freeAliases)) { @@ -6165,7 +6166,7 @@ test('buildFallbackChain: a free model never falls back to a paid one', async () // The bug: an explicit free request that hit a 429/5xx walked on to // deepseek/gemini and signed an x402 payment for it. - for (const free of ['poolside/laguna-xs-2.1', 'nvidia/nemotron-3-nano-30b', FREE_DEFAULT_MODEL]) { + for (const free of ['poolside/laguna-xs-2.1', 'nvidia/muse-glimmer-30b', FREE_DEFAULT_MODEL]) { const chain = buildFallbackChain(free); assert.equal(chain[0], free, 'the requested model leads its own chain'); for (const m of chain) { @@ -6314,7 +6315,6 @@ test('free routing profile stays free across router entry points', async () => { // keep a PAID id out of the free chain, not to freeze the roster — see // src/free-models.ts for why each id is in or out. 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning', - 'nvidia/nemotron-3-nano-30b', 'poolside/laguna-xs-2.1', 'cohere/north-mini-code', ]); @@ -7547,12 +7547,13 @@ test('picker trim: shortcuts for hidden models still resolve (muscle-memory pres // flagship-promotion pattern as kimi → k2.7. Explicit pins still resolve. assert.equal(resolveModel('grok'), 'xai/grok-4.5'); assert.equal(resolveModel('grok-4.3'), 'xai/grok-4.3'); - // grok-3 / grok-4-0709 / grok-4-1-fast are hidden from /v1/models but - // still served (probed 2026-08-29) — explicit pins resolve to the real ids. - assert.equal(resolveModel('grok-3'), 'xai/grok-3'); - assert.equal(resolveModel('grok-4'), 'xai/grok-4-0709'); - assert.equal(resolveModel('grok-fast'), 'xai/grok-4-1-fast-reasoning'); - assert.equal(resolveModel('grok-4.1'), 'xai/grok-4-1-fast-reasoning'); + // grok-3 / grok-4-0709 / grok-4-1-fast are retired at xAI; the gateway + // serves them all as grok-4.3 at grok-4.3's rate, so the pins name what + // answers. `grok-4` is the generation alias and follows `grok`. + assert.equal(resolveModel('grok-3'), 'xai/grok-4.3'); + assert.equal(resolveModel('grok-4'), 'xai/grok-4.5'); + assert.equal(resolveModel('grok-fast'), 'xai/grok-4.3'); + assert.equal(resolveModel('grok-4.1'), 'xai/grok-4.3'); assert.equal(resolveModel('grok-build'), 'xai/grok-build-0.1'); }); @@ -9250,16 +9251,17 @@ test('pickFreeFallback: respects alreadyFailed set', async () => { // every later test sees a catalog it never asked for. try { __primeGatewayModelsCache([ - freeEntry('nvidia/nemotron-3-nano-30b'), freeEntry(FREE_DEFAULT_MODEL), freeEntry('poolside/laguna-xs-2.1'), freeEntry('cohere/north-mini-code'), ]); - const failed = new Set(['nvidia/nemotron-3-nano-30b']); + assert.equal(pickFreeFallback('coding', new Set()), FREE_DEFAULT_MODEL, + 'the chain-safe default leads a warm free chain'); + const failed = new Set([FREE_DEFAULT_MODEL]); const pick = pickFreeFallback('coding', failed); - assert.notEqual(pick, 'nvidia/nemotron-3-nano-30b'); - assert.equal(pick, FREE_DEFAULT_MODEL, - `after the Base leader fails, coding should fall to the chain-safe default, got ${pick}`); + assert.notEqual(pick, FREE_DEFAULT_MODEL); + assert.equal(pick, 'poolside/laguna-xs-2.1', + `after the default fails, coding should fall to the next live rung, got ${pick}`); // Cold cache: the both-chains id leads, so a gateway missing the Base-only // rungs is never handed one first. But the chain must NOT collapse to a @@ -12214,9 +12216,11 @@ test('catalog sync 2026-08-29: hidden-but-served ids stay priced, routable and p resetUnavailableModels(); const served = { 'anthropic/claude-opus-4.6': 'opus-4.6', - 'xai/grok-4-0709': 'grok-4', - 'xai/grok-3': 'grok-3', - 'xai/grok-4-1-fast-reasoning': 'grok-fast', + // The retired xAI ids keep a price and stay routable by full id (the + // gateway answers them as grok-4.3); their short pins moved to grok-4.3. + 'xai/grok-4-0709': 'xai/grok-4-0709', + 'xai/grok-3': 'xai/grok-3', + 'xai/grok-4-1-fast-reasoning': 'xai/grok-4-1-fast-reasoning', 'moonshot/kimi-k2.7': 'moonshot/kimi-k2.7', 'openai/gpt-5-nano': 'nano', }; @@ -12255,7 +12259,8 @@ test('catalog sync 2026-08-29: the xAI non-reasoning fast ids in the core chains const { MODEL_PRICING } = await import('../dist/pricing.js'); const { getContextWindow } = await import('../dist/agent/tokens.js'); for (const id of ['xai/grok-4-1-fast-non-reasoning', 'xai/grok-4-fast-non-reasoning']) { - assert.deepEqual(MODEL_PRICING[id], { input: 0.2, output: 0.5 }, `${id} was charged today; it must not estimate as $0`); + // Retired at xAI; the gateway serves and bills them as grok-4.3 ($1.25/$2.50). + assert.deepEqual(MODEL_PRICING[id], { input: 1.25, output: 2.5 }, `${id} is charged at grok-4.3's rate; it must not estimate as $0`); assert.equal(getContextWindow(id), 131_072); } }); @@ -12466,3 +12471,49 @@ test('panel refuses DNS-rebinding hosts and never serves files outside the sessi assert.equal(ok.status, 200, 'normal local requests still work'); }); }); + +test('catalog sync 2026-10-20: every live gateway id is priced and pinnable', async () => { + // Ids that reached GET /api/v1/models (Base and Solana) after the 2026-10-02 + // sync. Prices are the live list, read 2026-10-20 — the static table is the + // offline fallback, so it must not price a live model at the unknown-id $0. + // Read the static table straight from source: MODEL_PRICING merges the + // bundled @blockrun/model-catalog snapshot over it, and that snapshot can + // lag the gateway (it carried deepseek-v4-flash-vision-exp at $0.44/$1.32 + // while /api/v1/models listed $0.30/$1.20). + const { readFileSync } = await import('node:fs'); + const pricingSrc = readFileSync(new URL('../src/pricing.ts', import.meta.url), 'utf8'); + const staticPrice = (id) => { + const esc = id.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&'); + const m = pricingSrc.match(new RegExp(`'${esc}':\\s*\\{\\s*input:\\s*([\\d.]+),\\s*output:\\s*([\\d.]+)`)); + return m ? { input: Number(m[1]), output: Number(m[2]) } : undefined; + }; + const { MODEL_PRICING, hasHistoricalPrice } = await import('../dist/pricing.js'); + const { resolveModel } = await import('../dist/ui/model-picker.js'); + const { isFreeModelId } = await import('../dist/free-models.js'); + const live = { + 'openai/gpt-6.1-sol': [2, 10, 'gpt-6.1-sol'], + 'google/gemini-3.8-flash': [0.75, 3.75, 'gemini-3.8-flash'], + 'deepseek/deepseek-v4-flash-vision-exp': [0.3, 1.2, 'deepseek-vision'], + 'qwen/qwen3.8-max': [2, 6, 'qwen3.8-max'], + 'qwen/qwen3.8-flash': [0.15, 0.47, 'qwen3.8-flash'], + 'tencent/hy4-preview': [0.834, 2.501, 'hy4'], + 'xiaomi/mimo-v2.5': [0.14, 0.28, 'mimo-v2.5'], + 'poolside/laguna-s-2.1': [0.09, 0.18, 'laguna-s'], + 'mistral/mistral-large-4': [0.68, 2.09, 'mistral'], + 'nvidia/muse-glimmer-30b': [0, 0, 'muse-glimmer'], + }; + for (const [id, [input, output, pin]] of Object.entries(live)) { + assert.equal(hasHistoricalPrice(id), true, `${id} needs an offline fallback price`); + assert.deepEqual(staticPrice(id), { input, output }, `${id} fallback price drifted from the live list`); + assert.ok(MODEL_PRICING[id], `${id} must be priced`); + assert.equal(resolveModel(pin), id, `${pin} must resolve to ${id}`); + } + // tencent/hy3 was retired 2026-09-24; the Tencent aliases follow Hy4. + assert.equal(resolveModel('hy3'), 'tencent/hy4-preview'); + assert.equal(resolveModel('tencent'), 'tencent/hy4-preview'); + // Free aliases never move to a paid model. + assert.equal(resolveModel('laguna'), 'poolside/laguna-xs-2.1'); + assert.equal(isFreeModelId('nvidia/muse-glimmer-30b'), true); + assert.equal(isFreeModelId('nvidia/nemotron-3-nano-30b'), true, + 'a delisted free id still prices old sessions at $0'); +});