Skip to content

feat(usage): show Codex and Claude subscription limits on a Limits tab - #9507

Merged
juliusmarminge merged 7 commits into
mainfrom
feat/provider-usage-limits
Sep 4, 2026
Merged

feat(usage): show Codex and Claude subscription limits on a Limits tab#9507
juliusmarminge merged 7 commits into
mainfrom
feat/provider-usage-limits

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Closes #228.

Users on Codex or Claude Code subscriptions could not see how much of their quota was left, or when it resets, without leaving T3 Code. And a user whose CLIs route through a CLIProxyAPI hub could not see it at all: the Claude CLI treats the proxy token as an API key, and the hub's Codex login is not the one on disk.

What changed

Limits tab on the Usage page (web) and a Limits card (mobile), next to Cost and Tokens. Each provider that knows its subscription usage gets a section with one row per window: label and percent, a bar spanning the whole window with a hairline at the elapsed share ("where even spending would be"), a pace icon, and a reset countdown. Hover a bar for the exact reset time in your configured clock format. Emails are blurred until clicked, as in provider settings.

Each driver owns its data. checkProvider returns usageLimits on the driver's own ServerProvider snapshot — Codex from account/rateLimits/read during the probe it already runs, Claude from the SDK's get_usage control request on the capabilities query it already opens. Adapters normalise their turn-driven rate-limit events at the boundary into a typed ProviderUsageLimitsUpdate, and a ~40-line driver-blind ingestion layer folds them onto the owning instance through applyUsageLimits, so bars move mid-turn without a central service that switches on driver kind. Claude's model-scoped weekly bucket (Fable today) is read from rate_limits.model_scoped[]; the probe records the model's name so the streamed seven_day_overage_included event lands on the same row.

CLIProxyAPI hubs as usage-limit sources (second commit). settings.usageLimitSources entries are polled on the provider health interval and published over the config stream, gated by a client capability flag the way environment themes are. Not a provider — nothing here can run a turn, and one source reports many accounts. The management key goes to the secret store; settings keep a redaction marker. Hub accounts show under the hub's name, badged via CLIProxyAPI so a pooled account is not mistaken for the local login. Add CLIProxyAPI hub on the view adds one; Remove confirms before deleting the key.

Cursor, Grok, OpenCode, and Antigravity report nothing — their drivers never set usageLimits, so they have no row. Adding one later is "return usageLimits from checkProvider" with no central change.

Provenance

Distilled from two earlier PRs rather than merged from either:

  • feat: display provider usage limits in settings #1732 by @Aditya190803 — the server model: limits on each provider's snapshot, the contract shape, the Codex app-server read, the per-provider rows and threshold colours. Its Claude path scraped claude --print /usage output that current CLIs no longer print, and its Cursor/Grok paths drove interactive TUIs; those are replaced or left out, and are welcome back as follow-ups against checkProvider if a non-scraping source appears.
  • feat(usage): Limits tab with provider quota windows and Codex banked resets #9421 by @StiensWout — the Limits-tab placement, the window-bar-with-elapsed-marker design, the pace maths in packages/shared, and anchoring countdowns to render time. Its central UsageLimitsService and reset-credit redemption are not carried over.

Both are co-authors on the first commit.

Screenshots

Cost (unchanged) Limits
Cost Limits
Add a hub Remove confirms
Add hub Remove hub

The local Codex row shows a 401 because that box's ~/.codex login is revoked; the same account via the hub is healthy. The local Claude row is an API-key session (proxy token), so it says so.

Verification

  • Focused tests: managed-provider merge (11), Claude and Codex mappers (13), adapter event join (1), contract forward-compat (12), shared selection/pace (7), hub mapper (2), settings map replacement (24), UsagePage (4). Server, web, mobile, client-runtime, contracts typecheck clean apart from pre-existing yauzl / mobile-markdown-text errors on main.
  • Exercised on a real server against a live CLIProxyAPI hub with four accounts: add and remove through the UI, key redaction on disk, refresh on the Limits tab re-reads the hub, Fable row from the hub's fable window.
  • Not verified live: real Claude get_usage windows (every Claude login on the test box goes through the hub, so the CLI reports API-key mode). The mapper is tested against the SDK's documented shape and the CLI's actual model_scoped output; the bars are proven with stubbed data.

