From c46ba026ba0aed17bdc9074df483ddb0130d4a0b Mon Sep 17 00:00:00 2001 From: h Date: Wed, 2 Sep 2026 04:46:43 +0200 Subject: [PATCH] feat(api): limits read unifiedWindows, unknown seven-day windows labelled by model --- src/beaver_gateway/frontends/api/frontend.py | 86 +++++++++++++++++--- tests/test_api.py | 27 +++++- ui/src/lib/api/types.ts | 2 + ui/src/lib/limits.ts | 12 ++- 4 files changed, 112 insertions(+), 15 deletions(-) diff --git a/src/beaver_gateway/frontends/api/frontend.py b/src/beaver_gateway/frontends/api/frontend.py index f7755b5..d31d882 100644 --- a/src/beaver_gateway/frontends/api/frontend.py +++ b/src/beaver_gateway/frontends/api/frontend.py @@ -737,21 +737,32 @@ def build_app( # noqa: PLR0915 await require_token(request, runtime, scope=SCOPE) rows = await conversations.rate_limits(limit=200) latest: dict[str, RateLimit] = {} + unified: dict[str, tuple[RateLimit, dict[str, Any]]] = {} for row in rows: latest.setdefault(row.window, row) + for name, info in _unified_windows(row).items(): + unified.setdefault(name, (row, info)) now = datetime.now(UTC) windows = [] - for window, row in latest.items(): - length = WINDOWS.get(window) - since = ( - _aware(row.resets_at) - length - if length is not None and row.resets_at is not None - else _aware(row.ts) - ) + for window in dict.fromkeys([*latest, *unified]): + row = latest.get(window) + seen = unified.get(window) + public = _limit_public(row) if row else _limit_blank(window, seen) + resets = row.resets_at if row else None + if seen and _newer_figure(seen, row): + carrier, info = seen + public["utilization"] = info.get("utilization") + public["reported_at"] = _iso(carrier.ts) + resets = _unix_dt(info.get("resetsAt")) or resets + public["resets_at"] = _iso(resets) + length = _window_length(window) + anchor = row if row is not None else (seen[0] if seen else None) + if length is not None and resets is not None: + since = _aware(resets) - length + else: + since = _aware(anchor.ts) if anchor else now gateway = _sum_usage(await _usage_rows(runtime, since, now)) gateway["since"] = since.isoformat(timespec="seconds") - # The API sends a figure only when a window nears its limit; - # the last one inside this window is the best lower bound. known = next( ( r @@ -764,13 +775,13 @@ def build_app( # noqa: PLR0915 ) windows.append( { - **_limit_public(row), + **public, "gateway": gateway, "last_known": _limit_public(known) if known else None, } ) windows.sort( - key=lambda w: (WINDOWS.get(w["window"], timedelta.max), w["window"]) + key=lambda w: (_window_length(w["window"]) or timedelta.max, w["window"]) ) return {"windows": windows, "history": [_limit_public(r) for r in rows[:50]]} @@ -1052,6 +1063,59 @@ def _queue_item(item: InjectQueueItem | None) -> dict[str, Any] | None: } +def _window_length(name: str) -> timedelta | None: + if name.startswith("five_hour"): + return timedelta(hours=5) + if "seven_day" in name: + return timedelta(days=7) + return WINDOWS.get(name) + + +def _unix_dt(value: Any) -> datetime | None: + if isinstance(value, int | float) and value > 0: + return datetime.fromtimestamp(value, tz=UTC) + return None + + +def _unified_windows(row: RateLimit) -> dict[str, dict[str, Any]]: + """Every window the event carried a figure for, not just the one it was about.""" + raw = row.raw if isinstance(row.raw, dict) else {} + found = raw.get("unifiedWindows") + if not isinstance(found, dict): + return {} + return { + str(name): info + for name, info in found.items() + if isinstance(info, dict) and info.get("utilization") is not None + } + + +def _newer_figure( + seen: tuple[RateLimit, dict[str, Any]], row: RateLimit | None +) -> bool: + if row is None or row.utilization is None: + return True + return seen[0].id != row.id and _aware(seen[0].ts) > _aware(row.ts) + + +def _limit_blank( + window: str, seen: tuple[RateLimit, dict[str, Any]] | None +) -> dict[str, Any]: + carrier = seen[0] if seen else None + return { + "id": None, + "ts": _iso(carrier.ts) if carrier else None, + "window": window, + "status": "allowed", + "utilization": None, + "resets_at": None, + "overage_status": None, + "overage_resets_at": None, + "agent": carrier.agent_name if carrier else None, + "session_id": carrier.session_id if carrier else None, + } + + def _limit_public(row: RateLimit) -> dict[str, Any]: return { "id": row.id, diff --git a/tests/test_api.py b/tests/test_api.py index fefa132..04a384e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -247,6 +247,7 @@ async def test_usage_groups_by_agent_day_and_model(world: World) -> None: async def test_limits_report_latest_window_with_gateway_spend(world: World) -> None: api = Api(world) now = datetime.now(UTC) + resets = int((now + timedelta(hours=4)).timestamp()) async with world.db.session() as session: session.add( RateLimit( @@ -264,7 +265,20 @@ async def test_limits_report_latest_window_with_gateway_spend(world: World) -> N resets_at=now + timedelta(hours=4), ) ) - session.add(RateLimit(window="seven_day", status="allowed", utilization=0.3)) + session.add( + RateLimit( + window="seven_day", + status="allowed", + utilization=0.3, + raw={ + "unifiedWindows": { + "five_hour": {"resetsAt": resets, "utilization": 0.72}, + "seven_day": {"resetsAt": resets, "utilization": 0.25}, + "seven_day_fable": {"resetsAt": resets, "utilization": 0.1}, + } + }, + ) + ) await session.commit() await seed_usage( world, @@ -280,12 +294,19 @@ async def test_limits_report_latest_window_with_gateway_spend(world: World) -> N ) out = await api.get("/limits") windows = {w["window"]: w for w in out["windows"]} - assert windows["five_hour"]["utilization"] == 0.9 + assert windows["five_hour"]["utilization"] == 0.72 + assert windows["five_hour"]["reported_at"] is not None assert windows["five_hour"]["status"] == "allowed_warning" assert windows["five_hour"]["gateway"]["output"] == 7 assert windows["five_hour"]["gateway"]["cost_usd"] == 0.25 assert windows["seven_day"]["utilization"] == 0.3 - assert [w["window"] for w in out["windows"]] == ["five_hour", "seven_day"] + assert windows["seven_day_fable"]["utilization"] == 0.1 + assert windows["seven_day_fable"]["status"] == "allowed" + assert [w["window"] for w in out["windows"]] == [ + "five_hour", + "seven_day", + "seven_day_fable", + ] assert len(out["history"]) == 3 diff --git a/ui/src/lib/api/types.ts b/ui/src/lib/api/types.ts index 253006f..6a2d3ee 100644 --- a/ui/src/lib/api/types.ts +++ b/ui/src/lib/api/types.ts @@ -215,6 +215,8 @@ export interface LimitWindow extends LimitRecord { gateway: UsageTotals & { since: string }; // The last figure reported inside this window, if any: a lower bound. last_known: LimitRecord | null; + // When the figure came from another event's unified report. + reported_at?: string | null; } export interface LimitsResponse { diff --git a/ui/src/lib/limits.ts b/ui/src/lib/limits.ts index 926f3e6..53179f7 100644 --- a/ui/src/lib/limits.ts +++ b/ui/src/lib/limits.ts @@ -6,8 +6,18 @@ export const LIMIT_LABELS: Record = { seven_day_sonnet: "7-day · Sonnet", }; +const SEVEN_DAY = "seven_day_"; + export function limitLabel(window: string): string { - return LIMIT_LABELS[window] ?? window; + const known = LIMIT_LABELS[window]; + if (known) { + return known; + } + if (window.startsWith(SEVEN_DAY)) { + const model = window.slice(SEVEN_DAY.length); + return `7-day · ${model.charAt(0).toUpperCase()}${model.slice(1)}`; + } + return window.replaceAll("_", " "); } // The status as a word, for the windows the API never puts a figure on.