fix(gateway): /usage falls back to the configured provider when no route is known
With no live/cached agent and no persisted billing route (fresh session right after `hermes auth add openai-codex`, or an evicted agent with an empty transcript) /usage returned the bare stub and never tried the account-usage fetch even though on-disk credentials could answer it. Resolve the provider from `model.provider` in the gateway config, as /status already does; the fetch stays fail-open. Complements the credential-pool-aware Codex resolver that already covers the token-lookup half of this report. Fixes #15167
This commit is contained in:
@@ -73,6 +73,14 @@ HISTORY_UNREADABLE = ("⚠️ I can't read this conversation's history right now
|
||||
"to start fresh.")
|
||||
|
||||
|
||||
def _configured_provider() -> str:
|
||||
"""``model.provider`` from the gateway config ("" when unset)."""
|
||||
from gateway.run import _load_gateway_config
|
||||
user_config = _load_gateway_config()
|
||||
model_cfg = user_config.get("model", {}) if isinstance(user_config, dict) else {}
|
||||
return _clean_str(model_cfg.get("provider")) if isinstance(model_cfg, dict) else ""
|
||||
|
||||
|
||||
def _quiet_sync(call, default=None):
|
||||
"""Sync twin of ``_quiet``."""
|
||||
try:
|
||||
@@ -573,6 +581,11 @@ class GatewayStatusCommandsMixin:
|
||||
)
|
||||
if not provider and getattr(self, "_session_db", None) is not None:
|
||||
provider, base_url = await self._persisted_billing_route(source)
|
||||
if not provider:
|
||||
# Fresh or evicted session with no persisted route (e.g. /usage right after login):
|
||||
# fall back to the configured provider, as /status does, so account limits such as
|
||||
# Codex subscription windows still render from on-disk credentials (#15167).
|
||||
provider = await _quiet(lambda: asyncio.to_thread(_configured_provider)) or None
|
||||
if wants_reset:
|
||||
if str(provider or "").strip().lower() != "openai-codex":
|
||||
return t("gateway.usage.reset_wrong_provider")
|
||||
|
||||
@@ -162,6 +162,43 @@ class TestUsageAccountSection:
|
||||
assert "📊 **Session Info**" in result
|
||||
assert "📈 **Account limits**" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_usage_command_falls_back_to_configured_provider_without_history(self, monkeypatch):
|
||||
"""#15167: no agent, no persisted route, empty transcript -> still fetch account limits
|
||||
for the configured provider instead of the bare "no data" stub."""
|
||||
runner = _make_runner(SK)
|
||||
runner._session_db = AsyncSessionDB(MagicMock())
|
||||
runner._session_db._db.get_session.return_value = {}
|
||||
runner._session_db._db.get_recent_session_model_route.return_value = None
|
||||
session_entry = MagicMock()
|
||||
session_entry.session_id = "sess-fresh"
|
||||
runner.session_store.get_or_create_session.return_value = session_entry
|
||||
runner.session_store.load_transcript.return_value = []
|
||||
|
||||
calls = []
|
||||
|
||||
async def _fake_to_thread(fn, *args, **kwargs):
|
||||
calls.append({"fn": fn, "args": args, "kwargs": kwargs})
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr("gateway.run.asyncio.to_thread", _fake_to_thread)
|
||||
monkeypatch.setattr("gateway.run._load_gateway_config", lambda: {"model": {"provider": "openai-codex"}})
|
||||
monkeypatch.setattr(
|
||||
"gateway.slash_commands_status.fetch_account_usage",
|
||||
lambda provider, base_url=None, api_key=None: object(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gateway.slash_commands_status.render_account_usage_lines",
|
||||
lambda snapshot, markdown=False: ["📈 **Account limits**", "Provider: openai-codex (Plus)",
|
||||
"Weekly: 91% remaining (9% used)"],
|
||||
)
|
||||
monkeypatch.setattr("agent.account_usage.nous_credits_lines", lambda markdown=False: [])
|
||||
|
||||
result = await runner._handle_usage_command(MagicMock())
|
||||
|
||||
assert any(c["args"] == ("openai-codex",) for c in calls)
|
||||
assert "📈 **Account limits**" in result and "Weekly: 91% remaining" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_usage_command_prefers_recent_persisted_route(self, monkeypatch):
|
||||
runner = _make_runner(SK)
|
||||
|
||||
Reference in New Issue
Block a user