fix(pricing): price direct OpenAI/xAI/Anthropic/Google models missing from the snapshot

A direct first-party route whose model is absent from the bundled
official-docs snapshot fell through to the provider's own /models listing,
which carries no prices on api.openai.com or api.x.ai, and recorded the
turn as cost_status=unknown / $0 despite real billed usage (gpt-5-nano on
OpenAI, grok-4.3 on xAI in the live provider canary).

Widen the trusted models.dev fallback from Xiaomi to every pay-per-token
first-party API (openai, xai, anthropic, google, deepseek, xiaomi), keyed
by billing-route provider -> the vendor's API domain. The price applies
only on HTTPS:443 to that domain (or with no base URL, i.e. the provider
default), so relays, downgraded origins, custom endpoints and
subscription routes (xai-oauth, openai-codex) keep their own policy.
Drops the blanket try/except: get_model_info(allow_network=False) reads
only the memory/disk cache and does not raise.
This commit is contained in:
teknium1
2026-09-23 09:03:09 -07:00
committed by Teknium
parent 9a45b25c0a
commit e91a458d8a
2 changed files with 22 additions and 25 deletions

View File

@@ -15,8 +15,16 @@ logger = logging.getLogger(__name__)
_ZERO = Decimal("0")
_ONE_MILLION = Decimal("1000000")
_NOUS_DEFAULT_BASE_URL = "https://inference-api.nousresearch.com/v1"
_MODELS_DEV_NO_BASE_PROVIDERS = frozenset({"xiaomi"})
_MODELS_DEV_DIRECT_HOSTS = frozenset({("xiaomi", "xiaomimimo.com")})
# Pay-per-token first-party APIs whose models.dev rate card is the vendor's own
# list price, keyed by billing-route provider -> API domain. A model missing from
# the snapshot below is priced from models.dev only on HTTPS:443 to that domain
# (or with no base URL, i.e. the provider default): a proxy, relay or custom
# endpoint serving the same model id may bill differently, and subscription
# routes (openai-codex, xai-oauth) keep their own policy.
_MODELS_DEV_DIRECT_HOSTS = {
"openai": "openai.com", "xai": "x.ai", "anthropic": "anthropic.com", "google": "googleapis.com",
"deepseek": "deepseek.com", "xiaomi": "xiaomimimo.com",
}
# Below $0.01, render at 4 dp so cheap-model costs never display as $0.00.
# Sub-cent cost threshold: below $0.01, render at 4 decimal places so the display is non-zero (e.g. $0.0046
@@ -490,27 +498,17 @@ def _pricing_entry_from_metadata(
def _models_dev_pricing_entry(route: BillingRoute) -> Optional[PricingEntry]:
"""Return models.dev pricing only for direct provider routes."""
if not route.provider or not route.model:
"""models.dev list price for a direct first-party route (see ``_MODELS_DEV_DIRECT_HOSTS``)."""
domain = _MODELS_DEV_DIRECT_HOSTS.get(route.provider)
if not domain or not route.model:
return None
try:
from agent.models_dev import get_model_info
origin = base_url_origin(route.base_url)
if (not route.base_url and route.provider not in _MODELS_DEV_NO_BASE_PROVIDERS) or (
route.base_url and not (
origin[0] == "https"
and origin[2] == 443
and any(
route.provider == provider and (origin[1] == host or origin[1].endswith("." + host))
for provider, host in _MODELS_DEV_DIRECT_HOSTS
)
)
):
if route.base_url:
scheme, host, port = base_url_origin(route.base_url)
if (scheme, port) != ("https", 443) or not (host == domain or host.endswith("." + domain)):
return None
model_info = get_model_info(route.provider, route.model)
except Exception:
return None
from agent.models_dev import get_model_info
model_info = get_model_info(route.provider, route.model)
if model_info is None or not model_info.has_cost_data():
return None
return PricingEntry(
@@ -518,12 +516,11 @@ def _models_dev_pricing_entry(route: BillingRoute) -> Optional[PricingEntry]:
output_cost_per_million=_to_decimal(model_info.cost_output),
cache_read_cost_per_million=_to_decimal(model_info.cost_cache_read),
cache_write_cost_per_million=_to_decimal(model_info.cost_cache_write),
source="provider_models_api",
source_url="https://models.dev",
pricing_version="models.dev",
source="provider_models_api", source_url="https://models.dev", pricing_version="models.dev",
fetched_at=_UTC_NOW(),
)
def get_pricing_entry(
model_name: str, provider: Optional[str] = None, base_url: Optional[str] = None,
api_key: Optional[str] = None,

View File

@@ -159,7 +159,7 @@ A persistent status bar sits above the input area, updating in real time:
| Model name | Current model (truncated if longer than 26 chars) |
| Token count | Context tokens used / max context window; `~` marks an estimate |
| Context bar | Visual fill indicator with color-coded thresholds |
| Cost | Estimated session cost (or `n/a` for unknown/zero-priced models) |
| Cost | Estimated session cost (or `n/a` for unknown/zero-priced models). Rates come from Hermes' bundled official price table, then the provider's `/models` listing; on a direct first-party API (OpenAI, xAI, Anthropic, Google, DeepSeek, Xiaomi) a model missing from both is priced at the vendor's list price from models.dev. Proxies, relays and custom endpoints serving the same model id stay `n/a` rather than inherit that price. |
| 🗜️ N | **Context compression count** — how many times the running session has been auto-compressed. Appears once the first compression fires. |
| ▶ N | **Active background tasks** — how many `/bg` prompts are still running in the current session. Appears whenever at least one task is in flight. |
| Duration | Elapsed session time |