Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/panel/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions internal/panel/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand All @@ -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)"},
Expand Down
22 changes: 22 additions & 0 deletions internal/pricing/pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}
Expand Down Expand Up @@ -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}),
Expand Down Expand Up @@ -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}),

Expand Down Expand Up @@ -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",
}
Expand Down
21 changes: 21 additions & 0 deletions internal/pricing/pricing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
Expand All @@ -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}},
Expand Down Expand Up @@ -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)
Expand Down
Loading