Follow-ups

Written by Claude Fable 5 via Claude Code, with the design and server model from the two PRs above.


Note

Medium Risk
Touches provider probes, runtime event shape (limits vs rateLimits), config streaming, and secret-store settings—clients must opt into new events; incorrect merge logic could show stale or wrong quota.

Overview
Adds a Limits view on Usage (web tab + mobile section) that shows subscription quota windows per connected environment—bars, pace vs. elapsed time, and reset countdowns—using shared helpers from @t3tools/shared/usageLimits.

Provider snapshots now carry normalized usageLimits: Codex reads account/rateLimits/read on probe; Claude adds SDK get_usage on the capabilities probe and maps streamed rate_limit_event into the same window ids (including model-scoped weekly via probe-recorded names). account.rate-limits.updated emits typed limits instead of raw payloads; ProviderUsageLimitsIngestion merges updates through ServerProviderShape.applyUsageLimits on makeManagedServerProvider (sparse merge by window id, keep last good bars on probe failure).

CLIProxyAPI hubs can be configured under settings.usageLimitSources (management key in secret store, redacted on disk). UsageLimitSources polls hubs and streams usageLimitSourcesUpdated when clients opt in via capability/subscription flags; refresh providers also refreshes sources. Web UI can add/remove hubs; mobile shows pooled accounts without emails.

Reviewed by Cursor Bugbot for commit 20594ca. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add Codex and Claude subscription limits to a Limits tab

  • Introduces structured usage-limit schemas and normalized windows for Codex and Claude providers, replacing untyped rate-limit payloads.
  • Provider adapters map streamed rate-limit events and probe responses into normalized usage windows; a new ingestion service folds live updates into provider snapshots without blocking probe refreshes.
  • Adds CLIProxyAPI usage-limit sources configured via server settings, with management keys stored in the secret store and redacted for clients.
  • Web and mobile apps gain a Limits view with quota bars, pacing indicators, and reset tooltips; clients opt in via the usageLimitSources capability.
  • Risk: AccountRateLimitsUpdatedPayload replaces the untyped rateLimits field with a structured limits field using ProviderUsageLimitsUpdate; settings persistence now writes management keys to the secret store and only persists redaction markers in usageLimitSources.

Macroscope summarized 20594ca.

juliusmarminge and others added 2 commits September 3, 2026 15:13
Users on Codex or Claude Code subscriptions had no way to see how much of
their quota was left or when it resets without leaving T3 Code.

Each driver decides for itself whether it has subscription usage and returns
it on its own `ServerProvider` snapshot as `usageLimits`: Codex from
`account/rateLimits/read` during the status probe, Claude from the SDK's
`get_usage` control request on the capabilities query it already opens.
Adapters normalise the turn-driven rate-limit events at the boundary into a
typed update, and a small driver-blind ingestion layer folds them onto the
owning instance through `applyUsageLimits`, so the bars move while a turn
runs without a central service that switches on driver kind.

The Usage page gains a Limits tab next to Cost and Tokens, and mobile a
Limits card, both reading the provider snapshots clients already hold. Each
window is a bar across its whole duration with a marker at the elapsed
share, a pace icon, and a reset countdown anchored to render time.

Distilled from #1732 (server model, provider rows) and #9421 (Limits tab,
window bars, pace maths).

Co-authored-by: Aditya Mer <101453576+Aditya190803@users.noreply.github.com>
Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
…view

A user whose CLIs route through a CLIProxyAPI hub sees no subscription
windows locally: the Claude CLI treats the proxy token as an API key and
the Codex login on the box may not be the one the hub uses. The hub already
knows every pooled account's windows.

Usage-limit sources are settings entries (`usageLimitSources`) polled on
the provider health interval by a small `UsageLimitSources` service and
published over the config stream, gated by a client capability flag like
environment themes. This is deliberately not a provider: nothing here can
run a turn, and one source reports many accounts. The management key goes
to the secret store and settings keep a redaction marker.

