From ad96d2e2d9257a91bffb5f9d9affbf89a7669284 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sat, 22 Aug 2026 03:52:16 +0530 Subject: [PATCH] fix(credits): suppress depleted banner on stealth-preview models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stealth-preview SKUs (e.g. stealth/ox-alpha) are free-tier but carry no :free suffix, so is_free_tier_model() returned False for them. On gateway sessions (which never run the model picker's pricing fetch), the free-model suppression of the credits.depleted banner never engaged, and any response carrying paid_access:false triggered a false "Credit access paused" notice. Add stealth/ prefix detection to is_free_tier_model() as a zero-network signal, same design as the existing :free suffix check. Fail-open to False (banner still shows) if the prefix changes — recoverable noise, never a masked depletion on a paid model. Closes #91843 --- agent/credits_tracker.py | 16 ++++++++++-- tests/agent/test_credits_policy.py | 41 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/agent/credits_tracker.py b/agent/credits_tracker.py index b47c3f274e..5988773044 100644 --- a/agent/credits_tracker.py +++ b/agent/credits_tracker.py @@ -226,12 +226,15 @@ def is_free_tier_model(model: str, base_url: str = "") -> bool: 1. The ``:free`` suffix — the canonical Nous free SKU marker (e.g. ``nvidia/nemotron-3-ultra:free``). Free by construction on the API side (spend is forced to 0 for ``:free`` ids). - 2. A peek into the in-process pricing cache in ``hermes_cli.models`` + 2. The ``stealth/`` prefix — Nous stealth-preview SKUs (e.g. + ``stealth/ox-alpha``) are free-tier but carry no ``:free`` suffix. Spend + is forced to zero server-side, so these are also free by construction. + 3. A peek into the in-process pricing cache in ``hermes_cli.models`` (populated when the model picker fetched ``/v1/models`` pricing for *base_url*). PEEK ONLY — a cache miss never triggers a fetch. This is CLI/TUI-session best-effort: gateway sessions never run the picker's pricing fetch, so suppression there rests entirely on the ``:free`` - suffix (which all Nous free SKUs carry). + suffix and ``stealth/`` prefix. Fail-open to False (the depleted notice still shows) on any error: wrongly showing the warning is recoverable noise; wrongly hiding it on a paid model @@ -241,6 +244,15 @@ def is_free_tier_model(model: str, base_url: str = "") -> bool: return False if model.endswith(":free"): return True + # Stealth-preview SKUs (e.g. stealth/ox-alpha) are free-tier but carry no + # ``:free`` suffix. Spend is forced to zero server-side, so a ``paid_access: + # false`` header on these models is a false positive for the depleted banner. + # The ``stealth/`` prefix is the Nous naming convention for these SKUs and + # is checked here as a zero-network signal, same design as the ``:free`` + # suffix above. Fail-open to False (the banner still shows) if the prefix + # ever changes — recoverable noise, never a masked depletion on a paid model. + if model.startswith("stealth/"): + return True if not base_url: return False try: diff --git a/tests/agent/test_credits_policy.py b/tests/agent/test_credits_policy.py index 45892a7a75..b03e9c28bb 100644 --- a/tests/agent/test_credits_policy.py +++ b/tests/agent/test_credits_policy.py @@ -351,6 +351,47 @@ class TestIsFreeTierModel: monkeypatch.setattr(models_mod, "_pricing_cache", _Exploding()) assert is_free_tier_model("some/model", "https://inference-api.nousresearch.com") is False + def test_stealth_prefix_detected_as_free(self): + """Stealth-preview SKUs (stealth/...) are free-tier but carry no + :free suffix. Suppression must engage so the depleted banner doesn't + fire on a false paid_access:false from the server's stealth pool.""" + from agent.credits_tracker import is_free_tier_model + + # No base_url needed — stealth/ is a zero-network signal, same as :free. + assert is_free_tier_model("stealth/ox-alpha", "") is True + assert is_free_tier_model("stealth/ox-alpha", "https://inference-api.nousresearch.com/v1") is True + # Non-stealth model without :free suffix → not free (without pricing cache). + assert is_free_tier_model("some/paid-model", "") is False + + def test_depleted_suppressed_for_stealth_model(self): + """End-to-end: paid_access:false on a stealth/ model must NOT fire + the depleted banner (the exact scenario from issue #91843).""" + from agent.credits_tracker import ( + CreditsState, evaluate_credits_notices, is_free_tier_model, + ) + + state = CreditsState( + version=1, + remaining_micros=0, + remaining_usd="0.00", + subscription_micros=0, + subscription_usd="0.00", + purchased_micros=0, + purchased_usd="0.00", + paid_access=False, + captured_at=1.0, + from_header=True, + ) + model = "stealth/ox-alpha" + base_url = "https://inference-api.nousresearch.com/v1" + model_is_free = is_free_tier_model(model, base_url) + assert model_is_free is True + + latch = fresh_latch() + to_show, to_clear = evaluate_credits_notices(state, latch, model_is_free=model_is_free) + assert all(n.key != "credits.depleted" for n in to_show) + assert "credits.depleted" not in latch["active"] + # ── Scenario 6: denominator none (uf is None) ────────────────────────────────