This commit is contained in:
jif-oai
2026-05-01 11:04:54 +01:00
parent 0d3a7e1d92
commit d61bbf2821
7 changed files with 45 additions and 9 deletions
+1 -1
View File
@@ -91,7 +91,7 @@ impl<T: HttpTransport> ResponsesClient<T> {
if let Some(ref thread_id) = thread_id {
insert_header(&mut headers, "x-client-request-id", thread_id);
}
headers.extend(build_session_headers(session_id));
headers.extend(build_session_headers(session_id, thread_id));
if let Some(subagent) = subagent_header(&session_source) {
insert_header(&mut headers, "x-openai-subagent", &subagent);
}
+4 -1
View File
@@ -2,11 +2,14 @@ use codex_protocol::protocol::SessionSource;
use http::HeaderMap;
use http::HeaderValue;
pub fn build_session_headers(session_id: Option<String>) -> HeaderMap {
pub fn build_session_headers(session_id: Option<String>, thread_id: Option<String>) -> HeaderMap {
let mut headers = HeaderMap::new();
if let Some(id) = session_id {
insert_header(&mut headers, "session_id", &id);
}
if let Some(id) = thread_id {
insert_header(&mut headers, "thread_id", &id);
}
headers
}
+4
View File
@@ -462,6 +462,10 @@ async fn azure_default_store_attaches_ids_and_headers() -> Result<()> {
req.headers.get("session_id").and_then(|v| v.to_str().ok()),
Some("sess_123")
);
assert_eq!(
req.headers.get("thread_id").and_then(|v| v.to_str().ok()),
Some("thread_123")
);
assert_eq!(
req.headers
.get("x-client-request-id")
+6 -5
View File
@@ -191,7 +191,7 @@ impl RequestRouteTelemetry {
/// A session-scoped client for model-provider API calls.
///
/// This holds configuration and state that should be shared across turns within a Codex session
/// (auth, provider selection, conversation id, and transport fallback state).
/// (auth, provider selection, thread id, and transport fallback state).
///
/// WebSocket fallback is session-scoped: once a turn activates the HTTP fallback, subsequent turns
/// will also use HTTP for the remainder of the session.
@@ -476,9 +476,10 @@ impl ModelClient {
extra_headers.insert(X_CODEX_INSTALLATION_ID_HEADER, header_value);
}
extra_headers.extend(self.build_responses_identity_headers());
extra_headers.extend(build_session_headers(Some(
self.state.session_id.to_string(),
)));
extra_headers.extend(build_session_headers(
Some(self.state.session_id.to_string()),
Some(self.state.thread_id.to_string()),
));
let trace_attempt = compaction_trace.start_attempt(&payload);
let result = client
.compact_input(&payload, extra_headers)
@@ -800,7 +801,7 @@ impl ModelClient {
if let Ok(header_value) = HeaderValue::from_str(&thread_id) {
headers.insert("x-client-request-id", header_value);
}
headers.extend(build_session_headers(Some(session_id)));
headers.extend(build_session_headers(Some(session_id), Some(thread_id)));
headers.extend(self.build_responses_identity_headers());
headers.insert(
OPENAI_BETA_HEADER,
+7 -1
View File
@@ -724,7 +724,7 @@ async fn resume_replays_image_tool_outputs_with_detail() {
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn includes_session_id_and_model_headers_in_request() {
async fn includes_session_id_thread_id_and_model_headers_in_request() {
skip_if_no_network!();
// Mock server
@@ -743,6 +743,7 @@ async fn includes_session_id_and_model_headers_in_request() {
.expect("create new conversation");
let codex = test.codex.clone();
let expected_session_id = test.session_configured.session_id;
let expected_thread_id = test.session_configured.thread_id;
codex
.submit(Op::UserInput {
@@ -762,6 +763,7 @@ async fn includes_session_id_and_model_headers_in_request() {
let request = resp_mock.single_request();
assert_eq!(request.path(), "/v1/responses");
let request_session_id = request.header("session_id").expect("session_id header");
let request_thread_id = request.header("thread_id").expect("thread_id header");
let request_authorization = request
.header("authorization")
.expect("authorization header");
@@ -772,6 +774,7 @@ async fn includes_session_id_and_model_headers_in_request() {
.expect("read installation id");
assert_eq!(request_session_id, expected_session_id.to_string());
assert_eq!(request_thread_id, expected_thread_id.to_string());
assert_eq!(request_originator, originator().value);
assert_eq!(request_authorization, "Bearer Test API Key");
assert_eq!(
@@ -1000,6 +1003,7 @@ async fn chatgpt_auth_sends_correct_request() {
.expect("create new conversation");
let codex = test.codex.clone();
let expected_session_id = test.session_configured.session_id;
let expected_thread_id = test.session_configured.thread_id;
codex
.submit(Op::UserInput {
@@ -1028,10 +1032,12 @@ async fn chatgpt_auth_sends_correct_request() {
let request_body = request.body_json();
let request_session_id = request.header("session_id").expect("session_id header");
let request_thread_id = request.header("thread_id").expect("thread_id header");
let installation_id =
std::fs::read_to_string(test.codex_home_path().join(INSTALLATION_ID_FILENAME))
.expect("read installation id");
assert_eq!(request_session_id, expected_session_id.to_string());
assert_eq!(request_thread_id, expected_thread_id.to_string());
assert_eq!(request_originator, originator().value);
assert_eq!(request_authorization, "Bearer Access Token");
+13 -1
View File
@@ -15,6 +15,7 @@ use codex_otel::MetricsConfig;
use codex_otel::SessionTelemetry;
use codex_otel::TelemetryAuthMode;
use codex_otel::current_span_w3c_trace_context;
use codex_protocol::SessionId;
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::config_types::ReasoningSummary;
@@ -87,6 +88,7 @@ fn assert_request_trace_matches(body: &serde_json::Value, expected_trace: &W3cTr
struct WebsocketTestHarness {
_codex_home: TempDir,
client: ModelClient,
session_id: SessionId,
thread_id: ThreadId,
model_info: ModelInfo,
effort: Option<ReasoningEffortConfig>,
@@ -127,6 +129,14 @@ async fn responses_websocket_streams_request() {
handshake.header(X_CLIENT_REQUEST_ID_HEADER),
Some(harness.thread_id.to_string())
);
assert_eq!(
handshake.header("session_id"),
Some(harness.session_id.to_string())
);
assert_eq!(
handshake.header("thread_id"),
Some(harness.thread_id.to_string())
);
assert_eq!(
handshake.header(USER_AGENT_HEADER),
Some(codex_login::default_client::get_codex_user_agent())
@@ -1828,6 +1838,7 @@ async fn websocket_harness_with_provider_options(
let config = Arc::new(config);
let model_info = codex_core::test_support::construct_model_info_offline(MODEL, &config);
let thread_id = ThreadId::new();
let session_id = SessionId::new();
let auth_manager =
codex_core::test_support::auth_manager_from_auth(CodexAuth::from_api_key("Test API Key"));
let exporter = InMemoryMetricExporter::default();
@@ -1853,7 +1864,7 @@ async fn websocket_harness_with_provider_options(
let summary = ReasoningSummary::Auto;
let client = ModelClient::new(
/*auth_manager*/ None,
thread_id.into(),
session_id,
thread_id,
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
provider.clone(),
@@ -1867,6 +1878,7 @@ async fn websocket_harness_with_provider_options(
WebsocketTestHarness {
_codex_home: codex_home,
client,
session_id,
thread_id,
model_info,
effort,
@@ -272,6 +272,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
.await?;
let codex = harness.test().codex.clone();
let session_id = harness.test().session_configured.session_id.to_string();
let thread_id = harness.test().session_configured.thread_id.to_string();
let responses_mock = responses::mount_sse_sequence(
harness.server(),
@@ -340,6 +341,10 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
compact_request.header("session_id").as_deref(),
Some(session_id.as_str())
);
assert_eq!(
compact_request.header("thread_id").as_deref(),
Some(thread_id.as_str())
);
let compact_body = compact_request.body_json();
assert_eq!(
compact_body.get("model").and_then(|v| v.as_str()),
@@ -515,6 +520,7 @@ async fn remote_compact_runs_automatically() -> Result<()> {
.await?;
let codex = harness.test().codex.clone();
let session_id = harness.test().session_configured.session_id.to_string();
let thread_id = harness.test().session_configured.thread_id.to_string();
mount_sse_once(
harness.server(),
@@ -566,6 +572,10 @@ async fn remote_compact_runs_automatically() -> Result<()> {
.as_deref(),
Some(session_id.as_str())
);
assert_eq!(
compact_mock.single_request().header("thread_id").as_deref(),
Some(thread_id.as_str())
);
let follow_up_request = responses_mock.single_request();
let follow_up_body = follow_up_request.body_json().to_string();
assert!(follow_up_body.contains("REMOTE_COMPACTED_SUMMARY"));