mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
feat: search_tool migrate to bring you own tool of Responses API (#14274)
## Why to support a new bring your own search tool in Responses API(https://developers.openai.com/api/docs/guides/tools-tool-search#client-executed-tool-search) we migrating our bm25 search tool to use official way to execute search on client and communicate additional tools to the model. ## What - replace the legacy `search_tool_bm25` flow with client-executed `tool_search` - add protocol, SSE, history, and normalization support for `tool_search_call` and `tool_search_output` - return namespaced Codex Apps search results and wire namespaced follow-up tool calls back into MCP dispatch
This commit is contained in:
1 parent
72631755e0
commit
77b0c75267
52 files changed
+2619
-1890
No files matched your search
@@ -294,7 +294,7 @@ pub fn connector_display_label(connector: &AppInfo) -> String {
|
||||
}
|
||||
|
||||
pub fn connector_mention_slug(connector: &AppInfo) -> String {
|
||||
connector_name_slug(&connector_display_label(connector))
|
||||
sanitize_name(&connector_display_label(connector))
|
||||
}
|
||||
|
||||
pub(crate) fn accessible_connectors_from_mcp_tools(
|
||||
@@ -307,10 +307,10 @@ pub(crate) fn accessible_connectors_from_mcp_tools(
|
||||
return None;
|
||||
}
|
||||
let connector_id = tool.connector_id.as_deref()?;
|
||||
let connector_name = normalize_connector_value(tool.connector_name.as_deref());
|
||||
Some((
|
||||
connector_id.to_string(),
|
||||
connector_name,
|
||||
normalize_connector_value(tool.connector_name.as_deref()),
|
||||
normalize_connector_value(tool.connector_description.as_deref()),
|
||||
tool.plugin_display_names.clone(),
|
||||
))
|
||||
});
|
||||
@@ -467,21 +467,13 @@ pub(crate) fn codex_app_tool_is_enabled(
|
||||
app_tool_policy(
|
||||
config,
|
||||
tool_info.connector_id.as_deref(),
|
||||
&tool_info.tool_name,
|
||||
&tool_info.tool.name,
|
||||
tool_info.tool.title.as_deref(),
|
||||
tool_info.tool.annotations.as_ref(),
|
||||
)
|
||||
.enabled
|
||||
}
|
||||
|
||||
pub(crate) fn filter_codex_apps_tools_by_policy(
|
||||
mut mcp_tools: HashMap<String, crate::mcp_connection_manager::ToolInfo>,
|
||||
config: &Config,
|
||||
) -> HashMap<String, crate::mcp_connection_manager::ToolInfo> {
|
||||
mcp_tools.retain(|_, tool_info| codex_app_tool_is_enabled(config, tool_info));
|
||||
mcp_tools
|
||||
}
|
||||
|
||||
const DISALLOWED_CONNECTOR_IDS: &[&str] = &[
|
||||
"asdk_app_6938a94a61d881918ef32cb999ff937c",
|
||||
"connector_2b0a9009c9c64bf9933a3dae3f2b1254",
|
||||
@@ -611,23 +603,38 @@ fn app_tool_policy_from_apps_config(
|
||||
|
||||
fn collect_accessible_connectors<I>(tools: I) -> Vec<AppInfo>
|
||||
where
|
||||
I: IntoIterator<Item = (String, Option<String>, Vec<String>)>,
|
||||
I: IntoIterator<Item = (String, Option<String>, Option<String>, Vec<String>)>,
|
||||
{
|
||||
let mut connectors: HashMap<String, (String, BTreeSet<String>)> = HashMap::new();
|
||||
for (connector_id, connector_name, plugin_display_names) in tools {
|
||||
let mut connectors: HashMap<String, (AppInfo, BTreeSet<String>)> = HashMap::new();
|
||||
for (connector_id, connector_name, connector_description, plugin_display_names) in tools {
|
||||
let connector_name = connector_name.unwrap_or_else(|| connector_id.clone());
|
||||
if let Some((existing_name, existing_plugin_display_names)) =
|
||||
connectors.get_mut(&connector_id)
|
||||
{
|
||||
if existing_name == &connector_id && connector_name != connector_id {
|
||||
*existing_name = connector_name;
|
||||
if let Some((existing, existing_plugin_display_names)) = connectors.get_mut(&connector_id) {
|
||||
if existing.name == connector_id && connector_name != connector_id {
|
||||
existing.name = connector_name;
|
||||
}
|
||||
if existing.description.is_none() && connector_description.is_some() {
|
||||
existing.description = connector_description;
|
||||
}
|
||||
existing_plugin_display_names.extend(plugin_display_names);
|
||||
} else {
|
||||
connectors.insert(
|
||||
connector_id,
|
||||
connector_id.clone(),
|
||||
(
|
||||
connector_name,
|
||||
AppInfo {
|
||||
id: connector_id.clone(),
|
||||
name: connector_name,
|
||||
description: connector_description,
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: None,
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
plugin_display_names
|
||||
.into_iter()
|
||||
.collect::<BTreeSet<String>>(),
|
||||
@@ -636,24 +643,12 @@ where
|
||||
}
|
||||
}
|
||||
let mut accessible: Vec<AppInfo> = connectors
|
||||
.into_iter()
|
||||
.map(
|
||||
|(connector_id, (connector_name, plugin_display_names))| AppInfo {
|
||||
id: connector_id.clone(),
|
||||
name: connector_name.clone(),
|
||||
description: None,
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: Some(connector_install_url(&connector_name, &connector_id)),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: plugin_display_names.into_iter().collect(),
|
||||
},
|
||||
)
|
||||
.into_values()
|
||||
.map(|(mut connector, plugin_display_names)| {
|
||||
connector.plugin_display_names = plugin_display_names.into_iter().collect();
|
||||
connector.install_url = Some(connector_install_url(&connector.name, &connector.id));
|
||||
connector
|
||||
})
|
||||
.collect();
|
||||
accessible.sort_by(|left, right| {
|
||||
right
|
||||
@@ -696,11 +691,11 @@ fn normalize_connector_value(value: Option<&str>) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn connector_install_url(name: &str, connector_id: &str) -> String {
|
||||
let slug = connector_name_slug(name);
|
||||
let slug = sanitize_name(name);
|
||||
format!("https://chatgpt.com/apps/{slug}/{connector_id}")
|
||||
}
|
||||
|
||||
pub fn connector_name_slug(name: &str) -> String {
|
||||
pub fn sanitize_name(name: &str) -> String {
|
||||
let mut normalized = String::with_capacity(name.len());
|
||||
for character in name.chars() {
|
||||
if character.is_ascii_alphanumeric() {
|
||||
@@ -728,10 +723,12 @@ mod tests {
|
||||
use crate::config::types::AppToolConfig;
|
||||
use crate::config::types::AppToolsConfig;
|
||||
use crate::config::types::AppsDefaultConfig;
|
||||
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::Tool;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn annotations(
|
||||
@@ -807,12 +804,19 @@ mod tests {
|
||||
connector_name: Option<&str>,
|
||||
plugin_display_names: &[&str],
|
||||
) -> ToolInfo {
|
||||
let tool_namespace = connector_name
|
||||
.map(sanitize_name)
|
||||
.map(|connector_name| format!("mcp__{CODEX_APPS_MCP_SERVER_NAME}__{connector_name}"))
|
||||
.unwrap_or_else(|| CODEX_APPS_MCP_SERVER_NAME.to_string());
|
||||
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
tool_name: tool_name.to_string(),
|
||||
tool_namespace,
|
||||
tool: test_tool_definition(tool_name),
|
||||
connector_id: Some(connector_id.to_string()),
|
||||
connector_name: connector_name.map(ToOwned::to_owned),
|
||||
connector_description: None,
|
||||
plugin_display_names: plugin_names(plugin_display_names),
|
||||
}
|
||||
}
|
||||
@@ -871,9 +875,11 @@ mod tests {
|
||||
ToolInfo {
|
||||
server_name: "sample".to_string(),
|
||||
tool_name: "echo".to_string(),
|
||||
tool_namespace: "sample".to_string(),
|
||||
tool: test_tool_definition("echo"),
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
plugin_display_names: plugin_names(&["ignored"]),
|
||||
},
|
||||
),
|
||||
@@ -930,6 +936,52 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accessible_connectors_from_mcp_tools_preserves_description() {
|
||||
let mcp_tools = HashMap::from([(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
tool_name: "calendar_create_event".to_string(),
|
||||
tool_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
tool: Tool {
|
||||
name: "calendar_create_event".to_string().into(),
|
||||
title: None,
|
||||
description: Some("Create a calendar event".into()),
|
||||
input_schema: Arc::new(JsonObject::default()),
|
||||
output_schema: None,
|
||||
annotations: None,
|
||||
execution: None,
|
||||
icons: None,
|
||||
meta: None,
|
||||
},
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
connector_description: Some("Plan events".to_string()),
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
)]);
|
||||
|
||||
assert_eq!(
|
||||
accessible_connectors_from_mcp_tools(&mcp_tools),
|
||||
vec![AppInfo {
|
||||
id: "calendar".to_string(),
|
||||
name: "Calendar".to_string(),
|
||||
description: Some("Plan events".to_string()),
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: Some(connector_install_url("Calendar", "calendar")),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_tool_policy_uses_global_defaults_for_destructive_hints() {
|
||||
let apps_config = AppsConfigToml {
|
||||
|
||||
Reference in new issue
Block a user