Summary
codex-acp already ingests Codex's RateLimitSnapshot (the ChatGPT subscription usage windows that the CLI shows in /status), but the structured snapshot never crosses the ACP boundary. It is only surfaced as human-readable markdown inside the /status slash command output. ACP clients that wrap codex-acp (Zed, Kepler, JetBrains plugins, etc.) therefore cannot render a live "47% used; resets in 2h 14m" usage badge without scraping /status text, and there is no warning state before a turn fails with the usage limit.
This is the new-repo continuation of zed-industries/codex-acp#305 (closed, with a note to re-file here), and the Codex twin of agentclientprotocol/claude-agent-acp#625, which requests the same indicator for Anthropic subscriptions.
What the adapter already has (half of #305 is done)
The data already reaches the wrapper and is stored per-session:
src/app-server/v2/AccountRateLimitsUpdatedNotification.ts delivers RateLimitSnapshot from the Codex App Server.
CodexEventHandler.handleRateLimitsUpdated stores the full snapshot (primary / secondary windows with usedPercent, window_minutes, resets_at, plus credits and plan_type) into sessionState.rateLimits (a RateLimitsMap).
CodexCommands.formatRateLimitLines / formatSingleRateLimit render that snapshot as markdown in the /status output.
- The App Server exposes
account/rateLimits/read and account/usage/read for on-demand reads.
So ingestion, storage, and a text view all exist.
What is still missing (the actual ask of #305)
The structured snapshot does not cross ACP as machine-readable data:
handleRateLimitsUpdated stores the snapshot and then return null — the account/rateLimits/updated notification is swallowed and nothing is emitted to the client.
- The
usage_update session notification carries only used / size (context window), not rate limits.
_meta.quota (CodexAcpServer.buildQuotaMeta) carries only token_count + model_usage, not rate limits.
Net effect: a client can only get the windowed allowance by invoking /status and parsing markdown. There is no programmatic, push-based path for a status indicator.
Why exposing it matters
- Clients cannot render a usage badge equivalent to what
codex /status shows in the CLI without scraping localized markdown.
- There is no warning state before a user hits the cap mid-turn.
- ChatGPT Plus / Pro / Business users who pay specifically for the windowed allowance get a worse experience under ACP than under the raw CLI.
- Cost-tracking is not the right metric for subscription users; the windowed allowance is, and it is the only signal that is currently text-only.
Proposed shape
Open to whatever fits the maintainers' constraints. Two non-exclusive routes, both reusing the snapshot already kept in sessionState.rateLimits:
- Push on update: when
account/rateLimits/updated arrives, instead of swallowing it (handleRateLimitsUpdated currently returns null), emit a session notification carrying the snapshot under _meta (e.g. _meta.codex/rateLimits or _meta.rateLimits), so clients can keep a status indicator current independent of an active turn.
- Per-turn: include the latest
rateLimits snapshot in the existing usage_update notification's _meta, alongside the context-window used / size it already carries.
UsageUpdate is @experimental in the ACP schema, so _meta is a reasonable place to land this without waiting on typed schema additions; it can be promoted to a typed ACP extension later.
The wire payload would mirror the snapshot already stored, e.g.:
planType is already present on the snapshot (RateLimitSnapshot.plan_type), so labelling Plus / Pro / Business is essentially free; clients can otherwise infer the tier from windowMinutes.
Related
Happy to draft a PR if there is alignment on the shape and on whether this goes through _meta or a typed ACP extension.
Summary
codex-acpalready ingests Codex'sRateLimitSnapshot(the ChatGPT subscription usage windows that the CLI shows in/status), but the structured snapshot never crosses the ACP boundary. It is only surfaced as human-readable markdown inside the/statusslash command output. ACP clients that wrapcodex-acp(Zed, Kepler, JetBrains plugins, etc.) therefore cannot render a live "47% used; resets in 2h 14m" usage badge without scraping/statustext, and there is no warning state before a turn fails with the usage limit.This is the new-repo continuation of zed-industries/codex-acp#305 (closed, with a note to re-file here), and the Codex twin of agentclientprotocol/claude-agent-acp#625, which requests the same indicator for Anthropic subscriptions.
What the adapter already has (half of #305 is done)
The data already reaches the wrapper and is stored per-session:
src/app-server/v2/AccountRateLimitsUpdatedNotification.tsdeliversRateLimitSnapshotfrom the Codex App Server.CodexEventHandler.handleRateLimitsUpdatedstores the full snapshot (primary/secondarywindows withusedPercent,window_minutes,resets_at, pluscreditsandplan_type) intosessionState.rateLimits(aRateLimitsMap).CodexCommands.formatRateLimitLines/formatSingleRateLimitrender that snapshot as markdown in the/statusoutput.account/rateLimits/readandaccount/usage/readfor on-demand reads.So ingestion, storage, and a text view all exist.
What is still missing (the actual ask of #305)
The structured snapshot does not cross ACP as machine-readable data:
handleRateLimitsUpdatedstores the snapshot and thenreturn null— theaccount/rateLimits/updatednotification is swallowed and nothing is emitted to the client.usage_updatesession notification carries onlyused/size(context window), not rate limits._meta.quota(CodexAcpServer.buildQuotaMeta) carries onlytoken_count+model_usage, not rate limits.Net effect: a client can only get the windowed allowance by invoking
/statusand parsing markdown. There is no programmatic, push-based path for a status indicator.Why exposing it matters
codex /statusshows in the CLI without scraping localized markdown.Proposed shape
Open to whatever fits the maintainers' constraints. Two non-exclusive routes, both reusing the snapshot already kept in
sessionState.rateLimits:account/rateLimits/updatedarrives, instead of swallowing it (handleRateLimitsUpdatedcurrently returnsnull), emit a session notification carrying the snapshot under_meta(e.g._meta.codex/rateLimitsor_meta.rateLimits), so clients can keep a status indicator current independent of an active turn.rateLimitssnapshot in the existingusage_updatenotification's_meta, alongside the context-windowused/sizeit already carries.UsageUpdateis@experimentalin the ACP schema, so_metais a reasonable place to land this without waiting on typed schema additions; it can be promoted to a typed ACP extension later.The wire payload would mirror the snapshot already stored, e.g.:
{ "primary": { "usedPercent": 47.0, "windowMinutes": 300, "resetsAt": "2026-05-25T18:14:00Z" }, "secondary": { "usedPercent": 12.5, "windowMinutes": 10080, "resetsAt": "2026-05-29T09:00:00Z" }, "credits": { "unlimited": false, "balance": null }, "planType": "Plus" }planTypeis already present on the snapshot (RateLimitSnapshot.plan_type), so labelling Plus / Pro / Business is essentially free; clients can otherwise infer the tier fromwindowMinutes.Related
Happy to draft a PR if there is alignment on the shape and on whether this goes through
_metaor a typed ACP extension.