From 5bf604ca694fa0cb2fa6211b792c9e7e0868e577 Mon Sep 17 00:00:00 2001 From: nicholasclark-openai Date: Thu, 19 Mar 2026 13:13:06 -0700 Subject: [PATCH] Keep parent request values in turn metadata only Co-authored-by: Codex --- codex-rs/app-server/src/in_process.rs | 31 ++++++------ codex-rs/core/src/client.rs | 5 -- codex-rs/core/src/turn_metadata.rs | 61 +----------------------- codex-rs/core/src/turn_metadata_tests.rs | 51 ++++++++------------ codex-rs/core/tests/responses_headers.rs | 20 +++++--- 5 files changed, 51 insertions(+), 117 deletions(-) diff --git a/codex-rs/app-server/src/in_process.rs b/codex-rs/app-server/src/in_process.rs index c3de1a7cdf..96f8bb288f 100644 --- a/codex-rs/app-server/src/in_process.rs +++ b/codex-rs/app-server/src/in_process.rs @@ -863,7 +863,7 @@ mod tests { } #[tokio::test] - async fn in_process_start_forwards_turn_metadata_headers_on_turn_requests() { + async fn in_process_start_forwards_turn_metadata_on_turn_requests() { let server = create_mock_responses_server_repeating_assistant("Done").await; let codex_home = TempDir::new().expect("tempdir should create"); let mut client = start(InProcessStartArgs { @@ -1004,26 +1004,25 @@ mod tests { .expect("timed out waiting for outbound requests"); assert!(!requests.is_empty()); for request in requests { + let turn_metadata_header = request + .headers + .get("x-codex-turn-metadata") + .and_then(|value| value.to_str().ok()) + .expect("turn metadata header should be present"); + let turn_metadata: serde_json::Value = + serde_json::from_str(turn_metadata_header).expect("turn metadata should be JSON"); + assert_eq!( - request - .headers - .get("x-openai-parent-conversation-id") - .and_then(|value| value.to_str().ok()), - Some("parent-conversation-123") + turn_metadata.pointer("/metadata/parentConversationId"), + Some(&serde_json::json!("parent-conversation-123")) ); assert_eq!( - request - .headers - .get("x-openai-parent-message-id") - .and_then(|value| value.to_str().ok()), - Some("parent-message-123") + turn_metadata.pointer("/metadata/parentMessageId"), + Some(&serde_json::json!("parent-message-123")) ); assert_eq!( - request - .headers - .get("x-openai-parent-turn-id") - .and_then(|value| value.to_str().ok()), - Some("parent-turn-123") + turn_metadata.pointer("/metadata/parentTurnId"), + Some(&serde_json::json!("parent-turn-123")) ); } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 15067a087b..ba71033c3b 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -108,7 +108,6 @@ use crate::response_debug_context::extract_response_debug_context_from_api_error use crate::response_debug_context::telemetry_api_error_message; use crate::response_debug_context::telemetry_transport_error_message; use crate::tools::spec::create_tools_json_for_responses_api; -use crate::turn_metadata::extend_known_request_headers; use crate::util::FeedbackRequestTags; use crate::util::emit_feedback_auth_recovery_tags; use crate::util::emit_feedback_request_tags_with_auth_env; @@ -1385,10 +1384,6 @@ fn build_responses_headers( turn_metadata_header: Option<&HeaderValue>, ) -> ApiHeaderMap { let mut headers = ApiHeaderMap::new(); - extend_known_request_headers( - &mut headers, - turn_metadata_header.and_then(|value| value.to_str().ok()), - ); if let Some(value) = beta_features_header && !value.is_empty() && let Ok(header_value) = HeaderValue::from_str(value) diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs index fd3f1e3522..439ac79cb6 100644 --- a/codex-rs/core/src/turn_metadata.rs +++ b/codex-rs/core/src/turn_metadata.rs @@ -5,9 +5,6 @@ use std::sync::Arc; use std::sync::Mutex; use std::sync::RwLock; -use http::HeaderMap; -use http::HeaderValue; -use serde::Deserialize; use serde::Serialize; use tokio::task::JoinHandle; @@ -19,14 +16,6 @@ use crate::sandbox_tags::sandbox_tag; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::protocol::SandboxPolicy; -pub(crate) const PARENT_CONVERSATION_ID_METADATA_KEY: &str = "parentConversationId"; -pub(crate) const PARENT_MESSAGE_ID_METADATA_KEY: &str = "parentMessageId"; -pub(crate) const PARENT_TURN_ID_METADATA_KEY: &str = "parentTurnId"; - -const X_OPENAI_PARENT_CONVERSATION_ID_HEADER: &str = "x-openai-parent-conversation-id"; -const X_OPENAI_PARENT_MESSAGE_ID_HEADER: &str = "x-openai-parent-message-id"; -const X_OPENAI_PARENT_TURN_ID_HEADER: &str = "x-openai-parent-turn-id"; - #[derive(Clone, Debug, Default)] struct WorkspaceGitMetadata { associated_remote_urls: Option>, @@ -42,7 +31,7 @@ impl WorkspaceGitMetadata { } } -#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[derive(Clone, Debug, Serialize, Default)] struct TurnMetadataWorkspace { #[serde(default, skip_serializing_if = "Option::is_none")] associated_remote_urls: Option>, @@ -62,7 +51,7 @@ impl From for TurnMetadataWorkspace { } } -#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[derive(Clone, Debug, Serialize, Default)] pub(crate) struct TurnMetadataBag { #[serde(default, skip_serializing_if = "Option::is_none")] session_id: Option, @@ -267,52 +256,6 @@ impl TurnMetadataState { } } } - -pub(crate) fn extend_known_request_headers( - headers: &mut HeaderMap, - turn_metadata_header: Option<&str>, -) { - let Some(turn_metadata_header) = turn_metadata_header else { - return; - }; - let Ok(turn_metadata) = serde_json::from_str::(turn_metadata_header) else { - return; - }; - - insert_if_valid( - headers, - X_OPENAI_PARENT_CONVERSATION_ID_HEADER, - turn_metadata - .metadata - .get(PARENT_CONVERSATION_ID_METADATA_KEY) - .map(String::as_str), - ); - insert_if_valid( - headers, - X_OPENAI_PARENT_MESSAGE_ID_HEADER, - turn_metadata - .metadata - .get(PARENT_MESSAGE_ID_METADATA_KEY) - .map(String::as_str), - ); - insert_if_valid( - headers, - X_OPENAI_PARENT_TURN_ID_HEADER, - turn_metadata - .metadata - .get(PARENT_TURN_ID_METADATA_KEY) - .map(String::as_str), - ); -} - -fn insert_if_valid(headers: &mut HeaderMap, name: &'static str, value: Option<&str>) { - if let Some(value) = value - && let Ok(header_value) = HeaderValue::from_str(value) - { - headers.insert(name, header_value); - } -} - #[cfg(test)] #[path = "turn_metadata_tests.rs"] mod tests; diff --git a/codex-rs/core/src/turn_metadata_tests.rs b/codex-rs/core/src/turn_metadata_tests.rs index 69df990b41..24c7fcb7a8 100644 --- a/codex-rs/core/src/turn_metadata_tests.rs +++ b/codex-rs/core/src/turn_metadata_tests.rs @@ -88,36 +88,27 @@ fn turn_metadata_state_uses_platform_sandbox_tag() { } #[test] -fn extend_known_request_headers_reads_parent_ids_from_turn_metadata() { - let mut headers = http::HeaderMap::new(); - let turn_metadata = serde_json::json!({ - "turn_id": "turn-a", - "metadata": { - PARENT_CONVERSATION_ID_METADATA_KEY: "conv-123", - PARENT_MESSAGE_ID_METADATA_KEY: "msg-123", - PARENT_TURN_ID_METADATA_KEY: "turn-123", - }, - }); - let turn_metadata = serde_json::to_string(&turn_metadata).expect("turn metadata json"); +fn turn_metadata_state_serializes_custom_metadata() { + let temp_dir = TempDir::new().expect("temp dir"); + let cwd = temp_dir.path().to_path_buf(); + let sandbox_policy = SandboxPolicy::new_read_only_policy(); + let metadata = BTreeMap::from([ + ("parentConversationId".to_string(), "conv-123".to_string()), + ("parentMessageId".to_string(), "msg-123".to_string()), + ("parentTurnId".to_string(), "turn-123".to_string()), + ]); - extend_known_request_headers(&mut headers, Some(turn_metadata.as_str())); + let state = TurnMetadataState::new( + "session-a".to_string(), + "turn-a".to_string(), + cwd, + &sandbox_policy, + WindowsSandboxLevel::Disabled, + metadata.clone(), + ); - assert_eq!( - headers - .get("x-openai-parent-conversation-id") - .and_then(|value| value.to_str().ok()), - Some("conv-123") - ); - assert_eq!( - headers - .get("x-openai-parent-message-id") - .and_then(|value| value.to_str().ok()), - Some("msg-123") - ); - assert_eq!( - headers - .get("x-openai-parent-turn-id") - .and_then(|value| value.to_str().ok()), - Some("turn-123") - ); + let header = state.current_header_value().expect("header"); + let json: Value = serde_json::from_str(&header).expect("json"); + + assert_eq!(json.get("metadata"), Some(&serde_json::json!(metadata))); } diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 75abf58298..c640f01d6e 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -549,7 +549,7 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() } #[tokio::test] -async fn responses_stream_includes_parent_headers_from_turn_metadata() { +async fn responses_stream_includes_parent_metadata_in_turn_metadata_header() { core_test_support::skip_if_no_network!(); let server = responses::start_mock_server().await; @@ -659,16 +659,22 @@ async fn responses_stream_includes_parent_headers_from_turn_metadata() { } let request = request_recorder.single_request(); + let turn_metadata_header = request + .header("x-codex-turn-metadata") + .expect("request should include turn metadata"); + let turn_metadata: serde_json::Value = + serde_json::from_str(&turn_metadata_header).expect("turn metadata should be valid JSON"); + assert_eq!( - request.header("x-openai-parent-conversation-id").as_deref(), - Some("conv-123") + turn_metadata.pointer("/metadata/parentConversationId"), + Some(&json!("conv-123")) ); assert_eq!( - request.header("x-openai-parent-message-id").as_deref(), - Some("msg-123") + turn_metadata.pointer("/metadata/parentMessageId"), + Some(&json!("msg-123")) ); assert_eq!( - request.header("x-openai-parent-turn-id").as_deref(), - Some("turn-123") + turn_metadata.pointer("/metadata/parentTurnId"), + Some(&json!("turn-123")) ); }