diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 3c21552dcb..14c068e31b 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -244,7 +244,12 @@ impl ModelClient { let max_attempts = self.provider.request_max_retries(); for attempt in 0..=max_attempts { match self - .attempt_stream_responses(attempt, &payload_json, &auth_manager) + .attempt_stream_responses( + attempt, + &payload_json, + &auth_manager, + prompt.is_review_turn, + ) .await { Ok(stream) => { @@ -272,6 +277,7 @@ impl ModelClient { attempt: u64, payload_json: &Value, auth_manager: &Option>, + is_review_turn: bool, ) -> std::result::Result { // Always fetch the latest auth in case a prior attempt refreshed the token. let auth = auth_manager.as_ref().and_then(|m| m.auth()); @@ -293,6 +299,10 @@ impl ModelClient { // Send session_id for compatibility. .header("conversation_id", self.conversation_id.to_string()) .header("session_id", self.conversation_id.to_string()) + .header( + "action_kind", + if is_review_turn { "review" } else { "turn" }, + ) .header(reqwest::header::ACCEPT, "text/event-stream") .json(payload_json); diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index d30da5192a..4a97dafdd4 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -41,6 +41,9 @@ pub struct Prompt { /// Optional the output schema for the model's response. pub output_schema: Option, + + /// Marks whether this prompt is part of a review turn. + pub is_review_turn: bool, } impl Prompt { diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index d11a53b59a..225117d66b 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1950,6 +1950,7 @@ async fn run_turn( parallel_tool_calls, base_instructions_override: turn_context.base_instructions.clone(), output_schema: turn_context.final_output_json_schema.clone(), + is_review_turn: turn_context.is_review_mode, }; let mut retries = 0; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index eb14dabb81..29dc5f6b81 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -349,12 +349,14 @@ async fn includes_conversation_id_and_model_headers_in_request() { let request_conversation_id = request.headers.get("conversation_id").unwrap(); let request_authorization = request.headers.get("authorization").unwrap(); let request_originator = request.headers.get("originator").unwrap(); + let turn_kind = request.headers.get("action_kind").unwrap(); assert_eq!( request_conversation_id.to_str().unwrap(), conversation_id.to_string() ); assert_eq!(request_originator.to_str().unwrap(), "codex_cli_rs"); + assert_eq!(turn_kind.to_str().unwrap(), "turn"); assert_eq!( request_authorization.to_str().unwrap(), "Bearer Test API Key" diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index f3eeb3a310..cdab96b77a 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -118,6 +118,14 @@ async fn review_op_emits_lifecycle_and_review_output() { assert_eq!(expected, review); let _complete = wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await; + // Assert header marks this as a review turn. + let request = &server.received_requests().await.unwrap()[0]; + let turn_kind = request + .headers + .get("action_kind") + .expect("missing action_kind header"); + assert_eq!(turn_kind.to_str().unwrap(), "review"); + // Also verify that a user message with the header and a formatted finding // was recorded back in the parent session's rollout. codex.submit(Op::GetPath).await.unwrap();