diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 9b785ff3373f..2a12e5b16ede 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -7056,7 +7056,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), @@ -7315,7 +7315,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), @@ -7473,7 +7473,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), @@ -7616,7 +7616,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index b85607454061..7ba76eaa4d09 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -772,6 +772,20 @@ async fn shell_timeout_includes_timeout_prefix_and_metadata() -> Result<()> { "timeout output missing `command timed out`: {stdout}" ); } else { + let normalized_output = output_str + .replace("\r\n", "\n") + .replace('\r', "\n") + .trim_end_matches('\n') + .to_string(); + + let shell_output_pattern = r"(?s)^Exit code: 124\nWall time: [0-9]+(?:\.[0-9]+)? seconds\nOutput:\ncommand timed out after [0-9]+ milliseconds\n(?:.*)?$"; + if Regex::new(shell_output_pattern) + .expect("shell timeout output regex should compile") + .is_match(&normalized_output) + { + return Ok(()); + } + // Fallback: accept the signal classification path to deflake the test. let signal_pattern = r"(?is)^execution error:.*signal.*$"; assert_regex_match(signal_pattern, output_str); diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 6c8ce49894de..721069a5bde7 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -818,8 +818,8 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::ApplyPatchFreeform, key: "apply_patch_freeform", - stage: Stage::UnderDevelopment, - default_enabled: false, + stage: Stage::Stable, + default_enabled: true, }, FeatureSpec { id: Feature::ApplyPatchStreamingEvents, diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 5464fa7a61a4..55d2d8ba2790 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -578,7 +578,7 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config( FeatureConfigSource::default(), FeatureOverrides::default(), ); - assert_eq!(replayed.enabled(Feature::ApplyPatchFreeform), false); + assert_eq!(replayed.enabled(Feature::ApplyPatchFreeform), true); } #[test] diff --git a/codex-rs/tools/src/tool_config.rs b/codex-rs/tools/src/tool_config.rs index 0bb4b8b156f1..2ebf0540f784 100644 --- a/codex-rs/tools/src/tool_config.rs +++ b/codex-rs/tools/src/tool_config.rs @@ -222,7 +222,7 @@ impl ToolsConfig { let apply_patch_tool_type = match model_info.apply_patch_tool_type { Some(ApplyPatchToolType::Freeform) => Some(ApplyPatchToolType::Freeform), Some(ApplyPatchToolType::Function) => Some(ApplyPatchToolType::Function), - None => include_apply_patch_tool.then_some(ApplyPatchToolType::Freeform), + None => include_apply_patch_tool.then_some(ApplyPatchToolType::Function), }; let agent_jobs_worker_tools = include_agent_jobs diff --git a/codex-rs/tools/src/tool_config_tests.rs b/codex-rs/tools/src/tool_config_tests.rs index 34c871cac327..3d1829c4e554 100644 --- a/codex-rs/tools/src/tool_config_tests.rs +++ b/codex-rs/tools/src/tool_config_tests.rs @@ -4,6 +4,7 @@ use codex_features::Features; use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; +use codex_protocol::openai_models::ApplyPatchToolType; use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::InputModality; use codex_protocol::openai_models::ModelInfo; @@ -154,6 +155,29 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() { ); } +#[test] +fn fallback_apply_patch_models_use_function_tool_by_default() { + let model_info = model_info(); + let features = Features::with_defaults(); + + let available_models = Vec::new(); + let tools_config = ToolsConfig::new(&ToolsConfigParams { + model_info: &model_info, + available_models: &available_models, + features: &features, + image_generation_tool_auth_allowed: true, + web_search_mode: Some(WebSearchMode::Cached), + session_source: SessionSource::Cli, + permission_profile: &PermissionProfile::Disabled, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + }); + + assert_eq!( + tools_config.apply_patch_tool_type, + Some(ApplyPatchToolType::Function) + ); +} + #[test] fn subagents_keep_request_user_input_config_and_agent_jobs_workers_opt_in_by_label() { let model_info = model_info();