## Why
Gateway OAuth reads and logins previously used only the current provider, preventing clients from authorizing another configured provider without changing the default.
## What changed
- Add optional `modelProvider` params to `account/gatewayOAuth/read` and `account/gatewayOAuth/login`, preserving current-provider behavior for omitted or null params and selectors.
- Validate explicit providers against current managed policy without changing the default or any thread's provider.
- Retain readiness state across providers and send `account/gatewayOAuth/changed` notifications for every configured provider sharing the changed credential.
- Update protocol schemas, TypeScript types, Python models, and documentation.
## Testing
Add coverage for non-default provider login, retained readiness when alternating providers, notifications for shared credentials without starting login, and legacy TypeScript request compatibility.
GitOrigin-RevId: cfd472c322b821fe36b8c133a8de774f4ba48534
## What changed
- Add `accountSession/list` and `accountSession/switch` for the active managed ChatGPT login, with optional workspace membership refresh.
- Validate membership and workspace policy, pause new turns, and unload running threads before switching credentials. Notify clients with `thread/closed` and `account/updated`; selecting the current workspace leaves threads running.
- Support browser sign-in for SSO through `allowBrowserLogin`, binding the callback to the requesting login and workspace. Require a separate sign-in for government workspaces.
- Preserve the selected workspace across token refresh and conditionally restore the previous workspace if routing validation fails. Guard credential writes against concurrent login or logout changes.
## Testing
Add app-server and auth tests covering workspace switching, credential preservation, policy and identity rejection, conditional rollback, stale sessions, and SSO callback validation.
GitOrigin-RevId: 1234abc33b71e69b66318da3e201fccc08867906
## What changed
- Return the selected workspace's display name as `workspaceName` in ChatGPT account responses, caching it alongside workspace routing and updating generated schemas and bindings.
- Display a `ChatGPT workspace` row with the name and account ID, falling back to the ID when the name is unavailable or invalid. Refresh workspace details with account identity updates and hide the row for providers that do not use ChatGPT authentication.
## Testing
Update account-response tests to cover workspace names and null values, add workspace details to the status snapshot, and verify that switching to a custom API-key provider hides the workspace row.
GitOrigin-RevId: 4b30ab8a39e1fa7ad1ba7367c5295544be8f6001
## What changed
- Support `model_provider = "gcp"` with a Vertex AI endpoint derived from `GOOGLE_CLOUD_PROJECT` and `GOOGLE_CLOUD_LOCATION`, defaulting to the `global` location.
- Obtain credentials through `gcloud auth application-default print-access-token` by default, and allow `base_url`, `auth`, and `http_headers` overrides under `model_providers.gcp`.
- Use a static model catalog tailored to Vertex AI, preserve function tools, namespaces, and client-side tool search, and support remote compaction V2. Disable unsupported reasoning summaries, Responses Lite, custom tools, web search, and image generation.
- Expose a `gcp` account type through the app-server protocol and generated clients without resolving credentials during account reads or requiring OpenAI authentication.
## Testing
Add coverage for endpoint resolution, configuration overrides, model catalog filtering, authenticated requests, unsupported request fields, missing credentials, account reporting across provider changes, and automatic remote compaction.
GitOrigin-RevId: 97a3158269e6dbc2605e2cf576998e09817ea12a
## Why
Clients with a gateway sign-in UI need to control when browser authorization starts and inspect credential readiness before making authenticated requests.
## What changed
- Add `initialize.capabilities.explicitGatewayOauth` to require explicit login while allowing existing credentials to refresh. Preserve automatic authorization for clients that omit the capability, and prevent later connections from undoing explicit opt-in.
- Add `account/gatewayOAuth/read`, `account/gatewayOAuth/login`, and `account/gatewayOAuth/cancel`, plus `account/gatewayOAuth/changed` notifications. Send the authorization URL only to the initiating connection and cancel its login on disconnect.
- Check gateway authentication before returning cached results from `model/list`, with restart guidance when provider settings have changed.
- Update protocol schemas, TypeScript and Python bindings, and document capability probing and the sign-in lifecycle.
## Testing
Add tests for passive readiness reads, credential reuse after login, token exchange failures, cancellation and immediate retry, connection ownership, legacy versus explicit login, and model-list authentication checks.
GitOrigin-RevId: a26e116a62c9f3db84dc238a40b7f4e816eb1594
## What changed
- Add experimental `account/read.workspaceRouting` metadata containing the selected ChatGPT workspace ID, resolved HTTPS backend origin, and routing override (`us`, `us_cr`, or `NO_CONSTRAINT`).
- Discover and cache routing through `accounts/check` for saved logins, new logins, and workspace switches. Return `null` for signed-out accounts, API-only accounts, and saved credentials without a selected workspace.
- Validate discovered origins against required `chatgpt_base_url` origins. Return errors for failed or malformed discovery and retry on later reads.
- Wait for requirements and routing before publishing account updates, including to newly initialized connections. Clear routing on logout, discard stale discovery results, and guard queued notifications against account changes.
## Testing
Add unit and integration coverage for origin resolution and validation, discovery failures and retries, startup discovery, workspace switching, logout, configuration changes during discovery, and authentication changes while notifications wait for queue capacity.
GitOrigin-RevId: 2c5367bb01dd7543b08d374f44444323e40f1981
## Why
When ordinary included usage is exhausted, eligible users need a way to keep working with the backend-provided Luna Reserve quota and return to their previous model once ordinary usage recovers.
## What changed
- Automatically switch eligible TUI tasks to Luna Reserve after an authoritative account usage read, preserving the prior model and reasoning effort per task.
- Restrict model selection while on Reserve to its supported reasoning levels, keep queued turns on the accepted model, and restore the saved model after a fresh read confirms recovery.
- Surface Reserve usage in the composer and `/status`, and expose `normalModelSlug` in app-server rate-limit snapshots so clients can use the associated model's display metadata without changing the request model.
- Poll account limits more frequently near exhaustion and retain compatibility with app servers that only accept the legacy null request parameters.
## Testing
- Added coverage for Reserve entry, queued-turn handling, task reconstruction, recovery authorization, account changes, model selection, usage rendering, and banner dismissal.
GitOrigin-RevId: 46a435e9531051f8cec3f6a46c4b9f7cfa479747
## What changed
- Accept optional `account/rateLimits/read` capabilities for Luna Reserve support
and lightweight background polling while preserving omitted and `null` params.
- Forward the Luna Reserve capability only for eligible ChatGPT sessions, and let
lightweight polls skip the separate reset-credit detail request.
- Return the backend's `ordinaryUsageAllowed` decision only when its account and
user identity match the active session.
## Testing
- Cover capability defaults, eligible and restricted authentication modes,
lightweight polling, identity filtering, and backend permission handling.
GitOrigin-RevId: f407a3ae1e70e411fdf245f072a16e453f59cd87
## What changed
- Carry optional backend-owned banner and account identity data through
`account/rateLimits/read`, filtering banners that do not match the authenticated
account and user.
- Render supported rate-limit notices above the composer with actions for usage,
credits, resets, owner notifications, and plan management while preserving the
existing fallback UI for absent or unsupported banners.
- Refresh usage after limit errors, reject stale responses, and keep queued input
paused until recovery completes. When instructed by the banner, switch to the
first available fallback model without changing unrelated thread settings.
## Testing
- Add protocol, banner parsing and rendering, CTA, identity filtering, refresh
ordering, fallback-model, account-change, and queued-input recovery coverage.
GitOrigin-RevId: e03d75a82e425deb87f4b120a71cf2286c0ef6cc
## What changed
- Add the experimental `amazonBedrockAccessKeys` app-server login flow, persist its credentials in the configured auth store, and use them for SigV4-signed Bedrock requests.
- Report `bedrockAccessKeys` as a distinct auth mode and document access-key login alongside AWS profile and environment credential setup.
- Clear the selected Bedrock provider, AWS settings, and Bedrock-specific model on logout while leaving external AWS credentials and `.env` untouched.
- Reject managed access-key login when higher-precedence configuration would prevent its region or profile settings from taking effect.
## Testing
- Cover access-key login, storage, request signing, configuration conflicts, status reporting, and logout cleanup.
GitOrigin-RevId: bbf297887c6503d3651ceb2aad85c4d626e32723
## Why
App-server response diagnostics can include access tokens returned by the ChatGPT auth refresh flow.
## What changed
- Log successfully parsed server responses instead of raw JSON-RPC response payloads.
- Redact `access_token` from the `Debug` representation of `ChatgptAuthTokensRefreshResponse`.
- Avoid formatting callback send errors that can retain the original response payload.
GitOrigin-RevId: fd47485b38ed89527b25937c759b3273581f6ae6
## What changed
- Extend `account/usage/read` with an optional `threadId` request and a
backward-compatible `threadUsage` response containing estimated credits,
optional USD cost, and model, reasoning, speed, and token breakdowns.
- Fetch thread usage for eligible Business and Enterprise plans when `/status`
is opened, then update the rendered status card asynchronously.
- Preserve terminal scrollback when replacing a visible status-card tail, and
append a refreshed card when the original tail can no longer be updated.
## Testing
- Add app-server coverage for authentication, canonical thread IDs,
unavailable billing routes, and malformed IDs.
- Add TUI coverage for usage formatting, stale responses, retries, history
updates, terminal reflow, and scrollback preservation.
GitOrigin-RevId: 49300914b3fba917c0f08e8595363e04e5ba6d37
## What changed
- Accept the allowlisted `.onboarding_entrypoint=life_sciences` suffix on a valid OAuth state while continuing to reject unknown or malformed suffixes.
- Return the parsed callback metadata from the login server without including the suffix in the token exchange redirect URI.
- Add the optional `onboardingEntrypoint` field and `life_sciences` value to the v2 `account/login/completed` notification and generated protocol exports.
## Testing
- Cover valid, invalid, and repeated OAuth state suffixes.
- Verify the end-to-end login callback result, token redirect URI, and app-server notification payload.
GitOrigin-RevId: c2ee146328ddd43c03a503260d5d492e186fa889
## Why
Normal app-server protocol builds do not need the `ts-rs` and `schemars`
implementations used to generate schema exports.
## What changed
- Embed compressed stable and experimental TypeScript and JSON schema exports,
and serve the existing export APIs from those artifacts.
- Use no-op schema derives in non-test builds while keeping the real generators
available for fixture regeneration and tests.
- Move schema fixture regeneration behind a Python helper and update
`just write-app-server-schema --experimental` to refresh the experimental
embedded exports.
## Testing
- Verify embedded exports match freshly generated stable and experimental
schemas.
- Verify export options and on-disk output remain compatible.
GitOrigin-RevId: e8536338b457e6eec34bdf29ec0684144bd13734
## What changed
- Add `thread/section/move` to atomically move a thread into, within, or out of a section. Threads can be inserted before an existing member or appended, and moves within a section preserve `sectionEnteredAt`.
- Add `section_position` sorting to `thread/list`, with ascending order as its default, and expose `sectionEnteredAt` in thread responses.
- Persist section positions and entry times in SQLite, including migration of existing section members into recency order. Section membership is no longer updated through `thread/metadata/update`.
## Testing
- Cover section moves, reordering, pagination, persistence across restarts and rollout reconciliation, concurrent updates, and rank renumbering.
GitOrigin-RevId: aec6d7ddedca5277029b5caf5c074975397e956c
## What changed
- Allow the built-in `amazon-bedrock` provider to override `base_url`, `auth`,
and `http_headers` in addition to its AWS profile and region.
- Use command-based bearer authentication and configured endpoints without
applying AWS request signing, while retaining regional endpoint resolution
for the default Bedrock configuration.
- Replace the Bedrock account `credentialSource` enum with the
`usesCodexManagedCredentials` boolean so command-authenticated and other
externally managed configurations are reported consistently.
## Testing
- Cover configuration merging and validation, command-authenticated proxy
requests with custom headers, and account reporting for managed and external
credentials.
GitOrigin-RevId: d1acbe602060470583b5e12f8d304bee5be46f4c
## Why
Sparse and out-of-order rate-limit updates can omit account metadata or let an
older full read overwrite a newer workspace hard stop. Credit availability also
does not always include a displayable balance.
## What changed
- Propagate the backend's `spendControlReached` state through rate-limit
snapshots and the app-server protocol, preserving it across sparse updates.
- Keep workspace hard-stop classifications for error routing and ignore stale
full-read results after a newer hard-stop notification.
- Base TUI warnings and model-switch prompts on credit availability and hard-stop
state, and show `Credits: Available` when a finite balance is hidden.
## Testing
Added coverage for spend-control mapping, snapshot merging, out-of-order reads,
workspace error routing, warning and prompt behavior, and credit status display.
GitOrigin-RevId: 18b09d493d5a620a7c079fb721ec50b38dd5b04c
## Why
App-server clients can report whether Amazon Bedrock is using
AWS-managed credentials or a Codex-managed API key, but they do not have
a matching API for creating the managed login. This PR defines that
experimental wire contract independently from its implementation.
Managed Bedrock API keys are already a primary `CodexAuth` mode. The API
therefore describes a normal Codex login that replaces the current
stored auth rather than introducing provider-scoped credential storage.
## What changed
- Add the experimental `amazonBedrock` variant to `account/login/start`.
- Accept an API key and AWS region and return a matching discriminated
response.
- Gate the request behind the app-server `experimentalApi` capability.
- Regenerate the JSON and TypeScript protocol schemas.
- Document the login contract, notifications, primary-auth replacement
semantics, restart boundary, and non-transactional durable writes.
## Impact
This PR defines the API shape but does not implement login behavior. The
next PR adds validation, persistence through the existing Codex auth
lifecycle, provider selection, and notifications.
## Validation
- `just test -p codex-app-server-protocol`
## Stack
1. **#31327 Managed Bedrock experimental API** — base: `main`
2. #31326 Managed Bedrock login — base: `codex/managed-bedrock-api`
3. #31325 Managed Bedrock logout — base:
`codex/managed-bedrock-login-v2`
## What
Adds an optional hosted login-success redirect path for app-server login
requests.
- Keeps the existing localhost success page as the default.
- Lets app-server callers opt into a hosted success page with an
optional protocol field.
- Persists credentials before redirecting to the hosted success page.
- Keeps org setup and existing CLI/device-code login flows on the local
success page.
- Accepts an optional typed `appBrand` value and forwards it to the
hosted page as `app_brand` so web can select the correct asset.
- Generates the app-server protocol schema updates for the new optional
fields.
## Why
This supports the hosted Codex login success page rollout without
changing existing login behavior by default. The Codex Apps frontend can
gate the opt-in with Statsig after the hosted web page.
## Rollout safety
- Old callers omit the new field and continue using localhost.
- New callers talking to old app-server builds remain safe because the
Codex Apps side treats the field as optional and defaults the flag off.
- Missing brand values default to Codex.
- The hosted redirect always uses the app-login source so the hosted
page can reopen Codex; the existing streamlined-login visual flag
remains separate.
## Validation
- `just fmt`
- `just fix -p codex-login -p codex-app-server-protocol -p
codex-app-server -p codex-app-server-test-client -p codex-tui`
- `just test -p codex-login`
- `just test -p codex-app-server-protocol`
- `just write-app-server-schema`
- `git diff HEAD --check`
The focused login and protocol run passed all 380 tests.
I also started the broader `just test -p codex-app-server`; it compiled
successfully, then many tests failed on this machine because spawned
test servers tried to use the ambient `/Users/rafaelj/.codex/sqlite`
state DB, which is read-only in this sandbox. I stopped that run after
confirming the failures shared that environment issue.
## Why
Supported clients currently receive only a reset-credit count from
`account/rateLimits/read`. The redemption UI and other app-server
clients need each available credit's expiry and ID so they can explain
what will expire and consume the credit a user selected. This
information belongs on the existing rate-limit read surface rather than
a second app-server list RPC that clients would need to coordinate.
## What changed
- extend `rateLimitResetCredits` on `account/rateLimits/read` with
nullable `credits` detail rows
- fetch usage and reset-credit details concurrently; if the detail
request fails, times out, or cannot be parsed, preserve the usage
response and return `credits: null`
- expose each credit's ID, reset type, status, grant time, expiry time,
title, and description
- add an optional nullable `creditId` to
`account/rateLimitResetCredit/consume`; omitting it preserves the
existing automatic-selection behavior
- forward a selected credit ID to the Codex backend and update the
app-server documentation and generated schemas
The TUI consumer is stacked in #30488.
## Validation
- `just test -p codex-app-server-protocol` (251 passed)
- `just test -p codex-backend-client` (16 passed)
- `just test -p codex-app-server rate_limit` (18 passed)
Part of #29618.
# Summary
Codex required every ChatGPT account to have an email address. A
service-account personal access token can return valid account metadata
without one, so PAT login failed while decoding the metadata response.
This change makes email optional in the account metadata type that owns
it and preserves that absence through authentication, provider account
state, the app-server API, generated clients, and TUI bootstrap.
Existing accounts with email addresses keep the same behavior.
## Behavior-changing call sites
| Call site | Behavior after this change |
| --- | --- |
| `login/src/auth/personal_access_token.rs` | PAT metadata accepts a
missing or null email and retains `None`. |
| `agent-identity/src/lib.rs` | Agent Identity JWT claims accept an
omitted email. |
| `login/src/auth/storage.rs` and `login/src/auth/agent_identity.rs` |
Stored and managed Agent Identity records carry `Option<String>`.
Deserialization maps the legacy empty-string sentinel to `None`. |
| `login/src/auth/manager.rs` | `get_account_email` returns the stored
option, and managed identity bootstrap no longer converts `None` to an
empty string. |
| `model-provider/src/provider.rs` and `protocol/src/account.rs` | A
ChatGPT provider account requires a plan type but may carry no email. |
| `app-server-protocol/src/protocol/v2/account.rs` | `account/read`
keeps the `email` field on the wire and returns `null` when the account
has no email. Generated TypeScript and JSON schemas describe a required,
nullable field. |
| `sdk/python/src/openai_codex/generated/v2_all.py` | The generated
Python `ChatgptAccount` model accepts `None` for email. |
| `tui/src/app_server_session.rs` | Email-less ChatGPT accounts
bootstrap normally, keep external feedback routing, omit account-email
telemetry, and display the plan in account status. |
## Design decisions
- Missing email remains `None` at every layer. The code never uses an
empty string as a substitute.
- The app-server response includes `"email": null` instead of omitting
the field. Clients retain a stable response shape.
- Plan type remains required for provider account state. This change
relaxes only the email assumption.
## Testing
Tests: affected test targets compile, scoped Clippy and formatting pass,
a focused TUI snapshot covers plan-only account status, real
before/after PAT login smoke covers metadata without email, app-server
smoke covers `account/read` with `email: null`, and a regression smoke
covers an existing email-bearing PAT. Unit tests run in CI.
## Evidence
Visual smoke evidence will be attached here.
## Why
`account/read` currently reports only `type: "amazonBedrock"`, so
clients cannot distinguish a Codex-managed Bedrock API key from
credentials supplied by AWS. The app UI needs that distinction to render
the appropriate account state without duplicating provider-auth logic.
Credential-source selection belongs to the Bedrock model provider
because it already owns the precedence between managed Bedrock auth and
the external AWS credential path. This builds on #27443 and #27689.
## What changed
- Added `AmazonBedrockCredentialSource` with `codexManaged` and
`awsManaged` values.
- Included the selected credential source in
`ProviderAccount::AmazonBedrock` and the app-server `Account` response.
- Made `AmazonBedrockModelProvider::account_state()` classify the source
from its managed-auth state.
- Regenerated the app-server JSON and TypeScript schemas.
- Updated app-server account documentation and downstream TUI matches.
`codexManaged` means the provider found a managed Bedrock API key.
`awsManaged` identifies the provider's external AWS credential path; it
does not assert that the AWS credential chain has been validated.
## Testing
- Added model-provider coverage for Codex-managed precedence and
AWS-managed fallback.
- Added app-server protocol serialization coverage for both wire values.
- Added app-server integration coverage for both `account/read`
responses.
- `just test -p codex-protocol -p codex-model-provider -p
codex-app-server-protocol` (497 tests passed).
After rebasing onto #27711, the `codex-app-server` test target compiled
past the image-generation `PathUri` migration. Local linking was then
interrupted by disk exhaustion (`No space left on device`).
## Why
Codex users can earn personal rate-limit reset credits, but app-server
clients do not currently have an API for reading or redeeming them. This
adds the backend and protocol foundation used by the `/usage` TUI flow
in #28154.
## What changed
- Extend `account/rateLimits/read` with a nullable
`rateLimitResetCredits` summary sourced from the existing usage
response.
- Add backend-client and app-server support for consuming a reset with a
caller-generated idempotency key. A UUID is recommended, and clients
reuse the same key when retrying the same logical reset.
- Return only the consume `outcome`; clients refetch
`account/rateLimits/read` for updated window state.
- Document the response field and each consume outcome, and regenerate
the JSON and TypeScript schema fixtures.
- Clarify in `AGENTS.md` that new app-server string enum values use
camelCase on the wire.
- Update the existing TUI response fixture for the expanded protocol
shape.
- Add coverage for authentication, response mapping, backend failures,
consume outcomes, and request timeout behavior.
## Validation
- `just test -p codex-app-server-protocol` — 231 passed.
- `just test -p codex-backend-client` — 14 passed.
- Focused `codex-app-server` reset-credit tests — 5 passed.
- Focused `codex-tui` protocol response fixture test — passed.
- `just fix -p codex-backend-client -p codex-app-server-protocol -p
codex-app-server` — passed.
- `just fmt` — passed.
## Why
Token activity is useful account-level context, but terminal clients
need a supported app-server path to fetch it without reaching into
ChatGPT backend details directly. The API should also live under the
broader account usage umbrella so future usage surfaces can be added
without proliferating user-facing concepts.
## What Changed
- Add `codex-backend-client` support for the ChatGPT profile token-usage
payload.
- Add the v2 `account/usage/read` app-server RPC.
- Map lifetime usage, peak daily usage, streak, longest task duration,
and daily buckets into app-server protocol types.
- Gate the request on Codex-backend auth, which supports ChatGPT auth
tokens and AgentIdentity.
- Regenerate the app-server JSON and TypeScript schema fixtures.
## Token Count Source
`account/usage/read` returns the token-usage aggregate supplied by the
ChatGPT profile backend. App-server maps that backend-owned aggregate
into protocol fields; it does not recompute cached-token treatment,
usage multipliers, or raw input/output totals locally.
## Stack
1. feat(app-server): expose account token usage [1 of 2] (this PR)
2. [#25345](https://github.com/openai/codex/pull/25345) feat(tui): add
token activity command [2 of 2]
## How to Test
1. Start an app-server client from this branch while authenticated with
ChatGPT or AgentIdentity.
2. Call `account/usage/read`.
3. Confirm the response includes `summary` and `dailyUsageBuckets`.
4. Also verify a session without Codex-backend auth receives the
existing auth error path.
Targeted tests:
- `just test -p codex-backend-client -p codex-app-server-protocol -p
codex-app-server`
- `just write-app-server-schema`
## Summary
Adds the app-server v2 `accountSession/*` protocol used by the Desktop
profile switcher and the backend account metadata client needed to
populate workspace choices.
This is the protocol layer only. The app-server lifecycle and
consolidated saved-session storage are split into a follow-up PR.
## Rust Stack
1. This PR
2. [openai/codex#25383](https://github.com/openai/codex/pull/25383) adds
app-server session lifecycle behavior and consolidated saved-session
storage.
## Validation
- Generated app-server schema fixtures are included from the existing
generation flow in the lifecycle PR where the routes are registered.
- Did not run tests per requested scope.
## Summary
Enterprise users can have an effective monthly credit limit, but Codex
`/status` currently drops that metadata from the account-usage response.
This change adds the optional `spend_control.individual_limit`
projection to the existing rate-limit snapshot flow. The backend client
reads the monthly limit, app-server exposes it as `individualLimit`, and
the TUI renders a `Monthly credit limit` row through the existing
progress-bar renderer.
When the backend does not return an effective monthly limit, existing
rate-limit behavior is unchanged.
## Existing backend state
The account-usage backend already returns the effective monthly limit
and current usage together:
```json
{
"spend_control": {
"reached": false,
"individual_limit": {
"limit": "25000",
"used": "8000",
"remaining": "17000",
"used_percent": 32,
"remaining_percent": 68,
"reset_after_seconds": 86400,
"reset_at": 1778137680
}
}
}
```
Before this change, Codex projected rolling `primary` and `secondary`
windows plus `credits`. It ignored `spend_control.individual_limit`, so
app-server clients and `/status` could not render the monthly cap.
The updated flow is:
```text
account usage backend
-> backend-client reads spend_control.individual_limit
-> existing rate-limit snapshot carries optional individual_limit
-> app-server exposes optional individualLimit
-> TUI renders Monthly credit limit
```
## App-server contract
`account/rateLimits/read` and sparse `account/rateLimits/updated`
notifications now include an additive nullable
`rateLimits.individualLimit` field:
```json
{
"individualLimit": {
"limit": "25000",
"used": "8000",
"remainingPercent": 68,
"resetsAt": 1778137680
}
}
```
In an `account/rateLimits/read` response, `null` means no monthly limit
is available. `account/rateLimits/updated` remains a sparse rolling
notification: clients merge available values into their most recent
`account/rateLimits/read` snapshot or refetch. Nullable account metadata
in a rolling notification does not clear a previously observed value.
## Design decisions
- Extend the existing rate-limit snapshot instead of introducing a
separate request or wire-level update protocol.
- Keep the Codex projection narrow: `/status` needs the effective limit,
current usage, remaining percentage, and reset timestamp.
- Render the monthly row through the existing progress-bar renderer,
with one optional detail line for `8,000 of 25,000 credits used`.
- Keep the backend response optional so existing accounts and older
usage states preserve their current behavior.
- Preserve cached monthly metadata when sparse rolling notifications
omit it. Live account-usage reads remain authoritative and can clear a
removed limit.
## Visual evidence
```text
Monthly credit limit: [██████████████░░░░░░] 68% left (resets 07:08 on 7 May)
8,000 of 25,000 credits used
```
Snapshot:
`codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_enterprise_monthly_credit_limit.snap`
## Testing
Tests: generated app-server schema verification, protocol tests,
backend-client tests, app-server integration coverage, TUI snapshot
coverage, formatting, and workspace lint cleanup.
`#[serde(default)]` wasn't sufficient for our generated TS types to
reflect that clients didn't have to set them. We also need
`skip_serializing_if = "std::ops::Not::not"`. This is already a rule in
our agents.md file.
## Why
`codex-rs/app-server-protocol/src/protocol/v2.rs` had grown into a
single ~12k-line definition file for the entire app-server v2 API.
This is purely a mechanical refactor to break up the monolithic `v2.rs`
file that contains all app-server API v2 types into more modular files,
grouped by resource (e.g. account, thread, turn, etc.).
`just write-app-server-schema` shows no real changes, so we can be sure
that this is purely an internal organizational change.
## What changed
- Replaced the monolithic `protocol/v2.rs` with a `protocol/v2/` module
tree and a small `mod.rs` that only declares and reexports modules.
- Grouped v2 API definitions by conceptual owner, including `account`,
`apps`, `collaboration_mode`, `command_exec`, `config`, `device_key`,
`experimental_feature`, `feedback`, `fs`, `hook`, `item`, `mcp`,
`model`, `notification`, `permissions`, `plugin`, `process`, `realtime`,
`review`, `thread`, `thread_data`, `turn`, and `windows_sandbox`.
- Moved v2 tests into `protocol/v2/tests.rs` so `mod.rs` stays small.
- Kept shared protocol helpers in `protocol/v2/shared.rs`, including the
enum mirroring macro and common cross-resource types.
- Co-located resource-specific notifications and server-request payloads
with the modules that own those resources.
- Regenerated app-server protocol schema fixtures. The schema diffs are
non-semantic newline-only changes after the refactor.
## Verification
- `cargo check -p codex-app-server-protocol`
- `cargo test -p codex-app-server-protocol`
- `just write-app-server-schema`