From ee59a8aac828a55ddedd66ce3b1c65b209023613 Mon Sep 17 00:00:00 2001 From: Roy Han Date: Mon, 16 Mar 2026 18:41:03 -0700 Subject: [PATCH] core: add local input metadata strip for responses compatibility --- codex-rs/core/src/client_common.rs | 28 ++++++++ codex-rs/core/src/client_common_tests.rs | 83 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 3f863c8afb..df17ec8299 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -21,6 +21,7 @@ pub const REVIEW_PROMPT: &str = include_str!("../review_prompt.md"); pub const REVIEW_EXIT_SUCCESS_TMPL: &str = include_str!("../templates/review/exit_success.xml"); pub const REVIEW_EXIT_INTERRUPTED_TMPL: &str = include_str!("../templates/review/exit_interrupted.xml"); +const STRIP_INPUT_ITEM_METADATA_ENV_VAR: &str = "CODEX_STRIP_INPUT_ITEM_METADATA"; /// API request payload for a single model turn #[derive(Default, Debug, Clone)] @@ -60,10 +61,37 @@ impl Prompt { reserialize_shell_outputs(&mut input); } + if should_strip_input_item_metadata() { + strip_input_item_metadata(&mut input); + } + input } } +fn should_strip_input_item_metadata() -> bool { + std::env::var(STRIP_INPUT_ITEM_METADATA_ENV_VAR) + .ok() + .is_some_and(|value| { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) +} + +fn strip_input_item_metadata(items: &mut [ResponseItem]) { + items.iter_mut().for_each(|item| match item { + ResponseItem::Message { metadata, .. } + | ResponseItem::LocalShellCall { metadata, .. } + | ResponseItem::FunctionCall { metadata, .. } + | ResponseItem::CustomToolCall { metadata, .. } => { + *metadata = None; + } + _ => {} + }); +} + fn reserialize_shell_outputs(items: &mut [ResponseItem]) { let mut shell_call_ids: HashSet = HashSet::new(); diff --git a/codex-rs/core/src/client_common_tests.rs b/codex-rs/core/src/client_common_tests.rs index 1b94c9119f..9a9d431b73 100644 --- a/codex-rs/core/src/client_common_tests.rs +++ b/codex-rs/core/src/client_common_tests.rs @@ -245,3 +245,86 @@ fn tool_search_output_namespace_serializes_with_deferred_child_tools() { }) ); } + +#[test] +fn strip_input_item_metadata_clears_supported_variants() { + let metadata = Some(codex_protocol::models::ResponseItemMetadata { + user_message_type: Some(codex_protocol::models::UserMessageType::Prompt), + sandbox_policy: Some(codex_protocol::models::SandboxPolicyMetadata::Sandbox), + is_tool_call_escalated: Some(true), + review_decision: Some(codex_protocol::models::ReviewDecisionMetadata::Denied), + }); + let mut items = vec![ + ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![codex_protocol::models::ContentItem::InputText { + text: "hello".to_string(), + }], + metadata: metadata.clone(), + end_turn: None, + phase: None, + }, + ResponseItem::LocalShellCall { + id: None, + call_id: Some("call-1".to_string()), + status: codex_protocol::models::LocalShellStatus::Completed, + action: codex_protocol::models::LocalShellAction::Exec( + codex_protocol::models::LocalShellExecAction { + command: vec!["echo".to_string(), "hello".to_string()], + env: None, + timeout_ms: None, + user: None, + working_directory: None, + }, + ), + metadata: metadata.clone(), + }, + ResponseItem::FunctionCall { + id: None, + name: "shell".to_string(), + namespace: None, + arguments: "{}".to_string(), + call_id: "call-2".to_string(), + metadata: metadata.clone(), + }, + ResponseItem::CustomToolCall { + id: None, + status: None, + call_id: "call-3".to_string(), + name: "apply_patch".to_string(), + input: "*** Begin Patch".to_string(), + metadata: metadata.clone(), + }, + ]; + + strip_input_item_metadata(&mut items); + + for item in items { + match item { + ResponseItem::Message { metadata, .. } + | ResponseItem::LocalShellCall { metadata, .. } + | ResponseItem::FunctionCall { metadata, .. } + | ResponseItem::CustomToolCall { metadata, .. } => assert_eq!(metadata, None), + _ => {} + } + } +} + +#[test] +fn strip_input_item_metadata_does_not_change_non_metadata_items() { + let mut items = vec![ + ResponseItem::FunctionCallOutput { + call_id: "call-4".to_string(), + output: FunctionCallOutputPayload::from_text("ok".to_string()), + }, + ResponseItem::CustomToolCallOutput { + call_id: "call-5".to_string(), + output: FunctionCallOutputPayload::from_text("ok".to_string()), + }, + ]; + + let expected = items.clone(); + strip_input_item_metadata(&mut items); + assert_eq!(items, expected); +}