[codex] Send turn state through compact requests (#28002)

## Context

Inline compaction is part of the active logical turn. Compact requests
and the sampling requests around them should use the same turn state,
including when compaction is the first request to establish it.

## Change

Pass the turn-scoped `OnceLock` directly to inline v1 compaction so
`/responses/compact` includes an established value in the existing HTTP
header. Capture `x-codex-turn-state` from the compact response into that
same lock, allowing pre-turn compact to establish the value that
subsequent sampling reuses.

V2 compact already uses the normal Responses HTTP/WebSocket path and
continues to share the same `OnceLock` without separate plumbing. The
first returned value wins for the logical turn.

## Test plan

Integration coverage verifies that:

- pre-turn v1 compact can establish state for the first sampling request
- inline v1 compact receives established state over HTTP
- inline v2 compact reuses established state over HTTP
- inline v2 compact reuses established state over WebSocket

CI validates the full change.
This commit is contained in:
Ahmed Ibrahim
2026-06-13 01:27:58 -07:00
committed by GitHub
Unverified
parent 640d61b121
commit f297b9f07d
5 changed files with 386 additions and 3 deletions
+15 -1
View File
@@ -11,8 +11,11 @@ use http::Method;
use serde::Deserialize;
use serde_json::to_value;
use std::sync::Arc;
use std::sync::OnceLock;
use std::time::Duration;
const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state";
pub struct CompactClient<T: HttpTransport> {
session: EndpointSession<T>,
}
@@ -39,6 +42,7 @@ impl<T: HttpTransport> CompactClient<T> {
body: serde_json::Value,
extra_headers: HeaderMap,
request_timeout: Duration,
turn_state: Option<&OnceLock<String>>,
) -> Result<Vec<ResponseItem>, ApiError> {
let resp = self
.session
@@ -52,6 +56,14 @@ impl<T: HttpTransport> CompactClient<T> {
},
)
.await?;
if let Some(turn_state) = turn_state
&& let Some(header_value) = resp
.headers
.get(X_CODEX_TURN_STATE_HEADER)
.and_then(|value| value.to_str().ok())
{
let _ = turn_state.set(header_value.to_string());
}
let parsed: CompactHistoryResponse =
serde_json::from_slice(&resp.body).map_err(|e| ApiError::Stream(e.to_string()))?;
Ok(parsed.output)
@@ -62,10 +74,12 @@ impl<T: HttpTransport> CompactClient<T> {
input: &CompactionInput<'_>,
extra_headers: HeaderMap,
request_timeout: Duration,
turn_state: Option<&OnceLock<String>>,
) -> Result<Vec<ResponseItem>, ApiError> {
let body = to_value(input)
.map_err(|e| ApiError::Stream(format!("failed to encode compaction input: {e}")))?;
self.compact(body, extra_headers, request_timeout).await
self.compact(body, extra_headers, request_timeout, turn_state)
.await
}
}