diff --git a/crates/tinyagents-registry/src/pricing/data.rs b/crates/tinyagents-registry/src/pricing/data.rs index 975ca6f58..b7eece79c 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. 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] = &[ - // ── 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,59 +159,28 @@ 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) ───────────────────────── - 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 Kimi (cache hit published) ────────────────────────────────── + // ── Moonshot: platform.kimi.ai/docs/pricing/chat (2026-11-11). 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, - }, - 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 / Alibaba (cache read ≈ 0.1× input) ───────────────────────────── - 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, + context_window: 262_144, }, + // ── 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×). 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 +189,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,32 +197,45 @@ 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). mistral-large is + // 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 { 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", - 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, + 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", - 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", @@ -256,38 +243,77 @@ 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", 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. 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, - 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, + context_window: 1_000_000, }, - ModelPrice { - provider: "minimax", - model_id: "minimax-m2.7-highspeed", +]; + +/// 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.06, + cached_input_per_mtok_usd: 0.12, output_per_mtok_usd: 2.40, - context_window: 204_800, }, ]; diff --git a/crates/tinyagents-registry/src/pricing/mod.rs b/crates/tinyagents-registry/src/pricing/mod.rs index 05951b347..45a38354a 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` @@ -166,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. @@ -184,17 +194,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 +221,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..2c358905e 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] @@ -182,12 +179,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] @@ -225,6 +259,74 @@ 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); } + +#[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); + } +} + +#[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", + "mistral-medium", + ] { + 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" + ); +} + +#[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}"); + } +} 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, +}