diff --git a/config.live.lua b/config.live.lua index 1ba7e2a..8c07ad7 100644 --- a/config.live.lua +++ b/config.live.lua @@ -509,7 +509,7 @@ return { ["claude-opus-4-8"] = { served_by = { { provider = "anthropic", provider_model_id = "claude-opus-4-8" }, - { provider = "openrouter", provider_model_id = "anthropic/claude-opus-4-8" }, + { provider = "openrouter", provider_model_id = "anthropic/claude-opus-4.8" }, }, capabilities = { context = 200000, supports_tools = true, supports_json_mode = true }, static_quality_hint = 0.93, @@ -522,7 +522,7 @@ return { ["claude-opus-4-7"] = { served_by = { { provider = "anthropic", provider_model_id = "claude-opus-4-7" }, - { provider = "openrouter", provider_model_id = "anthropic/claude-opus-4-7" }, + { provider = "openrouter", provider_model_id = "anthropic/claude-opus-4.7" }, }, capabilities = { context = 200000, supports_tools = true, supports_json_mode = true }, static_quality_hint = 0.92, @@ -530,7 +530,7 @@ return { ["claude-opus-4-6"] = { served_by = { { provider = "anthropic", provider_model_id = "claude-opus-4-6" }, - { provider = "openrouter", provider_model_id = "anthropic/claude-opus-4-6" }, + { provider = "openrouter", provider_model_id = "anthropic/claude-opus-4.6" }, }, capabilities = { context = 200000, supports_tools = true, supports_json_mode = true }, static_quality_hint = 0.91, @@ -602,7 +602,7 @@ return { served_by = { { provider = "anthropic", provider_model_id = "claude-sonnet-4-6" }, { provider = "bedrock", provider_model_id = "us.anthropic.claude-sonnet-4-6" }, - { provider = "openrouter", provider_model_id = "anthropic/claude-sonnet-4-6" }, + { provider = "openrouter", provider_model_id = "anthropic/claude-sonnet-4.6" }, }, capabilities = { context = 200000, supports_tools = true, supports_json_mode = true }, static_quality_hint = 0.88, @@ -616,7 +616,7 @@ return { ["claude-sonnet-4-5"] = { served_by = { { provider = "anthropic", provider_model_id = "claude-sonnet-4-5" }, - { provider = "openrouter", provider_model_id = "anthropic/claude-sonnet-4-5" }, + { provider = "openrouter", provider_model_id = "anthropic/claude-sonnet-4.5" }, }, capabilities = { context = 200000, supports_tools = true, supports_json_mode = true }, static_quality_hint = 0.86, @@ -624,7 +624,7 @@ return { ["claude-haiku-4-5"] = { served_by = { { provider = "anthropic", provider_model_id = "claude-haiku-4-5" }, - { provider = "openrouter", provider_model_id = "anthropic/claude-haiku-4-5" }, + { provider = "openrouter", provider_model_id = "anthropic/claude-haiku-4.5" }, }, capabilities = { context = 200000, supports_tools = true, supports_json_mode = true }, static_quality_hint = 0.80, diff --git a/saas_routes.py b/saas_routes.py index 65d3408..14ab305 100644 --- a/saas_routes.py +++ b/saas_routes.py @@ -79,7 +79,8 @@ def choices(host): def finite(value): - return value if isinstance(value, (float, int)) and math.isfinite(value) else None + # Per-token source prices scaled to $/Mtok carry float noise (0.19999999999999998). + return round(value, 6) if isinstance(value, (float, int)) and math.isfinite(value) else None def compile_intent(intent): diff --git a/tenant_providers.py b/tenant_providers.py index 5e2b8bc..79932cd 100644 --- a/tenant_providers.py +++ b/tenant_providers.py @@ -133,7 +133,10 @@ async def _openrouter_catalog(catalog): endpoint_details=False) try: prices = await source.pricing() - value = ({pid: source.offers_sync(pid) for pid in source.provider_ids if pid != 'openrouter'}, prices) + # `:batch` slugs are OpenRouter batch-processing endpoints at a discount: not + # for interactive calls, yet a cost-led policy would always pick them. + value = ({pid: [o for o in source.offers_sync(pid) if not o['wire_model_id'].endswith(':batch')] + for pid in source.provider_ids if pid != 'openrouter'}, prices) except Exception: _openrouter_public['failed_at'] = time.monotonic() if stale: diff --git a/tests/test_openrouter_curated_slugs.py b/tests/test_openrouter_curated_slugs.py new file mode 100644 index 0000000..83c9dcc --- /dev/null +++ b/tests/test_openrouter_curated_slugs.py @@ -0,0 +1,13 @@ +"""Curated OpenRouter slugs must be the ids OpenRouter serves (Anthropic uses dotted versions).""" +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def test_curated_anthropic_openrouter_slugs_use_dotted_versions(): + lua = (ROOT / "config.live.lua").read_text() + slugs = re.findall(r"provider\s*=\s*\"openrouter\",\s*provider_model_id\s*=\s*\"(anthropic/[^\"]+)\"", lua) + assert slugs, "expected curated Anthropic families served through OpenRouter" + # OpenRouter lists e.g. anthropic/claude-opus-4.7; anthropic/claude-opus-4-7 does not exist. + assert [s for s in slugs if re.search(r"-\d+-\d+$", s)] == [] diff --git a/tests/test_saas_openrouter_discovery.py b/tests/test_saas_openrouter_discovery.py index 888a8d1..34dc7a7 100644 --- a/tests/test_saas_openrouter_discovery.py +++ b/tests/test_saas_openrouter_discovery.py @@ -155,3 +155,18 @@ def test_first_failure_without_a_listing_fails_fast_for_the_next_caller(base, op asyncio.run(tp.prepare(child)) assert child._connection_errors["openrouter"] assert len([p for p in openrouter["paths"] if p.endswith("/models")]) == 1 + + +def test_batch_endpoints_are_not_offered_and_prices_are_rounded(base, openrouter): + openrouter["models"] = MODELS + [ + {"id": "anthropic/claude-opus-5.5", "context_length": 200000, "supported_parameters": ["tools"], + "pricing": {"prompt": "0.0000002", "completion": "0.0000012"}}, + {"id": "anthropic/claude-opus-5.5:batch", "context_length": 200000, "supported_parameters": ["tools"], + "pricing": {"prompt": "0.0000001", "completion": "0.0000006"}}] + child = tenant(base, 1) + asyncio.run(tp.prepare(child)) + rows = {row["id"]: row for row in choices(child)} + assert "openrouter_market|claude-opus-5.5" in rows + assert not any(i.endswith(":batch") for i in rows) + opus = rows["openrouter_market|claude-opus-5.5"] + assert (opus["price_in"], opus["price_out"]) == (0.2, 1.2)