Skip to content
This repository was archived by the owner on Oct 11, 2026. It is now read-only.
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
10 changes: 7 additions & 3 deletions calibre_toolkit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
# the latest model is a one-line edit when a new generation ships.
_ALIAS_MAP: dict[str, str] = {
"fast": "claude-haiku-4-5-20251001",
"latest": "claude-opus-4-7",
"latest": "claude-opus-4-8",
"legacy": "claude-sonnet-4-6",
}

Expand All @@ -50,8 +50,12 @@
# (so anyone who pinned a specific ID for reproducibility keeps working)
# but logs a one-time warning so the user knows it's time to switch.
_DEPRECATED_MODELS: dict[str, str] = {
# Add entries here as Anthropic retires model IDs. Example shape:
# "claude-sonnet-3-5": "claude-sonnet-4-6",
# Per Anthropic's deprecation table as of 2026-06-10.
"claude-opus-4-1": "claude-opus-4-8", # retires 2026-08-05
"claude-opus-4-1-20250805": "claude-opus-4-8", # retires 2026-08-05
"claude-opus-4-20250514": "claude-opus-4-8", # retires 2026-06-15
"claude-sonnet-4-20250514": "claude-sonnet-4-6", # retires 2026-06-15
"claude-3-haiku-20240307": "claude-haiku-4-5", # retired 2026-04-19
}


Expand Down
33 changes: 24 additions & 9 deletions calibre_toolkit/usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ def _get(name: str) -> int:

# ── Pricing ──────────────────────────────────────────────────────────────────
#
# USD per million tokens. Anthropic's public list prices as of January 2026
# for the Claude 4.x family. Sources: anthropic.com/pricing. Keyed by model
# family prefix so a new minor version (claude-sonnet-4-7, 4-8) inherits
# pricing until a deliberate update.
# USD per million tokens. Anthropic's public list prices, verified against
# platform.claude.com/docs/en/about-claude/pricing on 2026-06-10. Keyed by
# model family prefix, longest match wins — so the per-version Opus 4.5+
# entries take precedence over the bare "claude-opus-4" fallback, which
# keeps the old $15/$75 rates for Opus 4.0/4.1-era IDs.
#
# When the model is unknown we return None and the summary omits the
# dollar estimate — token counts are still shown so the user can plug
Expand All @@ -134,7 +135,20 @@ class _PriceTable:
cache_write_per_million: float


_OPUS_45_PLUS = _PriceTable(
input_per_million=5.00,
output_per_million=25.00,
cache_read_per_million=0.50,
cache_write_per_million=6.25,
)

_PRICES: dict[str, _PriceTable] = {
"claude-opus-4-8": _OPUS_45_PLUS,
"claude-opus-4-7": _OPUS_45_PLUS,
"claude-opus-4-6": _OPUS_45_PLUS,
"claude-opus-4-5": _OPUS_45_PLUS,
# Opus 4.0/4.1 (deprecated) — dated IDs like claude-opus-4-20250514
# fall through to this prefix.
"claude-opus-4": _PriceTable(
input_per_million=15.00,
output_per_million=75.00,
Expand All @@ -148,12 +162,13 @@ class _PriceTable:
cache_write_per_million=3.75,
),
"claude-haiku-4": _PriceTable(
input_per_million=0.80,
output_per_million=4.00,
cache_read_per_million=0.08,
cache_write_per_million=1.00,
input_per_million=1.00,
output_per_million=5.00,
cache_read_per_million=0.10,
cache_write_per_million=1.25,
),
# 3.x legacy support — older but still callable.
# 3.x legacy — retired models, kept so replaying old usage logs
# still produces a dollar estimate.
"claude-3-5-sonnet": _PriceTable(
input_per_million=3.00,
output_per_million=15.00,
Expand Down
15 changes: 10 additions & 5 deletions tests/test_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,19 @@ def test_resolves_sonnet_4_6_prefix(self):
assert table.output_per_million == 15.00

def test_resolves_opus_prefix(self):
table = _resolve_price_table("claude-opus-4-7")
# Opus 4.5+ bills at $5/MTok; the bare claude-opus-4 prefix keeps
# the old $15 rate for 4.0/4.1-era IDs.
table = _resolve_price_table("claude-opus-4-8")
assert table is not None
assert table.input_per_million == 15.00
assert table.input_per_million == 5.00
legacy = _resolve_price_table("claude-opus-4-20250514")
assert legacy is not None
assert legacy.input_per_million == 15.00

def test_resolves_haiku_prefix(self):
table = _resolve_price_table("claude-haiku-4-5-20251001")
assert table is not None
assert table.input_per_million == 0.80
assert table.input_per_million == 1.00

def test_unknown_model_returns_none(self):
assert _resolve_price_table("gpt-4") is None
Expand Down Expand Up @@ -205,9 +210,9 @@ def test_cost_estimate_combines_per_model(self):
agg = UsageAggregate()
# 1M Sonnet input → $3.00
agg.record(TokenUsage(input_tokens=1_000_000), "claude-sonnet-4-6")
# 1M Haiku input → $0.80
# 1M Haiku input → $1.00
agg.record(TokenUsage(input_tokens=1_000_000), "claude-haiku-4-5")
assert agg.cost_estimate_usd() == pytest.approx(3.80)
assert agg.cost_estimate_usd() == pytest.approx(4.00)

def test_cost_estimate_none_when_every_model_unpriced(self):
agg = UsageAggregate()
Expand Down
Loading