From acc0414005a845aee5d8ded78af9f327390022a1 Mon Sep 17 00:00:00 2001 From: Hermes Implementer Date: Fri, 10 Jul 2026 17:05:59 -0500 Subject: [PATCH] feat(pricing): add Bedrock rows for Opus 4.8/4.7 and Sonnet 5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_OFFICIAL_DOCS_PRICING` had no bedrock rows for the current-gen Claude models, so `us.anthropic.claude-opus-4-8`, `-opus-4-7`, and `-sonnet-5` sessions priced as `unknown` on `/usage` — the #50295 symptom, for the newest models specifically. Reproduce on main: from agent.usage_pricing import get_pricing_entry url = "https://bedrock-runtime.us-east-1.amazonaws.com" get_pricing_entry("us.anthropic.claude-opus-4-8", provider="bedrock", base_url=url) # None get_pricing_entry("us.anthropic.claude-sonnet-5", provider="bedrock", base_url=url) # None Rates are keyed to Anthropic's published list price (https://docs.anthropic.com/en/docs/about-claude/pricing + https://claude.com/pricing), which commercial Bedrock on-demand mirrors for the Claude line — the existing sonnet-4-5/4-6 rows already match Anthropic list exactly ($3/$15, cache $0.30/$3.75): Opus 4.8 / 4.7: $5 in / $25 out, cache-write $6.25 (5m TTL), cache-read $0.50 Sonnet 5: $3 in / $15 out, cache-write $3.75, cache-read $0.30 Sonnet 5 uses the STANDARD rate ($3/$15), not the promotional launch price ($2/$10 through 2026-08-31), so the snapshot does not silently under-report after the promo ends. Provenance note (in-code): AWS GovCloud carries a ~20% premium (Opus 4.8 = $6/$30 there), and the AWS Price List API had NOT published these SKUs machine-readably as of the 2026-07-07 us-east-1 offer, so these are the commercial-list figures pending an authoritative AWS machine source. Complements (does not overlap) open PR #46299, which adds cache-cost fields to the *existing* rows; this adds *new* rows. Orthogonal to the `-v1` suffix normalizer change. Test: test_bedrock_current_gen_claude_rows_resolve asserts each model resolves via bare id + cross-region prefix, carries cache fields, and is well-formed (output > input). Full file: 16 passed; ruff clean. E2E: a cached opus-4-8 session now estimates $1.13 instead of unknown. --- agent/usage_pricing.py | 51 +++++++++++++++++++++++++++++++ tests/agent/test_usage_pricing.py | 37 ++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/agent/usage_pricing.py b/agent/usage_pricing.py index aa306fa12f8c4..a48aad1dd4b0a 100644 --- a/agent/usage_pricing.py +++ b/agent/usage_pricing.py @@ -560,6 +560,57 @@ class CostResult: source_url="https://aws.amazon.com/bedrock/pricing/", pricing_version="bedrock-pricing-2026-04", ), + # Current-gen Claude on Bedrock. Rates keyed to Anthropic's published + # list price (https://docs.anthropic.com/en/docs/about-claude/pricing + + # https://claude.com/pricing), which commercial Bedrock on-demand mirrors + # for the Claude line — the existing sonnet-4-5/4-6 rows above ($3/$15, + # cache $0.30/$3.75) already match Anthropic list exactly. NOTE: AWS + # GovCloud carries a ~20% premium (Opus 4.8 = $6/$30 there), and the AWS + # Price List API had NOT published these SKUs machine-readably as of the + # 2026-07-07 us-east-1 offer, so these are the commercial-list figures + # pending an authoritative AWS machine source. cache_write is the 5-minute + # TTL rate (1.25x input); cache_read is 0.1x input, per Anthropic's table. + ( + "bedrock", + "anthropic.claude-opus-4-8", + ): PricingEntry( + input_cost_per_million=Decimal("5.00"), + output_cost_per_million=Decimal("25.00"), + cache_read_cost_per_million=Decimal("0.50"), + cache_write_cost_per_million=Decimal("6.25"), + source="official_docs_snapshot", + source_url="https://docs.anthropic.com/en/docs/about-claude/pricing", + pricing_version="anthropic-list-2026-07", + ), + ( + "bedrock", + "anthropic.claude-opus-4-7", + ): PricingEntry( + input_cost_per_million=Decimal("5.00"), + output_cost_per_million=Decimal("25.00"), + cache_read_cost_per_million=Decimal("0.50"), + cache_write_cost_per_million=Decimal("6.25"), + source="official_docs_snapshot", + source_url="https://docs.anthropic.com/en/docs/about-claude/pricing", + pricing_version="anthropic-list-2026-07", + ), + # Sonnet 5 launched 2026-06-30. Promotional launch pricing of $2/$10 is in + # effect through 2026-08-31; standard pricing of $3/$15 takes effect + # 2026-09-01. This snapshot uses the STANDARD rate so it does not silently + # under-report after the promo ends — the same convention as every other + # steady-state row here. + ( + "bedrock", + "anthropic.claude-sonnet-5", + ): PricingEntry( + input_cost_per_million=Decimal("3.00"), + output_cost_per_million=Decimal("15.00"), + cache_read_cost_per_million=Decimal("0.30"), + cache_write_cost_per_million=Decimal("3.75"), + source="official_docs_snapshot", + source_url="https://docs.anthropic.com/en/docs/about-claude/pricing", + pricing_version="anthropic-list-2026-07", + ), ( "bedrock", "amazon.nova-pro", diff --git a/tests/agent/test_usage_pricing.py b/tests/agent/test_usage_pricing.py index 3bd68ae2344db..81b3031af1651 100644 --- a/tests/agent/test_usage_pricing.py +++ b/tests/agent/test_usage_pricing.py @@ -278,6 +278,43 @@ def test_bedrock_claude_rows_all_carry_cache_pricing(): assert entry.cache_write_cost_per_million > entry.input_cost_per_million, key +def test_bedrock_current_gen_claude_rows_resolve(): + """Current-gen Claude models (Opus 4.8/4.7, Sonnet 5) must have Bedrock + pricing rows so cached sessions report a dollar cost, not ``unknown``. + Assert each resolves via the bare id and a cross-region inference profile + (us./global. prefix), that every id for a given model resolves to the same + entry, and that the row carries the cache fields a Bedrock Claude session + needs. + + (Version-suffixed IDs like ``...-v1:0`` are covered separately by the + normalizer test in the suffix-strip change; this test intentionally sticks + to id shapes that resolve on ``main`` so it is independent of that PR.) + """ + url = "https://bedrock-runtime.us-east-1.amazonaws.com" + for bare in ( + "anthropic.claude-opus-4-8", + "anthropic.claude-opus-4-7", + "anthropic.claude-sonnet-5", + ): + ref = get_pricing_entry(bare, provider="bedrock", base_url=url) + assert ref is not None, bare + assert ref.input_cost_per_million is not None, bare + assert ref.output_cost_per_million is not None, bare + # Output costs more than input across the Claude line; sanity-check the + # row isn't malformed (input < output). + assert ref.output_cost_per_million > ref.input_cost_per_million, bare + # Cache fields present so cached sessions price correctly (the #50295 + # symptom was unknown cost on cached Bedrock Claude sessions). + assert ref.cache_read_cost_per_million is not None, bare + assert ref.cache_write_cost_per_million is not None, bare + # Cross-region inference profiles resolve to the same entry. + for mid in (f"us.{bare}", f"global.{bare}"): + entry = get_pricing_entry(mid, provider="bedrock", base_url=url) + assert entry is not None, mid + assert entry.input_cost_per_million == ref.input_cost_per_million, mid + assert entry.output_cost_per_million == ref.output_cost_per_million, mid + + def test_bedrock_cross_region_profile_prefix_resolves_to_pricing(): """Cross-region inference profiles (us./global./eu. prefixes) must resolve to the same pricing entry as the bare foundation-model id. Without prefix