fix(credits): suppress usage gauge when top-up funds exist + add display.credits_notices toggle (#44716)

The subscription-cap usage gauge (50/75/90% bands) ignored purchased
(top-up) credits: a sub user with top-up funds got a sticky warn banner
at 90% of their cap — permanently at >=100%, alongside grant_spent —
despite being fully able to keep inferencing. The cap is the wrong
denominator for an account that can keep spending.

- evaluate_credits_notices: purchased_micros > 0 suppresses the usage
  band (grant_spent already covers the cap-reached + top-up case with
  the remaining balance). A top-up landing mid-session clears any
  showing band; spending top-up down to 0 resumes the gauge.
- New display.credits_notices config (default true): false silences all
  credits notices. State capture and /usage are unaffected. Read once
  per agent (cached) in _emit_credits_notices, fail-open true.
- Docs: configuration.md display block.
This commit is contained in:
Teknium
2026-06-12 01:06:46 -07:00
committed by GitHub
parent 906bee9cf7
commit c196269d8d
7 changed files with 224 additions and 11 deletions
+25
View File
@@ -2827,6 +2827,8 @@ class AIAgent:
"""
if getattr(self, "notice_callback", None) is None and getattr(self, "notice_clear_callback", None) is None:
return
if not self._credits_notices_enabled():
return
state = getattr(self, "_credits_state", None)
if state is None:
return
@@ -2850,6 +2852,29 @@ class AIAgent:
except Exception:
logger.warning("credits notice evaluation/emit failed", exc_info=True)
def _credits_notices_enabled(self) -> bool:
"""Whether credits notices are enabled (config display.credits_notices).
Read once per agent and cached — the policy runs after every API
response, and the setting governs UI noise, not correctness, so a
config flip applying on the next session is fine. Fail-open True
(preserve current behaviour) on any config error.
"""
cached = getattr(self, "_credits_notices_enabled_cache", None)
if cached is not None:
return cached
enabled = True
try:
from hermes_cli.config import load_config as _load_config
_cfg = _load_config() or {}
_display = _cfg.get("display") if isinstance(_cfg, dict) else None
if isinstance(_display, dict) and "credits_notices" in _display:
enabled = bool(_display.get("credits_notices"))
except Exception:
enabled = True
self._credits_notices_enabled_cache = enabled
return enabled
def get_credits_state(self):
"""Return the last captured CreditsState, or None."""
return self._credits_state