From b5c9cba7f24562423efd00b08e79c0138bc614ba Mon Sep 17 00:00:00 2001 From: Pratik Bhavsar Date: Wed, 11 Nov 2026 20:17:07 +0530 Subject: [PATCH 1/2] Price Claude Fable 5.2 and Mythos 5.2 Add the published rates ($8 input, $10 5m cache write, $0.20 cache hit, $40 output per MTok), the 1M context window, the frontier tier, and the fast-mode (2x) and US inference-geo (1.1x) modifiers. Rates are from platform.claude.com/docs/en/about-claude/pricing, checked 2026-11-11. Co-Authored-By: Claude Code --- tests/contracts/test_model_pricing.py | 16 +++++++++ tests/test_claude_cost_correctness.py | 42 ++++++++++++++++++++++++ token_meter/app_helpers/claude_traces.py | 4 +-- token_meter/app_helpers/common.py | 1 + token_meter/models/catalog.py | 9 +++++ token_meter/models/tiers.py | 2 ++ 6 files changed, 72 insertions(+), 2 deletions(-) diff --git a/tests/contracts/test_model_pricing.py b/tests/contracts/test_model_pricing.py index c2ebf1ba..0e2e0ff0 100644 --- a/tests/contracts/test_model_pricing.py +++ b/tests/contracts/test_model_pricing.py @@ -6,6 +6,7 @@ BUILTIN_MODEL_PRICE_HISTORY, BUILTIN_PRICE_SOURCES, BUILTIN_PRICE_TABLES, + CLAUDE_CONTEXT_WINDOWS, GEMINI_4_ARGON_PRICE_UPDATE_AT, GEMINI_FLASH_PRICE_UPDATE_AT, GPT_56_PRICE_UPDATE_AT, @@ -13,6 +14,7 @@ canonical_model_provider, ) from token_meter.models.pricing import long_context_multipliers, quote_for +from token_meter.models.tiers import model_tier class ModelPricingBoundaryTests(unittest.TestCase): @@ -47,6 +49,20 @@ def test_fable_and_mythos_5_1_use_their_published_cache_read_price(self): self.assertEqual(quote.cache_read_per_million, 0.25) self.assertEqual(quote.matched_rule, model_id) + def test_fable_and_mythos_5_2_use_their_published_prices(self): + for model_id in ("claude-fable-5-2", "claude-mythos-5-2"): + with self.subTest(model_id=model_id): + quote = quote_for(PriceQuery(ModelRef("anthropic", model_id))) + + self.assertTrue(quote.available) + self.assertEqual(quote.input_per_million, 8.0) + self.assertEqual(quote.output_per_million, 40.0) + self.assertEqual(quote.cache_write_per_million, 10.0) + self.assertEqual(quote.cache_read_per_million, 0.20) + self.assertEqual(quote.matched_rule, model_id) + self.assertEqual(CLAUDE_CONTEXT_WINDOWS[model_id], 1_000_000) + self.assertEqual(model_tier(model_id), "frontier") + def test_unknown_provider_is_explicitly_unavailable_without_cross_fallback(self): unknown_provider = quote_for(PriceQuery(ModelRef("unknown", "gpt-5.6"))) wrong_provider = quote_for(PriceQuery(ModelRef("anthropic", "gpt-5.6"))) diff --git a/tests/test_claude_cost_correctness.py b/tests/test_claude_cost_correctness.py index c6a6e0c4..2b880a6d 100644 --- a/tests/test_claude_cost_correctness.py +++ b/tests/test_claude_cost_correctness.py @@ -156,6 +156,48 @@ def test_opus_5_5_fast_us_pricing_uses_published_multipliers(self): for component, value in expected.items(): self.assertAlmostEqual(cost[component], value) + def test_fable_and_mythos_5_2_fast_us_pricing_uses_published_multipliers(self): + # Fast $16/$80 (2x), then US-only inference 1.1x; 1h writes are 2x input. + expected = { + "input": 17.6, + "cache_write": 57.2, + "cache_read": 0.44, + "output": 88.0, + "server_tools": 0.0, + } + for model in ("claude-fable-5-2", "claude-mythos-5-2"): + with self.subTest(model=model): + cost = meter.cost_of( + claude_usage( + input_tokens=1_000_000, + output_tokens=1_000_000, + cache_read=1_000_000, + cache_write_5m=1_000_000, + cache_write_1h=1_000_000, + speed="fast", + inference_geo="us", + ), + model, + "claude", + ) + for component, value in expected.items(): + self.assertAlmostEqual(cost[component], value) + + def test_fable_5_2_standard_pricing_and_dated_snapshot(self): + usage = claude_usage( + input_tokens=1_000_000, + output_tokens=1_000_000, + cache_read=1_000_000, + cache_write_5m=1_000_000, + cache_write_1h=1_000_000, + ) + expected = {"input": 8.0, "cache_write": 26.0, "cache_read": 0.2, "output": 40.0} + for model in ("claude-fable-5-2", "claude-fable-5-2-20261110"): + with self.subTest(model=model): + cost = meter.cost_of(usage, model, "claude") + for component, value in expected.items(): + self.assertAlmostEqual(cost[component], value) + def test_five_minute_and_one_hour_cache_writes_use_distinct_rates(self): cost = meter.cost_of( claude_usage( diff --git a/token_meter/app_helpers/claude_traces.py b/token_meter/app_helpers/claude_traces.py index 193c2958..af480776 100644 --- a/token_meter/app_helpers/claude_traces.py +++ b/token_meter/app_helpers/claude_traces.py @@ -50,8 +50,8 @@ def _claude_effective_duration(group, duration_s, timing_basis): _CLAUDE_INFERENCE_GEO_RULES = frozenset(( - "claude-mythos-5", "claude-mythos-5-1", - "claude-fable-5", "claude-fable-5-1", + "claude-mythos-5", "claude-mythos-5-1", "claude-mythos-5-2", + "claude-fable-5", "claude-fable-5-1", "claude-fable-5-2", "claude-opus-5-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-5-5", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-5-5", diff --git a/token_meter/app_helpers/common.py b/token_meter/app_helpers/common.py index fb6ea550..1c63a3fc 100644 --- a/token_meter/app_helpers/common.py +++ b/token_meter/app_helpers/common.py @@ -991,6 +991,7 @@ def _cached_price_query(model_provider, model_id, variant, timestamp): _CLAUDE_FAST_PREMIUM_RULES = frozenset(( + "claude-fable-5-2", "claude-mythos-5-2", "claude-opus-5-5", "claude-opus-5", "claude-opus-4-8", )) diff --git a/token_meter/models/catalog.py b/token_meter/models/catalog.py index daf5fe26..7a15ab71 100644 --- a/token_meter/models/catalog.py +++ b/token_meter/models/catalog.py @@ -80,6 +80,13 @@ "claude-mythos-5-1": { "input": 10.0, "output": 50.0, "cache_write": 12.50, "cache_read": 0.25, }, + # Fable 5.2 and Mythos 5.2 pricing page, checked 2026-11-11: cache hits are 0.025x input. + "claude-fable-5-2": { + "input": 8.0, "output": 40.0, "cache_write": 10.0, "cache_read": 0.20, + }, + "claude-mythos-5-2": { + "input": 8.0, "output": 40.0, "cache_write": 10.0, "cache_read": 0.20, + }, # Current published standard pricing is $2 input / $10 output per 1M tokens. "claude-sonnet-5": { "input": 2.0, "output": 10.0, "cache_write": 2.50, "cache_read": 0.20, @@ -342,6 +349,8 @@ CLAUDE_CONTEXT_WINDOWS_REVIEWED_ON = "2026-10-30" CLAUDE_CONTEXT_WINDOWS_SOURCE = "https://platform.claude.com/docs/en/about-claude/models/overview" CLAUDE_CONTEXT_WINDOWS = { + "claude-fable-5-2": 1_000_000, + "claude-mythos-5-2": 1_000_000, "claude-fable-5-1": 1_000_000, "claude-opus-5-5": 1_000_000, "claude-sonnet-5-5": 1_000_000, diff --git a/token_meter/models/tiers.py b/token_meter/models/tiers.py index 3d11f575..c101265e 100644 --- a/token_meter/models/tiers.py +++ b/token_meter/models/tiers.py @@ -11,6 +11,8 @@ # https://developers.openai.com/api/docs/models TIER_MODELS = { "anthropic": { + "claude-mythos-5-2": "frontier", + "claude-fable-5-2": "frontier", "claude-mythos-5-1": "frontier", "claude-mythos-5": "frontier", "claude-fable-5-1": "frontier", From ea4a03225aad6e159a313e9dc30409638383a541 Mon Sep 17 00:00:00 2001 From: Pratik Bhavsar Date: Wed, 11 Nov 2026 20:21:36 +0530 Subject: [PATCH 2/2] Keep Mythos 5.2 context window unknown; bump review dates The models overview lists Fable 5.2 at 1M tokens but publishes no window for Mythos 5.2, so that entry stays unknown, as for earlier Mythos models. Co-Authored-By: Claude Code --- tests/contracts/test_model_pricing.py | 5 ++++- tests/test_pricing.py | 2 +- token_meter/models/catalog.py | 5 ++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/contracts/test_model_pricing.py b/tests/contracts/test_model_pricing.py index 0e2e0ff0..5038adf3 100644 --- a/tests/contracts/test_model_pricing.py +++ b/tests/contracts/test_model_pricing.py @@ -60,9 +60,12 @@ def test_fable_and_mythos_5_2_use_their_published_prices(self): self.assertEqual(quote.cache_write_per_million, 10.0) self.assertEqual(quote.cache_read_per_million, 0.20) self.assertEqual(quote.matched_rule, model_id) - self.assertEqual(CLAUDE_CONTEXT_WINDOWS[model_id], 1_000_000) self.assertEqual(model_tier(model_id), "frontier") + # The models overview lists Fable 5.2's window; Mythos 5.2's is unpublished, so it stays unknown. + self.assertEqual(CLAUDE_CONTEXT_WINDOWS["claude-fable-5-2"], 1_000_000) + self.assertNotIn("claude-mythos-5-2", CLAUDE_CONTEXT_WINDOWS) + def test_unknown_provider_is_explicitly_unavailable_without_cross_fallback(self): unknown_provider = quote_for(PriceQuery(ModelRef("unknown", "gpt-5.6"))) wrong_provider = quote_for(PriceQuery(ModelRef("anthropic", "gpt-5.6"))) diff --git a/tests/test_pricing.py b/tests/test_pricing.py index 07f07d1f..08202731 100644 --- a/tests/test_pricing.py +++ b/tests/test_pricing.py @@ -116,7 +116,7 @@ def test_reading_settings_never_rewrites_obsolete_terms(self): class PricingTests(unittest.TestCase): def test_builtin_pricing_exposes_reviewed_primary_sources(self): pricing = meter.model_pricing_settings() - self.assertEqual(pricing["reviewed_on"], "2026-09-30") + self.assertEqual(pricing["reviewed_on"], "2026-11-11") self.assertEqual( [source["provider"] for source in pricing["sources"]], ["anthropic", "openai", "cursor", "google"], diff --git a/token_meter/models/catalog.py b/token_meter/models/catalog.py index 7a15ab71..a80751ab 100644 --- a/token_meter/models/catalog.py +++ b/token_meter/models/catalog.py @@ -20,7 +20,7 @@ "google": "google", } MODEL_PRICE_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+-]{0,159}$") -BUILTIN_PRICE_REVIEWED_ON = "2026-09-30" +BUILTIN_PRICE_REVIEWED_ON = "2026-11-11" BUILTIN_PRICE_SOURCES = ( { "provider": "anthropic", @@ -346,11 +346,10 @@ # Documented context windows for the current Claude lineup. Older or unlisted # model ids are absent on purpose: their window stays unknown, never guessed. -CLAUDE_CONTEXT_WINDOWS_REVIEWED_ON = "2026-10-30" +CLAUDE_CONTEXT_WINDOWS_REVIEWED_ON = "2026-11-11" CLAUDE_CONTEXT_WINDOWS_SOURCE = "https://platform.claude.com/docs/en/about-claude/models/overview" CLAUDE_CONTEXT_WINDOWS = { "claude-fable-5-2": 1_000_000, - "claude-mythos-5-2": 1_000_000, "claude-fable-5-1": 1_000_000, "claude-opus-5-5": 1_000_000, "claude-sonnet-5-5": 1_000_000,