core: Consolidate Responses API Codex metadata (#27122)

## What
Introduce a `CodexResponsesMetadata` struct that defines all the core
metadata we send to Responses API. Example fields are `thread_id`,
`turn_id`, `window_id`, etc.

Going forward, `client_metadata["x-codex-turn-metadata"]` will be the
canonical way Codex sends metadata to Responses API across both HTTP and
websocket transports.

For now, we continue to emit the existing top-level HTTP headers and
top-level `client_metadata` fields from the same
`CodexResponsesMetadata` struct for compatibility reasons.

Also, app-server clients who specify additional
`responsesapi_client_metadata` via `turn/start` and `turn/steer` will
have those fields merged into
`client_metadata["x-codex-turn-metadata"]`, but cannot override the
reserved fields that core uses (i.e. the fields in
`CodexResponsesMetadata`).

## Why

Responses API request instrumentation is the source of truth for
downstream Codex analytics that join requests by Codex IDs such as
session, thread, turn, and context window. Before this change, those
values were assembled through several request-specific paths: HTTP
request bodies, websocket handshake headers, websocket `response.create`
payloads, compaction requests, and the rich `x-codex-turn-metadata`
envelope all had their own wiring.

That made metadata propagation easy to drift across API-key/direct
Responses API requests, ChatGPT-auth/proxied requests, websocket
requests, and compaction requests. It also made additions like
`window_id` error-prone because a field could be added to one transport
projection but missed in another.

## What changed

- Added `CodexResponsesMetadata` as the core-owned snapshot for Codex
metadata sent to ResponsesAPI.
- Render `client_metadata["x-codex-turn-metadata"]`, flat
`client_metadata` projections, and direct compatibility headers from
that same snapshot.
- Include the known Codex-owned fields in the turn metadata blob,
including installation/session/thread/turn/window IDs, request kind,
lineage, sandbox/workspace metadata, timing, and compaction details.
- Treat app-server `responsesapi_client_metadata` as enrichment for the
Codex turn metadata blob while preventing those extras from overriding
Codex-owned fields.
- Use the same metadata path for normal turns, websocket prewarm, local
compaction, remote v1 compaction, and remote v2 compaction.
- Keep websocket connection-only preconnect metadata separate so
handshakes carry compatibility identity headers without inventing a fake
turn metadata blob.

## Verification

- `cargo check -p codex-core`
- `just fix -p codex-core`
This commit is contained in:
Owen Lin
2026-06-11 13:42:09 -07:00
committed by GitHub
Unverified
parent 4a5a676499
commit 14df0e8833
27 changed files with 1213 additions and 803 deletions
@@ -68,7 +68,13 @@ pub struct TurnStartParams {
#[ts(optional = nullable)]
pub client_user_message_id: Option<String>,
pub input: Vec<UserInput>,
/// Optional turn-scoped Responses API client metadata.
/// Optional metadata to enrich Codex's ResponsesAPI turn metadata.
///
/// Entries are flattened into the JSON string sent as
/// `client_metadata["x-codex-turn-metadata"]` on ResponsesAPI HTTP and websocket requests.
///
/// They are not sent as top-level ResponsesAPI `client_metadata` keys, and reserved keys
/// such as `session_id`, `thread_id`, `turn_id`, and `window_id` cannot be overridden.
#[experimental("turn/start.responsesapiClientMetadata")]
#[ts(optional = nullable)]
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
@@ -161,7 +167,13 @@ pub struct TurnSteerParams {
#[ts(optional = nullable)]
pub client_user_message_id: Option<String>,
pub input: Vec<UserInput>,
/// Optional turn-scoped Responses API client metadata.
/// Optional metadata to enrich Codex's ResponsesAPI turn metadata.
///
/// Entries are flattened into the JSON string sent as
/// `client_metadata["x-codex-turn-metadata"]` on ResponsesAPI HTTP and websocket requests.
///
/// They are not sent as top-level ResponsesAPI `client_metadata` keys, and reserved keys
/// such as `session_id`, `thread_id`, `turn_id`, and `window_id` cannot be overridden.
#[experimental("turn/steer.responsesapiClientMetadata")]
#[ts(optional = nullable)]
pub responsesapi_client_metadata: Option<HashMap<String, String>>,