The Limits view groups each hub's accounts under its name, badges every row
"via CLIProxyAPI" so a pooled account is not mistaken for the local login,
blurs emails until clicked as provider settings do, and labels plans the way
the matching provider would. A dialog on the view adds a hub; each hub has
a remove control.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Sep 3, 2026
Comment thread apps/web/src/components/usage/AddUsageLimitSourceDialog.tsx Outdated
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.7 KiB 13.7 KiB −2 B (−0.0%) 15.1 KiB
Codex Thread snapshot wire 7.0 KiB 7.0 KiB −2 B (−0.0%) 7.3 KiB
Codex Live turn WebSocket wire 6.7 KiB 6.7 KiB 0 B (0.0%) 7.8 KiB
Codex Live turn WebSocket decoded 58.5 KiB 58.5 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 10 10 0 (0.0%) 21
Claude Total thread wire 13.8 KiB 13.6 KiB −166 B (−1.2%) 15.1 KiB
Claude Thread snapshot wire 7.0 KiB 7.0 KiB −2 B (−0.0%) 7.3 KiB
Claude Live turn WebSocket wire 6.7 KiB 6.6 KiB −164 B (−2.4%) 7.8 KiB
Claude Live turn WebSocket decoded 59.3 KiB 57.9 KiB −1.4 KiB (−2.4%) 66.4 KiB
Claude Live turn messages 10 10 0 (0.0%) 21

Baseline: 80b5373 · PR result: 20594ca · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment thread apps/server/src/serverSettings.ts
Comment thread apps/server/src/usage/UsageLimitSources.ts
Comment thread apps/web/src/components/usage/UsageLimits.tsx Outdated
Comment thread apps/server/src/ws.ts Outdated
Comment thread apps/server/src/provider/Layers/ClaudeAdapter.ts
Comment thread apps/server/src/provider/makeManagedServerProvider.ts
Comment thread apps/web/src/components/usage/AddUsageLimitSourceDialog.tsx
Comment thread apps/server/src/usage/UsageLimitSources.ts Outdated
Comment thread apps/server/src/provider/Layers/claudeUsageLimits.ts Outdated
Comment thread apps/server/src/usage/cliproxyUsageLimits.ts Outdated
The form hand-rolled its padding, which also skipped the panel's scroll
area, scroll fade, and the header-spacing rule keyed on
data-slot="dialog-panel". Wrap it in DialogPanel like the other dialogs.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Comment thread apps/web/src/components/usage/AddUsageLimitSourceDialog.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 0786184. Configure here.

Comment thread apps/server/src/usage/UsageLimitSources.ts Outdated
Comment thread apps/server/src/usage/UsageLimitSources.ts Outdated
- The Claude capabilities probe test's fake CLI only answered
  `initialize`, so the new `get_usage` request hung until the suite timed
  out; it now answers both and the test asserts the usage it returns.
- `serverRefreshProviders` awaited the usage-source refresh instead of
  forking it into an RPC scope that closed before the hub answered.
- A bad `usageLimitSources[].url` is reported on that source's row instead
  of failing the whole refresh batch; refreshes are serialised so a slow
  read cannot resurrect a source removed meanwhile.
- The overage-included bucket name is taken from the first scoped entry that
  drew a row, so a null-utilization entry cannot open a stray row.
- Hub auth-file emails keep hyphenated local parts.
- The add-hub dialog keeps dots and dashes in the host when deriving the id,
  and Cancel clears the typed key.
- Remove is offered only for the primary environment's own sources; a
  remote environment's row with the same id is read-only.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Comment thread apps/server/src/provider/Layers/codexUsageLimits.ts Outdated
Comment thread apps/server/src/usage/UsageLimitSources.ts
Comment thread apps/server/src/ws.ts
Comment thread apps/web/src/components/usage/UsageLimits.tsx Outdated
@macroscopeapp

macroscopeapp Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new Limits capability, changes provider probing and live runtime updates, and introduces an authenticated CLIProxyAPI integration with persistent secret handling. The breadth of the runtime and security-sensitive changes warrants human review.

You can add or adjust custom eligibility rules. Learn more.

