diff --git a/internal/panel/format.go b/internal/panel/format.go index e1c0e893..d5e1153c 100644 --- a/internal/panel/format.go +++ b/internal/panel/format.go @@ -75,6 +75,7 @@ func formatCompactDecimal(v float64) string { // claude-opus-4-8 → "Opus 4.8" // claude-sonnet-4-6 → "Sonnet 4.6" // claude-haiku-4-5-20251001 → "Haiku 4.5" (trailing date stamp dropped) +// claude-fable-5-1 → "Fable 5.1" // claude-3-5-sonnet → "Sonnet 3.5" (older ordering) // gpt-5.5 → "GPT-5.5" // gpt-5.3-codex → "GPT-5.3 Codex" @@ -98,7 +99,7 @@ func displayModel(raw string) string { ver := make([]string, 0, len(parts)) for _, p := range parts { switch strings.ToLower(p) { - case "opus", "sonnet", "haiku": + case "opus", "sonnet", "haiku", "fable", "mythos": tier = titleWord(p) default: ver = append(ver, p) diff --git a/internal/panel/format_test.go b/internal/panel/format_test.go index 8a978c96..a0da58e8 100644 --- a/internal/panel/format_test.go +++ b/internal/panel/format_test.go @@ -30,6 +30,9 @@ func TestDisplayModel(t *testing.T) { {"claude-sonnet-4-6", "Sonnet 4.6"}, {"claude-haiku-4-5-20251001", "Haiku 4.5"}, // trailing date stamp dropped {"claude-3-5-sonnet", "Sonnet 3.5"}, // older number-first ordering + {"claude-fable-5", "Fable 5"}, + {"claude-fable-5-1", "Fable 5.1"}, + {"claude-mythos-5-1", "Mythos 5.1"}, {"claude-3-5-sonnet-20241022", "Sonnet 3.5"}, {"gpt-5.5", "GPT-5.5"}, {"gpt-5.4", "GPT-5.4"}, @@ -39,6 +42,8 @@ func TestDisplayModel(t *testing.T) { {"gpt-daybreak-blue-latest", "GPT-Daybreak Blue"}, {"gemini-2.5-pro", "Gemini 2.5 Pro"}, {"gemini-2.0-flash", "Gemini 2.0 Flash"}, + {"gpt-6-astra", "GPT-6 Astra"}, + {"grok-4.6", "Grok 4.6"}, // unknown vendor → title-cased fallback {" claude-opus-4-8 ", "Opus 4.8"}, // trimmed {"", "(none)"}, {"(none)", "(none)"}, diff --git a/internal/pricing/pricing.go b/internal/pricing/pricing.go index c8309798..b520eca3 100644 --- a/internal/pricing/pricing.go +++ b/internal/pricing/pricing.go @@ -46,6 +46,14 @@ var gpt56SolPeriods = []ratePeriod{ {From: gpt56SolPromoFrom, Rates: Rates{Input: 4.0e-6, Output: 2.0e-5, CacheRead: 4.0e-7, CacheCreation: 5.0e-6}}, } +// fable51Periods is shared by claude-fable-5-1 and claude-mythos-5-1, which +// ship at the same tariff. Anthropic left the $0.25/MTok cache read +// unconfirmed for Mythos 5.1 at launch; the shared slice mirrors Fable 5.1 +// and keeps the two from drifting. +var fable51Periods = []ratePeriod{ + {Rates: Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 2.5e-7, CacheCreation: 1.25e-5}}, +} + func fixed(r Rates) []ratePeriod { return []ratePeriod{{Rates: r}} } @@ -74,6 +82,11 @@ var ratesByModel = map[string][]ratePeriod{ // fast-mode sessions are estimated at the standard rate. "claude-opus-5": fixed(Rates{Input: 5.0e-6, Output: 2.5e-5, CacheRead: 5.0e-7, CacheCreation: 6.25e-6}), "claude-mythos-5": fixed(Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 1.0e-6, CacheCreation: 1.25e-5}), + // The 5.1 generation kept the $10/$50 tariff but cut the cache read to + // $0.25/MTok. Both ids need their own key so the prefix fallback cannot + // price them at the 5.0 generation's $1/MTok read. + "claude-fable-5-1": fable51Periods, + "claude-mythos-5-1": fable51Periods, // OpenAI — GPT-5 generation. "gpt-5": fixed(Rates{Input: 1.25e-6, Output: 1.0e-5, CacheRead: 1.25e-7}), @@ -113,6 +126,13 @@ var ratesByModel = map[string][]ratePeriod{ // future suffix through the prefix fallback. "gpt-daybreak-blue": gpt56SolPeriods, + // OpenAI — GPT-6 generation. + // Prompts over 272K input reprice the whole request at 2x input and 1.5x + // output, and Fast mode doubles the tariff under the same model id. + // Neither is modelled: session totals carry no per-request prompt length, + // the same documented approximation as the Gemini and Grok context tiers. + "gpt-6-astra": fixed(Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 1.0e-6, CacheCreation: 1.25e-5}), + // OpenAI — o-series reasoning models. "o3": fixed(Rates{Input: 2.0e-6, Output: 8.0e-6, CacheRead: 5.0e-7}), @@ -159,6 +179,8 @@ var modelAliases = map[string]string{ "claude-sonnet-4.5": "claude-sonnet-4-5", "claude-sonnet-4.6": "claude-sonnet-4-6", "claude-haiku-4.5": "claude-haiku-4-5", + "claude-fable-5.1": "claude-fable-5-1", + "claude-mythos-5.1": "claude-mythos-5-1", "gpt-codex-5.3": "gpt-5.3-codex", "gpt-5.6": "gpt-5.6-sol", } diff --git a/internal/pricing/pricing_test.go b/internal/pricing/pricing_test.go index 87eebcc8..65b44e15 100644 --- a/internal/pricing/pricing_test.go +++ b/internal/pricing/pricing_test.go @@ -149,6 +149,12 @@ func TestLookupKnownModelsFromRealStore(t *testing.T) { {"claude-fable-5-20260601", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 1.0e-6, CacheCreation: 1.25e-5}}, {"claude-opus-5", Rates{Input: 5.0e-6, Output: 2.5e-5, CacheRead: 5.0e-7, CacheCreation: 6.25e-6}}, {"claude-mythos-5", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 1.0e-6, CacheCreation: 1.25e-5}}, + // The 5.1 generation reads cache at $0.25/MTok, not $1. + {"claude-fable-5-1", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 2.5e-7, CacheCreation: 1.25e-5}}, + {"claude-fable-5-1-20260901", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 2.5e-7, CacheCreation: 1.25e-5}}, + {"claude-fable-5.1", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 2.5e-7, CacheCreation: 1.25e-5}}, + {"claude-mythos-5-1", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 2.5e-7, CacheCreation: 1.25e-5}}, + {"claude-mythos-5.1", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 2.5e-7, CacheCreation: 1.25e-5}}, {"composer-1", Rates{Input: 1.25e-6, Output: 1.0e-5, CacheRead: 1.25e-7}}, {"composer-1.5", Rates{Input: 3.5e-6, Output: 1.75e-5, CacheRead: 3.5e-7}}, {"composer-2", Rates{Input: 5.0e-7, Output: 2.5e-6, CacheRead: 2.0e-7}}, @@ -175,6 +181,8 @@ func TestLookupKnownModelsFromRealStore(t *testing.T) { {"gpt-5.6-terra", Rates{Input: 2.5e-6, Output: 1.5e-5, CacheRead: 2.5e-7, CacheCreation: 3.125e-6}}, {"gpt-5.6-terra-high", Rates{Input: 2.5e-6, Output: 1.5e-5, CacheRead: 2.5e-7, CacheCreation: 3.125e-6}}, {"gpt-5.6-luna", Rates{Input: 1.0e-6, Output: 6.0e-6, CacheRead: 1.0e-7, CacheCreation: 1.25e-6}}, + {"gpt-6-astra", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 1.0e-6, CacheCreation: 1.25e-5}}, + {"gpt-6-astra-high", Rates{Input: 1.0e-5, Output: 5.0e-5, CacheRead: 1.0e-6, CacheCreation: 1.25e-5}}, // Daybreak program alias for Sol; Codex records the -latest form, // which resolves via the prefix fallback. {"gpt-daybreak-blue", Rates{Input: 5.0e-6, Output: 3.0e-5, CacheRead: 5.0e-7, CacheCreation: 6.25e-6}}, @@ -229,6 +237,19 @@ func TestLookupOpus47DoesNotInheritOpus4Rate(t *testing.T) { // TestLookupComposer2FastDoesNotInheritComposer2Rate is the composer-side // twin of the opus-4-7 guard: Cursor's fast variant costs 3× the standard // tier, so a prefix collapse would under-report it. +// TestLookupFable51DoesNotInheritFable5CacheRate pins the reason +// claude-fable-5-1 needs its own key: without it the prefix fallback lands on +// claude-fable-5 and overstates every cache read by 4x. +func TestLookupFable51DoesNotInheritFable5CacheRate(t *testing.T) { + got, ok := Lookup("claude-fable-5-1", pricingTestTime) + require.True(t, ok) + require.Equal(t, 2.5e-7, got.CacheRead) + + base, ok := Lookup("claude-fable-5", pricingTestTime) + require.True(t, ok) + require.Equal(t, 1.0e-6, base.CacheRead) +} + func TestLookupComposer2FastDoesNotInheritComposer2Rate(t *testing.T) { got, ok := Lookup("composer-2-fast", pricingTestTime) require.True(t, ok)