diff --git a/codex-rs/core/src/tools/handlers/tool_search.rs b/codex-rs/core/src/tools/handlers/tool_search.rs index 272da2f226..6d54e269a1 100644 --- a/codex-rs/core/src/tools/handlers/tool_search.rs +++ b/codex-rs/core/src/tools/handlers/tool_search.rs @@ -18,6 +18,7 @@ use codex_tools::ToolSearchEntry; use codex_tools::ToolSearchInfo; use codex_tools::ToolSpec; use codex_tools::coalesce_loadable_tool_specs; +use codex_tools::tool_search_tool_name; use std::sync::Arc; use std::sync::Mutex; use tracing::instrument; @@ -96,7 +97,7 @@ impl ToolSearchHandler { impl ToolExecutor for ToolSearchHandler { fn tool_name(&self) -> ToolName { - ToolName::plain(TOOL_SEARCH_TOOL_NAME) + tool_search_tool_name() } fn spec(&self) -> ToolSpec { diff --git a/codex-rs/core/src/tools/router.rs b/codex-rs/core/src/tools/router.rs index b488448398..e0a5c0fe17 100644 --- a/codex-rs/core/src/tools/router.rs +++ b/codex-rs/core/src/tools/router.rs @@ -18,6 +18,7 @@ use codex_tools::ToolCall as ExtensionToolCall; use codex_tools::ToolExecutor; use codex_tools::ToolName; use codex_tools::ToolSpec; +use codex_tools::tool_search_tool_name; use std::sync::Arc; use std::sync::atomic::AtomicBool; use tokio_util::sync::CancellationToken; @@ -139,7 +140,7 @@ impl ToolRouter { )) })?; Ok(Some(ToolCall { - tool_name: ToolName::plain("tool_search"), + tool_name: tool_search_tool_name(), call_id, payload: ToolPayload::ToolSearch { arguments }, })) diff --git a/codex-rs/core/src/tools/router_tests.rs b/codex-rs/core/src/tools/router_tests.rs index a38ab8af26..b0cbd0be5b 100644 --- a/codex-rs/core/src/tools/router_tests.rs +++ b/codex-rs/core/src/tools/router_tests.rs @@ -176,6 +176,25 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<() Ok(()) } +#[test] +fn build_tool_call_uses_canonical_tool_search_name() -> anyhow::Result<()> { + let call = ToolRouter::build_tool_call(ResponseItem::ToolSearchCall { + id: None, + call_id: Some("call-tool-search".to_string()), + status: None, + execution: "client".to_string(), + arguments: json!({ "query": "calendar" }), + })? + .expect("client tool_search_call should produce a tool call"); + + assert_eq!( + call.tool_name, + ToolName::namespaced("tool_search", "tool_search_tool") + ); + + Ok(()) +} + #[tokio::test] async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()> { let (_, turn) = make_session_and_context().await; diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 3e2268d92d..b6470888b4 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -87,6 +87,7 @@ use codex_tools::default_namespace_description; use codex_tools::request_user_input_available_modes; use codex_tools::shell_command_backend_for_features; use codex_tools::shell_type_for_model_and_features; +use codex_tools::tool_search_tool_name; use std::collections::BTreeMap; use std::collections::HashSet; use std::sync::Arc; @@ -999,6 +1000,7 @@ fn append_extension_tool_executors( .any(|executor| executor.exposure() == ToolExposure::Deferred) { reserved_tool_names.insert(ToolName::plain(TOOL_SEARCH_TOOL_NAME)); + reserved_tool_names.insert(tool_search_tool_name()); } let standalone_web_search_enabled = standalone_web_search_enabled(turn_context); diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index 5d70fb8f17..966317d67f 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -27,6 +27,7 @@ use codex_tools::ToolExposure; use codex_tools::ToolName; use codex_tools::ToolOutput; use codex_tools::ToolSpec; +use codex_tools::tool_search_tool_name; use pretty_assertions::assert_eq; use serde_json::json; @@ -742,10 +743,12 @@ async fn mcp_and_tool_search_follow_direct_and_deferred_tool_exposure() { ) .await; enabled.assert_visible_contains(&["tool_search"]); + let tool_search_name = tool_search_tool_name().to_string(); enabled.assert_registered_contains(&[ - "tool_search", + &tool_search_name, &ToolName::namespaced("mcp__searchable", "lookup").to_string(), ]); + enabled.assert_registered_lacks(&["tool_search"]); } #[tokio::test] @@ -1085,7 +1088,7 @@ async fn excluded_deferred_namespaces_do_not_enable_nested_tool_guidance() { ); plan.assert_registered_contains(&[ &ToolName::namespaced("excluded", "lookup").to_string(), - "tool_search", + &tool_search_tool_name().to_string(), ]); } diff --git a/codex-rs/tools/src/lib.rs b/codex-rs/tools/src/lib.rs index 8bcad84981..20f95e4449 100644 --- a/codex-rs/tools/src/lib.rs +++ b/codex-rs/tools/src/lib.rs @@ -93,6 +93,7 @@ pub use tool_discovery::TOOL_SEARCH_TOOL_NAME; pub use tool_discovery::ToolSearchSourceInfo; pub use tool_discovery::collect_request_plugin_install_entries; pub use tool_discovery::filter_request_plugin_install_discoverable_tools_for_client; +pub use tool_discovery::tool_search_tool_name; pub use tool_executor::ToolExecutor; pub use tool_executor::ToolExecutorFuture; pub use tool_executor::ToolExposure; diff --git a/codex-rs/tools/src/tool_discovery.rs b/codex-rs/tools/src/tool_discovery.rs index 2584d7f17a..80e33b4a98 100644 --- a/codex-rs/tools/src/tool_discovery.rs +++ b/codex-rs/tools/src/tool_discovery.rs @@ -1,13 +1,21 @@ use codex_app_server_protocol::AppInfo; +use codex_protocol::ToolName; use serde::Deserialize; use serde::Serialize; const TUI_CLIENT_NAME: &str = "codex-tui"; pub const TOOL_SEARCH_TOOL_NAME: &str = "tool_search"; +const TOOL_SEARCH_NAMESPACE: &str = "tool_search"; +const TOOL_SEARCH_FUNCTION_NAME: &str = "tool_search_tool"; pub const TOOL_SEARCH_DEFAULT_LIMIT: usize = 8; pub const LIST_AVAILABLE_PLUGINS_TO_INSTALL_TOOL_NAME: &str = "list_available_plugins_to_install"; pub const REQUEST_PLUGIN_INSTALL_TOOL_NAME: &str = "request_plugin_install"; +/// Returns the model-facing identity used for client tool-search calls. +pub fn tool_search_tool_name() -> ToolName { + ToolName::namespaced(TOOL_SEARCH_NAMESPACE, TOOL_SEARCH_FUNCTION_NAME) +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct ToolSearchSourceInfo { pub name: String,