Skip to content

feat: surface provider quota in composer#3352

Open
AJV20 wants to merge 10 commits into
nesquena:masterfrom
AJV20:feat/provider-quota-composer-meter
Open

feat: surface provider quota in composer#3352
AJV20 wants to merge 10 commits into
nesquena:masterfrom
AJV20:feat/provider-quota-composer-meter

Conversation

@AJV20
Copy link
Copy Markdown
Contributor

@AJV20 AJV20 commented Jun 1, 2026

Summary

  • Adds a Codex-style provider quota meter in the chat composer next to the send button.
  • Renders quota as a circular percentage ring that drains as remaining quota decreases, with yellow/red low-quota states.
  • Defaults account-limit providers to the 5-hour window while the popover shows both 5-hour and weekly usage, with an in-popover switch for the ring window.
  • Keeps unsupported/error quota states quiet and reuses the existing server-side quota endpoint.

Test Plan

  • node --check static/ui.js static/messages.js static/sessions.js static/panels.js static/boot.js static/i18n.js
  • python -m py_compile api/config.py api/providers.py api/routes.py
  • python -m pytest tests/test_issue1766_quota_topbar.py tests/test_quota_chip_settings_toggle.py tests/test_provider_quota_status.py -q
  • git diff --check

@AJV20 AJV20 force-pushed the feat/provider-quota-composer-meter branch from 88860db to 77bc6ad Compare June 1, 2026 19:20
@AJV20 AJV20 force-pushed the feat/provider-quota-composer-meter branch from 77bc6ad to db39bdf Compare June 1, 2026 19:35
@AJV20 AJV20 marked this pull request as draft June 1, 2026 20:13
@AJV20 AJV20 marked this pull request as ready for review June 1, 2026 22:45
@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Pulled this branch and read the diff against origin/master. The ring/popover UI is nicely done and CI is green. Two things I'd flag before merge — one is a recorded-decision conflict, the other a server-cost concern.

1. This reverses an explicit maintainer directive on the default

The PR flips show_quota_chip from opt-in to opt-out. On master the default is False, and the existing test that pins it (tests/test_quota_chip_settings_toggle.py:1-4) carries the rationale:

Quota chip default state is now OFF (per Nathan's directive 2026-05-16, immediately
after the stage-371 release of #2082). Users opt in via Settings → Preferences.

git log -S '"show_quota_chip": False' shows that default was deliberately set in 69ff9ee8/3480e75e ("feat(quota-chip): add Settings toggle, flip default to off"). This PR rewrites that test (test_quota_chip_default_off_in_config_defaults_default_on_…, asserting "show_quota_chip": True) and api/config.py:4837, plus the !==true===false boot/render guards. That's not a test that drifted — it encodes a product decision. Flipping it back to default-on should be a maintainer call, not something folded silently into a feature PR. There was also a desktop-only @media (max-width:1399.98px){ .provider-quota-chip{display:none} } gate in style.css (removed here with a comment block citing PR #1721 composer-real-estate constraints) — same story: it was added on purpose. Worth calling out both reversals explicitly in the PR description so Nathan can sign off rather than discover them post-merge.

2. refresh=1 on every turn spawns a probe subprocess

attachProviderQuotaToLastAssistant (static/ui.js:873) fires on each assistant-turn completion via static/messages.js:2006, and it hits the endpoint with refresh=1:

const status=await api('/api/provider/quota?refresh=1',{timeoutToast:false});

On the server, refresh=1 bypasses the cache in _fetch_account_usage_with_profile_context (api/providers.py:1299-1311) and goes straight to _agent_fetch_account_usage_for_home, which spawns an account-usage probe subprocess (up to 35s each, capped only by _MAX_CONCURRENT_ACCOUNT_USAGE_PROBES). For OAuth/account-limit providers that means a fresh subprocess on every completed turn, per active tab. The Settings-page poller deliberately uses the cached path; the ironic part is the existing test you're editing even says the disabled-chip short-circuit exists "so we don't burn quota API calls for chip-disabled users" — and now it's default-on firing uncached probes per turn.

Recommendation: drop refresh=1 here (let it serve the cached snapshot, which get_provider_quota already keeps warm from the ambient poller), or gate the forced refresh behind a minimum interval so back-to-back turns don't each trigger a probe. The composer ring will still update on the next ambient refreshProviderQuotaIndicator() tick.

Smaller notes

  • _providerQuotaWindowDisplayName/_providerQuotaWindowMode (ui.js:790-805) match window labels with English regexes (/week/i, /5\s*(h|hour)/i). If account_limits.windows[].label is ever localized server-side, the 5-hour/weekly classification silently falls back to 'Window'/''. Probably fine today, but worth a comment that label-matching assumes the agent's English labels.
  • _formatQuotaResetDate hard-codes MM/DD/YY; consider toLocaleDateString() for non-US locales since the rest of the UI is i18n-aware.

The feature itself is solid — the ring, popover, window switch, and ephemeral-field carry-forward (_providerQuota added to _EPHEMERAL_TURN_FIELDS) are all clean. It's really just the default-flip needing maintainer sign-off and the per-turn refresh=1 cost that I'd resolve first.

@AJV20
Copy link
Copy Markdown
Contributor Author

AJV20 commented Jun 2, 2026

Thanks for the detailed review — addressed the two merge blockers:

  • restored the quota chip to opt-in/default-off semantics and pinned that in the settings regression test
  • restored the sub-1400px composer real-estate gate
  • removed refresh=1 from the per-turn footer quota attach path so it uses the cached quota endpoint instead of forcing a probe per completed turn

Verification:

  • git diff --check
  • python3 -m pytest tests/test_quota_chip_settings_toggle.py tests/test_issue1766_quota_topbar.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants