diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 75622e525..0c67c9ff5 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -31,9 +31,6 @@ pub enum Feature { GhostCommit, /// Use the single unified PTY-backed exec tool. UnifiedExec, - /// Use the shell command tool that takes `command` as a single string of - /// shell instead of an array of args passed to `execvp(3)`. - ShellCommandTool, /// Enable experimental RMCP features such as OAuth login. RmcpClient, /// Include the freeform apply_patch tool. @@ -275,12 +272,6 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Experimental, default_enabled: false, }, - FeatureSpec { - id: Feature::ShellCommandTool, - key: "shell_command_tool", - stage: Stage::Experimental, - default_enabled: false, - }, FeatureSpec { id: Feature::RmcpClient, key: "rmcp_client", diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index eb4585925..b3d0330f8 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -57,8 +57,6 @@ impl ToolsConfig { ConfigShellToolType::Disabled } else if features.enabled(Feature::UnifiedExec) { ConfigShellToolType::UnifiedExec - } else if features.enabled(Feature::ShellCommandTool) { - ConfigShellToolType::ShellCommand } else { model_family.shell_type.clone() }; @@ -1468,22 +1466,6 @@ mod tests { assert_contains_tool_names(&tools, &subset); } - #[test] - fn test_build_specs_shell_command_present() { - assert_model_tools( - "codex-mini-latest", - Features::with_defaults().enable(Feature::ShellCommandTool), - &[ - "shell_command", - "list_mcp_resources", - "list_mcp_resource_templates", - "read_mcp_resource", - "update_plan", - "view_image", - ], - ); - } - #[test] #[ignore] fn test_parallel_support_flags() { diff --git a/codex-rs/core/tests/suite/shell_serialization.rs b/codex-rs/core/tests/suite/shell_serialization.rs index 24c90bde2..4db284767 100644 --- a/codex-rs/core/tests/suite/shell_serialization.rs +++ b/codex-rs/core/tests/suite/shell_serialization.rs @@ -2,6 +2,7 @@ #![allow(clippy::expect_used)] use anyhow::Result; +use codex_core::config::Config; use codex_core::features::Feature; use codex_core::model_family::find_family_for_model; use codex_core::protocol::SandboxPolicy; @@ -40,6 +41,20 @@ const FIXTURE_JSON: &str = r#"{ } "#; +fn configure_shell_command_model(output_type: ShellModelOutput, config: &mut Config) { + if !matches!(output_type, ShellModelOutput::ShellCommand) { + return; + } + + if let Some(shell_command_family) = find_family_for_model("test-gpt-5-codex") { + if config.model_family.shell_type == shell_command_family.shell_type { + return; + } + config.model = shell_command_family.slug.clone(); + config.model_family = shell_command_family; + } +} + fn shell_responses( call_id: &str, command: Vec<&str>, @@ -112,10 +127,7 @@ async fn shell_output_stays_json_without_freeform_apply_patch( config.features.disable(Feature::ApplyPatchFreeform); config.model = "gpt-5".to_string(); config.model_family = find_family_for_model("gpt-5").expect("gpt-5 is a model family"); - if matches!(output_type, ShellModelOutput::ShellCommand) { - config.features.enable(Feature::ShellCommandTool); - } - let _ = output_type; + configure_shell_command_model(output_type, config); }); let test = builder.build(&server).await?; @@ -170,10 +182,7 @@ async fn shell_output_is_structured_with_freeform_apply_patch( let server = start_mock_server().await; let mut builder = test_codex().with_config(move |config| { config.features.enable(Feature::ApplyPatchFreeform); - if matches!(output_type, ShellModelOutput::ShellCommand) { - config.features.enable(Feature::ShellCommandTool); - } - let _ = output_type; + configure_shell_command_model(output_type, config); }); let test = builder.build(&server).await?; @@ -223,10 +232,7 @@ async fn shell_output_preserves_fixture_json_without_serialization( config.features.disable(Feature::ApplyPatchFreeform); config.model = "gpt-5".to_string(); config.model_family = find_family_for_model("gpt-5").expect("gpt-5 is a model family"); - if matches!(output_type, ShellModelOutput::ShellCommand) { - config.features.enable(Feature::ShellCommandTool); - } - let _ = output_type; + configure_shell_command_model(output_type, config); }); let test = builder.build(&server).await?; @@ -293,10 +299,7 @@ async fn shell_output_structures_fixture_with_serialization( let server = start_mock_server().await; let mut builder = test_codex().with_config(move |config| { config.features.enable(Feature::ApplyPatchFreeform); - if matches!(output_type, ShellModelOutput::ShellCommand) { - config.features.enable(Feature::ShellCommandTool); - } - let _ = output_type; + configure_shell_command_model(output_type, config); }); let test = builder.build(&server).await?; @@ -358,10 +361,7 @@ async fn shell_output_for_freeform_tool_records_duration( let server = start_mock_server().await; let mut builder = test_codex().with_config(move |config| { config.include_apply_patch_tool = true; - if matches!(output_type, ShellModelOutput::ShellCommand) { - config.features.enable(Feature::ShellCommandTool); - } - let _ = output_type; + configure_shell_command_model(output_type, config); }); let test = builder.build(&server).await?; @@ -417,10 +417,7 @@ async fn shell_output_reserializes_truncated_content(output_type: ShellModelOutp config.model_family = find_family_for_model("gpt-5.1-codex").expect("gpt-5.1-codex is a model family"); config.tool_output_token_limit = Some(200); - if matches!(output_type, ShellModelOutput::ShellCommand) { - config.features.enable(Feature::ShellCommandTool); - } - let _ = output_type; + configure_shell_command_model(output_type, config); }); let test = builder.build(&server).await?; @@ -722,9 +719,7 @@ async fn shell_output_is_structured_for_nonzero_exit(output_type: ShellModelOutp config.model_family = find_family_for_model("gpt-5.1-codex").expect("gpt-5.1-codex is a model family"); config.include_apply_patch_tool = true; - if matches!(output_type, ShellModelOutput::ShellCommand) { - config.features.enable(Feature::ShellCommandTool); - } + configure_shell_command_model(output_type, config); }); let test = builder.build(&server).await?; @@ -760,7 +755,7 @@ async fn shell_command_output_is_freeform() -> Result<()> { let server = start_mock_server().await; let mut builder = test_codex().with_config(move |config| { - config.features.enable(Feature::ShellCommandTool); + configure_shell_command_model(ShellModelOutput::ShellCommand, config); }); let test = builder.build(&server).await?; @@ -812,11 +807,7 @@ async fn shell_command_output_is_not_truncated_under_10k_bytes() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let mut builder = test_codex() - .with_model("gpt-5.1") - .with_config(move |config| { - config.features.enable(Feature::ShellCommandTool); - }); + let mut builder = test_codex().with_model("gpt-5.1"); let test = builder.build(&server).await?; let call_id = "shell-command"; @@ -866,11 +857,7 @@ async fn shell_command_output_is_not_truncated_over_10k_bytes() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let mut builder = test_codex() - .with_model("gpt-5.1") - .with_config(move |config| { - config.features.enable(Feature::ShellCommandTool); - }); + let mut builder = test_codex().with_model("gpt-5.1"); let test = builder.build(&server).await?; let call_id = "shell-command";