Skip to content

Commit 26bb3fc

Browse files
authored
feat: add beta_supported_tools (openai#4669)
Gate the new read_file tool behind a new `beta_supported_tools` flag and only enable it for `gpt-5-codex`
1 parent 8a1cd50 commit 26bb3fc

File tree

3 files changed

+57
-27
lines changed

3 files changed

+57
-27
lines changed

codex-rs/core/src/model_family.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub struct ModelFamily {
4141

4242
// Instructions to use for querying the model
4343
pub base_instructions: String,
44+
45+
/// Names of beta tools that should be exposed to this model family.
46+
pub experimental_supported_tools: Vec<String>,
4447
}
4548

4649
macro_rules! model_family {
@@ -57,6 +60,7 @@ macro_rules! model_family {
5760
uses_local_shell_tool: false,
5861
apply_patch_tool_type: None,
5962
base_instructions: BASE_INSTRUCTIONS.to_string(),
63+
experimental_supported_tools: Vec::new(),
6064
};
6165
// apply overrides
6266
$(
@@ -105,6 +109,7 @@ pub fn find_family_for_model(slug: &str) -> Option<ModelFamily> {
105109
supports_reasoning_summaries: true,
106110
reasoning_summary_format: ReasoningSummaryFormat::Experimental,
107111
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
112+
experimental_supported_tools: vec!["read_file".to_string()],
108113
)
109114
} else if slug.starts_with("gpt-5") {
110115
model_family!(
@@ -127,5 +132,6 @@ pub fn derive_default_model_family(model: &str) -> ModelFamily {
127132
uses_local_shell_tool: false,
128133
apply_patch_tool_type: None,
129134
base_instructions: BASE_INSTRUCTIONS.to_string(),
135+
experimental_supported_tools: Vec::new(),
130136
}
131137
}

codex-rs/core/src/tools/spec.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub(crate) struct ToolsConfig {
2828
pub web_search_request: bool,
2929
pub include_view_image_tool: bool,
3030
pub experimental_unified_exec_tool: bool,
31+
pub experimental_supported_tools: Vec<String>,
3132
}
3233

3334
pub(crate) struct ToolsConfigParams<'a> {
@@ -78,6 +79,7 @@ impl ToolsConfig {
7879
web_search_request: *include_web_search_request,
7980
include_view_image_tool: *include_view_image_tool,
8081
experimental_unified_exec_tool: *experimental_unified_exec_tool,
82+
experimental_supported_tools: model_family.experimental_supported_tools.clone(),
8183
}
8284
}
8385
}
@@ -515,7 +517,6 @@ pub(crate) fn build_specs(
515517
let exec_stream_handler = Arc::new(ExecStreamHandler);
516518
let unified_exec_handler = Arc::new(UnifiedExecHandler);
517519
let plan_handler = Arc::new(PlanHandler);
518-
let read_file_handler = Arc::new(ReadFileHandler);
519520
let apply_patch_handler = Arc::new(ApplyPatchHandler);
520521
let view_image_handler = Arc::new(ViewImageHandler);
521522
let mcp_handler = Arc::new(McpHandler);
@@ -566,8 +567,15 @@ pub(crate) fn build_specs(
566567
builder.register_handler("apply_patch", apply_patch_handler);
567568
}
568569

569-
builder.push_spec(create_read_file_tool());
570-
builder.register_handler("read_file", read_file_handler);
570+
if config
571+
.experimental_supported_tools
572+
.iter()
573+
.any(|tool| tool == "read_file")
574+
{
575+
let read_file_handler = Arc::new(ReadFileHandler);
576+
builder.push_spec(create_read_file_tool());
577+
builder.register_handler("read_file", read_file_handler);
578+
}
571579

572580
if config.web_search_request {
573581
builder.push_spec(ToolSpec::WebSearch {});
@@ -648,13 +656,7 @@ mod tests {
648656

649657
assert_eq_tool_names(
650658
&tools,
651-
&[
652-
"unified_exec",
653-
"update_plan",
654-
"read_file",
655-
"web_search",
656-
"view_image",
657-
],
659+
&["unified_exec", "update_plan", "web_search", "view_image"],
658660
);
659661
}
660662

@@ -674,16 +676,28 @@ mod tests {
674676

675677
assert_eq_tool_names(
676678
&tools,
677-
&[
678-
"unified_exec",
679-
"update_plan",
680-
"read_file",
681-
"web_search",
682-
"view_image",
683-
],
679+
&["unified_exec", "update_plan", "web_search", "view_image"],
684680
);
685681
}
686682

683+
#[test]
684+
fn test_build_specs_includes_beta_read_file_tool() {
685+
let model_family = find_family_for_model("gpt-5-codex")
686+
.expect("gpt-5-codex should be a valid model family");
687+
let config = ToolsConfig::new(&ToolsConfigParams {
688+
model_family: &model_family,
689+
include_plan_tool: false,
690+
include_apply_patch_tool: false,
691+
include_web_search_request: false,
692+
use_streamable_shell_tool: false,
693+
include_view_image_tool: false,
694+
experimental_unified_exec_tool: true,
695+
});
696+
let (tools, _) = build_specs(&config, Some(HashMap::new())).build();
697+
698+
assert_eq_tool_names(&tools, &["unified_exec", "read_file"]);
699+
}
700+
687701
#[test]
688702
fn test_build_specs_mcp_tools() {
689703
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family");
@@ -739,15 +753,14 @@ mod tests {
739753
&tools,
740754
&[
741755
"unified_exec",
742-
"read_file",
743756
"web_search",
744757
"view_image",
745758
"test_server/do_something_cool",
746759
],
747760
);
748761

749762
assert_eq!(
750-
tools[4],
763+
tools[3],
751764
ToolSpec::Function(ResponsesApiTool {
752765
name: "test_server/do_something_cool".to_string(),
753766
parameters: JsonSchema::Object {
@@ -858,7 +871,6 @@ mod tests {
858871
&tools,
859872
&[
860873
"unified_exec",
861-
"read_file",
862874
"view_image",
863875
"test_server/cool",
864876
"test_server/do",
@@ -869,7 +881,8 @@ mod tests {
869881

870882
#[test]
871883
fn test_mcp_tool_property_missing_type_defaults_to_string() {
872-
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family");
884+
let model_family = find_family_for_model("gpt-5-codex")
885+
.expect("gpt-5-codex should be a valid model family");
873886
let config = ToolsConfig::new(&ToolsConfigParams {
874887
model_family: &model_family,
875888
include_plan_tool: false,
@@ -937,7 +950,8 @@ mod tests {
937950

938951
#[test]
939952
fn test_mcp_tool_integer_normalized_to_number() {
940-
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family");
953+
let model_family = find_family_for_model("gpt-5-codex")
954+
.expect("gpt-5-codex should be a valid model family");
941955
let config = ToolsConfig::new(&ToolsConfigParams {
942956
model_family: &model_family,
943957
include_plan_tool: false,
@@ -1000,7 +1014,8 @@ mod tests {
10001014

10011015
#[test]
10021016
fn test_mcp_tool_array_without_items_gets_default_string_items() {
1003-
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family");
1017+
let model_family = find_family_for_model("gpt-5-codex")
1018+
.expect("gpt-5-codex should be a valid model family");
10041019
let config = ToolsConfig::new(&ToolsConfigParams {
10051020
model_family: &model_family,
10061021
include_plan_tool: false,
@@ -1066,7 +1081,8 @@ mod tests {
10661081

10671082
#[test]
10681083
fn test_mcp_tool_anyof_defaults_to_string() {
1069-
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family");
1084+
let model_family = find_family_for_model("gpt-5-codex")
1085+
.expect("gpt-5-codex should be a valid model family");
10701086
let config = ToolsConfig::new(&ToolsConfigParams {
10711087
model_family: &model_family,
10721088
include_plan_tool: false,
@@ -1144,7 +1160,8 @@ mod tests {
11441160

11451161
#[test]
11461162
fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
1147-
let model_family = find_family_for_model("o3").expect("o3 should be a valid model family");
1163+
let model_family = find_family_for_model("gpt-5-codex")
1164+
.expect("gpt-5-codex should be a valid model family");
11481165
let config = ToolsConfig::new(&ToolsConfigParams {
11491166
model_family: &model_family,
11501167
include_plan_tool: false,

codex-rs/core/tests/suite/model_tools.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,21 @@ async fn model_selects_expected_tools() {
111111
let codex_tools = collect_tool_identifiers_for_model("codex-mini-latest").await;
112112
assert_eq!(
113113
codex_tools,
114-
vec!["local_shell".to_string(), "read_file".to_string()],
114+
vec!["local_shell".to_string()],
115115
"codex-mini-latest should expose the local shell tool",
116116
);
117117

118118
let o3_tools = collect_tool_identifiers_for_model("o3").await;
119119
assert_eq!(
120120
o3_tools,
121-
vec!["shell".to_string(), "read_file".to_string()],
121+
vec!["shell".to_string()],
122122
"o3 should expose the generic shell tool",
123123
);
124+
125+
let gpt5_codex_tools = collect_tool_identifiers_for_model("gpt-5-codex").await;
126+
assert_eq!(
127+
gpt5_codex_tools,
128+
vec!["shell".to_string(), "read_file".to_string()],
129+
"gpt-5-codex should expose the beta read_file tool",
130+
);
124131
}

0 commit comments

Comments
 (0)