mirror of
https://github.com/openai/codex.git
synced 2026-05-03 10:56:37 +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:
@@ -207,8 +207,6 @@ use crate::mcp::maybe_prompt_and_install_mcp_dependencies;
|
||||
use crate::mcp::with_codex_apps_mcp;
|
||||
use crate::mcp_connection_manager::McpConnectionManager;
|
||||
use crate::mcp_connection_manager::codex_apps_tools_cache_key;
|
||||
use crate::mcp_connection_manager::filter_codex_apps_mcp_tools_only;
|
||||
use crate::mcp_connection_manager::filter_mcp_tools_by_name;
|
||||
use crate::mcp_connection_manager::filter_non_codex_apps_mcp_tools_only;
|
||||
use crate::memories;
|
||||
use crate::mentions::build_connector_slug_counts;
|
||||
@@ -287,7 +285,6 @@ use crate::tasks::SessionTask;
|
||||
use crate::tasks::SessionTaskContext;
|
||||
use crate::tools::ToolRouter;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::handlers::SEARCH_TOOL_BM25_TOOL_NAME;
|
||||
use crate::tools::js_repl::JsReplHandle;
|
||||
use crate::tools::js_repl::resolve_compatible_node;
|
||||
use crate::tools::network_approval::NetworkApprovalService;
|
||||
@@ -1880,26 +1877,6 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn merge_mcp_tool_selection(&self, tool_names: Vec<String>) -> Vec<String> {
|
||||
let mut state = self.state.lock().await;
|
||||
state.merge_mcp_tool_selection(tool_names)
|
||||
}
|
||||
|
||||
pub(crate) async fn set_mcp_tool_selection(&self, tool_names: Vec<String>) {
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_mcp_tool_selection(tool_names);
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mcp_tool_selection(&self) -> Option<Vec<String>> {
|
||||
let state = self.state.lock().await;
|
||||
state.get_mcp_tool_selection()
|
||||
}
|
||||
|
||||
pub(crate) async fn clear_mcp_tool_selection(&self) {
|
||||
let mut state = self.state.lock().await;
|
||||
state.clear_mcp_tool_selection();
|
||||
}
|
||||
|
||||
// Merges connector IDs into the session-level explicit connector selection.
|
||||
pub(crate) async fn merge_connector_selection(
|
||||
&self,
|
||||
@@ -1923,7 +1900,6 @@ impl Session {
|
||||
|
||||
async fn record_initial_history(&self, conversation_history: InitialHistory) {
|
||||
let turn_context = self.new_default_turn().await;
|
||||
self.clear_mcp_tool_selection().await;
|
||||
let is_subagent = {
|
||||
let state = self.state.lock().await;
|
||||
matches!(
|
||||
@@ -1939,8 +1915,6 @@ impl Session {
|
||||
}
|
||||
InitialHistory::Resumed(resumed_history) => {
|
||||
let rollout_items = resumed_history.history;
|
||||
let restored_tool_selection =
|
||||
Self::extract_mcp_tool_selection_from_rollout(&rollout_items);
|
||||
|
||||
let reconstructed_rollout = self
|
||||
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
|
||||
@@ -1986,9 +1960,6 @@ impl Session {
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_token_info(Some(info));
|
||||
}
|
||||
if let Some(selected_tools) = restored_tool_selection {
|
||||
self.set_mcp_tool_selection(selected_tools).await;
|
||||
}
|
||||
|
||||
// Defer seeding the session's initial context until the first turn starts so
|
||||
// turn/start overrides can be merged before we write to the rollout.
|
||||
@@ -1997,9 +1968,6 @@ impl Session {
|
||||
}
|
||||
}
|
||||
InitialHistory::Forked(rollout_items) => {
|
||||
let restored_tool_selection =
|
||||
Self::extract_mcp_tool_selection_from_rollout(&rollout_items);
|
||||
|
||||
let reconstructed_rollout = self
|
||||
.reconstruct_history_from_rollout(&turn_context, &rollout_items)
|
||||
.await;
|
||||
@@ -2027,9 +1995,6 @@ impl Session {
|
||||
let mut state = self.state.lock().await;
|
||||
state.set_token_info(Some(info));
|
||||
}
|
||||
if let Some(selected_tools) = restored_tool_selection {
|
||||
self.set_mcp_tool_selection(selected_tools).await;
|
||||
}
|
||||
|
||||
// If persisting, persist all rollout items as-is (recorder filters)
|
||||
if !rollout_items.is_empty() {
|
||||
@@ -2063,54 +2028,6 @@ impl Session {
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_mcp_tool_selection_from_rollout(
|
||||
rollout_items: &[RolloutItem],
|
||||
) -> Option<Vec<String>> {
|
||||
let mut search_call_ids = HashSet::new();
|
||||
let mut active_selected_tools: Option<Vec<String>> = None;
|
||||
|
||||
for item in rollout_items {
|
||||
let RolloutItem::ResponseItem(response_item) = item else {
|
||||
continue;
|
||||
};
|
||||
match response_item {
|
||||
ResponseItem::FunctionCall { name, call_id, .. } => {
|
||||
if name == SEARCH_TOOL_BM25_TOOL_NAME {
|
||||
search_call_ids.insert(call_id.clone());
|
||||
}
|
||||
}
|
||||
ResponseItem::FunctionCallOutput { call_id, output } => {
|
||||
if !search_call_ids.contains(call_id) {
|
||||
continue;
|
||||
}
|
||||
let Some(content) = output.body.to_text() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(payload) = serde_json::from_str::<Value>(&content) else {
|
||||
continue;
|
||||
};
|
||||
let Some(selected_tools) = payload
|
||||
.get("active_selected_tools")
|
||||
.and_then(Value::as_array)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(selected_tools) = selected_tools
|
||||
.iter()
|
||||
.map(|value| value.as_str().map(str::to_string))
|
||||
.collect::<Option<Vec<_>>>()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
active_selected_tools = Some(selected_tools);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
active_selected_tools
|
||||
}
|
||||
|
||||
async fn previous_turn_settings(&self) -> Option<PreviousTurnSettings> {
|
||||
let state = self.state.lock().await;
|
||||
state.previous_turn_settings()
|
||||
@@ -3852,7 +3769,20 @@ impl Session {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn parse_mcp_tool_name(&self, tool_name: &str) -> Option<(String, String)> {
|
||||
pub(crate) async fn parse_mcp_tool_name(
|
||||
&self,
|
||||
name: &str,
|
||||
namespace: &Option<String>,
|
||||
) -> Option<(String, String)> {
|
||||
let tool_name = if let Some(namespace) = namespace {
|
||||
if name.starts_with(namespace.as_str()) {
|
||||
name
|
||||
} else {
|
||||
&format!("{namespace}{name}")
|
||||
}
|
||||
} else {
|
||||
name
|
||||
};
|
||||
self.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
@@ -6068,7 +5998,7 @@ fn filter_codex_apps_mcp_tools(
|
||||
.iter()
|
||||
.filter(|(_, tool)| {
|
||||
if tool.server_name != CODEX_APPS_MCP_SERVER_NAME {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
let Some(connector_id) = codex_apps_connector_id(tool) else {
|
||||
return false;
|
||||
@@ -6284,18 +6214,13 @@ async fn built_tools(
|
||||
);
|
||||
|
||||
let mut selected_mcp_tools = filter_non_codex_apps_mcp_tools_only(&mcp_tools);
|
||||
|
||||
if let Some(selected_tools) = sess.get_mcp_tool_selection().await {
|
||||
selected_mcp_tools.extend(filter_mcp_tools_by_name(&mcp_tools, &selected_tools));
|
||||
}
|
||||
|
||||
selected_mcp_tools.extend(filter_codex_apps_mcp_tools_only(
|
||||
selected_mcp_tools.extend(filter_codex_apps_mcp_tools(
|
||||
&mcp_tools,
|
||||
explicitly_enabled.as_ref(),
|
||||
&turn_context.config,
|
||||
));
|
||||
|
||||
mcp_tools =
|
||||
connectors::filter_codex_apps_tools_by_policy(selected_mcp_tools, &turn_context.config);
|
||||
mcp_tools = selected_mcp_tools;
|
||||
}
|
||||
|
||||
Ok(Arc::new(ToolRouter::from_config(
|
||||
|
||||
Reference in New Issue
Block a user