Commit Graph

202 Commits

  • Send warmup request (#11258)
    Send a request with `generate: falls` but a full set of tools and
    instructions to pre-warm inference.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Prefer v2 websockets if available (#12428)
    And also cleanup settings flow to avoid reading many separate flags.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Reuse connection between turns (#12294)
    Add a pool of one to the model client to reuse connections across turns.
  • Hide the first websocket retry (#11548)
    Sometimes connection needs to be quickly reestablished, don't produce an
    error for that.
  • Do not attempt to append after response.completed (#11402)
    Completed responses are fully done, and new response must be created.
  • chore: rename disable_websockets -> websockets_disabled (#11420)
    `disable_websockets()` is confusing because its a getter. rename for
    clarity
  • Do not resend output items in incremental websockets connections (#11383)
    In the incremental websocket output items are already part of the
    context, no need to send them again and duplicate.
  • Prefer websocket transport when model opts in (#11386)
    Summary
    - add a `prefer_websockets` field to `ModelInfo`, defaulting to `false`
    in all fixtures and constructors
    - wire the new flag into websocket selection so models that opt in
    always use websocket transport even when the feature gate is off
    
    Testing
    - Not run (not requested)
  • Compare full request for websockets incrementality (#11343)
    Tools can dynamically change mid-turn now. We need to be more thorough
    about reusing incremental connections.
  • Remove ApiPrompt (#11265)
    Keep things simple and build a full Responses API request request right
    in the model client
  • memories: add extraction and prompt module foundation (#11200)
    ## Summary
    - add the new `core/src/memories` module (phase-one parsing, rollout
    filtering, storage, selection, prompts)
    - add Askama-backed memory templates for stage-one input/system and
    consolidation prompts
    - add module tests for parsing, filtering, path bucketing, and summary
    maintenance
    
    ## Testing
    - just fmt
    - cargo test -p codex-core --lib memories::
  • Move warmup to the task level (#11216)
    Instead of storing a special connection on the client level make the
    regular task responsible for establishing a normal client session and
    open a connection on it.
    
    Then when the turn is started we pass in a pre-established session.
  • Fallback to HTTP on UPGRADE_REQUIRED (#10824)
    Allow the server to trigger a connection downgrade in case the protocol
    changes in incompatible ways.
  • Support alternative websocket API (#10861)
    **Test plan**
    
    ```
    cargo build -p codex-cli && RUST_LOG='codex_api::endpoint::responses_websocket=trace,codex_core::client=debug,codex_core::codex=debug' \
      ./target/debug/codex \
        --enable responses_websockets_v2 \
        --profile byok \
        --full-auto
    ```
  • core: preconnect Responses websocket for first turn (#10698)
    ## Problem
    The first user turn can pay websocket handshake latency even when a
    session has already started. We want to reduce that initial delay while
    preserving turn semantics and avoiding any prompt send during startup.
    
    Reviewer feedback also called out duplicated connect/setup paths and
    unnecessary preconnect state complexity.
    
    ## Mental model
    `ModelClient` owns session-scoped transport state. During session
    startup, it can opportunistically warm one websocket handshake slot. A
    turn-scoped `ModelClientSession` adopts that slot once if available,
    restores captured sticky turn-state, and otherwise opens a websocket
    through the same shared connect path.
    
    If startup preconnect is still in flight, first turn setup awaits that
    task and treats it as the first connection attempt for the turn.
    
    Preconnect is handshake-only. The first `response.create` is still sent
    only when a turn starts.
    
    ## Non-goals
    This change does not make preconnect required for correctness and does
    not change prompt/turn payload semantics. It also does not expand
    fallback behavior beyond clearing preconnect state when fallback
    activates.
    
    ## Tradeoffs
    The implementation prioritizes simpler ownership and shared connection
    code over header-match gating for reuse. The single-slot cache keeps
    lifecycle straightforward but only benefits the immediate next turn.
    
    Awaiting in-flight preconnect has the same app-level connect-timeout
    semantics as existing websocket connect behavior (no new timeout class
    introduced by this PR).
    
    ## Architecture
    `core/src/client.rs`:
    - Added session-level preconnect lifecycle state (`Idle` / `InFlight` /
    `Ready`) carrying one warmed websocket plus optional captured
    turn-state.
    - Added `pre_establish_connection()` startup warmup and `preconnect()`
    handshake-only setup.
    - Deduped auth/provider resolution into `current_client_setup()` and
    websocket handshake wiring into `connect_websocket()` /
    `build_websocket_headers()`.
    - Updated turn websocket path to adopt preconnect first, await in-flight
    preconnect when present, then create a new websocket only when needed.
    - Ensured fallback activation clears warmed preconnect state.
    - Added documentation for lifecycle, ownership, sticky-routing
    invariants, and timeout semantics.
    
    `core/src/codex.rs`:
    - Session startup invokes `model_client.pre_establish_connection(...)`.
    - Turn metadata resolution uses the shared timeout helper.
    
    `core/src/turn_metadata.rs`:
    - Centralized shared timeout helper used by both turn-time metadata
    resolution and startup preconnect metadata building.
    
    `core/tests/common/responses.rs` + websocket test suites:
    - Added deterministic handshake waiting helper (`wait_for_handshakes`)
    with bounded polling.
    - Added startup preconnect and in-flight preconnect reuse coverage.
    - Fallback expectations now assert exactly two websocket attempts in
    covered scenarios (startup preconnect + turn attempt before fallback
    sticks).
    
    ## Observability
    Preconnect remains best-effort and non-fatal. Existing
    websocket/fallback telemetry remains in place, and debug logs now make
    preconnect-await behavior and preconnect failures easier to reason
    about.
    
    ## Tests
    Validated with:
    1. `just fmt`
    2. `cargo test -p codex-core websocket_preconnect -- --nocapture`
    3. `cargo test -p codex-core websocket_fallback -- --nocapture`
    4. `cargo test -p codex-core
    websocket_first_turn_waits_for_inflight_preconnect -- --nocapture`
  • chore: rm web-search-eligible header (#10660)
    default-enablement of web_search is now client-side, no need to send
    eligibility headers to backend.
    
    Tested locally, headers no longer sent.
    
    will wait for corresponding backend change to deploy before merging
  • fix(auth): isolate chatgptAuthTokens concept to auth manager and app-server (#10423)
    So that the rest of the codebase (like TUI) don't need to be concerned
    whether ChatGPT auth was handled by Codex itself or passed in via
    app-server's external auth mode.
  • Session-level model client (#10664)
    Make ModelClient a session-scoped object.
    Move state that is session level onto the client, and make state that is
    per-turn explicit on corresponding methods.
    Stop taking a huge Config object, instead only pass in values that are
    actually needed.
    
    ---------
    
    Co-authored-by: Josh McKinney <joshka@openai.com>
  • Stop client from being state carrier (#10595)
    I'd like to make client session wide. This requires shedding all random
    state it has to carry.
  • Move metadata calculation out of client (#10589)
    Model client shouldn't be responsible for this.
  • Add more detail to 401 error (#10508)
    Add the error.message if it exists, the body otherwise. Truncate body to
    1k characters. Print the cf-ray and the requestId.
    
    **Before:**
    <img width="860" height="305" alt="Screenshot 2026-02-03 at 13 15 28"
    src="https://github.com/user-attachments/assets/949d5a4d-2b51-488c-a723-c6deffde0353"
    />
    
    **After:**
    <img width="1523" height="373" alt="Screenshot 2026-02-03 at 13 15 38"
    src="https://github.com/user-attachments/assets/f96a747e-e596-4a7a-aae9-64210d805b26"
    />
  • make codex better at git (#10145)
    adds basic git context to the session prefix so the model can anchor git
    actions and be a bit more version-aware. structured it in a
    multiroot-friendly shape even though we only have one root today
  • Add websocket telemetry metrics and labels (#10316)
    Summary
    - expose websocket telemetry hooks through the responses client so
    request durations and event processing can be reported
    - record websocket request/event metrics and emit runtime telemetry
    events that the history UI now surfaces
    - improve tests to cover websocket telemetry reporting and guard runtime
    summary updates
    
    
    <img width="824" height="79" alt="Screenshot 2026-01-31 at 5 28 12 PM"
    src="https://github.com/user-attachments/assets/ea9a7965-d8b4-4e3c-a984-ef4fdc44c81d"
    />
  • feat: refactor CodexAuth so invalid state cannot be represented (#10208)
    Previously, `CodexAuth` was defined as follows:
    
    
    https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L39-L46
    
    But if you looked at its constructors, we had creation for
    `AuthMode::ApiKey` where `storage` was built using a nonsensical path
    (`PathBuf::new()`) and `auth_dot_json` was `None`:
    
    
    https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L212-L220
    
    By comparison, when `AuthMode::ChatGPT` was used, `api_key` was always
    `None`:
    
    
    https://github.com/openai/codex/blob/d550fbf41afc09d7d7b5ac813aea38de07b2a73f/codex-rs/core/src/auth.rs#L665-L671
    
    https://github.com/openai/codex/pull/10012 took things further because
    it introduced a new `ChatgptAuthTokens` variant to `AuthMode`, which is
    important in when invoking `account/login/start` via the app server, but
    most logic _internal_ to the app server should just reason about two
    `AuthMode` variants: `ApiKey` and `ChatGPT`.
    
    This PR tries to clean things up as follows:
    
    - `LoginAccountParams` and `AuthMode` in `codex-rs/app-server-protocol/`
    both continue to have the `ChatgptAuthTokens` variant, though it is used
    exclusively for the on-the-wire messaging.
    - `codex-rs/core/src/auth.rs` now has its own `AuthMode` enum, which
    only has two variants: `ApiKey` and `ChatGPT`.
    - `CodexAuth` has been changed from a struct to an enum. It is a
    disjoint union where each variant (`ApiKey`, `ChatGpt`, and
    `ChatGptAuthTokens`) have only the associated fields that make sense for
    that variant.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10208).
    * #10224
    * __->__ #10208
  • feat(app-server): support external auth mode (#10012)
    This enables a new use case where `codex app-server` is embedded into a
    parent application that will directly own the user's ChatGPT auth
    lifecycle, which means it owns the user’s auth tokens and refreshes it
    when necessary. The parent application would just want a way to pass in
    the auth tokens for codex to use directly.
    
    The idea is that we are introducing a new "auth mode" currently only
    exposed via app server: **`chatgptAuthTokens`** which consist of the
    `id_token` (stores account metadata) and `access_token` (the bearer
    token used directly for backend API calls). These auth tokens are only
    stored in-memory. This new mode is in addition to the existing `apiKey`
    and `chatgpt` auth modes.
    
    This PR reuses the shape of our existing app-server account APIs as much
    as possible:
    - Update `account/login/start` with a new `chatgptAuthTokens` variant,
    which will allow the client to pass in the tokens and have codex
    app-server use them directly. Upon success, the server emits
    `account/login/completed` and `account/updated` notifications.
    - A new server->client request called
    `account/chatgptAuthTokens/refresh` which the server can use whenever
    the access token previously passed in has expired and it needs a new one
    from the parent application.
    
    I leveraged the core 401 retry loop which typically triggers auth token
    refreshes automatically, but made it pluggable:
    - **chatgpt** mode refreshes internally, as usual.
    - **chatgptAuthTokens** mode calls the client via
    `account/chatgptAuthTokens/refresh`, the client responds with updated
    tokens, codex updates its in-memory auth, then retries. This RPC has a
    10s timeout and handles JSON-RPC errors from the client.
    
    Also some additional things:
    - chatgpt logins are blocked while external auth is active (have to log
    out first. typically clients will pick one OR the other, not support
    both)
    - `account/logout` clears external auth in memory
    - Ensures that if `forced_chatgpt_workspace_id` is set via the user's
    config, we respect it in both:
    - `account/login/start` with `chatgptAuthTokens` (returns a JSON-RPC
    error back to the client)
    - `account/chatgptAuthTokens/refresh` (fails the turn, and on next
    request app-server will send another `account/chatgptAuthTokens/refresh`
    request to the client).
  • Remove WebSocket wire format (#10179)
    I'd like WireApi to go away (when chat is removed) and WebSockets is
    still responses API just over a different transport.
  • Fall back to http when websockets fail (#10139)
    I expect not all proxies work with websockets, fall back to http if
    websockets fail.
  • fix: enable per-turn updates to web search mode (#10040)
    web_search can now be updated per-turn, for things like changes to
    sandbox policy.
    
    `SandboxPolicy::DangerFullAccess` now sets web_search to `live`, and the
    default is still `cached`.
    
    Added integration tests.
  • make cached web_search client-side default (#9974)
    [Experiment](https://console.statsig.com/50aWbk2p4R76rNX9lN5VUw/experiments/codex_web_search_rollout/summary)
    for default cached `web_search` completed; cached chosen as default.
    
    Update client to reflect that.
  • Aligned feature stage names with public feature maturity stages (#9929)
    We've recently standardized a [feature maturity
    model](https://developers.openai.com/codex/feature-maturity) that we're
    using in our docs and support forums to communicate expectations to
    users. This PR updates the internal stage names and descriptions to
    match.
    
    This change involves a simple internal rename and updates to a few
    user-visible strings. No functional change.
  • fix(core) Preserve base_instructions in SessionMeta (#9427)
    ## Summary
    This PR consolidates base_instructions onto SessionMeta /
    SessionConfiguration, so we ensure `base_instructions` is set once per
    session and should be (mostly) immutable, unless:
    - overridden by config on resume / fork
    - sub-agent tasks, like review or collab
    
    
    In a future PR, we should convert all references to `base_instructions`
    to consistently used the typed struct, so it's less likely that we put
    other strings there. See #9423. However, this PR is already quite
    complex, so I'm deferring that to a follow-up.
    
    ## Testing
    - [x] Added a resume test to assert that instructions are preserved. In
    particular, `resume_switches_models_preserves_base_instructions` fails
    against main.
    
    Existing test coverage thats assert base instructions are preserved
    across multiple requests in a session:
    - Manual compact keeps baseline instructions:
    core/tests/suite/compact.rs:199
    - Auto-compact keeps baseline instructions:
    core/tests/suite/compact.rs:1142
    - Prompt caching reuses the same instructions across two requests:
    core/tests/suite/prompt_caching.rs:150 and
    core/tests/suite/prompt_caching.rs:157
    - Prompt caching with explicit expected string across two requests:
    core/tests/suite/prompt_caching.rs:213 and
    core/tests/suite/prompt_caching.rs:222
    - Resume with model switch keeps original instructions:
    core/tests/suite/resume.rs:136
    - Compact/resume/fork uses request 0 instructions for later expected
    payloads: core/tests/suite/compact_resume_fork.rs:215
  • Turn-state sticky routing per turn (#9332)
    - capture the header from SSE/WS handshakes, store it per
    ModelClientSession using `Oncelock`, echo it on turn-scoped requests,
    and add SSE+WS integration tests for within-turn persistence +
    cross-turn reset.
    
    - keep `x-codex-turn-state` sticky within a user turn to maintain
    routing continuity for retries/tool follow-ups.
  • [search] allow explicitly disabling web search (#9249)
    moving `web_search` rollout serverside, so need a way to explicitly
    disable search + signal eligibility from the client.
    
    - Add `x‑oai‑web‑search‑eligible` header that signifies whether the
    request can have web search.
    - Only attach the `web_search` tool when the resolved `WebSearchMode` is
    `Live` or `Cached`.
  • Websocket append support (#9128)
    Support an incremental append request in websocket transport.
  • Reuse websocket connection (#9127)
    Reuses the connection but still sends full requests.
  • Add model client sessions (#9102)
    Maintain a long-running session.
  • Add URL to responses error messages (#8984)
    Put the URL in error messages, to aid debugging Codex pointing at wrong
    endpoints.
    
    <img width="759" height="164" alt="Screenshot 2026-01-09 at 16 32 49"
    src="https://github.com/user-attachments/assets/77a0622c-955d-426d-86bb-c035210a4ecc"
    />
  • Attempt to reload auth as a step in 401 recovery (#8880)
    When authentication fails, first attempt to reload the auth from file
    and then attempt to refresh it.