From 2580fbcf3fa2e936337fe4295c60691e9b31a10d Mon Sep 17 00:00:00 2001 From: jimmyfraiture Date: Fri, 3 Oct 2025 17:17:24 +0100 Subject: [PATCH 1/2] feat: add `beta_supported_tools` --- codex-rs/core/src/model_family.rs | 6 +++ codex-rs/core/src/tools/spec.rs | 67 +++++++++++++++--------- codex-rs/core/tests/suite/model_tools.rs | 11 +++- 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/codex-rs/core/src/model_family.rs b/codex-rs/core/src/model_family.rs index 8796d7e7ad..7536299abe 100644 --- a/codex-rs/core/src/model_family.rs +++ b/codex-rs/core/src/model_family.rs @@ -41,6 +41,9 @@ pub struct ModelFamily { // Instructions to use for querying the model pub base_instructions: String, + + /// Names of beta tools that should be exposed to this model family. + pub beta_supported_tools: Vec, } macro_rules! model_family { @@ -57,6 +60,7 @@ macro_rules! model_family { uses_local_shell_tool: false, apply_patch_tool_type: None, base_instructions: BASE_INSTRUCTIONS.to_string(), + beta_supported_tools: Vec::new(), }; // apply overrides $( @@ -105,6 +109,7 @@ pub fn find_family_for_model(slug: &str) -> Option { supports_reasoning_summaries: true, reasoning_summary_format: ReasoningSummaryFormat::Experimental, base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(), + beta_supported_tools: vec!["read_file".to_string()], ) } else if slug.starts_with("gpt-5") { model_family!( @@ -127,5 +132,6 @@ pub fn derive_default_model_family(model: &str) -> ModelFamily { uses_local_shell_tool: false, apply_patch_tool_type: None, base_instructions: BASE_INSTRUCTIONS.to_string(), + beta_supported_tools: Vec::new(), } } diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 18d8555a92..5fa4093fe0 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -28,6 +28,7 @@ pub(crate) struct ToolsConfig { pub web_search_request: bool, pub include_view_image_tool: bool, pub experimental_unified_exec_tool: bool, + pub beta_supported_tools: Vec, } pub(crate) struct ToolsConfigParams<'a> { @@ -78,6 +79,7 @@ impl ToolsConfig { web_search_request: *include_web_search_request, include_view_image_tool: *include_view_image_tool, experimental_unified_exec_tool: *experimental_unified_exec_tool, + beta_supported_tools: model_family.beta_supported_tools.clone(), } } } @@ -515,7 +517,6 @@ pub(crate) fn build_specs( let exec_stream_handler = Arc::new(ExecStreamHandler); let unified_exec_handler = Arc::new(UnifiedExecHandler); let plan_handler = Arc::new(PlanHandler); - let read_file_handler = Arc::new(ReadFileHandler); let apply_patch_handler = Arc::new(ApplyPatchHandler); let view_image_handler = Arc::new(ViewImageHandler); let mcp_handler = Arc::new(McpHandler); @@ -566,8 +567,15 @@ pub(crate) fn build_specs( builder.register_handler("apply_patch", apply_patch_handler); } - builder.push_spec(create_read_file_tool()); - builder.register_handler("read_file", read_file_handler); + if config + .beta_supported_tools + .iter() + .any(|tool| tool == "read_file") + { + let read_file_handler = Arc::new(ReadFileHandler); + builder.push_spec(create_read_file_tool()); + builder.register_handler("read_file", read_file_handler); + } if config.web_search_request { builder.push_spec(ToolSpec::WebSearch {}); @@ -648,13 +656,7 @@ mod tests { assert_eq_tool_names( &tools, - &[ - "unified_exec", - "update_plan", - "read_file", - "web_search", - "view_image", - ], + &["unified_exec", "update_plan", "web_search", "view_image"], ); } @@ -674,16 +676,28 @@ mod tests { assert_eq_tool_names( &tools, - &[ - "unified_exec", - "update_plan", - "read_file", - "web_search", - "view_image", - ], + &["unified_exec", "update_plan", "web_search", "view_image"], ); } + #[test] + fn test_build_specs_includes_beta_read_file_tool() { + let model_family = find_family_for_model("gpt-5-codex") + .expect("gpt-5-codex should be a valid model family"); + let config = ToolsConfig::new(&ToolsConfigParams { + model_family: &model_family, + include_plan_tool: false, + include_apply_patch_tool: false, + include_web_search_request: false, + use_streamable_shell_tool: false, + include_view_image_tool: false, + experimental_unified_exec_tool: true, + }); + let (tools, _) = build_specs(&config, Some(HashMap::new())).build(); + + assert_eq_tool_names(&tools, &["unified_exec", "read_file"]); + } + #[test] fn test_build_specs_mcp_tools() { let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); @@ -739,7 +753,6 @@ mod tests { &tools, &[ "unified_exec", - "read_file", "web_search", "view_image", "test_server/do_something_cool", @@ -747,7 +760,7 @@ mod tests { ); assert_eq!( - tools[4], + tools[3], ToolSpec::Function(ResponsesApiTool { name: "test_server/do_something_cool".to_string(), parameters: JsonSchema::Object { @@ -858,7 +871,6 @@ mod tests { &tools, &[ "unified_exec", - "read_file", "view_image", "test_server/cool", "test_server/do", @@ -869,7 +881,8 @@ mod tests { #[test] fn test_mcp_tool_property_missing_type_defaults_to_string() { - let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); + let model_family = find_family_for_model("gpt-5-codex") + .expect("gpt-5-codex should be a valid model family"); let config = ToolsConfig::new(&ToolsConfigParams { model_family: &model_family, include_plan_tool: false, @@ -937,7 +950,8 @@ mod tests { #[test] fn test_mcp_tool_integer_normalized_to_number() { - let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); + let model_family = find_family_for_model("gpt-5-codex") + .expect("gpt-5-codex should be a valid model family"); let config = ToolsConfig::new(&ToolsConfigParams { model_family: &model_family, include_plan_tool: false, @@ -1000,7 +1014,8 @@ mod tests { #[test] fn test_mcp_tool_array_without_items_gets_default_string_items() { - let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); + let model_family = find_family_for_model("gpt-5-codex") + .expect("gpt-5-codex should be a valid model family"); let config = ToolsConfig::new(&ToolsConfigParams { model_family: &model_family, include_plan_tool: false, @@ -1066,7 +1081,8 @@ mod tests { #[test] fn test_mcp_tool_anyof_defaults_to_string() { - let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); + let model_family = find_family_for_model("gpt-5-codex") + .expect("gpt-5-codex should be a valid model family"); let config = ToolsConfig::new(&ToolsConfigParams { model_family: &model_family, include_plan_tool: false, @@ -1144,7 +1160,8 @@ mod tests { #[test] fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() { - let model_family = find_family_for_model("o3").expect("o3 should be a valid model family"); + let model_family = find_family_for_model("gpt-5-codex") + .expect("gpt-5-codex should be a valid model family"); let config = ToolsConfig::new(&ToolsConfigParams { model_family: &model_family, include_plan_tool: false, diff --git a/codex-rs/core/tests/suite/model_tools.rs b/codex-rs/core/tests/suite/model_tools.rs index 29a319115e..9c29e46932 100644 --- a/codex-rs/core/tests/suite/model_tools.rs +++ b/codex-rs/core/tests/suite/model_tools.rs @@ -111,14 +111,21 @@ async fn model_selects_expected_tools() { let codex_tools = collect_tool_identifiers_for_model("codex-mini-latest").await; assert_eq!( codex_tools, - vec!["local_shell".to_string(), "read_file".to_string()], + vec!["local_shell".to_string()], "codex-mini-latest should expose the local shell tool", ); let o3_tools = collect_tool_identifiers_for_model("o3").await; assert_eq!( o3_tools, - vec!["shell".to_string(), "read_file".to_string()], + vec!["shell".to_string()], "o3 should expose the generic shell tool", ); + + let gpt5_codex_tools = collect_tool_identifiers_for_model("gpt-5-codex").await; + assert_eq!( + gpt5_codex_tools, + vec!["shell".to_string(), "read_file".to_string()], + "gpt-5-codex should expose the beta read_file tool", + ); } From fe39a10fa6a28e544c5c6aa0affbe33b7f2168e3 Mon Sep 17 00:00:00 2001 From: jimmyfraiture Date: Fri, 3 Oct 2025 17:38:06 +0100 Subject: [PATCH 2/2] Rename --- codex-rs/core/src/model_family.rs | 8 ++++---- codex-rs/core/src/tools/spec.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/codex-rs/core/src/model_family.rs b/codex-rs/core/src/model_family.rs index 7536299abe..5ddaaca291 100644 --- a/codex-rs/core/src/model_family.rs +++ b/codex-rs/core/src/model_family.rs @@ -43,7 +43,7 @@ pub struct ModelFamily { pub base_instructions: String, /// Names of beta tools that should be exposed to this model family. - pub beta_supported_tools: Vec, + pub experimental_supported_tools: Vec, } macro_rules! model_family { @@ -60,7 +60,7 @@ macro_rules! model_family { uses_local_shell_tool: false, apply_patch_tool_type: None, base_instructions: BASE_INSTRUCTIONS.to_string(), - beta_supported_tools: Vec::new(), + experimental_supported_tools: Vec::new(), }; // apply overrides $( @@ -109,7 +109,7 @@ pub fn find_family_for_model(slug: &str) -> Option { supports_reasoning_summaries: true, reasoning_summary_format: ReasoningSummaryFormat::Experimental, base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(), - beta_supported_tools: vec!["read_file".to_string()], + experimental_supported_tools: vec!["read_file".to_string()], ) } else if slug.starts_with("gpt-5") { model_family!( @@ -132,6 +132,6 @@ pub fn derive_default_model_family(model: &str) -> ModelFamily { uses_local_shell_tool: false, apply_patch_tool_type: None, base_instructions: BASE_INSTRUCTIONS.to_string(), - beta_supported_tools: Vec::new(), + experimental_supported_tools: Vec::new(), } } diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 5fa4093fe0..5e24e46e87 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -28,7 +28,7 @@ pub(crate) struct ToolsConfig { pub web_search_request: bool, pub include_view_image_tool: bool, pub experimental_unified_exec_tool: bool, - pub beta_supported_tools: Vec, + pub experimental_supported_tools: Vec, } pub(crate) struct ToolsConfigParams<'a> { @@ -79,7 +79,7 @@ impl ToolsConfig { web_search_request: *include_web_search_request, include_view_image_tool: *include_view_image_tool, experimental_unified_exec_tool: *experimental_unified_exec_tool, - beta_supported_tools: model_family.beta_supported_tools.clone(), + experimental_supported_tools: model_family.experimental_supported_tools.clone(), } } } @@ -568,7 +568,7 @@ pub(crate) fn build_specs( } if config - .beta_supported_tools + .experimental_supported_tools .iter() .any(|tool| tool == "read_file") {