diff --git a/codex-rs/core/src/tools/code_mode/execute_handler.rs b/codex-rs/core/src/tools/code_mode/execute_handler.rs index e101e1a8006b..02b60a426552 100644 --- a/codex-rs/core/src/tools/code_mode/execute_handler.rs +++ b/codex-rs/core/src/tools/code_mode/execute_handler.rs @@ -94,8 +94,8 @@ impl ToolExecutor for CodeModeExecuteHandler { ToolName::plain(PUBLIC_TOOL_NAME) } - fn spec(&self) -> Option { - Some(self.spec.clone()) + fn spec(&self) -> ToolSpec { + self.spec.clone() } async fn handle( diff --git a/codex-rs/core/src/tools/code_mode/wait_handler.rs b/codex-rs/core/src/tools/code_mode/wait_handler.rs index 725535339fbf..fccf516b145b 100644 --- a/codex-rs/core/src/tools/code_mode/wait_handler.rs +++ b/codex-rs/core/src/tools/code_mode/wait_handler.rs @@ -47,8 +47,8 @@ impl ToolExecutor for CodeModeWaitHandler { ToolName::plain(WAIT_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_wait_tool()) + fn spec(&self) -> ToolSpec { + create_wait_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/agent_jobs/report_agent_job_result.rs b/codex-rs/core/src/tools/handlers/agent_jobs/report_agent_job_result.rs index 4641e793c83a..6f4463c6eaca 100644 --- a/codex-rs/core/src/tools/handlers/agent_jobs/report_agent_job_result.rs +++ b/codex-rs/core/src/tools/handlers/agent_jobs/report_agent_job_result.rs @@ -19,8 +19,8 @@ impl ToolExecutor for ReportAgentJobResultHandler { ToolName::plain("report_agent_job_result") } - fn spec(&self) -> Option { - Some(create_report_agent_job_result_tool()) + fn spec(&self) -> ToolSpec { + create_report_agent_job_result_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/agent_jobs/spawn_agents_on_csv.rs b/codex-rs/core/src/tools/handlers/agent_jobs/spawn_agents_on_csv.rs index 0e51390e7961..26438ca7ba49 100644 --- a/codex-rs/core/src/tools/handlers/agent_jobs/spawn_agents_on_csv.rs +++ b/codex-rs/core/src/tools/handlers/agent_jobs/spawn_agents_on_csv.rs @@ -20,8 +20,8 @@ impl ToolExecutor for SpawnAgentsOnCsvHandler { ToolName::plain("spawn_agents_on_csv") } - fn spec(&self) -> Option { - Some(create_spawn_agents_on_csv_tool()) + fn spec(&self) -> ToolSpec { + create_spawn_agents_on_csv_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index 500751137b2c..bfc3ab94b369 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -303,8 +303,8 @@ impl ToolExecutor for ApplyPatchHandler { ToolName::plain("apply_patch") } - fn spec(&self) -> Option { - Some(create_apply_patch_freeform_tool(self.multi_environment)) + fn spec(&self) -> ToolSpec { + create_apply_patch_freeform_tool(self.multi_environment) } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/dynamic.rs b/codex-rs/core/src/tools/handlers/dynamic.rs index 5d20128ff3e5..e5e0d1053432 100644 --- a/codex-rs/core/src/tools/handlers/dynamic.rs +++ b/codex-rs/core/src/tools/handlers/dynamic.rs @@ -31,7 +31,7 @@ use tracing::warn; pub struct DynamicToolHandler { tool_name: ToolName, - spec: Option, + spec: ToolSpec, exposure: ToolExposure, search_text: String, } @@ -50,7 +50,7 @@ impl DynamicToolHandler { }; Some(Self { tool_name, - spec: Some(spec), + spec, exposure: if tool.defer_loading { ToolExposure::Deferred } else { @@ -67,7 +67,7 @@ impl ToolExecutor for DynamicToolHandler { self.tool_name.clone() } - fn spec(&self) -> Option { + fn spec(&self) -> ToolSpec { self.spec.clone() } @@ -130,7 +130,7 @@ impl CoreToolRuntime for DynamicToolHandler { fn search_info(&self) -> Option { ToolSearchInfo::from_spec( self.search_text.clone(), - self.spec()?, + self.spec(), Some(ToolSearchSourceInfo { name: "Dynamic tools".to_string(), description: Some("Tools provided by the current Codex thread.".to_string()), diff --git a/codex-rs/core/src/tools/handlers/extension_tools.rs b/codex-rs/core/src/tools/handlers/extension_tools.rs index dd2ac42ea4aa..764e66f0cec1 100644 --- a/codex-rs/core/src/tools/handlers/extension_tools.rs +++ b/codex-rs/core/src/tools/handlers/extension_tools.rs @@ -37,7 +37,7 @@ impl ToolExecutor for ExtensionToolAdapter { self.0.tool_name() } - fn spec(&self) -> Option { + fn spec(&self) -> ToolSpec { self.0.spec() } @@ -130,25 +130,23 @@ mod tests { codex_tools::ToolName::plain("extension_echo") } - fn spec(&self) -> Option { - Some(codex_tools::ToolSpec::Function( - codex_tools::ResponsesApiTool { - name: "extension_echo".to_string(), - description: "Echoes arguments.".to_string(), - strict: true, - parameters: codex_tools::parse_tool_input_schema(&json!({ - "type": "object", - "properties": { - "message": { "type": "string" }, - }, - "required": ["message"], - "additionalProperties": false, - })) - .expect("extension schema should parse"), - output_schema: None, - defer_loading: None, - }, - )) + fn spec(&self) -> codex_tools::ToolSpec { + codex_tools::ToolSpec::Function(codex_tools::ResponsesApiTool { + name: "extension_echo".to_string(), + description: "Echoes arguments.".to_string(), + strict: true, + parameters: codex_tools::parse_tool_input_schema(&json!({ + "type": "object", + "properties": { + "message": { "type": "string" }, + }, + "required": ["message"], + "additionalProperties": false, + })) + .expect("extension schema should parse"), + output_schema: None, + defer_loading: None, + }) } async fn handle( @@ -171,6 +169,17 @@ mod tests { codex_tools::ToolName::plain("extension_echo") } + fn spec(&self) -> codex_tools::ToolSpec { + codex_tools::ToolSpec::Function(codex_tools::ResponsesApiTool { + name: "extension_echo".to_string(), + description: "Captures arguments.".to_string(), + strict: false, + parameters: codex_tools::JsonSchema::default(), + output_schema: None, + defer_loading: None, + }) + } + async fn handle( &self, call: codex_tools::ToolCall, diff --git a/codex-rs/core/src/tools/handlers/goal/create_goal.rs b/codex-rs/core/src/tools/handlers/goal/create_goal.rs index 9ae0107f34b3..1791a5e85f9b 100644 --- a/codex-rs/core/src/tools/handlers/goal/create_goal.rs +++ b/codex-rs/core/src/tools/handlers/goal/create_goal.rs @@ -24,8 +24,8 @@ impl ToolExecutor for CreateGoalHandler { ToolName::plain(CREATE_GOAL_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_create_goal_tool()) + fn spec(&self) -> ToolSpec { + create_create_goal_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/goal/get_goal.rs b/codex-rs/core/src/tools/handlers/goal/get_goal.rs index ff40227fabeb..ff460706759d 100644 --- a/codex-rs/core/src/tools/handlers/goal/get_goal.rs +++ b/codex-rs/core/src/tools/handlers/goal/get_goal.rs @@ -21,8 +21,8 @@ impl ToolExecutor for GetGoalHandler { ToolName::plain(GET_GOAL_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_get_goal_tool()) + fn spec(&self) -> ToolSpec { + create_get_goal_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/goal/update_goal.rs b/codex-rs/core/src/tools/handlers/goal/update_goal.rs index 16883e075527..373c849fc2a8 100644 --- a/codex-rs/core/src/tools/handlers/goal/update_goal.rs +++ b/codex-rs/core/src/tools/handlers/goal/update_goal.rs @@ -26,8 +26,8 @@ impl ToolExecutor for UpdateGoalHandler { ToolName::plain(UPDATE_GOAL_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_update_goal_tool()) + fn spec(&self) -> ToolSpec { + create_update_goal_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs b/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs index 143eb5a800f2..d18a1ca36e10 100644 --- a/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs +++ b/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs @@ -60,8 +60,8 @@ impl ToolExecutor for ListAvailablePluginsToInstallHandler { ToolName::plain(LIST_AVAILABLE_PLUGINS_TO_INSTALL_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_list_available_plugins_to_install_tool()) + fn spec(&self) -> ToolSpec { + create_list_available_plugins_to_install_tool() } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/handlers/mcp.rs b/codex-rs/core/src/tools/handlers/mcp.rs index 6f4f79505b1b..a83e0b01e541 100644 --- a/codex-rs/core/src/tools/handlers/mcp.rs +++ b/codex-rs/core/src/tools/handlers/mcp.rs @@ -29,19 +29,25 @@ use serde_json::Value; pub struct McpHandler { tool_info: ToolInfo, + spec: ToolSpec, exposure: ToolExposure, } impl McpHandler { - pub fn new(tool_info: ToolInfo) -> Self { + pub fn new(tool_info: ToolInfo) -> Result { Self::with_exposure(tool_info, ToolExposure::Direct) } - pub fn with_exposure(tool_info: ToolInfo, exposure: ToolExposure) -> Self { - Self { + pub fn with_exposure( + tool_info: ToolInfo, + exposure: ToolExposure, + ) -> Result { + let spec = create_tool_spec(&tool_info)?; + Ok(Self { tool_info, + spec, exposure, - } + }) } } @@ -51,32 +57,8 @@ impl ToolExecutor for McpHandler { self.tool_info.canonical_tool_name() } - fn spec(&self) -> Option { - let tool_name = self.tool_name(); - let namespace_name = tool_name.namespace.as_ref()?; - let tool = mcp_tool_to_responses_api_tool(&tool_name, &self.tool_info.tool).ok()?; - let description = self - .tool_info - .namespace_description - .as_deref() - .map(str::trim) - .filter(|description| !description.is_empty()) - .map(str::to_string) - .or_else(|| { - self.tool_info - .connector_name - .as_deref() - .map(str::trim) - .filter(|connector_name| !connector_name.is_empty()) - .map(|connector_name| format!("Tools for working with {connector_name}.")) - }) - .unwrap_or_default(); - - Some(ToolSpec::Namespace(ResponsesApiNamespace { - name: namespace_name.clone(), - description, - tools: vec![ResponsesApiNamespaceTool::Function(tool)], - })) + fn spec(&self) -> ToolSpec { + self.spec.clone() } fn exposure(&self) -> ToolExposure { @@ -152,7 +134,7 @@ impl CoreToolRuntime for McpHandler { ToolSearchInfo::from_spec( build_mcp_search_text(&self.tool_info), - self.spec()?, + self.spec(), source_info, ) } @@ -223,6 +205,32 @@ impl CoreToolRuntime for McpHandler { } } +fn create_tool_spec(tool_info: &ToolInfo) -> Result { + let tool_name = tool_info.canonical_tool_name(); + let tool = mcp_tool_to_responses_api_tool(&tool_name, &tool_info.tool)?; + let description = tool_info + .namespace_description + .as_deref() + .map(str::trim) + .filter(|description| !description.is_empty()) + .map(str::to_string) + .or_else(|| { + tool_info + .connector_name + .as_deref() + .map(str::trim) + .filter(|connector_name| !connector_name.is_empty()) + .map(|connector_name| format!("Tools for working with {connector_name}.")) + }) + .unwrap_or_default(); + + Ok(ToolSpec::Namespace(ResponsesApiNamespace { + name: tool_info.callable_namespace.clone(), + description, + tools: vec![ResponsesApiNamespaceTool::Function(tool)], + })) +} + fn mcp_hook_tool_input(raw_arguments: &str) -> Value { if raw_arguments.trim().is_empty() { return Value::Object(Map::new()); @@ -306,7 +314,8 @@ mod tests { .to_string(), }; let (session, turn) = make_session_and_context().await; - let handler = McpHandler::new(tool_info("memory", "mcp__memory__", "create_entities")); + let handler = McpHandler::new(tool_info("memory", "mcp__memory__", "create_entities")) + .expect("MCP tool spec should build"); assert_eq!( handler.pre_tool_use_payload(&ToolInvocation { session: session.into(), @@ -336,7 +345,8 @@ mod tests { arguments: json!({ "message": "hello" }).to_string(), }; let (session, turn) = make_session_and_context().await; - let handler = McpHandler::new(tool_info("foo", "mcp__foo__", "exec_command")); + let handler = McpHandler::new(tool_info("foo", "mcp__foo__", "exec_command")) + .expect("MCP tool spec should build"); assert_eq!( handler.pre_tool_use_payload(&ToolInvocation { @@ -362,7 +372,8 @@ mod tests { arguments: json!({ "message": "hello" }).to_string(), }; let (session, turn) = make_session_and_context().await; - let handler = McpHandler::new(tool_info("foo", "mcp__foo__", "exec_command")); + let handler = McpHandler::new(tool_info("foo", "mcp__foo__", "exec_command")) + .expect("MCP tool spec should build"); let invocation = handler .with_updated_hook_input( @@ -411,7 +422,8 @@ mod tests { truncation_policy: codex_utils_output_truncation::TruncationPolicy::Bytes(1024), }; let (session, turn) = make_session_and_context().await; - let handler = McpHandler::new(tool_info("filesystem", "mcp__filesystem__", "read_file")); + let handler = McpHandler::new(tool_info("filesystem", "mcp__filesystem__", "read_file")) + .expect("MCP tool spec should build"); let invocation = ToolInvocation { session: session.into(), turn: turn.into(), diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs index 866c7a9e123f..a0996fb37b1d 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs @@ -32,8 +32,8 @@ impl ToolExecutor for ListMcpResourceTemplatesHandler { ToolName::plain("list_mcp_resource_templates") } - fn spec(&self) -> Option { - Some(create_list_mcp_resource_templates_tool()) + fn spec(&self) -> ToolSpec { + create_list_mcp_resource_templates_tool() } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs index c87747eea42f..b9699b330e1f 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs @@ -32,8 +32,8 @@ impl ToolExecutor for ListMcpResourcesHandler { ToolName::plain("list_mcp_resources") } - fn spec(&self) -> Option { - Some(create_list_mcp_resources_tool()) + fn spec(&self) -> ToolSpec { + create_list_mcp_resources_tool() } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs index 3bb4d7f0ecce..d1b73322c028 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs @@ -32,8 +32,8 @@ impl ToolExecutor for ReadMcpResourceHandler { ToolName::plain("read_mcp_resource") } - fn spec(&self) -> Option { - Some(create_read_mcp_resource_tool()) + fn spec(&self) -> ToolSpec { + create_read_mcp_resource_tool() } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/handlers/mcp_search_tests.rs b/codex-rs/core/src/tools/handlers/mcp_search_tests.rs index b9467bad4e63..588254ae27a5 100644 --- a/codex-rs/core/src/tools/handlers/mcp_search_tests.rs +++ b/codex-rs/core/src/tools/handlers/mcp_search_tests.rs @@ -6,7 +6,7 @@ use serde_json::json; #[test] fn search_info_uses_mcp_tool_metadata_and_parameter_names() { - let handler = McpHandler::new(tool_info()); + let handler = McpHandler::new(tool_info()).expect("MCP tool spec should build"); let search_info = handler.search_info().expect("MCP search info"); assert_eq!( @@ -26,7 +26,7 @@ fn search_info_uses_mcp_tool_metadata_and_parameter_names() { fn search_info_uses_connector_name_for_output_namespace_description() { let mut tool_info = tool_info(); tool_info.namespace_description = None; - let handler = McpHandler::new(tool_info); + let handler = McpHandler::new(tool_info).expect("MCP tool spec should build"); let search_info = handler.search_info().expect("MCP search info"); let LoadableToolSpec::Namespace(namespace) = search_info.entry.output else { diff --git a/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs b/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs index bcbde4a6d3e4..de069854f106 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs @@ -11,8 +11,8 @@ impl ToolExecutor for Handler { ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "close_agent") } - fn spec(&self) -> Option { - Some(create_close_agent_tool_v1()) + fn spec(&self) -> ToolSpec { + create_close_agent_tool_v1() } async fn handle( @@ -110,7 +110,7 @@ impl CoreToolRuntime for Handler { fn search_info(&self) -> Option { multi_agent_tool_search_info( "close_agent close shutdown stop agent subagent thread status target", - self.spec()?, + self.spec(), ) } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs b/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs index ddfe6ee6a79f..a536c8d70437 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs @@ -13,8 +13,8 @@ impl ToolExecutor for Handler { ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "resume_agent") } - fn spec(&self) -> Option { - Some(create_resume_agent_tool()) + fn spec(&self) -> ToolSpec { + create_resume_agent_tool() } async fn handle( @@ -138,7 +138,7 @@ impl CoreToolRuntime for Handler { fn search_info(&self) -> Option { multi_agent_tool_search_info( "resume_agent resume reopen closed agent subagent thread id target", - self.spec()?, + self.spec(), ) } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs b/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs index 3322e1b3d472..8499bac52c09 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs @@ -12,8 +12,8 @@ impl ToolExecutor for Handler { ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "send_input") } - fn spec(&self) -> Option { - Some(create_send_input_tool_v1()) + fn spec(&self) -> ToolSpec { + create_send_input_tool_v1() } async fn handle( @@ -94,7 +94,7 @@ impl CoreToolRuntime for Handler { fn search_info(&self) -> Option { multi_agent_tool_search_info( "send_input send message existing agent subagent follow up interrupt redirect queue target", - self.spec()?, + self.spec(), ) } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs index dc0518de6b83..a6461b358132 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs @@ -28,8 +28,8 @@ impl ToolExecutor for Handler { ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "spawn_agent") } - fn spec(&self) -> Option { - Some(create_spawn_agent_tool_v1(self.options.clone())) + fn spec(&self) -> ToolSpec { + create_spawn_agent_tool_v1(self.options.clone()) } async fn handle( @@ -206,7 +206,7 @@ impl CoreToolRuntime for Handler { fn search_info(&self) -> Option { multi_agent_tool_search_info( "spawn_agent spawn agent subagent sub-agent delegate delegation parallel work worker explorer no-apps fork model reasoning", - self.spec()?, + self.spec(), ) } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/wait.rs b/codex-rs/core/src/tools/handlers/multi_agents/wait.rs index 68f3dd7ee4fe..2e4fb6100a0e 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/wait.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/wait.rs @@ -33,8 +33,8 @@ impl ToolExecutor for Handler { ToolName::namespaced(MULTI_AGENT_V1_NAMESPACE, "wait_agent") } - fn spec(&self) -> Option { - Some(create_wait_agent_tool_v1(self.options)) + fn spec(&self) -> ToolSpec { + create_wait_agent_tool_v1(self.options) } async fn handle( @@ -206,7 +206,7 @@ impl CoreToolRuntime for Handler { fn search_info(&self) -> Option { multi_agent_tool_search_info( "wait_agent wait agent subagent status final result complete timeout targets", - self.spec()?, + self.spec(), ) } diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/close_agent.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/close_agent.rs index 180cf1a9d89b..088e5310623a 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/close_agent.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/close_agent.rs @@ -11,8 +11,8 @@ impl ToolExecutor for Handler { ToolName::plain("close_agent") } - fn spec(&self) -> Option { - Some(create_close_agent_tool_v2()) + fn spec(&self) -> ToolSpec { + create_close_agent_tool_v2() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs index 4b3edcd4873b..2fe09f5f217d 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs @@ -13,8 +13,8 @@ impl ToolExecutor for Handler { ToolName::plain("followup_task") } - fn spec(&self) -> Option { - Some(create_followup_task_tool()) + fn spec(&self) -> ToolSpec { + create_followup_task_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs index b8b9072da9ac..dfd5483a9cb8 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs @@ -11,8 +11,8 @@ impl ToolExecutor for Handler { ToolName::plain("list_agents") } - fn spec(&self) -> Option { - Some(create_list_agents_tool()) + fn spec(&self) -> ToolSpec { + create_list_agents_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs index 202e28e0c972..a8e3605840ed 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs @@ -13,8 +13,8 @@ impl ToolExecutor for Handler { ToolName::plain("send_message") } - fn spec(&self) -> Option { - Some(create_send_message_tool()) + fn spec(&self) -> ToolSpec { + create_send_message_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index 74a87d8a546c..9a4e39195d75 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -30,8 +30,8 @@ impl ToolExecutor for Handler { ToolName::plain("spawn_agent") } - fn spec(&self) -> Option { - Some(create_spawn_agent_tool_v2(self.options.clone())) + fn spec(&self) -> ToolSpec { + create_spawn_agent_tool_v2(self.options.clone()) } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs index 285abd933052..0aba86782eaf 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs @@ -25,8 +25,8 @@ impl ToolExecutor for Handler { ToolName::plain("wait_agent") } - fn spec(&self) -> Option { - Some(create_wait_agent_tool_v2(self.options)) + fn spec(&self) -> ToolSpec { + create_wait_agent_tool_v2(self.options) } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/plan.rs b/codex-rs/core/src/tools/handlers/plan.rs index f5c94826f73b..bd1060b3d794 100644 --- a/codex-rs/core/src/tools/handlers/plan.rs +++ b/codex-rs/core/src/tools/handlers/plan.rs @@ -51,8 +51,8 @@ impl ToolExecutor for PlanHandler { ToolName::plain("update_plan") } - fn spec(&self) -> Option { - Some(create_update_plan_tool()) + fn spec(&self) -> ToolSpec { + create_update_plan_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/request_permissions.rs b/codex-rs/core/src/tools/handlers/request_permissions.rs index 007c8d220898..70a31fe0e063 100644 --- a/codex-rs/core/src/tools/handlers/request_permissions.rs +++ b/codex-rs/core/src/tools/handlers/request_permissions.rs @@ -22,10 +22,8 @@ impl ToolExecutor for RequestPermissionsHandler { ToolName::plain("request_permissions") } - fn spec(&self) -> Option { - Some(create_request_permissions_tool( - request_permissions_tool_description(), - )) + fn spec(&self) -> ToolSpec { + create_request_permissions_tool(request_permissions_tool_description()) } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/request_plugin_install.rs b/codex-rs/core/src/tools/handlers/request_plugin_install.rs index ffb5bb568b5d..b73a81a940c6 100644 --- a/codex-rs/core/src/tools/handlers/request_plugin_install.rs +++ b/codex-rs/core/src/tools/handlers/request_plugin_install.rs @@ -45,8 +45,8 @@ impl ToolExecutor for RequestPluginInstallHandler { ToolName::plain(REQUEST_PLUGIN_INSTALL_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_request_plugin_install_tool()) + fn spec(&self) -> ToolSpec { + create_request_plugin_install_tool() } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/handlers/request_user_input.rs b/codex-rs/core/src/tools/handlers/request_user_input.rs index 8384e02637ae..0aa4b4f6f848 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -26,10 +26,8 @@ impl ToolExecutor for RequestUserInputHandler { ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_request_user_input_tool( - request_user_input_tool_description(&self.available_modes), - )) + fn spec(&self) -> ToolSpec { + create_request_user_input_tool(request_user_input_tool_description(&self.available_modes)) } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/shell/shell_command.rs b/codex-rs/core/src/tools/handlers/shell/shell_command.rs index 4dffdd688c66..ab79510adda9 100644 --- a/codex-rs/core/src/tools/handlers/shell/shell_command.rs +++ b/codex-rs/core/src/tools/handlers/shell/shell_command.rs @@ -22,6 +22,7 @@ use crate::tools::registry::CoreToolRuntime; use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PreToolUsePayload; use crate::tools::registry::ToolExecutor; +use crate::tools::registry::ToolExposure; use crate::tools::runtimes::shell::ShellRuntimeBackend; use codex_tools::ToolSpec; @@ -39,7 +40,8 @@ enum ShellCommandBackend { pub struct ShellCommandHandler { backend: ShellCommandBackend, - options: Option, + options: ShellCommandHandlerOptions, + exposure: ToolExposure, } #[derive(Clone, Copy)] @@ -51,9 +53,22 @@ pub(crate) struct ShellCommandHandlerOptions { impl ShellCommandHandler { pub(crate) fn new(options: ShellCommandHandlerOptions) -> Self { + Self::with_exposure(options, ToolExposure::Direct) + } + + pub(crate) fn hidden(options: ShellCommandHandlerOptions) -> Self { + Self::with_exposure(options, ToolExposure::Hidden) + } + + fn with_exposure(options: ShellCommandHandlerOptions, exposure: ToolExposure) -> Self { + let backend = match options.backend_config { + ShellCommandBackendConfig::Classic => ShellCommandBackend::Classic, + ShellCommandBackendConfig::ZshFork => ShellCommandBackend::ZshFork, + }; Self { - options: Some(options), - ..Self::from(options.backend_config) + backend, + options, + exposure, } } @@ -114,15 +129,12 @@ impl ShellCommandHandler { } impl From for ShellCommandHandler { - fn from(config: ShellCommandBackendConfig) -> Self { - let backend = match config { - ShellCommandBackendConfig::Classic => ShellCommandBackend::Classic, - ShellCommandBackendConfig::ZshFork => ShellCommandBackend::ZshFork, - }; - Self { - backend, - options: None, - } + fn from(backend_config: ShellCommandBackendConfig) -> Self { + Self::hidden(ShellCommandHandlerOptions { + backend_config, + allow_login_shell: false, + exec_permission_approvals_enabled: false, + }) } } @@ -132,17 +144,19 @@ impl ToolExecutor for ShellCommandHandler { ToolName::plain("shell_command") } - fn spec(&self) -> Option { - self.options.map(|options| { - create_shell_command_tool(CommandToolOptions { - allow_login_shell: options.allow_login_shell, - exec_permission_approvals_enabled: options.exec_permission_approvals_enabled, - }) + fn spec(&self) -> ToolSpec { + create_shell_command_tool(CommandToolOptions { + allow_login_shell: self.options.allow_login_shell, + exec_permission_approvals_enabled: self.options.exec_permission_approvals_enabled, }) } + fn exposure(&self) -> ToolExposure { + self.exposure + } + fn supports_parallel_tool_calls(&self) -> bool { - self.options.is_some() + self.exposure != ToolExposure::Hidden } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/test_sync.rs b/codex-rs/core/src/tools/handlers/test_sync.rs index d3c95dea7eab..0e5fe894ad19 100644 --- a/codex-rs/core/src/tools/handlers/test_sync.rs +++ b/codex-rs/core/src/tools/handlers/test_sync.rs @@ -63,8 +63,8 @@ impl ToolExecutor for TestSyncHandler { ToolName::plain("test_sync_tool") } - fn spec(&self) -> Option { - Some(create_test_sync_tool()) + fn spec(&self) -> ToolSpec { + create_test_sync_tool() } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/handlers/tool_search.rs b/codex-rs/core/src/tools/handlers/tool_search.rs index ffb25cb88088..27a0a1c58d2c 100644 --- a/codex-rs/core/src/tools/handlers/tool_search.rs +++ b/codex-rs/core/src/tools/handlers/tool_search.rs @@ -59,11 +59,8 @@ impl ToolExecutor for ToolSearchHandler { ToolName::plain(TOOL_SEARCH_TOOL_NAME) } - fn spec(&self) -> Option { - Some(create_tool_search_tool( - &self.search_source_infos, - TOOL_SEARCH_DEFAULT_LIMIT, - )) + fn spec(&self) -> ToolSpec { + create_tool_search_tool(&self.search_source_infos, TOOL_SEARCH_DEFAULT_LIMIT) } fn supports_parallel_tool_calls(&self) -> bool { @@ -174,6 +171,7 @@ mod tests { .iter() .map(|tool| { McpHandler::new(tool.clone()) + .expect("MCP tool should convert") .search_info() .expect("MCP handler should return search info") }) diff --git a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs index 62b1ed3d9ff9..83236881f38b 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs @@ -74,14 +74,14 @@ impl ToolExecutor for ExecCommandHandler { ToolName::plain("exec_command") } - fn spec(&self) -> Option { - Some(create_exec_command_tool_with_environment_id( + fn spec(&self) -> ToolSpec { + create_exec_command_tool_with_environment_id( CommandToolOptions { allow_login_shell: self.options.allow_login_shell, exec_permission_approvals_enabled: self.options.exec_permission_approvals_enabled, }, self.options.include_environment_id, - )) + ) } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs b/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs index 77565eaac2de..dfa4240ebce6 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs @@ -36,8 +36,8 @@ impl ToolExecutor for WriteStdinHandler { ToolName::plain("write_stdin") } - fn spec(&self) -> Option { - Some(create_write_stdin_tool()) + fn spec(&self) -> ToolSpec { + create_write_stdin_tool() } async fn handle( diff --git a/codex-rs/core/src/tools/handlers/view_image.rs b/codex-rs/core/src/tools/handlers/view_image.rs index c3e6792c0694..5a67db1a660e 100644 --- a/codex-rs/core/src/tools/handlers/view_image.rs +++ b/codex-rs/core/src/tools/handlers/view_image.rs @@ -70,8 +70,8 @@ impl ToolExecutor for ViewImageHandler { ToolName::plain("view_image") } - fn spec(&self) -> Option { - Some(create_view_image_tool(self.options)) + fn spec(&self) -> ToolSpec { + create_view_image_tool(self.options) } fn supports_parallel_tool_calls(&self) -> bool { diff --git a/codex-rs/core/src/tools/parallel.rs b/codex-rs/core/src/tools/parallel.rs index 15954869e2b0..bac636132999 100644 --- a/codex-rs/core/src/tools/parallel.rs +++ b/codex-rs/core/src/tools/parallel.rs @@ -250,6 +250,17 @@ mod tests { self.tool_name.clone() } + fn spec(&self) -> codex_tools::ToolSpec { + codex_tools::ToolSpec::Function(codex_tools::ResponsesApiTool { + name: self.tool_name.name.clone(), + description: "Immediate test tool.".to_string(), + strict: false, + defer_loading: None, + parameters: codex_tools::JsonSchema::default(), + output_schema: None, + }) + } + async fn handle( &self, _invocation: ToolInvocation, diff --git a/codex-rs/core/src/tools/registry.rs b/codex-rs/core/src/tools/registry.rs index 96837497f837..24782a833a37 100644 --- a/codex-rs/core/src/tools/registry.rs +++ b/codex-rs/core/src/tools/registry.rs @@ -184,7 +184,7 @@ impl ToolExecutor for ExposureOverride { self.handler.tool_name() } - fn spec(&self) -> Option { + fn spec(&self) -> ToolSpec { self.handler.spec() } diff --git a/codex-rs/core/src/tools/registry_tests.rs b/codex-rs/core/src/tools/registry_tests.rs index e3ecfc8f9890..8517730d32c9 100644 --- a/codex-rs/core/src/tools/registry_tests.rs +++ b/codex-rs/core/src/tools/registry_tests.rs @@ -11,6 +11,10 @@ impl ToolExecutor for TestHandler { self.tool_name.clone() } + fn spec(&self) -> codex_tools::ToolSpec { + test_spec(&self.tool_name) + } + async fn handle( &self, _invocation: ToolInvocation, @@ -40,6 +44,10 @@ impl ToolExecutor for LifecycleTestHandler { self.tool_name.clone() } + fn spec(&self) -> codex_tools::ToolSpec { + test_spec(&self.tool_name) + } + async fn handle( &self, _invocation: ToolInvocation, @@ -60,6 +68,17 @@ impl ToolExecutor for LifecycleTestHandler { impl CoreToolRuntime for LifecycleTestHandler {} +fn test_spec(tool_name: &codex_tools::ToolName) -> codex_tools::ToolSpec { + codex_tools::ToolSpec::Function(codex_tools::ResponsesApiTool { + name: tool_name.name.clone(), + description: "Test tool.".to_string(), + strict: false, + defer_loading: None, + parameters: codex_tools::JsonSchema::default(), + output_schema: None, + }) +} + #[derive(Debug, PartialEq, Eq)] enum RecordedToolLifecycle { Start { diff --git a/codex-rs/core/src/tools/router_tests.rs b/codex-rs/core/src/tools/router_tests.rs index 92073d56b142..375faba248e9 100644 --- a/codex-rs/core/src/tools/router_tests.rs +++ b/codex-rs/core/src/tools/router_tests.rs @@ -49,8 +49,8 @@ impl ToolExecutor for ExtensionEchoExecutor { ToolName::namespaced("extension/", "echo") } - fn spec(&self) -> Option { - Some(ToolSpec::Namespace(ResponsesApiNamespace { + fn spec(&self) -> ToolSpec { + ToolSpec::Namespace(ResponsesApiNamespace { name: "extension/".to_string(), description: default_namespace_description("extension/"), tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool { @@ -69,7 +69,7 @@ impl ToolExecutor for ExtensionEchoExecutor { output_schema: None, defer_loading: None, })], - })) + }) } async fn handle( diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index a47ed9da34fe..fcbefc1eed98 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -173,10 +173,9 @@ fn build_model_visible_specs_and_registry( continue; } let exposure = runtime.exposure(); - if exposure.is_direct() - && !is_hidden_by_code_mode_only(turn_context, &tool_name, exposure) - && let Some(spec) = runtime.spec() + if exposure.is_direct() && !is_hidden_by_code_mode_only(turn_context, &tool_name, exposure) { + let spec = runtime.spec(); specs.push(spec_for_model_request(turn_context, exposure, spec)); } } @@ -375,9 +374,10 @@ fn build_code_mode_executors( continue; } - let Some(spec) = executor.spec() else { + if exposure == ToolExposure::Hidden { continue; - }; + } + let spec = executor.spec(); if exposure != ToolExposure::Deferred { exec_prompt_tool_specs.push(spec.clone()); @@ -666,16 +666,25 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu fn add_mcp_runtime_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) { if let Some(mcp_tools) = context.mcp_tools { for tool in mcp_tools { - planned_tools.add_runtime(McpHandler::new(tool.clone())); + match McpHandler::new(tool.clone()) { + Ok(handler) => planned_tools.add_runtime(handler), + Err(err) => warn!( + "Skipping MCP tool `{}`: failed to build tool spec: {err}", + tool.canonical_tool_name() + ), + } } } if let Some(deferred_mcp_tools) = context.deferred_mcp_tools { for tool in deferred_mcp_tools { - planned_tools.add_runtime(McpHandler::with_exposure( - tool.clone(), - ToolExposure::Deferred, - )); + match McpHandler::with_exposure(tool.clone(), ToolExposure::Deferred) { + Ok(handler) => planned_tools.add_runtime(handler), + Err(err) => warn!( + "Skipping deferred MCP tool `{}`: failed to build tool spec: {err}", + tool.canonical_tool_name() + ), + } } } } @@ -815,14 +824,14 @@ impl ToolExecutor for MultiAgentV2NamespaceOverride { ToolName::namespaced(self.namespace.clone(), self.handler.tool_name().name) } - fn spec(&self) -> Option { - match self.handler.spec()? { - ToolSpec::Function(tool) => Some(ToolSpec::Namespace(ResponsesApiNamespace { + fn spec(&self) -> ToolSpec { + match self.handler.spec() { + ToolSpec::Function(tool) => ToolSpec::Namespace(ResponsesApiNamespace { name: self.namespace.clone(), description: MULTI_AGENT_V2_NAMESPACE_DESCRIPTION.to_string(), tools: vec![ResponsesApiNamespaceTool::Function(tool)], - })), - spec => Some(spec), + }), + spec => spec, } } diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index a936ac0ea16a..32913bf2e7ee 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -288,6 +288,14 @@ fn mcp_tool(server: &str, namespace: &str, name: &str) -> ToolInfo { } } +fn invalid_mcp_tool(server: &str, namespace: &str, name: &str) -> ToolInfo { + let mut tool = mcp_tool(server, namespace, name); + tool.tool.input_schema = Arc::new(rmcp::model::object(json!({ + "type": "null", + }))); + tool +} + fn dynamic_tool(namespace: Option<&str>, name: &str, defer_loading: bool) -> DynamicToolSpec { DynamicToolSpec { namespace: namespace.map(str::to_string), @@ -342,7 +350,7 @@ async fn shell_family_registers_visible_unified_exec_and_hidden_legacy_shell() { plan.assert_visible_contains(&["exec_command", "write_stdin"]); plan.assert_visible_lacks(&["shell_command"]); plan.assert_registered_contains(&["exec_command", "write_stdin", "shell_command"]); - assert_eq!(plan.exposure("shell_command"), ToolExposure::Direct); + assert_eq!(plan.exposure("shell_command"), ToolExposure::Hidden); } #[tokio::test] @@ -507,6 +515,25 @@ async fn mcp_and_tool_search_follow_direct_and_deferred_tool_exposure() { enabled.assert_registered_contains(&["tool_search", "mcp__searchable__lookup"]); } +#[tokio::test] +async fn invalid_mcp_tools_are_not_registered() { + let plan = probe_with( + |_| {}, + ToolPlanInputs { + mcp_tools: Some(vec![invalid_mcp_tool( + "invalid", + "mcp__invalid__", + "lookup", + )]), + ..ToolPlanInputs::default() + }, + ) + .await; + + plan.assert_visible_lacks(&["mcp__invalid__"]); + plan.assert_registered_lacks(&["mcp__invalid__lookup"]); +} + #[tokio::test] async fn request_plugin_install_requires_all_discovery_features_and_discoverable_tools() { let discoverable_tools = Some(vec![discoverable_plugin("github", "GitHub")]); diff --git a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs index bccaf60c829d..774a393aa012 100644 --- a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs +++ b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs @@ -36,6 +36,17 @@ impl ToolExecutor for TestHandler { self.tool_name.clone() } + fn spec(&self) -> codex_tools::ToolSpec { + codex_tools::ToolSpec::Function(codex_tools::ResponsesApiTool { + name: self.tool_name.name.clone(), + description: "Test tool.".to_string(), + strict: false, + defer_loading: None, + parameters: codex_tools::JsonSchema::default(), + output_schema: None, + }) + } + async fn handle( &self, _invocation: ToolInvocation, diff --git a/codex-rs/core/src/tools/tool_family/shell.rs b/codex-rs/core/src/tools/tool_family/shell.rs index 10777562b899..09b6d13a298b 100644 --- a/codex-rs/core/src/tools/tool_family/shell.rs +++ b/codex-rs/core/src/tools/tool_family/shell.rs @@ -43,7 +43,11 @@ pub(crate) fn build_shell_tools(options: ShellToolsOptions) -> Vec {} diff --git a/codex-rs/ext/goal/src/tool.rs b/codex-rs/ext/goal/src/tool.rs index fa3b7d508988..39142cfe28d3 100644 --- a/codex-rs/ext/goal/src/tool.rs +++ b/codex-rs/ext/goal/src/tool.rs @@ -125,12 +125,12 @@ impl ToolExecutor for GoalToolExecutor { }) } - fn spec(&self) -> Option { - Some(match self.kind { + fn spec(&self) -> ToolSpec { + match self.kind { GoalToolKind::Get => create_get_goal_tool(), GoalToolKind::Create => create_create_goal_tool(), GoalToolKind::Update => create_update_goal_tool(), - }) + } } async fn handle(&self, invocation: ToolCall) -> Result, FunctionCallError> { diff --git a/codex-rs/ext/memories/src/tools/list.rs b/codex-rs/ext/memories/src/tools/list.rs index baed3c30a234..5133790763e0 100644 --- a/codex-rs/ext/memories/src/tools/list.rs +++ b/codex-rs/ext/memories/src/tools/list.rs @@ -43,11 +43,11 @@ where memory_tool_name(LIST_TOOL_NAME) } - fn spec(&self) -> Option { - Some(memory_function_tool::( + fn spec(&self) -> ToolSpec { + memory_function_tool::( LIST_TOOL_NAME, "List immediate files and directories under a path in the Codex memories store.", - )) + ) } async fn handle( diff --git a/codex-rs/ext/memories/src/tools/read.rs b/codex-rs/ext/memories/src/tools/read.rs index 6e7f771d756b..f32e1ba82dfa 100644 --- a/codex-rs/ext/memories/src/tools/read.rs +++ b/codex-rs/ext/memories/src/tools/read.rs @@ -42,11 +42,11 @@ where memory_tool_name(READ_TOOL_NAME) } - fn spec(&self) -> Option { - Some(memory_function_tool::( + fn spec(&self) -> ToolSpec { + memory_function_tool::( READ_TOOL_NAME, "Read a Codex memory file by relative path, optionally starting at a 1-indexed line offset and limiting the number of lines returned.", - )) + ) } async fn handle( diff --git a/codex-rs/ext/memories/src/tools/search.rs b/codex-rs/ext/memories/src/tools/search.rs index 422ce06b7e19..b94f937d9552 100644 --- a/codex-rs/ext/memories/src/tools/search.rs +++ b/codex-rs/ext/memories/src/tools/search.rs @@ -51,11 +51,11 @@ where memory_tool_name(SEARCH_TOOL_NAME) } - fn spec(&self) -> Option { - Some(memory_function_tool::( + fn spec(&self) -> ToolSpec { + memory_function_tool::( SEARCH_TOOL_NAME, "Search Codex memory files for substring matches, optionally normalizing separators or requiring all query substrings on the same line or within a line window.", - )) + ) } async fn handle( diff --git a/codex-rs/tools/src/tool_executor.rs b/codex-rs/tools/src/tool_executor.rs index 2557dc349006..1e5a94ee48a6 100644 --- a/codex-rs/tools/src/tool_executor.rs +++ b/codex-rs/tools/src/tool_executor.rs @@ -21,6 +21,9 @@ pub enum ToolExposure { /// In code-mode-only sessions, this keeps the tool callable as a normal /// model tool while excluding it from the nested code-mode tool surface. DirectModelOnly, + + /// Keep this tool registered for dispatch without exposing it to the model. + Hidden, } impl ToolExposure { @@ -39,9 +42,7 @@ pub trait ToolExecutor: Send + Sync { /// The concrete tool name handled by this runtime instance. fn tool_name(&self) -> ToolName; - fn spec(&self) -> Option { - None - } + fn spec(&self) -> ToolSpec; fn exposure(&self) -> ToolExposure { ToolExposure::Direct