Compare commits

...

1 Commits

Author SHA1 Message Date
Matthew Zeng
d803f4694a update 2026-03-21 15:34:21 -07:00
9 changed files with 29 additions and 19 deletions

View File

@@ -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}")
}

View File

@@ -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",

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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;
@@ -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 },

View File

@@ -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",
@@ -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."));
@@ -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."));
@@ -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"));

View File

@@ -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.

View File

@@ -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.

View File

@@ -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";
@@ -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)
@@ -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");
@@ -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(())
@@ -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:?}"
@@ -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));
@@ -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!(