From 80c8455657f9d763c58c78c2e22b744ba73ea165 Mon Sep 17 00:00:00 2001 From: Caian Ertl Date: Wed, 9 Sep 2026 15:42:56 -0300 Subject: [PATCH 1/2] feat(store,panel,server): price GPT-6 Astra, Fable 5.1 and Mythos 5.1 Adds gpt-6-astra, claude-fable-5-1 and claude-mythos-5-1. Sessions on all three were reporting tokens with no cost attached, or with an overstated one. gpt-6-astra bills $10/$50 per MTok with cache reads at $1 and cache writes at $12.50. Prompts over 272K input reprice the entire request at 2x input and 1.5x output, and Fast mode doubles the tariff under the same model id; neither is modelled, because a session total carries no per-request prompt length. Same documented approximation the table already applies to the Gemini context tiers, the Grok 200K surcharge and Opus 5 fast mode. The 5.1 Claude generation is the subtle one. It keeps Fable 5's $10/$50 tariff but cuts the cache read from $1 to $0.25 per MTok, so the prefix fallback onto claude-fable-5 resolves without looking broken while overstating every cache read by 4x. Both ids get their own key, sharing one rate slice so the pair cannot drift; Anthropic left the $0.25 read unconfirmed for Mythos 5.1 at launch, and the shared slice records that assumption in one place. The dot forms claude-fable-5.1 and claude-mythos-5.1 go in the alias table: the prefix fallback requires a "key-" separator, so a dotted minor version can never reach the canonical key on its own. Sources: openai.com/index/gpt-6-astra and OpenAI's model reference page; Anthropic's model pricing table. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015UmSUq3v73JA3ZJr4nFhm9 --- internal/pricing/pricing.go | 22 ++++++++++++++++++++++ internal/pricing/pricing_test.go | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+) 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) From adaafd6beb21de31a5edcbb77e0688b43b5670bf Mon Sep 17 00:00:00 2001 From: Caian Ertl Date: Wed, 9 Sep 2026 15:43:22 -0300 Subject: [PATCH 2/2] fix(panel): render Claude Fable and Mythos tiers with dotted versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit displayModel only recognised opus, sonnet and haiku as Claude tier words, so anything else fell through to the title-cased fallback and lost the dot in the version: claude-fable-5-1 rendered as "Fable 5 1". Adding fable and mythos to the tier switch gives "Fable 5.1" and "Mythos 5.1". The 5.0 ids are unaffected — claude-fable-5 and claude-mythos-5 have a single version segment and still render "Fable 5" and "Mythos 5". Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015UmSUq3v73JA3ZJr4nFhm9 --- internal/panel/format.go | 3 ++- internal/panel/format_test.go | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) 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)"},