Codex and hub read failures were copied verbatim onto the snapshot, which
put request URLs and response bodies on the wire and could produce an
empty string the contract rejects. Both now narrow the typed failure to a
short category (JSON-RPC code, HTTP status, timeout, bad URL, bad shape)
and log the raw cause. The Effect `catch` also stops returning a global
Error, which the repo's diagnostics flag.

Add and remove read the current source map at click time so two edits
before the first write echoes back cannot resurrect a removed hub.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Comment thread apps/server/src/usage/UsageLimitSources.ts
Comment thread apps/web/src/components/usage/AddUsageLimitSourceDialog.tsx Outdated
Comment thread apps/server/src/usage/UsageLimitSources.ts Outdated
juliusmarminge and others added 2 commits September 3, 2026 16:48
Adding or removing a hub sent the whole map, so two edits before the
first echoed back clobbered each other. The patch now names only the
entries it changes, with `null` removing one, and the server merges it
into its current map. `InvalidUrl` is a tagged error that keeps the URL
and the underlying cause for the log.

Co-Authored-By: Claude Code <noreply@anthropic.com>
… nothing

Codex sends account/rateLimits/updated beside every token-usage tick,
almost always with unchanged numbers. The merge now compares per window
on the way through and hands back the published object itself when
nothing moved, so the ingestion path drops the event by identity without
allocating a snapshot or running a structural equality.

Also records in resolveUsageLimitsAfterProbe why a successful probe
replaces a runtime update that landed while it ran.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 19d8ab2 into main Sep 4, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the feat/provider-usage-limits branch September 4, 2026 00:47
juliusmarminge added a commit that referenced this pull request Sep 4, 2026
Follow-up to #9507, carrying over the reset-credit redemption from #9421.

Codex grants a reset credit when it has rate-limited an account unfairly (`"Thanks for using Codex! You've been granted one free rate limit reset."`). Redeeming one clears the current 5h/weekly windows. The Limits tab now shows how many are banked and when the next expires, with a confirmed **Use a reset credit** action.

## How it works

- `ServerProviderUsageLimits.resetCredits` carries the count and soonest expiry; the Codex probe reads it from the same `account/rateLimits/read` it already makes.
- `ProviderInstance.consumeResetCredit` is a new optional hook — account-level, so it sits beside `refreshModels` rather than on the thread-routed adapter. The Codex driver implements it over a short-lived app-server (via `withCodexAppServerClient`, factored out of the status and skills probes which duplicated the setup), then re-probes.
- Single-flight per instance with one idempotency key kept until Codex reports an outcome, so a retry after a timeout does not open a second attempt.
- New `provider.consumeResetCredit` RPC under the operate scope; the outcome (`reset` / `nothingToReset` / `noCredit` / `alreadyRedeemed`) is shown inline.

Only Codex reports credits today. A provider without the hook gets a clear "does not bank reset credits" error; one without credits shows nothing.

## Screenshots

The local Codex row with one banked credit (the same account via the CLIProxyAPI hub above it shows no credit, as expected — the hub does not relay them):

![Limits tab with the Codex row showing "1 reset credit banked · next expires in 16d 22h" and a "Use a reset credit" button](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2897db5e357f12b3/credits-tab.png)

Close-up of the row:

![Codex row: Weekly 25% used bar, then "1 reset credit banked · next expires in 16d 22h" with the "Use a reset credit" button](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/0e80f9114cab8f36/credits-row.png)

Clicking it opens the confirmation; nothing is sent until **Use credit**:

![Confirm dialog: "Use a reset credit? This redeems one credit on your account and clears the current rate-limit windows. It cannot be undone." with Cancel and Use credit](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dc728580889ef1fe/credits-confirm.png)

## Verification

- Mapper tests for the credit summary; provider, contract, and Usage page suites pass; typecheck clean.
- Verified against a real Codex Pro account holding one credit: summary and expiry render, the confirm dialog opens. **Not redeemed** — that would spend the credit.

