mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
085ffb445642452d6fcf79dcda34e88f4d108afb
12 Commits
-
Option to Notify Workspace Owner When Usage Limit is Reached (#16969)
## Summary - Replace the manual `/notify-owner` flow with an inline confirmation prompt when a usage-based workspace member hits a credits-depleted limit. - Fetch the current workspace role from the live ChatGPT `accounts/check/v4-2023-04-27` endpoint so owner/member behavior matches the desktop and web clients. - Keep owner, member, and spend-cap messaging distinct so we only offer the owner nudge when the workspace is actually out of credits. ## What Changed - `backend-client` - Added a typed fetch for the current account role from `accounts/check`. - Mapped backend role values into a Rust workspace-role enum. - `app-server` and protocol - Added `workspaceRole` to `account/read` and `account/updated`. - Derived `isWorkspaceOwner` from the live role, with a fallback to the cached token claim when the role fetch is unavailable. - `tui` - Removed the explicit `/notify-owner` slash command. - When a member is blocked because the workspace is out of credits, the error now prompts: - `Your workspace is out of credits. Request more from your workspace owner? [y/N]` - Choosing `y` sends the existing owner-notification request. - Choosing `n`, pressing `Esc`, or accepting the default selection dismisses the prompt without sending anything. - Selection popups now honor explicit item shortcuts, which is how the `y` / `n` interaction is wired. ## Reviewer Notes - The main behavior change is scoped to usage-based workspace members whose workspace credits are depleted. - Spend-cap reached should not show the owner-notification prompt. - Owners and admins should continue to see `/usage` guidance instead of the member prompt. - The live role fetch is best-effort; if it fails, we fall back to the existing token-derived ownership signal. ## Testing - Manual verification - Workspace owner does not see the member prompt. - Workspace member with depleted credits sees the confirmation prompt and can send the nudge with `y`. - Workspace member with spend cap reached does not see the owner-notification prompt. ### Workspace member out of usage https://github.com/user-attachments/assets/341ac396-eff4-4a7f-bf0c-60660becbea1 ### Workspace owner <img width="1728" height="1086" alt="Screenshot 2026-04-09 at 11 48 22 AM" src="https://github.com/user-attachments/assets/06262a45-e3fc-4cc4-8326-1cbedad46ed6" />
richardopenai ·
2026-04-09 21:15:17 -07:00 -
Forward app-server turn clientMetadata to Responses (#16009)
## Summary App-server v2 already receives turn-scoped `clientMetadata`, but the Rust app-server was dropping it before the outbound Responses request. This change keeps the fix lightweight by threading that metadata through the existing turn-metadata path rather than inventing a new transport. ## What we're trying to do and why We want turn-scoped metadata from the app-server protocol layer, especially fields like Hermes/GAAS run IDs, to survive all the way to the actual Responses API request so it is visible in downstream websocket request logging and analytics. The specific bug was: - app-server protocol uses camelCase `clientMetadata` - Responses transport already has an existing turn metadata carrier: `x-codex-turn-metadata` - websocket transport already rewrites that header into `request.request_body.client_metadata["x-codex-turn-metadata"]` - but the Rust app-server never parsed or stored `clientMetadata`, so nothing from the app-server request was making it into that existing path This PR fixes that without adding a new header or a second metadata channel. ## How we did it ### Protocol surface - Add optional `clientMetadata` to v2 `TurnStartParams` and `TurnSteerParams` - Regenerate the JSON schema / TypeScript fixtures - Update app-server docs to describe the field and its behavior ### Runtime plumbing - Add a dedicated core op for app-server user input carrying turn-scoped metadata: `Op::UserInputWithClientMetadata` - Wire `turn/start` and `turn/steer` through that op / signature path instead of dropping the metadata at the message-processor boundary - Store the metadata in `TurnMetadataState` ### Transport behavior - Reuse the existing serialized `x-codex-turn-metadata` payload - Merge the new app-server `clientMetadata` into that JSON additively - Do **not** replace built-in reserved fields already present in the turn metadata payload - Keep websocket behavior unchanged at the outer shape level: it still sends only `client_metadata["x-codex-turn-metadata"]`, but that JSON string now contains the merged fields - Keep HTTP fallback behavior unchanged except that the existing `x-codex-turn-metadata` header now includes the merged fields too ### Request shape before / after Before, a websocket `response.create` looked like: ```json { "type": "response.create", "client_metadata": { "x-codex-turn-metadata": "{\"session_id\":\"...\",\"turn_id\":\"...\"}" } } ``` Even if the app-server caller supplied `clientMetadata`, it was not represented there. After, the same request shape is preserved, but the serialized payload now includes the new turn-scoped fields: ```json { "type": "response.create", "client_metadata": { "x-codex-turn-metadata": "{\"session_id\":\"...\",\"turn_id\":\"...\",\"fiber_run_id\":\"fiber-start-123\",\"origin\":\"gaas\"}" } } ``` ## Validation ### Targeted tests added / updated - protocol round-trip coverage for `clientMetadata` on `turn/start` and `turn/steer` - protocol round-trip coverage for `Op::UserInputWithClientMetadata` - `TurnMetadataState` merge test proving client metadata is added without overwriting reserved built-in fields - websocket request-shape test proving outbound `response.create` contains merged metadata inside `client_metadata["x-codex-turn-metadata"]` - app-server integration tests proving: - `turn/start` forwards `clientMetadata` into the outbound Responses request path - websocket warmup + real turn request both behave correctly - `turn/steer` updates the follow-up request metadata ### Commands run - `just write-app-server-schema` - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-protocol` - `cargo test -p codex-core turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields --lib` - `cargo test -p codex-core --test all responses_websocket_preserves_custom_turn_metadata_fields` - `cargo test -p codex-app-server --test all client_metadata` - `cargo test -p codex-app-server --test all turn_start_forwards_client_metadata_to_responses_websocket_request_body_v2 -- --nocapture` - `just fmt` - `just fix -p codex-core -p codex-protocol -p codex-app-server-protocol -p codex-app-server` - `just fix -p codex-exec -p codex-tui-app-server` - `just argument-comment-lint` ### Full suite note `cargo test` in `codex-rs` still fails in: - `suite::v2::turn_interrupt::turn_interrupt_resolves_pending_command_approval_request` I verified that same failure on a clean detached `HEAD` worktree with an isolated `CARGO_TARGET_DIR`, so it is not caused by this patch.neil-oai ·
2026-04-09 11:52:37 -07:00 -
Add realtime voice selection (#17176)
- Add realtime voice selection for realtime/start. - Expose the supported v1/v2 voice lists and cover explicit, configured, default, and invalid voice paths.
Ahmed Ibrahim ·
2026-04-08 20:19:15 -07:00 -
fix(tui): reduce startup and new-session latency (#17039)
## TL;DR - Fetches account/rateLimits/read asynchronously so the TUI can continue starting without waiting for the rate-limit response. - Fixes the /status card so it no longer leaves a stale “refreshing cached limits...” notice in terminal history. ## Problem The TUI bootstrap path fetched account rate limits synchronously (`account/rateLimits/read`) before the event loop started for ChatGPT/OpenAI-authenticated startups. This added ~670 ms of blocking latency in the measured hot-start case, even though rate-limit data is not needed to render the initial UI or accept user input. The delay was especially noticeable on hot starts where every other RPC (`account/read`, `model/list`, `thread/start`) completed in under 70 ms total. Moving that fetch to the background also exposed a `/status` UI bug: the status card is flattened into terminal scrollback when it is inserted. A transient "refreshing limits in background..." line could not be cleared later, because the async completion updated the retained `HistoryCell`, not the already-written terminal history. ## Mental model Before this change, `AppServerSession::bootstrap()` performed three sequential RPCs: `account/read` → `model/list` → `account/rateLimits/read`. The result of the third call was baked into `AppServerBootstrap` and applied to the chat widget before the event loop began. After this change, `bootstrap()` only performs two RPCs (`account/read` + `model/list`), and rate-limit fetching is kicked off as an async background task immediately after the first frame is scheduled. A new enum, `RateLimitRefreshOrigin`, tags each fetch so the event handler knows whether the result came from the startup prefetch or from a user-initiated `/status` command; they have different completion side-effects. The `get_login_status()` helper (used outside the main app flow) was also decoupled: it previously called the full `bootstrap()` just to check auth mode, wasting model-list and rate-limit work. It now calls the narrower `read_account()` directly. For `/status`, this PR keeps the background refresh request but stops printing transient refresh notices into status history when cached limits are already available. If a refresh updates the cache, the next `/status` command will render the new values. ## Non-goals - This change does not alter the rate-limit data itself. - This change does not introduce caching, retries, or staleness management for rate limits. - This change does not affect the `model/list` or `thread/start` RPCs; they remain on the critical startup path. ## Tradeoffs - **Stale-on-first-render**: The status bar will briefly show no rate-limit info until the background fetch completes; observed background fetches landed roughly in the 400-900 ms range after the UI appeared. This is acceptable because the user cannot meaningfully act on rate-limit data in the first fraction of a second. - **Error silence on startup prefetch**: If the startup prefetch fails, the error is logged but the UI is not notified (unlike `/status` refresh failures, which go through the status-command completion path). This avoids surfacing transient network errors as a startup blocker. - **Static `/status` history**: `/status` output is terminal history, not a live widget. The card now avoids progress-style language that would appear stuck in scrollback; users can run `/status` again to see newly cached values. - **`account_auth_mode` field removed from `AppServerBootstrap`**: The only consumer was `get_login_status()`, which no longer goes through `bootstrap()`. The field was dead weight. ## Architecture ### New types - `RateLimitRefreshOrigin` (in `app_event.rs`): A `Copy` enum distinguishing `StartupPrefetch` from `StatusCommand { request_id }`. Carried through `RefreshRateLimits` and `RateLimitsLoaded` events so the handler applies the right completion behavior. ### Modified types - `AppServerBootstrap`: Lost `account_auth_mode` and `rate_limit_snapshots`; gained `requires_openai_auth: bool` (passed through from the account response so the caller can decide whether to fire the prefetch). ### Control flow 1. `bootstrap()` returns with `requires_openai_auth` and `has_chatgpt_account`. 2. After scheduling the first frame, `App::run_inner` fires `refresh_rate_limits(StartupPrefetch)` if both flags are true. 3. When `RateLimitsLoaded { StartupPrefetch, Ok(..) }` arrives, snapshots are applied and a frame is scheduled to repaint the status bar. 4. When `RateLimitsLoaded { StartupPrefetch, Err(..) }` arrives, the error is logged and no UI update occurs. 5. `/status`-initiated refreshes continue to use `StatusCommand { request_id }` and call `finish_status_rate_limit_refresh` on completion (success or failure). 6. `/status` history cells with cached rate-limit rows no longer render an additional "refreshing limits" notice; the async refresh updates the cache for future status output. ### Extracted method - `AppServerSession::read_account()`: Factored out of `bootstrap()` so that `get_login_status()` can call it independently without triggering model-list or rate-limit work. ## Observability - The existing `tracing::warn!` for rate-limit fetch failures is preserved for the startup path. - No new metrics or spans are introduced. The startup-time improvement is observable via the existing `ready` timestamp in TUI startup logs. ## Tests - Existing tests in `status_command_tests.rs` are updated to match on `RateLimitRefreshOrigin::StatusCommand { request_id }` instead of a bare `request_id`. - Focused `/status` tests now assert that status history avoids transient refresh text, continues to request an async refresh, and uses refreshed cached limits in future status output. - No new tests are added for the startup prefetch path because it is a fire-and-forget spawn with no observable side-effect other than the widget state update, which is already covered by the snapshot-application tests. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>Felipe Coury ·
2026-04-07 22:16:09 -03:00 -
Use model metadata for Fast Mode status (#16949)
Fast Mode status was still tied to one model name in the TUI and model-list plumbing. This changes the model metadata shape so a model can advertise additional speed tiers, carries that field through the app-server model list, and uses it to decide when to show Fast Mode status. For people using Codex, the behavior is intended to stay the same for existing models. Fast Mode still requires the existing signed-in / feature-gated path; the difference is that the UI can now recognize any model the model list marks as Fast-capable, instead of requiring a new client-side slug check.
pash-openai ·
2026-04-07 17:55:40 -07:00 -
Add WebRTC transport to realtime start (#16960)
Adds WebRTC startup to the experimental app-server `thread/realtime/start` method with an optional transport enum. The websocket path remains the default; WebRTC offers create the realtime session through the shared start flow and emit the answer SDP via `thread/realtime/sdp`. --------- Co-authored-by: Codex <noreply@openai.com>
Ahmed Ibrahim ·
2026-04-07 15:43:38 -07:00 -
[codex] reduce module visibility (#16978)
## Summary - reduce public module visibility across Rust crates, preferring private or crate-private modules with explicit crate-root public exports - update external call sites and tests to use the intended public crate APIs instead of reaching through module trees - add the module visibility guideline to AGENTS.md ## Validation - `cargo check --workspace --all-targets --message-format=short` passed before the final fix/format pass - `just fix` completed successfully - `just fmt` completed successfully - `git diff --check` passed
pakrym-oai ·
2026-04-07 08:03:35 -07:00 -
[codex-analytics] add protocol-native turn timestamps (#16638)
--- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/16638). * #16870 * #16706 * #16659 * #16641 * #16640 * __->__ #16638
rhan-oai ·
2026-04-06 16:22:59 -07:00 -
Add remote --cd forwarding for app-server sessions (#16700)
Addresses #16124 Problem: `codex --remote --cd <path>` canonicalized the path locally and then omitted it from remote thread lifecycle requests, so remote-only working directories failed or were ignored. Solution: Keep remote startup on the local cwd, forward explicit `--cd` values verbatim to `thread/start`, `thread/resume`, and `thread/fork`, and cover the behavior with `codex-tui` tests. Testing: I manually tested `--remote --cd` with both absolute and relative paths and validated correct behavior. --- Update based on code review feedback: Problem: Remote `--cd` was forwarded to `thread/resume` and `thread/fork`, but not to `thread/list` lookups, so `--resume --last` and picker flows could select a session from the wrong cwd; relative cwd filters also failed against stored absolute paths. Solution: Apply explicit remote `--cd` to `thread/list` lookups for `--last` and picker flows, normalize relative cwd filters on the app-server before exact matching, and document/test the behavior.
Eric Traut ·
2026-04-03 11:26:45 -07:00 -
Fix fork source display in /status (expose forked_from_id in app server) (#16596)
Addresses #16560 Problem: `/status` stopped showing the source thread id in forked TUI sessions after the app-server migration. Solution: Carry fork source ids through app-server v2 thread data and the TUI session adapter, and update TUI fixtures so `/status` matches the old TUI behavior.
Eric Traut ·
2026-04-02 14:05:29 -07:00 -
Fix stale /status rate limits in active TUI sessions (#16201)
Fix stale weekly limit in `/status` (#16194): /status reused the session’s cached rate-limit snapshot, so the weekly remaining limit could stay frozen within an active session. With this change, we now dynamically update the rate limits after status is displayed. I needed to delete a few low-value test cases from the chatWidget tests because the test.rs file is really large, and the new tests in this PR pushed us over the 512K mandated limit. I'm working on a separate PR to refactor that test file.
Eric Traut ·
2026-03-31 17:03:05 -06:00 -
Rename tui_app_server to tui (#16104)
This is a follow-up to https://github.com/openai/codex/pull/15922. That previous PR deleted the old `tui` directory and left the new `tui_app_server` directory in place. This PR renames `tui_app_server` to `tui` and fixes up all references.
Eric Traut ·
2026-03-28 11:23:07 -06:00