From 4d62dffffce18e159cc5e50b101a3feba1c4cecb Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Wed, 11 Nov 2026 15:27:33 +0530 Subject: [PATCH 1/4] Refresh the price table against provider pages and model long-context steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every row was checked against its provider's pricing page on 2026-11-11; each provider block now names its page and the date. - Cached input for GPT-5.5/5.4/5.4-mini/5.4-nano and Gemini 2.5 Pro/Flash/Flash-Lite is 0.1× input, not the guessed 0.25×. - Context windows: gpt-5.5 and gpt-5.4 1,050,000; kimi-k2.6 262,144; codestral 128K; qwen-plus and qwen-flash 1M. - qwen-max is 1.60 / 0.32 (implicit cache) / 6.40. - Retired or renamed rows (DeepSeek aliases, kimi-k2.5, qwen3-max, MiniMax m2.7, four Mistral ids) keep their last values and say so. Long-context steps (GPT-5.5/5.4 above 272K, Gemini 2.5 Pro above 200K, qwen-plus/flash above 256K, MiniMax M3 above 512K) live in one LONG_CONTEXT_TIERS table. estimate_cost_usd uses it in place of the MiniMax-only special case, and flat_catalog_entry projects it as price tiers, so both cost paths agree for every call size. M3's row is now its <=512K rate. Closes #393 Closes #394 --- .../tinyagents-registry/src/pricing/data.rs | 133 +++++++++++++----- crates/tinyagents-registry/src/pricing/mod.rs | 72 +++++++--- .../src/pricing/pricing_tests.rs | 64 ++++++++- .../tinyagents-registry/src/pricing/types.rs | 17 +++ 4 files changed, 229 insertions(+), 57 deletions(-) diff --git a/crates/tinyagents-registry/src/pricing/data.rs b/crates/tinyagents-registry/src/pricing/data.rs index 975ca6f58..95bce393c 100644 --- a/crates/tinyagents-registry/src/pricing/data.rs +++ b/crates/tinyagents-registry/src/pricing/data.rs @@ -1,17 +1,19 @@ -//! July 2026 published price and context-window snapshot. +//! Published price and context-window snapshot, last checked 2026-11-11. -use super::ModelPrice; +use super::{LongContextTier, ModelPrice}; /// Published list prices and context windows for the default models the product /// can route to. /// -/// Sources (captured [`super::PRICING_AS_OF`]): vendor pricing/model pages. Anthropic -/// price rows are authoritative (cached = 0.1× input, the documented cache-read -/// rate); other providers' cached rates use the published discount where known -/// and a conservative provider-typical fraction otherwise. Context windows are -/// the published maximums. +/// Each provider block names the page its rows were checked against and the +/// date ([`super::PRICING_AS_OF`]). Rates are standard (not batch) list prices; +/// the cached rate is the cache-read price. A row marked "retired" was no longer +/// priced by its provider on that date and keeps its last known values. Context +/// windows are the published maximums. Rows with a long-context price step are +/// the short-context rates; [`LONG_CONTEXT_TIERS`] holds the step. pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ - // ── Anthropic (authoritative prices; cache read = 0.1× input) ──────────── + // ── Anthropic: platform.claude.com/docs/en/about-claude/pricing (2026-11-11). + // Cache read = 0.1× input; 4.6 and later bill the full 1M window flat. ModelPrice { provider: "anthropic", model_id: "claude-fable-5", @@ -76,28 +78,29 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 5.00, context_window: 200_000, }, - // ── OpenAI (cache read ≈ 0.25× input — published 75% off) ──────────────── + // ── OpenAI: developers.openai.com/api/docs/pricing (2026-11-11). + // Cache read is 0.1× input on GPT-5.x and 0.25× on GPT-4.1 and o3. ModelPrice { provider: "openai", model_id: "gpt-5.5", input_per_mtok_usd: 5.00, - cached_input_per_mtok_usd: 1.25, + cached_input_per_mtok_usd: 0.50, output_per_mtok_usd: 30.00, - context_window: 1_000_000, + context_window: 1_050_000, }, ModelPrice { provider: "openai", model_id: "gpt-5.4", input_per_mtok_usd: 2.50, - cached_input_per_mtok_usd: 0.625, + cached_input_per_mtok_usd: 0.25, output_per_mtok_usd: 15.00, - context_window: 1_000_000, + context_window: 1_050_000, }, ModelPrice { provider: "openai", model_id: "gpt-5.4-mini", input_per_mtok_usd: 0.75, - cached_input_per_mtok_usd: 0.1875, + cached_input_per_mtok_usd: 0.075, output_per_mtok_usd: 4.50, context_window: 400_000, }, @@ -105,7 +108,7 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ provider: "openai", model_id: "gpt-5.4-nano", input_per_mtok_usd: 0.20, - cached_input_per_mtok_usd: 0.05, + cached_input_per_mtok_usd: 0.02, output_per_mtok_usd: 1.25, context_window: 400_000, }, @@ -133,12 +136,14 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 8.00, context_window: 200_000, }, - // ── Google Gemini (cache read ≈ 0.25× input; 1M-token windows) ─────────── + // ── Google: aistudio.google.com/docs/pricing (2026-11-11). Text cache read + // = 0.1× input. 2.5 Pro and Flash are deprecated and re-routed from + // 2026-12-01. ModelPrice { provider: "google", model_id: "gemini-2.5-pro", input_per_mtok_usd: 1.25, - cached_input_per_mtok_usd: 0.3125, + cached_input_per_mtok_usd: 0.125, output_per_mtok_usd: 10.00, context_window: 1_048_576, }, @@ -146,7 +151,7 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ provider: "google", model_id: "gemini-2.5-flash", input_per_mtok_usd: 0.30, - cached_input_per_mtok_usd: 0.075, + cached_input_per_mtok_usd: 0.03, output_per_mtok_usd: 2.50, context_window: 1_048_576, }, @@ -154,11 +159,12 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ provider: "google", model_id: "gemini-2.5-flash-lite", input_per_mtok_usd: 0.10, - cached_input_per_mtok_usd: 0.025, + cached_input_per_mtok_usd: 0.01, output_per_mtok_usd: 0.40, context_window: 1_048_576, }, - // ── DeepSeek (cache hit = 0.1× input, published) ───────────────────────── + // ── DeepSeek: api-docs.deepseek.com/updates (2026-11-11). Both aliases are + // retired (discontinued 2026-07-24); values are the last known ones. ModelPrice { provider: "deepseek", model_id: "deepseek-chat", @@ -175,14 +181,15 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 2.19, context_window: 128_000, }, - // ── Moonshot Kimi (cache hit published) ────────────────────────────────── + // ── Moonshot: platform.kimi.ai/docs/pricing/chat (2026-11-11). kimi-k2.5 is + // retired (listed as deprecated, unpriced). ModelPrice { provider: "moonshot", model_id: "kimi-k2.6", input_per_mtok_usd: 0.95, cached_input_per_mtok_usd: 0.16, output_per_mtok_usd: 4.00, - context_window: 256_000, + context_window: 262_144, }, ModelPrice { provider: "moonshot", @@ -192,7 +199,9 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 3.00, context_window: 256_000, }, - // ── Qwen / Alibaba (cache read ≈ 0.1× input) ───────────────────────────── + // ── Qwen: alibabacloud.com/help/en/model-studio/model-pricing, International + // (2026-11-11). Cache read is the explicit-cache rate (0.1× input); + // qwen-max has implicit cache only (0.2×). qwen3-max is retired (unlisted). ModelPrice { provider: "qwen", model_id: "qwen3-max", @@ -204,9 +213,9 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ ModelPrice { provider: "qwen", model_id: "qwen-max", - input_per_mtok_usd: 1.20, - cached_input_per_mtok_usd: 0.12, - output_per_mtok_usd: 6.00, + input_per_mtok_usd: 1.60, + cached_input_per_mtok_usd: 0.32, + output_per_mtok_usd: 6.40, context_window: 256_000, }, ModelPrice { @@ -215,7 +224,7 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ input_per_mtok_usd: 0.40, cached_input_per_mtok_usd: 0.04, output_per_mtok_usd: 1.20, - context_window: 256_000, + context_window: 1_000_000, }, ModelPrice { provider: "qwen", @@ -223,9 +232,12 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ input_per_mtok_usd: 0.05, cached_input_per_mtok_usd: 0.005, output_per_mtok_usd: 0.40, - context_window: 256_000, + context_window: 1_000_000, }, - // ── Mistral (cache read ≈ 0.1× input) ──────────────────────────────────── + // ── Mistral: docs.mistral.ai model cards (2026-11-11). Cache rates are + // estimates (no published cache price). mistral-large, mistral-medium, + // mistral-small and ministral-8b now name newer or retired models and are + // not re-priced here; codestral (25.08) is checked. ModelPrice { provider: "mistral", model_id: "mistral-large", @@ -256,7 +268,7 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ input_per_mtok_usd: 0.30, cached_input_per_mtok_usd: 0.03, output_per_mtok_usd: 0.90, - context_window: 256_000, + context_window: 128_000, }, ModelPrice { provider: "mistral", @@ -266,12 +278,14 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 0.10, context_window: 128_000, }, + // ── MiniMax: platform.minimax.io/docs/guides/pricing (2026-11-11). M3's row + // is the ≤512K rate. m2.7 and m2.7-highspeed are retired (2026-11-18). ModelPrice { provider: "minimax", model_id: "minimax-m3", - input_per_mtok_usd: 0.60, - cached_input_per_mtok_usd: 0.12, - output_per_mtok_usd: 2.40, + input_per_mtok_usd: 0.30, + cached_input_per_mtok_usd: 0.06, + output_per_mtok_usd: 1.20, context_window: 1_000_000, }, ModelPrice { @@ -291,3 +305,56 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ context_window: 204_800, }, ]; + +/// Published long-context price steps, from the same pages as the rows. Above +/// `above_input_tokens` input tokens (cached tokens included) the whole call +/// bills at the step's rates. +pub const LONG_CONTEXT_TIERS: &[LongContextTier] = &[ + // OpenAI: "Long context: >272K input tokens". + LongContextTier { + model_id: "gpt-5.5", + above_input_tokens: 272_000, + input_per_mtok_usd: 10.00, + cached_input_per_mtok_usd: 1.00, + output_per_mtok_usd: 45.00, + }, + LongContextTier { + model_id: "gpt-5.4", + above_input_tokens: 272_000, + input_per_mtok_usd: 5.00, + cached_input_per_mtok_usd: 0.50, + output_per_mtok_usd: 22.50, + }, + // Google: prompts >200K. + LongContextTier { + model_id: "gemini-2.5-pro", + above_input_tokens: 200_000, + input_per_mtok_usd: 2.50, + cached_input_per_mtok_usd: 0.25, + output_per_mtok_usd: 15.00, + }, + // Qwen: the 256K-1M input tier (non-thinking), billed per request. + LongContextTier { + model_id: "qwen-plus", + above_input_tokens: 256_000, + input_per_mtok_usd: 1.20, + cached_input_per_mtok_usd: 0.12, + output_per_mtok_usd: 3.60, + }, + LongContextTier { + model_id: "qwen-flash", + above_input_tokens: 256_000, + input_per_mtok_usd: 0.25, + cached_input_per_mtok_usd: 0.025, + output_per_mtok_usd: 2.00, + }, + // MiniMax: "> 512k input tokens". The page does not say whether cached + // tokens count toward the boundary; they are counted here. + LongContextTier { + model_id: "minimax-m3", + above_input_tokens: 512_000, + input_per_mtok_usd: 0.60, + cached_input_per_mtok_usd: 0.12, + output_per_mtok_usd: 2.40, + }, +]; diff --git a/crates/tinyagents-registry/src/pricing/mod.rs b/crates/tinyagents-registry/src/pricing/mod.rs index 05951b347..b4c363586 100644 --- a/crates/tinyagents-registry/src/pricing/mod.rs +++ b/crates/tinyagents-registry/src/pricing/mod.rs @@ -1,17 +1,25 @@ //! Offline published model prices, context windows and decorated-id lookup. //! -//! Values preserve the July 2026 consumer snapshot; estimates remain best effort. +//! Values were checked against provider pages on 2026-11-11; estimates remain +//! best effort. //! This module performs no I/O. Consumers own billing authority, runtime capability //! overlays and product configuration. /// Month the published values below were last verified. Bump when refreshing. -pub const PRICING_AS_OF: &str = "2026-07"; +pub const PRICING_AS_OF: &str = "2026-11-11"; mod data; mod types; -pub use data::KNOWN_MODEL_PRICING; -pub use types::ModelPrice; +pub use data::{KNOWN_MODEL_PRICING, LONG_CONTEXT_TIERS}; +pub use types::{LongContextTier, ModelPrice}; + +/// The long-context price step for a catalogued model id, if it has one. +pub fn long_context_tier(model_id: &str) -> Option<&'static LongContextTier> { + LONG_CONTEXT_TIERS + .iter() + .find(|tier| tier.model_id == model_id) +} /// Normalise a model string for matching: lower-case, trim, drop a trailing /// `:tag` / `@date` decoration and a `[...]` suffix. @@ -109,15 +117,23 @@ pub fn estimate_cost_usd( }; let cached = cached_input_tokens.min(input_tokens); let standard_input = input_tokens.saturating_sub(cached); - if p.model_id == "minimax-m3" && input_tokens <= 512_000 { - return (standard_input as f64) * 0.30 / 1_000_000.0 - + (cached as f64) * 0.06 / 1_000_000.0 - + (output_tokens as f64) * 1.20 / 1_000_000.0; - } + let (input_rate, cached_rate, output_rate) = + match long_context_tier(p.model_id).filter(|tier| input_tokens > tier.above_input_tokens) { + Some(tier) => ( + tier.input_per_mtok_usd, + tier.cached_input_per_mtok_usd, + tier.output_per_mtok_usd, + ), + None => ( + p.input_per_mtok_usd, + p.cached_input_per_mtok_usd, + p.output_per_mtok_usd, + ), + }; let per_tok = |mtok_rate: f64| mtok_rate / 1_000_000.0; - (standard_input as f64) * per_tok(p.input_per_mtok_usd) - + (cached as f64) * per_tok(p.cached_input_per_mtok_usd) - + (output_tokens as f64) * per_tok(p.output_per_mtok_usd) + (standard_input as f64) * per_tok(input_rate) + + (cached as f64) * per_tok(cached_rate) + + (output_tokens as f64) * per_tok(output_rate) } /// Resolve a model's flat list-price projection, including explicit `:free` @@ -184,17 +200,16 @@ pub fn model_accepts_image_input(model_id: &str) -> bool { lookup(model_id).is_some_and(|price| VISION_INPUT_MODELS.contains(&price.model_id)) } -/// Project one published catalog row as flat metadata (not a tier-aware estimate) into a TinyAgents model-catalog entry. +/// Project one published catalog row into a TinyAgents model-catalog entry. /// /// This is intentionally a pricing/window projection only. Consumers still /// derives live runtime capability flags (tools, vision, streaming) from the /// provider adapter at construction time, because the static cost catalog does /// not yet encode those fields authoritatively for every provider/model. /// -/// This preserves the migrated flat metadata projection. For MiniMax M3, -/// `estimate_cost_usd` applies a lower rate through 512K input tokens, while -/// this projection uses the row rates and intentionally contains no tiers. -/// Consumers must not treat these two paths as equivalent call estimates. +/// A model with a [`LongContextTier`] gets two price tiers (the row's rates up +/// to the step, the step's above it), so a catalogue estimate and +/// [`estimate_cost_usd`] agree on every call size. pub fn flat_catalog_entry(price: &ModelPrice, source: &str) -> crate::ModelCatalogEntry { crate::ModelCatalogEntry { provider: price.provider.to_string(), @@ -212,8 +227,27 @@ pub fn flat_catalog_entry(price: &ModelPrice, source: &str) -> crate::ModelCatal cache_creation_input_per_token: None, input_audio_per_token: None, output_reasoning_per_token: None, - // The local price table is flat; context-size tiers are not modeled. - tiers: Vec::new(), + // The same long-context step `estimate_cost_usd` applies. + tiers: long_context_tier(price.model_id) + .map(|tier| { + vec![ + tinyagents_harness::cost::PriceTier { + up_to_tokens: Some(tier.above_input_tokens), + input: per_token(price.input_per_mtok_usd), + output: per_token(price.output_per_mtok_usd), + cache_read: per_token(price.cached_input_per_mtok_usd), + cache_write: None, + }, + tinyagents_harness::cost::PriceTier { + up_to_tokens: None, + input: per_token(tier.input_per_mtok_usd), + output: per_token(tier.output_per_mtok_usd), + cache_read: per_token(tier.cached_input_per_mtok_usd), + cache_write: None, + }, + ] + }) + .unwrap_or_default(), }, capabilities: crate::ModelCapabilities { prompt_caching: PROMPT_CACHING_MODELS.contains(&price.model_id), diff --git a/crates/tinyagents-registry/src/pricing/pricing_tests.rs b/crates/tinyagents-registry/src/pricing/pricing_tests.rs index 91eb70ed2..6ea2d819a 100644 --- a/crates/tinyagents-registry/src/pricing/pricing_tests.rs +++ b/crates/tinyagents-registry/src/pricing/pricing_tests.rs @@ -182,12 +182,49 @@ fn short_ids_do_not_match_inside_unrelated_tokens() { } #[test] -fn flat_metadata_projection_is_distinct_from_tier_aware_call_estimates() { - let row = lookup("minimax-m3").unwrap(); - let entry = flat_catalog_entry(row, "consumer"); - assert_eq!(entry.pricing.input_per_token, Some(0.60 / 1_000_000.0)); +fn catalogue_tiers_and_call_estimates_agree_at_every_step() { + use tinyagents_harness::cost::{ModelPricing, estimate_cost}; + use tinyinference_llm::usage::Usage; + for tier in LONG_CONTEXT_TIERS { + let row = lookup(tier.model_id).unwrap(); + let pricing: ModelPricing = flat_catalog_entry(row, "consumer").pricing; + assert_eq!(pricing.tiers.len(), 2, "{}", tier.model_id); + for input in [1_000, tier.above_input_tokens, tier.above_input_tokens + 1] { + let usage = Usage { + cache_read_tokens: input / 2, + ..Usage::new(input, 1_000) + }; + approx( + estimate_cost(&pricing, &usage).total_cost, + estimate_cost_usd(tier.model_id, input, 1_000, input / 2), + ); + } + } +} + +#[test] +fn long_context_steps_bill_the_whole_call_at_the_higher_rate() { + approx(estimate_cost_usd("gpt-5.5", 272_000, 0, 0), 1.36); + approx(estimate_cost_usd("gpt-5.5", 272_001, 0, 0), 2.72001); + approx( + estimate_cost_usd("gemini-2.5-pro", 200_001, 1_000, 0), + 0.5150025, + ); + approx(estimate_cost_usd("gpt-5.4-mini", 400_000, 0, 0), 0.30); +} + +#[test] +fn rows_without_a_step_have_no_catalogue_tiers() { + let entry = flat_catalog_entry(lookup("claude-opus-4-8").unwrap(), "consumer"); assert!(entry.pricing.tiers.is_empty()); - approx(estimate_cost_usd("minimax-m3", 100_000, 0, 0), 0.03); + assert!(long_context_tier("claude-opus-4-8").is_none()); + for tier in LONG_CONTEXT_TIERS { + assert!( + lookup(tier.model_id).is_some(), + "{} has no row", + tier.model_id + ); + } } #[test] @@ -228,3 +265,20 @@ fn approximate_cached_rates_do_not_advertise_prompt_caching() { let mistral = flat_catalog_entry(lookup("mistral-large").unwrap(), "consumer"); assert!(!mistral.capabilities.prompt_caching); } + +#[test] +fn gpt5_and_gemini_cache_reads_are_a_tenth_of_input() { + // #393: these were filled in as 0.25× input; the published rate is 0.1×. + for id in [ + "gpt-5.5", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gemini-2.5-pro", + "gemini-2.5-flash", + "gemini-2.5-flash-lite", + ] { + let row = lookup(id).unwrap(); + approx(row.cached_input_per_mtok_usd, row.input_per_mtok_usd / 10.0); + } +} diff --git a/crates/tinyagents-registry/src/pricing/types.rs b/crates/tinyagents-registry/src/pricing/types.rs index b5454ae71..847244ccd 100644 --- a/crates/tinyagents-registry/src/pricing/types.rs +++ b/crates/tinyagents-registry/src/pricing/types.rs @@ -21,3 +21,20 @@ pub struct ModelPrice { /// widely (128K–1M+); callers budget prompts / trigger compaction off this. pub context_window: u32, } + +/// A model's long-context price step: above `above_input_tokens` input tokens +/// the whole call bills at these rates instead of its [`ModelPrice`] row's. +#[derive(Debug, Clone, Copy)] +pub struct LongContextTier { + /// The [`ModelPrice::model_id`] this step belongs to. + pub model_id: &'static str, + /// Largest input-token count (cached included) still billed at the row's + /// rates. + pub above_input_tokens: u64, + /// USD per million standard input tokens above the step. + pub input_per_mtok_usd: f64, + /// USD per million cached input tokens above the step. + pub cached_input_per_mtok_usd: f64, + /// USD per million output tokens above the step. + pub output_per_mtok_usd: f64, +} From a2eb22b400c318f059e0b769295e59f13808490d Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Wed, 11 Nov 2026 17:06:38 +0530 Subject: [PATCH 2/4] Re-point renamed Mistral ids and drop retired models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Mistral ids now name newer models, priced from their model cards (2026-11-11): mistral-large is Large 4 (0.70 / 0.07 / 2.10, 1M), mistral-small is Small 4 (0.15 / 0.60, 256K) and ministral-8b is Ministral 3 8B (0.15 / 0.15, 256K); the last two publish no cache price and keep the 0.1× estimate. mistral-medium (Medium 3.5) is deprecated and unpriced and keeps its last values. Models their provider retired are removed rather than kept at stale prices: the deepseek-chat and deepseek-reasoner aliases (discontinued 2026-07-24), kimi-k2.5, qwen3-max, minimax-m2.7 and minimax-m2.7-highspeed. A lookup for them now returns None, so an estimate treats them as unpriced. --- .../tinyagents-registry/src/pricing/data.rs | 92 +++++-------------- crates/tinyagents-registry/src/pricing/mod.rs | 6 -- .../src/pricing/pricing_tests.rs | 39 ++++++-- 3 files changed, 54 insertions(+), 83 deletions(-) diff --git a/crates/tinyagents-registry/src/pricing/data.rs b/crates/tinyagents-registry/src/pricing/data.rs index 95bce393c..143af0a59 100644 --- a/crates/tinyagents-registry/src/pricing/data.rs +++ b/crates/tinyagents-registry/src/pricing/data.rs @@ -7,8 +7,8 @@ use super::{LongContextTier, ModelPrice}; /// /// Each provider block names the page its rows were checked against and the /// date ([`super::PRICING_AS_OF`]). Rates are standard (not batch) list prices; -/// the cached rate is the cache-read price. A row marked "retired" was no longer -/// priced by its provider on that date and keeps its last known values. Context +/// the cached rate is the cache-read price. Models their provider retired are +/// removed rather than kept at stale prices. Context /// windows are the published maximums. Rows with a long-context price step are /// the short-context rates; [`LONG_CONTEXT_TIERS`] holds the step. pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ @@ -163,26 +163,7 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 0.40, context_window: 1_048_576, }, - // ── DeepSeek: api-docs.deepseek.com/updates (2026-11-11). Both aliases are - // retired (discontinued 2026-07-24); values are the last known ones. - ModelPrice { - provider: "deepseek", - model_id: "deepseek-chat", - input_per_mtok_usd: 0.14, - cached_input_per_mtok_usd: 0.014, - output_per_mtok_usd: 0.28, - context_window: 128_000, - }, - ModelPrice { - provider: "deepseek", - model_id: "deepseek-reasoner", - input_per_mtok_usd: 0.55, - cached_input_per_mtok_usd: 0.055, - output_per_mtok_usd: 2.19, - context_window: 128_000, - }, - // ── Moonshot: platform.kimi.ai/docs/pricing/chat (2026-11-11). kimi-k2.5 is - // retired (listed as deprecated, unpriced). + // ── Moonshot: platform.kimi.ai/docs/pricing/chat (2026-11-11). ModelPrice { provider: "moonshot", model_id: "kimi-k2.6", @@ -191,25 +172,9 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 4.00, context_window: 262_144, }, - ModelPrice { - provider: "moonshot", - model_id: "kimi-k2.5", - input_per_mtok_usd: 0.60, - cached_input_per_mtok_usd: 0.10, - output_per_mtok_usd: 3.00, - context_window: 256_000, - }, // ── Qwen: alibabacloud.com/help/en/model-studio/model-pricing, International // (2026-11-11). Cache read is the explicit-cache rate (0.1× input); - // qwen-max has implicit cache only (0.2×). qwen3-max is retired (unlisted). - ModelPrice { - provider: "qwen", - model_id: "qwen3-max", - input_per_mtok_usd: 1.20, - cached_input_per_mtok_usd: 0.12, - output_per_mtok_usd: 6.00, - context_window: 256_000, - }, + // qwen-max has implicit cache only (0.2×). ModelPrice { provider: "qwen", model_id: "qwen-max", @@ -234,17 +199,18 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 0.40, context_window: 1_000_000, }, - // ── Mistral: docs.mistral.ai model cards (2026-11-11). Cache rates are - // estimates (no published cache price). mistral-large, mistral-medium, - // mistral-small and ministral-8b now name newer or retired models and are - // not re-priced here; codestral (25.08) is checked. + // ── Mistral: docs.mistral.ai model cards (2026-11-11). mistral-large is + // Large 4 (26.10, published cache price); mistral-small is Small 4 (26.03); + // ministral-8b is Ministral 3 8B (25.12); codestral is 25.08. Small, 8B and + // Codestral publish no cache price, so theirs is an estimate (0.1× input). + // mistral-medium (Medium 3.5) is deprecated and unpriced; last known values. ModelPrice { provider: "mistral", model_id: "mistral-large", - input_per_mtok_usd: 2.00, - cached_input_per_mtok_usd: 0.20, - output_per_mtok_usd: 6.00, - context_window: 128_000, + input_per_mtok_usd: 0.70, + cached_input_per_mtok_usd: 0.07, + output_per_mtok_usd: 2.10, + context_window: 1_000_000, }, ModelPrice { provider: "mistral", @@ -257,10 +223,10 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ ModelPrice { provider: "mistral", model_id: "mistral-small", - input_per_mtok_usd: 0.20, - cached_input_per_mtok_usd: 0.02, + input_per_mtok_usd: 0.15, + cached_input_per_mtok_usd: 0.015, output_per_mtok_usd: 0.60, - context_window: 128_000, + context_window: 256_000, }, ModelPrice { provider: "mistral", @@ -273,13 +239,13 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ ModelPrice { provider: "mistral", model_id: "ministral-8b", - input_per_mtok_usd: 0.10, - cached_input_per_mtok_usd: 0.01, - output_per_mtok_usd: 0.10, - context_window: 128_000, + input_per_mtok_usd: 0.15, + cached_input_per_mtok_usd: 0.015, + output_per_mtok_usd: 0.15, + context_window: 256_000, }, // ── MiniMax: platform.minimax.io/docs/guides/pricing (2026-11-11). M3's row - // is the ≤512K rate. m2.7 and m2.7-highspeed are retired (2026-11-18). + // is the ≤512K rate. ModelPrice { provider: "minimax", model_id: "minimax-m3", @@ -288,22 +254,6 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 1.20, context_window: 1_000_000, }, - ModelPrice { - provider: "minimax", - model_id: "minimax-m2.7", - input_per_mtok_usd: 0.30, - cached_input_per_mtok_usd: 0.06, - output_per_mtok_usd: 1.20, - context_window: 204_800, - }, - ModelPrice { - provider: "minimax", - model_id: "minimax-m2.7-highspeed", - input_per_mtok_usd: 0.60, - cached_input_per_mtok_usd: 0.06, - output_per_mtok_usd: 2.40, - context_window: 204_800, - }, ]; /// Published long-context price steps, from the same pages as the rows. Above diff --git a/crates/tinyagents-registry/src/pricing/mod.rs b/crates/tinyagents-registry/src/pricing/mod.rs index b4c363586..45a38354a 100644 --- a/crates/tinyagents-registry/src/pricing/mod.rs +++ b/crates/tinyagents-registry/src/pricing/mod.rs @@ -182,17 +182,11 @@ const PROMPT_CACHING_MODELS: &[&str] = &[ "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", - "deepseek-chat", - "deepseek-reasoner", "kimi-k2.6", - "kimi-k2.5", - "qwen3-max", "qwen-max", "qwen-plus", "qwen-flash", "minimax-m3", - "minimax-m2.7", - "minimax-m2.7-highspeed", ]; /// Whether the published vision allowlist includes this decorated model id. diff --git a/crates/tinyagents-registry/src/pricing/pricing_tests.rs b/crates/tinyagents-registry/src/pricing/pricing_tests.rs index 6ea2d819a..efc076ad1 100644 --- a/crates/tinyagents-registry/src/pricing/pricing_tests.rs +++ b/crates/tinyagents-registry/src/pricing/pricing_tests.rs @@ -21,11 +21,8 @@ fn lookup_strips_vendor_prefix_openrouter_style() { lookup("anthropic/claude-sonnet-4-6").unwrap().model_id, "claude-sonnet-4-6" ); - assert_eq!( - lookup("deepseek/deepseek-chat").unwrap().model_id, - "deepseek-chat" - ); - assert_eq!(lookup("qwen/qwen3-max").unwrap().model_id, "qwen3-max"); + assert_eq!(lookup("moonshot/kimi-k2.6").unwrap().model_id, "kimi-k2.6"); + assert_eq!(lookup("qwen/qwen-plus").unwrap().model_id, "qwen-plus"); } #[test] @@ -262,7 +259,7 @@ fn free_model_pricing_is_zero_and_does_not_change_reported_charges() { #[test] fn approximate_cached_rates_do_not_advertise_prompt_caching() { - let mistral = flat_catalog_entry(lookup("mistral-large").unwrap(), "consumer"); + let mistral = flat_catalog_entry(lookup("mistral-small").unwrap(), "consumer"); assert!(!mistral.capabilities.prompt_caching); } @@ -282,3 +279,33 @@ fn gpt5_and_gemini_cache_reads_are_a_tenth_of_input() { approx(row.cached_input_per_mtok_usd, row.input_per_mtok_usd / 10.0); } } + +#[test] +fn retired_models_are_gone_and_renamed_mistral_ids_carry_their_new_models() { + for id in [ + "deepseek-chat", + "deepseek-reasoner", + "kimi-k2.5", + "qwen3-max", + "minimax-m2.7", + "minimax-m2.7-highspeed", + ] { + assert!(lookup(id).is_none(), "{id} is retired"); + approx(estimate_cost_usd(id, 1_000, 1_000, 0), 0.0); + } + for (id, input, cached, output, window) in [ + ("mistral-large", 0.70, 0.07, 2.10, 1_000_000), + ("mistral-small", 0.15, 0.015, 0.60, 256_000), + ("ministral-8b", 0.15, 0.015, 0.15, 256_000), + ] { + let row = lookup(id).unwrap(); + approx(row.input_per_mtok_usd, input); + approx(row.cached_input_per_mtok_usd, cached); + approx(row.output_per_mtok_usd, output); + assert_eq!(row.context_window, window, "{id}"); + } + assert_eq!( + lookup("ministral-8b-2512").unwrap().model_id, + "ministral-8b" + ); +} From a5438be6c24cb5defa6820c6378a6cabec16e09b Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Wed, 11 Nov 2026 17:13:57 +0530 Subject: [PATCH 3/4] Drop mistral-medium from the price table mistral-medium now names Medium 3.5, which Mistral marks deprecated (2026-10-31) with no published price, pointing new integrations at Large 4. It is removed like the other retired models rather than kept at a stale price. --- crates/tinyagents-registry/src/pricing/data.rs | 9 --------- crates/tinyagents-registry/src/pricing/pricing_tests.rs | 1 + 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/crates/tinyagents-registry/src/pricing/data.rs b/crates/tinyagents-registry/src/pricing/data.rs index 143af0a59..3356cc534 100644 --- a/crates/tinyagents-registry/src/pricing/data.rs +++ b/crates/tinyagents-registry/src/pricing/data.rs @@ -203,7 +203,6 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ // Large 4 (26.10, published cache price); mistral-small is Small 4 (26.03); // ministral-8b is Ministral 3 8B (25.12); codestral is 25.08. Small, 8B and // Codestral publish no cache price, so theirs is an estimate (0.1× input). - // mistral-medium (Medium 3.5) is deprecated and unpriced; last known values. ModelPrice { provider: "mistral", model_id: "mistral-large", @@ -212,14 +211,6 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 2.10, context_window: 1_000_000, }, - ModelPrice { - provider: "mistral", - model_id: "mistral-medium", - input_per_mtok_usd: 0.40, - cached_input_per_mtok_usd: 0.04, - output_per_mtok_usd: 2.00, - context_window: 128_000, - }, ModelPrice { provider: "mistral", model_id: "mistral-small", diff --git a/crates/tinyagents-registry/src/pricing/pricing_tests.rs b/crates/tinyagents-registry/src/pricing/pricing_tests.rs index efc076ad1..67571ced3 100644 --- a/crates/tinyagents-registry/src/pricing/pricing_tests.rs +++ b/crates/tinyagents-registry/src/pricing/pricing_tests.rs @@ -289,6 +289,7 @@ fn retired_models_are_gone_and_renamed_mistral_ids_carry_their_new_models() { "qwen3-max", "minimax-m2.7", "minimax-m2.7-highspeed", + "mistral-medium", ] { assert!(lookup(id).is_none(), "{id} is retired"); approx(estimate_cost_usd(id, 1_000, 1_000, 0), 0.0); From d7c0740744bc5f876f0f5760efd70c61b57503d0 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Wed, 11 Nov 2026 17:29:25 +0530 Subject: [PATCH 4/4] Price Mistral Large 4 under its own model ids mistral-large-4 and mistral-large-4-0 did not resolve to the mistral-large row: lookup only strips multi-digit snapshot suffixes, so an estimate for them came back as zero. Both ids now have rows with Large 4's rates, and a test keeps them equal to mistral-large's. --- .../tinyagents-registry/src/pricing/data.rs | 20 ++++++++++++++++++- .../src/pricing/pricing_tests.rs | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/tinyagents-registry/src/pricing/data.rs b/crates/tinyagents-registry/src/pricing/data.rs index 3356cc534..b7eece79c 100644 --- a/crates/tinyagents-registry/src/pricing/data.rs +++ b/crates/tinyagents-registry/src/pricing/data.rs @@ -200,7 +200,9 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ context_window: 1_000_000, }, // ── Mistral: docs.mistral.ai model cards (2026-11-11). mistral-large is - // Large 4 (26.10, published cache price); mistral-small is Small 4 (26.03); + // Large 4 (26.10, published cache price), also listed under its own ids + // mistral-large-4 and mistral-large-4-0 (a one-digit suffix is not a + // snapshot decoration, so lookup would not reach the alias row); mistral-small is Small 4 (26.03); // ministral-8b is Ministral 3 8B (25.12); codestral is 25.08. Small, 8B and // Codestral publish no cache price, so theirs is an estimate (0.1× input). ModelPrice { @@ -211,6 +213,22 @@ pub const KNOWN_MODEL_PRICING: &[ModelPrice] = &[ output_per_mtok_usd: 2.10, context_window: 1_000_000, }, + ModelPrice { + provider: "mistral", + model_id: "mistral-large-4", + input_per_mtok_usd: 0.70, + cached_input_per_mtok_usd: 0.07, + output_per_mtok_usd: 2.10, + context_window: 1_000_000, + }, + ModelPrice { + provider: "mistral", + model_id: "mistral-large-4-0", + input_per_mtok_usd: 0.70, + cached_input_per_mtok_usd: 0.07, + output_per_mtok_usd: 2.10, + context_window: 1_000_000, + }, ModelPrice { provider: "mistral", model_id: "mistral-small", diff --git a/crates/tinyagents-registry/src/pricing/pricing_tests.rs b/crates/tinyagents-registry/src/pricing/pricing_tests.rs index 67571ced3..2c358905e 100644 --- a/crates/tinyagents-registry/src/pricing/pricing_tests.rs +++ b/crates/tinyagents-registry/src/pricing/pricing_tests.rs @@ -310,3 +310,23 @@ fn retired_models_are_gone_and_renamed_mistral_ids_carry_their_new_models() { "ministral-8b" ); } + +#[test] +fn mistral_large_4_ids_price_like_mistral_large() { + let alias = lookup("mistral-large").unwrap(); + for id in [ + "mistral-large-4", + "mistral-large-4-0", + "mistral/mistral-large-4-0", + ] { + let row = lookup(id).unwrap_or_else(|| panic!("{id} has no row")); + approx(row.input_per_mtok_usd, alias.input_per_mtok_usd); + approx( + row.cached_input_per_mtok_usd, + alias.cached_input_per_mtok_usd, + ); + approx(row.output_per_mtok_usd, alias.output_per_mtok_usd); + assert_eq!(row.context_window, alias.context_window, "{id}"); + assert!(estimate_cost_usd(id, 1_000_000, 0, 0) > 0.0, "{id}"); + } +}