The provider heading's status slot now shows a fuel gauge for the
tightest usage window: hidden while there's room, "17% left · resets
3:00 PM" at 20% or less (amber from 10%), with a bar along its bottom
edge for what's left, then the existing "Limited until" once the login
is out. Hovering lists every window (5-hour, weekly, per-family).
model.options attaches `usage: { windows }` from a per-profile cache in
agent/account_usage_cache.py and refreshes it in the background, so the
picker never waits on a usage API. Every fetch_account_usage call lands
in that cache, including the one session.usage runs after each turn, so
the chip is at most a turn or two minutes behind. Covers the built-in
fetchers (Anthropic, Codex, OpenRouter) and any provider plugin's usage
hook.
Both pickers now render one ProviderStatusChip instead of separate
limit badges.
52 lines
2.2 KiB
Python
52 lines
2.2 KiB
Python
"""The model picker shows subscription usage before the wall, without ever waiting on a usage API.
|
|
|
|
Usage windows come from the provider's API, so ``model.options`` must read them from cache and
|
|
refresh in the background: a slow or hung usage endpoint can't be allowed to stall the picker.
|
|
"""
|
|
|
|
import threading
|
|
import time
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from agent import account_usage
|
|
from agent.account_usage import AccountUsageSnapshot, AccountUsageWindow
|
|
from agent.account_usage_cache import cached_account_usage
|
|
from hermes_cli.inventory import build_model_options_payload, load_picker_context
|
|
|
|
|
|
def _snapshot(used: float) -> AccountUsageSnapshot:
|
|
reset = datetime.now(timezone.utc) + timedelta(hours=2)
|
|
return AccountUsageSnapshot(
|
|
provider="openrouter", source="test", fetched_at=datetime.now(timezone.utc),
|
|
windows=(AccountUsageWindow(label="Current session", used_percent=used, reset_at=reset),))
|
|
|
|
|
|
def test_picker_reads_cached_usage_and_refreshes_without_waiting(monkeypatch):
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "test-key-123")
|
|
release, called = threading.Event(), threading.Event()
|
|
answers = iter([_snapshot(92.0)])
|
|
|
|
def slow_usage_api(_base_url, _api_key):
|
|
called.set()
|
|
release.wait(10)
|
|
return next(answers)
|
|
|
|
monkeypatch.setitem(account_usage._USAGE_FETCHERS, "openrouter", slow_usage_api)
|
|
ctx = load_picker_context()
|
|
|
|
started = time.monotonic()
|
|
first = next(r for r in build_model_options_payload(ctx)["providers"] if r["slug"] == "openrouter")
|
|
assert time.monotonic() - started < 5, "the picker waited on the usage API"
|
|
assert "usage" not in first, "nothing cached yet, so nothing to show"
|
|
assert called.wait(5), "opening the picker must start a background usage refresh"
|
|
|
|
release.set()
|
|
deadline = time.monotonic() + 5
|
|
while cached_account_usage("openrouter") is None and time.monotonic() < deadline:
|
|
time.sleep(0.05)
|
|
|
|
row = next(r for r in build_model_options_payload(ctx)["providers"] if r["slug"] == "openrouter")
|
|
(window,) = row["usage"]["windows"]
|
|
assert window["label"] == "Current session" and window["used_percent"] == 92.0
|
|
assert datetime.fromisoformat(window["resets_at"]) > datetime.now(timezone.utc)
|