Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions codex-rs/core/src/model_family.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 experimental_supported_tools: Vec<String>,
}

macro_rules! model_family {
Expand All @@ -57,6 +60,7 @@ macro_rules! model_family {
uses_local_shell_tool: false,
apply_patch_tool_type: None,
base_instructions: BASE_INSTRUCTIONS.to_string(),
experimental_supported_tools: Vec::new(),
};
// apply overrides
$(
Expand Down Expand Up @@ -105,6 +109,7 @@ pub fn find_family_for_model(slug: &str) -> Option<ModelFamily> {
supports_reasoning_summaries: true,
reasoning_summary_format: ReasoningSummaryFormat::Experimental,
base_instructions: GPT_5_CODEX_INSTRUCTIONS.to_string(),
experimental_supported_tools: vec!["read_file".to_string()],
)
} else if slug.starts_with("gpt-5") {
model_family!(
Expand All @@ -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(),
experimental_supported_tools: Vec::new(),
}
}
67 changes: 42 additions & 25 deletions codex-rs/core/src/tools/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 experimental_supported_tools: Vec<String>,
}

pub(crate) struct ToolsConfigParams<'a> {
Expand Down Expand Up @@ -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,
experimental_supported_tools: model_family.experimental_supported_tools.clone(),
}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
.experimental_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 {});
Expand Down Expand Up @@ -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"],
);
}

Expand All @@ -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");
Expand Down Expand Up @@ -739,15 +753,14 @@ mod tests {
&tools,
&[
"unified_exec",
"read_file",
"web_search",
"view_image",
"test_server/do_something_cool",
],
);

assert_eq!(
tools[4],
tools[3],
ToolSpec::Function(ResponsesApiTool {
name: "test_server/do_something_cool".to_string(),
parameters: JsonSchema::Object {
Expand Down Expand Up @@ -858,7 +871,6 @@ mod tests {
&tools,
&[
"unified_exec",
"read_file",
"view_image",
"test_server/cool",
"test_server/do",
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 9 additions & 2 deletions codex-rs/core/tests/suite/model_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
}
Loading