Written by Claude Fable 5 via Claude Code; design and single-flight approach from @StiensWout's #9421.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Redemption spends real account credits over a new RPC; correctness depends on per-account locking and idempotency, though disabled instances and non-Codex providers are rejected explicitly.
> 
> **Overview**
> Adds **end-to-end redemption of banked Codex rate-limit reset credits** from the Limits UI on web and mobile, backed by a new operate-scoped `provider.consumeResetCredit` RPC.
> 
> **Contracts and server:** `ServerProviderUsageLimits` can include `resetCredits` (count + next expiry). Codex probes attach that from `account/rateLimits/read`. Optional `ProviderInstance.consumeResetCredit` is implemented for Codex via a scoped app-server call to `account/rateLimitResetCredit/consume`, then a limits refresh. `CodexResetCreditCoordinator` serializes redemptions per Codex account directory, reuses one idempotency key until Codex returns an outcome, and times out hung requests. `withCodexAppServerClient` is extracted so status, skills, and redemption share the same short-lived app-server setup.
> 
> **Clients:** Limits rows show banked credits and a confirmed **Use a reset credit** action that calls `serverEnvironment.consumeResetCredit` and surfaces outcomes (`reset`, `nothingToReset`, etc.) or errors.
> 
> **Web usage sources (same PR):** Adding/removing CLIProxyAPI hubs and the add dialog target a **selected connected environment** (with picker when several are connected), gated by operate access—not only the primary environment.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 98f32e6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->


Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
sheehanmunim added a commit to munimtechnologies/mtcode that referenced this pull request Sep 4, 2026
…ity ACP, browser import, settings reorg, limits tab)

Brings the fork up to upstream/main fee2e0f. Highlights: Google Antigravity
via the official ACP agent (pingdotgg#9348) + model manifest refreshes, browser-cookie
import (Chrome/Edge/Brave/Firefox, pingdotgg#7255/pingdotgg#7260/pingdotgg#7261), settings page reorg
(pingdotgg#9354), Codex/Claude subscription Limits tab (pingdotgg#9507, pingdotgg#9534), context
compaction command (pingdotgg#9293), async Codex questions (pingdotgg#9512), full-access
OpenCode threads skip approvals (pingdotgg#9282), project icons default (pingdotgg#9457).

Unification decisions (keep-both unless noted):
- BrowserImport: upstream implementation wins (Linux libsecret, Windows DPAPI
  unwrap, writeCookies); fork's Safari engine ported in (jar definition,
  candidate path, profile listing, running check, count skip, import branch,
  FullDiskAccess wizard step + SafariCookies kept, domain widened only for
  dotted hosts).
- Antigravity: upstream ACP provider/adapter/driver/textgen win; fork branding
  ported (providerDisabledMessage), AntigravitySettings unified (fork fields +
  upstream auth fields; enabled stays default-on).
- contracts/model: Antigravity defaults follow upstream's manifest model.
- ClaudeAdapter compact_boundary: fork's resolve helper kept, renamed to
  upstream's compactedUsage to match downstream.
- Claude capabilities probe: fork's timeout races kept; upstream's raw usage
  fetch added under the same timeout so stalled usage still degrades.
- Codex provider: upstream's withCodexAppServerClient + enriched rate-limits
  probe win (fork title branding already inside buildCodexInitializeParams).
- ProviderCommandReactor: fork goal-continuation + correction-aware first-turn
  kept; upstream compact-command exclusion added.
- OpenCodeAdapter ask path: fork's pendingGate/acceptingRequests gate kept;
  upstream full-access autoReply + terminal guard + emitUnsafe added.
- makeManagedServerProvider: fork probe-timeout protection kept; upstream
  usage-limits reconciliation applied to the checked snapshot.
- Manager.ts preview CDP: fork reuse-if-attached + detach resilience kept;
  upstream wcDebugger hardening applied.
- mobile threadSyncPhase pill dropped (upstream ThreadDetail redesign covers
  loading/sync presentation); outbox deliveryMode + goal handling kept.
- Usage: fork client-version projection kept; upstream pricing()/refreshRates
  adopted on both clients.
- Settings: fork sections (MT Teams+badge, voice, notifications, account
  sign-in) kept; upstream reorg (ids, submenus, behaviour section) adopted.
- README: fork copy kept; Antigravity added to provider lists.
- Cursor skill test macOS /var-vs-/private/var path failure is pre-existing
  upstream breakage, unrelated to this merge.

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add usage / quota visibility for Codex sessions and accounts

1 participant