diff --git a/CHANGELOG.md b/CHANGELOG.md index 016a9073..2f2ec7ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## Unreleased + +### Fixed +- **Grok 4.6 prices from day one** — xAI's launch-day rates + ($2 in / $0.50 cached / $6 out per MTok, doubling for ≥200k-token + prompts; the fast variant at 2x) are baked in until the public + catalogs learn the model, so spend from Grok 4.6 sessions shows + dollars instead of the unpriced ⚠ — including Cursor sessions, whose + usage export brands the model `cursor-grok-4.6-xhigh`. + ## 0.4.33 — 2026-08-12 ### Fixed diff --git a/src-tauri/src/pricing.rs b/src-tauri/src/pricing.rs index e055a9de..83b113cd 100644 --- a/src-tauri/src/pricing.rs +++ b/src-tauri/src/pricing.rs @@ -166,7 +166,7 @@ pub fn generation() -> u64 { /// fingerprinted below — an app update that reprices the same files would /// otherwise leave history at the old dollars until upstream happens to /// rewrite a catalog. -const CORRECTIONS_REV: u32 = 3; // 3: zero-rate catalog placeholders no longer price at $0.00 +const CORRECTIONS_REV: u32 = 4; // 4: grok-4.6 builtin launch pricing /// Stable fingerprint of the effective pricing inputs: the on-disk catalog /// files plus this binary's corrections revision. The persistent spend @@ -668,6 +668,7 @@ fn builtin_price(canonical: &str) -> Option { let bare = canonical .strip_prefix("moonshot/") .or_else(|| canonical.strip_prefix("moonshot-ai/")) + .or_else(|| canonical.strip_prefix("xai/")) .unwrap_or(canonical); match bare { "kimi-k3" | "kimi-k3-code" => Some(Price::flat(3.0, 15.0, 0.3, 3.0)), @@ -675,6 +676,26 @@ fn builtin_price(canonical: &str) -> Option { // output $6, implicit cache read $0.25, explicit cache write $2.50. // Public catalogs still carry 0/0 placeholders for these slugs. "qwen3.8-max" | "qwen3.8-max-preview" => Some(Price::flat(2.0, 6.0, 0.25, 2.5)), + // Grok 4.6, released 2026-08-12 — docs.x.ai/docs/pricing (USD/MTok): + // $2 in / $0.50 cached / $6 out; prompts ≥200k bill $4 / $1 / $12 + // for the WHOLE request (xAI's long-context rule matches + // request_cost's tiering, and Grok spend passes the default 200k + // threshold). xAI bills no separate cache-write rate — writes are + // plain input. The announced 2x "-fast" variant needs no entry: + // the -fast resolution path applies the default 2x multiplier to + // this rate card. Public catalogs don't carry 4.6 yet. + // The cursor- spellings are Cursor's CSV branding for the same + // vendor-billed model ("cursor-grok-4.6-xhigh" on launch day) — + // matched EXPLICITLY rather than via a generic cursor- prefix + // strip, so other cursor-branded slugs (which Cursor may bill at + // its own rates) can never silently price off this baked table. + "grok-4.6" | "grok-4-6" | "cursor-grok-4.6" | "cursor-grok-4-6" => Some(Price { + input_200k: Some(4.0), + output_200k: Some(12.0), + cache_read_200k: Some(1.0), + cache_write_200k: Some(4.0), + ..Price::flat(2.0, 6.0, 0.5, 2.0) + }), _ => None, } } @@ -768,6 +789,33 @@ mod tests { assert_eq!((terra.input, terra.output), (1.75, 11.0)); } + #[test] + fn grok_46_builtin_prices_with_long_context_tier() { + // Vendor rates (docs.x.ai/docs/pricing): $2/$0.50/$6, doubling for + // ≥200k prompts — resolvable in every spelling before the public + // catalogs learn the model. Empty store = builtin only. + let store = super::Store::default(); + // cursor-grok-4.6-xhigh is the exact slug Cursor's CSV logged on + // launch day — 20.6M real tokens showed $0.00 until it resolved. + for slug in + ["grok-4.6", "grok-4-6", "xai/grok-4.6", "grok-4.6-high", "cursor-grok-4.6-xhigh"] + { + let p = super::resolve(&store, slug, 0) + .unwrap_or_else(|| panic!("{slug} did not price")); + assert_eq!((p.input, p.output, p.cache_read, p.cache_write), (2.0, 6.0, 0.5, 2.0), "{slug}"); + assert_eq!( + (p.input_200k, p.output_200k, p.cache_read_200k), + (Some(4.0), Some(12.0), Some(1.0)), + "{slug}" + ); + } + // The fast variant is "twice the price" (launch post): the -fast + // path scales the whole rate card, long-context tier included. + let fast = super::resolve(&store, "grok-4.6-fast", 0).unwrap(); + assert_eq!((fast.input, fast.output, fast.cache_read), (4.0, 12.0, 1.0)); + assert_eq!(fast.input_200k, Some(8.0)); + } + #[test] fn kimi_k3_builtin_prices_every_spelling() { // Vendor-documented rates (platform.kimi.ai): $3 in, $15 out, $0.30 diff --git a/src-tauri/src/spend.rs b/src-tauri/src/spend.rs index b0288ef1..ae1334ae 100644 --- a/src-tauri/src/spend.rs +++ b/src-tauri/src/spend.rs @@ -413,7 +413,18 @@ fn codex_price(model: &str) -> (f64, f64, f64) { fn grok_price(model: &str) -> (f64, f64) { let m = model.to_lowercase(); - if m.contains("code") || m.contains("fast") { + // Grok 4.6's "fast" is a 2x PREMIUM speed tier (launch post: "twice + // the price"), the opposite of the older grok-4-fast/grok-code-fast + // line where "fast" meant a smaller, cheaper model — 4.6 slugs must + // never fall into that cheap branch. (Normally unreachable: the + // baked-in catalog entry resolves 4.6 before this backstop.) + if m.contains("4.6") || m.contains("4-6") { + if m.contains("fast") { + (4.0, 12.0) + } else { + (2.0, 6.0) + } + } else if m.contains("code") || m.contains("fast") { (0.2, 1.5) } else { (3.0, 15.0)