Skip to content
Closed
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
2 changes: 1 addition & 1 deletion codex-rs/core/src/apps/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG;

pub(crate) fn render_apps_section() -> String {
let body = format!(
"## Apps (Connectors)\nApps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{{connector_id}})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps, the available apps will be listed by the `tool_search` tool.\nAn app is equivalent to a set of MCP tools within the `{CODEX_APPS_MCP_SERVER_NAME}` MCP.\nAn installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool.\nDo not additionally call list_mcp_resources or list_mcp_resource_templates for apps."
"## Apps (Connectors)\nApps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{{connector_id}})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps, the available apps will be listed by the `tool_search_tool` tool.\nAn app is equivalent to a set of MCP tools within the `{CODEX_APPS_MCP_SERVER_NAME}` MCP.\nAn installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search_tool` tool.\nDo not additionally call list_mcp_resources or list_mcp_resource_templates for apps."
);
format!("{APPS_INSTRUCTIONS_OPEN_TAG}\n{body}\n{APPS_INSTRUCTIONS_CLOSE_TAG}")
}
2 changes: 1 addition & 1 deletion codex-rs/core/src/client_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ pub(crate) mod tools {
pub(crate) fn name(&self) -> &str {
match self {
ToolSpec::Function(tool) => tool.name.as_str(),
ToolSpec::ToolSearch { .. } => "tool_search",
ToolSpec::ToolSearch { .. } => "tool_search_tool",
ToolSpec::LocalShell {} => "local_shell",
ToolSpec::ImageGeneration { .. } => "image_generation",
ToolSpec::WebSearch { .. } => "web_search",
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6394,8 +6394,9 @@ pub(crate) async fn built_tools(
mcp_tools = selected_mcp_tools;
}

// Expose app tools directly when tool_search is disabled, or when tool_search
// is enabled but the accessible app tool set stays below the direct-exposure threshold.
// Expose app tools directly when the search tool is disabled, or when the
// search tool is enabled but the accessible app tool set stays below the
// direct-exposure threshold.
let expose_app_tools_directly = !turn_context.tools_config.search_tool
|| app_tools
.as_ref()
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/tools/handlers/tool_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub struct ToolSearchHandler {
tools: HashMap<String, ToolInfo>,
}

pub(crate) const TOOL_SEARCH_TOOL_NAME: &str = "tool_search";
pub(crate) const TOOL_SEARCH_TOOL_NAME: &str = "tool_search_tool";
pub(crate) const DEFAULT_LIMIT: usize = 8;

impl ToolSearchHandler {
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/core/src/tools/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::tools::context::SharedTurnDiffTracker;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::discoverable::DiscoverableTool;
use crate::tools::handlers::TOOL_SEARCH_TOOL_NAME;
use crate::tools::registry::AnyToolResult;
use crate::tools::registry::ConfiguredToolSpec;
use crate::tools::registry::ToolRegistry;
Expand Down Expand Up @@ -159,7 +160,7 @@ impl ToolRouter {
))
})?;
Ok(Some(ToolCall {
tool_name: "tool_search".to_string(),
tool_name: TOOL_SEARCH_TOOL_NAME.to_string(),
tool_namespace: None,
call_id,
payload: ToolPayload::ToolSearch { arguments },
Expand Down
8 changes: 4 additions & 4 deletions codex-rs/core/src/tools/spec_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ fn deferred_responses_api_tool_serializes_with_defer_loading() {
fn tool_name(tool: &ToolSpec) -> &str {
match tool {
ToolSpec::Function(ResponsesApiTool { name, .. }) => name,
ToolSpec::ToolSearch { .. } => "tool_search",
ToolSpec::ToolSearch { .. } => "tool_search_tool",
ToolSpec::LocalShell {} => "local_shell",
ToolSpec::ImageGeneration { .. } => "image_generation",
ToolSpec::WebSearch { .. } => "web_search",
Expand Down Expand Up @@ -1894,7 +1894,7 @@ fn search_tool_description_lists_each_codex_apps_connector_once() {

let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
let ToolSpec::ToolSearch { description, .. } = &search_tool.spec else {
panic!("expected tool_search tool");
panic!("expected tool_search_tool tool");
};
let description = description.as_str();
assert!(description.contains("- Calendar: Plan events and manage your calendar."));
Expand Down Expand Up @@ -2013,7 +2013,7 @@ fn search_tool_description_handles_no_enabled_apps() {
let (tools, _) = build_specs(&tools_config, None, Some(HashMap::new()), &[]).build();
let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
let ToolSpec::ToolSearch { description, .. } = &search_tool.spec else {
panic!("expected tool_search tool");
panic!("expected tool_search_tool tool");
};

assert!(description.contains("None currently enabled."));
Expand Down Expand Up @@ -2061,7 +2061,7 @@ fn search_tool_description_falls_back_to_connector_name_without_description() {
.build();
let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
let ToolSpec::ToolSearch { description, .. } = &search_tool.spec else {
panic!("expected tool_search tool");
panic!("expected tool_search_tool tool");
};

assert!(description.contains("- Calendar"));
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/templates/search_tool/tool_description.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Searches over apps/connectors tool metadata with BM25 and exposes matching tools

You have access to all the tools of the following apps/connectors:
{{app_descriptions}}
Some of the tools may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required tools and load them for the apps mentioned above. For the apps mentioned above, always use `tool_search` instead of `list_mcp_resources` or `list_mcp_resource_templates` for tool discovery.
Some of the tools may not have been provided to you upfront, and you should use this tool (`tool_search_tool`) to search for the required tools and load them for the apps mentioned above. For the apps mentioned above, always use `tool_search_tool` instead of `list_mcp_resources` or `list_mcp_resource_templates` for tool discovery.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Suggests a discoverable connector or plugin when the user clearly wants a capabi

Use this ONLY when:
- There's no available tool to handle the user's request
- And tool_search fails to find a good match
- And tool_search_tool fails to find a good match, if tool_search_tool is available
- AND the user's request strongly matches one of the discoverable tools listed below.

Tool suggestions should only use the discoverable tools listed here. DO NOT explore or recommend tools that are not on this list.
Expand Down
22 changes: 15 additions & 7 deletions codex-rs/core/tests/suite/search_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const SEARCH_TOOL_DESCRIPTION_SNIPPETS: [&str; 2] = [
"You have access to all the tools of the following apps/connectors",
"- Calendar: Plan events and manage your calendar.",
];
const TOOL_SEARCH_TOOL_NAME: &str = "tool_search";
const TOOL_SEARCH_TOOL_TYPE: &str = "tool_search";
const CALENDAR_CREATE_TOOL: &str = "mcp__codex_apps__calendar_create_event";
const CALENDAR_LIST_TOOL: &str = "mcp__codex_apps__calendar_list_events";
const SEARCH_CALENDAR_NAMESPACE: &str = "mcp__codex_apps__calendar";
Expand Down Expand Up @@ -63,7 +63,7 @@ fn tool_search_description(body: &Value) -> Option<String> {
.and_then(Value::as_array)
.and_then(|tools| {
tools.iter().find_map(|tool| {
if tool.get("type").and_then(Value::as_str) == Some(TOOL_SEARCH_TOOL_NAME) {
if tool.get("type").and_then(Value::as_str) == Some(TOOL_SEARCH_TOOL_TYPE) {
tool.get("description")
.and_then(Value::as_str)
.map(str::to_string)
Expand Down Expand Up @@ -144,7 +144,7 @@ async fn search_tool_flag_adds_tool_search() -> Result<()> {
.expect("tools array should exist");
let tool_search = tools
.iter()
.find(|tool| tool.get("type").and_then(Value::as_str) == Some(TOOL_SEARCH_TOOL_NAME))
.find(|tool| tool.get("type").and_then(Value::as_str) == Some(TOOL_SEARCH_TOOL_TYPE))
.cloned()
.expect("tool_search should be present");

Expand Down Expand Up @@ -200,8 +200,8 @@ async fn search_tool_is_hidden_for_api_key_auth() -> Result<()> {
let body = mock.single_request().body_json();
let tools = tool_names(&body);
assert!(
!tools.iter().any(|name| name == TOOL_SEARCH_TOOL_NAME),
"tools list should not include {TOOL_SEARCH_TOOL_NAME} for API key auth: {tools:?}"
!tools.iter().any(|name| name == TOOL_SEARCH_TOOL_TYPE),
"tools list should not include {TOOL_SEARCH_TOOL_TYPE} for API key auth: {tools:?}"
);

Ok(())
Expand Down Expand Up @@ -241,6 +241,14 @@ async fn search_tool_adds_discovery_instructions_to_tool_description() -> Result
.all(|snippet| description.contains(snippet)),
"tool_search description should include the updated workflow: {description:?}"
);
assert!(
description.contains("`tool_search_tool`"),
"tool_search description should mention the callable tool name: {description:?}"
);
assert!(
!description.contains("`tool_search`"),
"tool_search description should not mention the legacy callable tool name: {description:?}"
);
assert!(
!description.contains("remainder of the current session/thread"),
"tool_search description should not mention legacy client-side persistence: {description:?}"
Expand Down Expand Up @@ -277,7 +285,7 @@ async fn search_tool_hides_apps_tools_without_search() -> Result<()> {

let body = mock.single_request().body_json();
let tools = tool_names(&body);
assert!(tools.iter().any(|name| name == TOOL_SEARCH_TOOL_NAME));
assert!(tools.iter().any(|name| name == TOOL_SEARCH_TOOL_TYPE));
assert!(!tools.iter().any(|name| name == CALENDAR_CREATE_TOOL));
assert!(!tools.iter().any(|name| name == CALENDAR_LIST_TOOL));

Expand Down Expand Up @@ -461,7 +469,7 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() -
assert!(
first_request_tools
.iter()
.any(|name| name == TOOL_SEARCH_TOOL_NAME),
.any(|name| name == TOOL_SEARCH_TOOL_TYPE),
"first request should advertise tool_search: {first_request_tools:?}"
);
assert!(
Expand Down
Loading