mirror of
https://github.com/openai/codex.git
synced 2026-05-05 20:07:02 +00:00
Compare commits
2 Commits
pr20314-ap
...
dev/sayan/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
258951b6ae | ||
|
|
bb086dfbdf |
@@ -9,24 +9,20 @@ use codex_tools::ToolsConfig;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::connectors;
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
|
||||
pub(crate) const DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD: usize = 100;
|
||||
|
||||
pub(crate) struct McpToolExposure {
|
||||
pub(crate) direct_tools: HashMap<String, McpToolInfo>,
|
||||
pub(crate) deferred_tools: Option<HashMap<String, McpToolInfo>>,
|
||||
}
|
||||
|
||||
pub(crate) fn build_mcp_tool_exposure(
|
||||
all_mcp_tools: &HashMap<String, McpToolInfo>,
|
||||
connectors: Option<&[connectors::AppInfo]>,
|
||||
explicitly_enabled_connectors: &[connectors::AppInfo],
|
||||
config: &Config,
|
||||
tools_config: &ToolsConfig,
|
||||
) -> McpToolExposure {
|
||||
let mut deferred_tools = filter_non_codex_apps_mcp_tools_only(all_mcp_tools);
|
||||
) -> HashMap<String, McpToolInput> {
|
||||
let mut candidate_tools = filter_non_codex_apps_mcp_tools_only(all_mcp_tools);
|
||||
if let Some(connectors) = connectors {
|
||||
deferred_tools.extend(filter_codex_apps_mcp_tools(
|
||||
candidate_tools.extend(filter_codex_apps_mcp_tools(
|
||||
all_mcp_tools,
|
||||
connectors,
|
||||
config,
|
||||
@@ -37,25 +33,47 @@ pub(crate) fn build_mcp_tool_exposure(
|
||||
&& (config
|
||||
.features
|
||||
.enabled(Feature::ToolSearchAlwaysDeferMcpTools)
|
||||
|| deferred_tools.len() >= DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD);
|
||||
|| candidate_tools.len() >= DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD);
|
||||
|
||||
if !should_defer {
|
||||
return McpToolExposure {
|
||||
direct_tools: deferred_tools,
|
||||
deferred_tools: None,
|
||||
};
|
||||
return candidate_tools
|
||||
.into_iter()
|
||||
.map(|(name, tool_info)| {
|
||||
(
|
||||
name,
|
||||
McpToolInput {
|
||||
tool_info,
|
||||
defer_loading: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
let direct_tools =
|
||||
filter_codex_apps_mcp_tools(all_mcp_tools, explicitly_enabled_connectors, config);
|
||||
for direct_tool_name in direct_tools.keys() {
|
||||
deferred_tools.remove(direct_tool_name);
|
||||
let mut tools = HashMap::new();
|
||||
for (name, tool_info) in direct_tools {
|
||||
candidate_tools.remove(&name);
|
||||
tools.insert(
|
||||
name,
|
||||
McpToolInput {
|
||||
tool_info,
|
||||
defer_loading: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
for (name, tool_info) in candidate_tools {
|
||||
tools.insert(
|
||||
name,
|
||||
McpToolInput {
|
||||
tool_info,
|
||||
defer_loading: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
McpToolExposure {
|
||||
direct_tools,
|
||||
deferred_tools: (!deferred_tools.is_empty()).then_some(deferred_tools),
|
||||
}
|
||||
tools
|
||||
}
|
||||
|
||||
fn filter_codex_apps_mcp_tools(
|
||||
|
||||
@@ -91,6 +91,18 @@ fn numbered_mcp_tools(count: usize) -> HashMap<String, ToolInfo> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn tool_names_with_defer_loading(
|
||||
exposed_tools: &HashMap<String, McpToolInput>,
|
||||
defer_loading: bool,
|
||||
) -> Vec<String> {
|
||||
let mut names = exposed_tools
|
||||
.iter()
|
||||
.filter_map(|(name, tool)| (tool.defer_loading == defer_loading).then_some(name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
||||
async fn tools_config_for_mcp_tool_exposure(search_tool: bool) -> ToolsConfig {
|
||||
let config = test_config().await;
|
||||
let model_info =
|
||||
@@ -112,12 +124,12 @@ async fn tools_config_for_mcp_tool_exposure(search_tool: bool) -> ToolsConfig {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn directly_exposes_small_effective_tool_sets() {
|
||||
async fn directly_exposes_small_candidate_tool_sets() {
|
||||
let config = test_config().await;
|
||||
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
|
||||
let mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD - 1);
|
||||
|
||||
let exposure = build_mcp_tool_exposure(
|
||||
let exposed_tools = build_mcp_tool_exposure(
|
||||
&mcp_tools,
|
||||
/*connectors*/ None,
|
||||
&[],
|
||||
@@ -125,21 +137,21 @@ async fn directly_exposes_small_effective_tool_sets() {
|
||||
&tools_config,
|
||||
);
|
||||
|
||||
let mut direct_tool_names: Vec<_> = exposure.direct_tools.keys().cloned().collect();
|
||||
direct_tool_names.sort();
|
||||
let direct_tool_names =
|
||||
tool_names_with_defer_loading(&exposed_tools, /*defer_loading*/ false);
|
||||
let mut expected_tool_names: Vec<_> = mcp_tools.keys().cloned().collect();
|
||||
expected_tool_names.sort();
|
||||
assert_eq!(direct_tool_names, expected_tool_names);
|
||||
assert!(exposure.deferred_tools.is_none());
|
||||
assert!(exposed_tools.values().all(|tool| !tool.defer_loading));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn searches_large_effective_tool_sets() {
|
||||
async fn searches_large_candidate_tool_sets() {
|
||||
let config = test_config().await;
|
||||
let tools_config = tools_config_for_mcp_tool_exposure(/*search_tool*/ true).await;
|
||||
let mcp_tools = numbered_mcp_tools(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD);
|
||||
|
||||
let exposure = build_mcp_tool_exposure(
|
||||
let exposed_tools = build_mcp_tool_exposure(
|
||||
&mcp_tools,
|
||||
/*connectors*/ None,
|
||||
&[],
|
||||
@@ -147,13 +159,8 @@ async fn searches_large_effective_tool_sets() {
|
||||
&tools_config,
|
||||
);
|
||||
|
||||
assert!(exposure.direct_tools.is_empty());
|
||||
let deferred_tools = exposure
|
||||
.deferred_tools
|
||||
.as_ref()
|
||||
.expect("large tool sets should be discoverable through tool_search");
|
||||
let mut deferred_tool_names: Vec<_> = deferred_tools.keys().cloned().collect();
|
||||
deferred_tool_names.sort();
|
||||
let deferred_tool_names =
|
||||
tool_names_with_defer_loading(&exposed_tools, /*defer_loading*/ true);
|
||||
let mut expected_tool_names: Vec<_> = mcp_tools.keys().cloned().collect();
|
||||
expected_tool_names.sort();
|
||||
assert_eq!(deferred_tool_names, expected_tool_names);
|
||||
@@ -175,7 +182,7 @@ async fn directly_exposes_explicit_apps_without_deferred_overlap() {
|
||||
)]);
|
||||
let connectors = vec![make_connector("calendar", "Calendar")];
|
||||
|
||||
let exposure = build_mcp_tool_exposure(
|
||||
let exposed_tools = build_mcp_tool_exposure(
|
||||
&mcp_tools,
|
||||
Some(connectors.as_slice()),
|
||||
connectors.as_slice(),
|
||||
@@ -183,28 +190,25 @@ async fn directly_exposes_explicit_apps_without_deferred_overlap() {
|
||||
&tools_config,
|
||||
);
|
||||
|
||||
let mut tool_names: Vec<String> = exposure.direct_tools.into_keys().collect();
|
||||
tool_names.sort();
|
||||
let tool_names = tool_names_with_defer_loading(&exposed_tools, /*defer_loading*/ false);
|
||||
assert_eq!(
|
||||
tool_names,
|
||||
vec!["mcp__codex_apps__calendar_create_event".to_string()]
|
||||
);
|
||||
let deferred_tool_names =
|
||||
tool_names_with_defer_loading(&exposed_tools, /*defer_loading*/ true);
|
||||
assert_eq!(
|
||||
exposure.deferred_tools.as_ref().map(HashMap::len),
|
||||
Some(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD - 1)
|
||||
deferred_tool_names.len(),
|
||||
DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD - 1
|
||||
);
|
||||
let deferred_tools = exposure
|
||||
.deferred_tools
|
||||
.as_ref()
|
||||
.expect("large tool sets should be discoverable through tool_search");
|
||||
assert!(
|
||||
tool_names
|
||||
.iter()
|
||||
.all(|direct_tool_name| !deferred_tools.contains_key(direct_tool_name)),
|
||||
.all(|direct_tool_name| !deferred_tool_names.contains(direct_tool_name)),
|
||||
"direct tools should not also be deferred: {tool_names:?}"
|
||||
);
|
||||
assert!(!deferred_tools.contains_key("mcp__codex_apps__calendar_create_event"));
|
||||
assert!(deferred_tools.contains_key("mcp__rmcp__tool_0"));
|
||||
assert!(!deferred_tool_names.contains(&"mcp__codex_apps__calendar_create_event".to_string()));
|
||||
assert!(deferred_tool_names.contains(&"mcp__rmcp__tool_0".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -234,7 +238,7 @@ async fn always_defer_feature_preserves_explicit_apps() {
|
||||
]);
|
||||
let connectors = vec![make_connector("calendar", "Calendar")];
|
||||
|
||||
let exposure = build_mcp_tool_exposure(
|
||||
let exposed_tools = build_mcp_tool_exposure(
|
||||
&mcp_tools,
|
||||
Some(connectors.as_slice()),
|
||||
connectors.as_slice(),
|
||||
@@ -242,16 +246,14 @@ async fn always_defer_feature_preserves_explicit_apps() {
|
||||
&tools_config,
|
||||
);
|
||||
|
||||
let mut direct_tool_names: Vec<String> = exposure.direct_tools.into_keys().collect();
|
||||
direct_tool_names.sort();
|
||||
let direct_tool_names =
|
||||
tool_names_with_defer_loading(&exposed_tools, /*defer_loading*/ false);
|
||||
assert_eq!(
|
||||
direct_tool_names,
|
||||
vec!["mcp__codex_apps__calendar_create_event".to_string()]
|
||||
);
|
||||
let deferred_tools = exposure
|
||||
.deferred_tools
|
||||
.as_ref()
|
||||
.expect("MCP tools should be discoverable through tool_search");
|
||||
assert!(deferred_tools.contains_key("mcp__rmcp__tool"));
|
||||
assert!(!deferred_tools.contains_key("mcp__codex_apps__calendar_create_event"));
|
||||
let deferred_tool_names =
|
||||
tool_names_with_defer_loading(&exposed_tools, /*defer_loading*/ true);
|
||||
assert!(deferred_tool_names.contains(&"mcp__rmcp__tool".to_string()));
|
||||
assert!(!deferred_tool_names.contains(&"mcp__codex_apps__calendar_create_event".to_string()));
|
||||
}
|
||||
|
||||
@@ -463,7 +463,6 @@ fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> T
|
||||
&turn_context.tools_config,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
mcp_tools: None,
|
||||
deferred_mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names: HashSet::new(),
|
||||
discoverable_tools: None,
|
||||
@@ -7359,11 +7358,21 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
|
||||
.list_all_tools()
|
||||
.await
|
||||
};
|
||||
let deferred_mcp_tools = Some(tools.clone());
|
||||
let tools = tools
|
||||
.into_iter()
|
||||
.map(|(name, tool_info)| {
|
||||
(
|
||||
name,
|
||||
crate::tools::mcp_tool_input::McpToolInput {
|
||||
tool_info,
|
||||
defer_loading: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let router = ToolRouter::from_config(
|
||||
&turn_context.tools_config,
|
||||
crate::tools::router::ToolRouterParams {
|
||||
deferred_mcp_tools,
|
||||
mcp_tools: Some(tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names: HashSet::new(),
|
||||
|
||||
@@ -1196,15 +1196,14 @@ pub(crate) async fn built_tools(
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mcp_tool_exposure = build_mcp_tool_exposure(
|
||||
let exposed_mcp_tools = build_mcp_tool_exposure(
|
||||
&all_mcp_tools,
|
||||
connectors.as_deref(),
|
||||
explicitly_enabled.as_slice(),
|
||||
&turn_context.config,
|
||||
&turn_context.tools_config,
|
||||
);
|
||||
let mcp_tools = has_mcp_servers.then_some(mcp_tool_exposure.direct_tools);
|
||||
let deferred_mcp_tools = mcp_tool_exposure.deferred_tools;
|
||||
let mcp_tools = has_mcp_servers.then_some(exposed_mcp_tools);
|
||||
let unavailable_called_tools = if turn_context
|
||||
.config
|
||||
.features
|
||||
@@ -1212,7 +1211,6 @@ pub(crate) async fn built_tools(
|
||||
{
|
||||
let exposed_tool_names = mcp_tools
|
||||
.iter()
|
||||
.chain(deferred_mcp_tools.iter())
|
||||
.flat_map(|tools| tools.keys().map(String::as_str))
|
||||
.collect::<HashSet<_>>();
|
||||
collect_unavailable_called_tools(input, &exposed_tool_names)
|
||||
@@ -1236,7 +1234,6 @@ pub(crate) async fn built_tools(
|
||||
&turn_context.tools_config,
|
||||
ToolRouterParams {
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
parallel_mcp_server_names,
|
||||
discoverable_tools,
|
||||
|
||||
@@ -24,6 +24,7 @@ use crate::tools::ToolRouter;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
use crate::tools::parallel::ToolCallRuntime;
|
||||
use crate::tools::router::ToolCall;
|
||||
use crate::tools::router::ToolCallSource;
|
||||
@@ -262,7 +263,7 @@ pub(super) async fn build_enabled_tools(
|
||||
exec: &ExecContext,
|
||||
) -> Vec<codex_code_mode::ToolDefinition> {
|
||||
let router = build_nested_router(exec).await;
|
||||
let specs = router.specs();
|
||||
let specs = router.specs_including_deferred();
|
||||
collect_code_mode_tool_definitions(&specs)
|
||||
}
|
||||
|
||||
@@ -280,6 +281,18 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
|
||||
.await
|
||||
.list_all_tools()
|
||||
.await;
|
||||
let listed_mcp_tools = listed_mcp_tools
|
||||
.into_iter()
|
||||
.map(|(name, tool_info)| {
|
||||
(
|
||||
name,
|
||||
McpToolInput {
|
||||
tool_info,
|
||||
defer_loading: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let parallel_mcp_server_names = exec
|
||||
.turn
|
||||
.config
|
||||
@@ -296,7 +309,6 @@ async fn build_nested_router(exec: &ExecContext) -> ToolRouter {
|
||||
ToolRouter::from_config(
|
||||
&nested_tools_config,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: Some(listed_mcp_tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names,
|
||||
@@ -324,8 +336,18 @@ async fn call_nested_tool(
|
||||
)));
|
||||
}
|
||||
|
||||
let actual_kind = match tool_kind_for_name(tool_runtime.find_spec(&tool_name), &tool_name) {
|
||||
Ok(kind) => kind,
|
||||
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
|
||||
};
|
||||
|
||||
let (tool_call_name, payload) =
|
||||
if let Some(tool_info) = exec.session.resolve_mcp_tool_info(&tool_name).await {
|
||||
if actual_kind != codex_code_mode::CodeModeToolKind::Function {
|
||||
return Err(FunctionCallError::RespondToModel(format!(
|
||||
"MCP tool `{tool_name}` must be invoked with function arguments"
|
||||
)));
|
||||
}
|
||||
let raw_arguments = match serialize_function_tool_arguments(&tool_name, input) {
|
||||
Ok(raw_arguments) => raw_arguments,
|
||||
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
|
||||
@@ -339,7 +361,7 @@ async fn call_nested_tool(
|
||||
},
|
||||
)
|
||||
} else {
|
||||
match build_nested_tool_payload(tool_runtime.find_spec(&tool_name), &tool_name, input) {
|
||||
match build_nested_tool_payload(actual_kind, &tool_name, input) {
|
||||
Ok(payload) => (tool_name, payload),
|
||||
Err(error) => return Err(FunctionCallError::RespondToModel(error)),
|
||||
}
|
||||
@@ -381,11 +403,10 @@ fn tool_kind_for_name(
|
||||
}
|
||||
|
||||
fn build_nested_tool_payload(
|
||||
spec: Option<ToolSpec>,
|
||||
actual_kind: codex_code_mode::CodeModeToolKind,
|
||||
tool_name: &ToolName,
|
||||
input: Option<JsonValue>,
|
||||
) -> Result<ToolPayload, String> {
|
||||
let actual_kind = tool_kind_for_name(spec, tool_name)?;
|
||||
match actual_kind {
|
||||
codex_code_mode::CodeModeToolKind::Function => {
|
||||
build_function_tool_payload(tool_name, input)
|
||||
|
||||
@@ -173,6 +173,7 @@ fn default_limit_for_bucket(bucket: &str) -> usize {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
use crate::tools::tool_search_entry::build_tool_search_entries;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
@@ -239,7 +240,9 @@ mod tests {
|
||||
/*required*/ None,
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
output_schema: Some(codex_tools::mcp_call_tool_result_output_schema(
|
||||
serde_json::json!({}),
|
||||
)),
|
||||
}),
|
||||
ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: "list_events".to_string(),
|
||||
@@ -251,7 +254,9 @@ mod tests {
|
||||
/*required*/ None,
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
output_schema: Some(codex_tools::mcp_call_tool_result_output_schema(
|
||||
serde_json::json!({}),
|
||||
)),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@@ -426,6 +431,23 @@ mod tests {
|
||||
mcp_tools: Option<&std::collections::HashMap<String, ToolInfo>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> ToolSearchHandler {
|
||||
ToolSearchHandler::new(build_tool_search_entries(mcp_tools, dynamic_tools))
|
||||
let exposed_mcp_tools = mcp_tools.map(|mcp_tools| {
|
||||
mcp_tools
|
||||
.iter()
|
||||
.map(|(name, tool_info)| {
|
||||
(
|
||||
name.clone(),
|
||||
McpToolInput {
|
||||
tool_info: tool_info.clone(),
|
||||
defer_loading: true,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<std::collections::HashMap<_, _>>()
|
||||
});
|
||||
ToolSearchHandler::new(build_tool_search_entries(
|
||||
exposed_mcp_tools.as_ref(),
|
||||
dynamic_tools,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
7
codex-rs/core/src/tools/mcp_tool_input.rs
Normal file
7
codex-rs/core/src/tools/mcp_tool_input.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use codex_mcp::ToolInfo;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct McpToolInput {
|
||||
pub(crate) tool_info: ToolInfo,
|
||||
pub(crate) defer_loading: bool,
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub(crate) mod context;
|
||||
pub(crate) mod events;
|
||||
pub(crate) mod handlers;
|
||||
pub(crate) mod hook_names;
|
||||
pub(crate) mod mcp_tool_input;
|
||||
pub(crate) mod network_approval;
|
||||
pub(crate) mod orchestrator;
|
||||
pub(crate) mod parallel;
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::session::turn_context::TurnContext;
|
||||
use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
use crate::tools::registry::AnyToolResult;
|
||||
use crate::tools::registry::ToolArgumentDiffConsumer;
|
||||
use crate::tools::registry::ToolRegistry;
|
||||
use crate::tools::spec::build_specs_with_discoverable_tools;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::models::LocalShellAction;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
@@ -44,8 +44,7 @@ pub struct ToolRouter {
|
||||
}
|
||||
|
||||
pub(crate) struct ToolRouterParams<'a> {
|
||||
pub(crate) mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
pub(crate) deferred_mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
pub(crate) mcp_tools: Option<HashMap<String, McpToolInput>>,
|
||||
pub(crate) unavailable_called_tools: Vec<ToolName>,
|
||||
pub(crate) parallel_mcp_server_names: HashSet<String>,
|
||||
pub(crate) discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
@@ -56,7 +55,6 @@ impl ToolRouter {
|
||||
pub fn from_config(config: &ToolsConfig, params: ToolRouterParams<'_>) -> Self {
|
||||
let ToolRouterParams {
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
parallel_mcp_server_names,
|
||||
discoverable_tools,
|
||||
@@ -65,17 +63,11 @@ impl ToolRouter {
|
||||
let builder = build_specs_with_discoverable_tools(
|
||||
config,
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
discoverable_tools,
|
||||
dynamic_tools,
|
||||
);
|
||||
let (specs, registry) = builder.build();
|
||||
let deferred_dynamic_tools = dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading)
|
||||
.map(|tool| ToolName::new(tool.namespace.clone(), tool.name.clone()))
|
||||
.collect::<HashSet<_>>();
|
||||
let model_visible_specs = specs
|
||||
.iter()
|
||||
.filter_map(|configured_tool| {
|
||||
@@ -85,10 +77,7 @@ impl ToolRouter {
|
||||
return None;
|
||||
}
|
||||
|
||||
filter_deferred_dynamic_tool_spec(
|
||||
configured_tool.spec.clone(),
|
||||
&deferred_dynamic_tools,
|
||||
)
|
||||
filter_deferred_tool_spec(configured_tool.spec.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -100,7 +89,7 @@ impl ToolRouter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn specs(&self) -> Vec<ToolSpec> {
|
||||
pub fn specs_including_deferred(&self) -> Vec<ToolSpec> {
|
||||
self.specs
|
||||
.iter()
|
||||
.map(|config| config.spec.clone())
|
||||
@@ -164,8 +153,7 @@ impl ToolRouter {
|
||||
|
||||
pub fn tool_supports_parallel(&self, call: &ToolCall) -> bool {
|
||||
match &call.payload {
|
||||
// MCP parallel support is configured per server, including for deferred
|
||||
// tools that may not have a matching spec entry. Use the parsed payload
|
||||
// MCP parallel support is configured per server. Use the parsed payload
|
||||
// server so similarly named servers/tools cannot collide.
|
||||
ToolPayload::Mcp { server, .. } => self.parallel_mcp_server_names.contains(server),
|
||||
_ => self.configured_tool_supports_parallel(&call.tool_name),
|
||||
@@ -297,28 +285,18 @@ impl ToolRouter {
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_deferred_dynamic_tool_spec(
|
||||
spec: ToolSpec,
|
||||
deferred_dynamic_tools: &HashSet<ToolName>,
|
||||
) -> Option<ToolSpec> {
|
||||
if deferred_dynamic_tools.is_empty() {
|
||||
return Some(spec);
|
||||
}
|
||||
|
||||
fn filter_deferred_tool_spec(spec: ToolSpec) -> Option<ToolSpec> {
|
||||
match spec {
|
||||
ToolSpec::Function(tool) => {
|
||||
if deferred_dynamic_tools.contains(&ToolName::plain(tool.name.as_str())) {
|
||||
if tool.defer_loading == Some(true) {
|
||||
None
|
||||
} else {
|
||||
Some(ToolSpec::Function(tool))
|
||||
}
|
||||
}
|
||||
ToolSpec::Namespace(mut namespace) => {
|
||||
let namespace_name = namespace.name.clone();
|
||||
namespace.tools.retain(|tool| match tool {
|
||||
ResponsesApiNamespaceTool::Function(tool) => !deferred_dynamic_tools.contains(
|
||||
&ToolName::namespaced(namespace_name.as_str(), tool.name.as_str()),
|
||||
),
|
||||
ResponsesApiNamespaceTool::Function(tool) => tool.defer_loading != Some(true),
|
||||
});
|
||||
if namespace.tools.is_empty() {
|
||||
None
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::session::tests::make_session_and_context;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
@@ -29,10 +32,10 @@ async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow
|
||||
.await
|
||||
.list_all_tools()
|
||||
.await;
|
||||
let mcp_tools = expose_mcp_tools(mcp_tools, /*defer_loading*/ false);
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: Some(mcp_tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names: HashSet::new(),
|
||||
@@ -105,7 +108,6 @@ async fn mcp_parallel_support_uses_exact_payload_server() -> anyhow::Result<()>
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names: HashSet::from(["echo".to_string()]),
|
||||
@@ -172,7 +174,6 @@ async fn model_visible_specs_filter_deferred_dynamic_tools() -> anyhow::Result<(
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
ToolRouterParams {
|
||||
deferred_mcp_tools: None,
|
||||
mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names: HashSet::new(),
|
||||
@@ -187,7 +188,7 @@ async fn model_visible_specs_filter_deferred_dynamic_tools() -> anyhow::Result<(
|
||||
.is_some()
|
||||
);
|
||||
assert_eq!(
|
||||
namespace_function_names(&router.specs(), "codex_app"),
|
||||
namespace_function_names(&router.specs_including_deferred(), "codex_app"),
|
||||
vec![hidden_tool.to_string(), visible_tool.to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -198,6 +199,100 @@ async fn model_visible_specs_filter_deferred_dynamic_tools() -> anyhow::Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_visible_specs_filter_deferred_mcp_tools() -> anyhow::Result<()> {
|
||||
let (_, turn) = make_session_and_context().await;
|
||||
let hidden_tool = "hidden_mcp_tool";
|
||||
let visible_tool = "visible_mcp_tool";
|
||||
let mcp_tools = HashMap::from([
|
||||
(
|
||||
format!("mcp__test__{hidden_tool}"),
|
||||
exposed_mcp_tool(hidden_tool, /*defer_loading*/ true),
|
||||
),
|
||||
(
|
||||
format!("mcp__test__{visible_tool}"),
|
||||
exposed_mcp_tool(visible_tool, /*defer_loading*/ false),
|
||||
),
|
||||
]);
|
||||
|
||||
let router = ToolRouter::from_config(
|
||||
&turn.tools_config,
|
||||
ToolRouterParams {
|
||||
mcp_tools: Some(mcp_tools),
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names: HashSet::new(),
|
||||
discoverable_tools: None,
|
||||
dynamic_tools: &[],
|
||||
},
|
||||
);
|
||||
|
||||
assert!(
|
||||
router
|
||||
.find_spec(&ToolName::namespaced("mcp__test__", hidden_tool))
|
||||
.is_some()
|
||||
);
|
||||
assert_eq!(
|
||||
namespace_function_names(&router.specs_including_deferred(), "mcp__test__"),
|
||||
vec![hidden_tool.to_string(), visible_tool.to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
namespace_function_names(&router.model_visible_specs(), "mcp__test__"),
|
||||
vec![visible_tool.to_string()]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expose_mcp_tools(
|
||||
mcp_tools: HashMap<String, ToolInfo>,
|
||||
defer_loading: bool,
|
||||
) -> HashMap<String, McpToolInput> {
|
||||
mcp_tools
|
||||
.into_iter()
|
||||
.map(|(name, tool_info)| {
|
||||
(
|
||||
name,
|
||||
McpToolInput {
|
||||
tool_info,
|
||||
defer_loading,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn exposed_mcp_tool(tool_name: &str, defer_loading: bool) -> McpToolInput {
|
||||
let tool = rmcp::model::Tool {
|
||||
name: tool_name.to_string().into(),
|
||||
title: None,
|
||||
description: Some(format!("Test MCP tool {tool_name}").into()),
|
||||
input_schema: std::sync::Arc::new(rmcp::model::object(json!({
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false,
|
||||
}))),
|
||||
output_schema: None,
|
||||
annotations: None,
|
||||
execution: None,
|
||||
icons: None,
|
||||
meta: None,
|
||||
};
|
||||
McpToolInput {
|
||||
tool_info: ToolInfo {
|
||||
server_name: "test".to_string(),
|
||||
callable_name: tool_name.to_string(),
|
||||
callable_namespace: "mcp__test__".to_string(),
|
||||
server_instructions: None,
|
||||
tool,
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
plugin_display_names: Vec::new(),
|
||||
connector_description: None,
|
||||
},
|
||||
defer_loading,
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_function_names(specs: &[ToolSpec], namespace_name: &str) -> Vec<String> {
|
||||
specs
|
||||
.iter()
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::tools::handlers::agent_jobs::BatchJobHandler;
|
||||
use crate::tools::handlers::multi_agents_common::DEFAULT_WAIT_TIMEOUT_MS;
|
||||
use crate::tools::handlers::multi_agents_common::MAX_WAIT_TIMEOUT_MS;
|
||||
use crate::tools::handlers::multi_agents_common::MIN_WAIT_TIMEOUT_MS;
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
use crate::tools::registry::ToolRegistryBuilder;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tools::AdditionalProperties;
|
||||
use codex_tools::DiscoverableTool;
|
||||
@@ -14,7 +14,6 @@ use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolHandlerKind;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolNamespace;
|
||||
use codex_tools::ToolRegistryPlanDeferredTool;
|
||||
use codex_tools::ToolRegistryPlanMcpTool;
|
||||
use codex_tools::ToolRegistryPlanParams;
|
||||
use codex_tools::ToolUserShellType;
|
||||
@@ -41,18 +40,26 @@ struct McpToolPlanInputs<'a> {
|
||||
tool_namespaces: HashMap<String, ToolNamespace>,
|
||||
}
|
||||
|
||||
fn map_mcp_tools_for_plan(mcp_tools: &HashMap<String, ToolInfo>) -> McpToolPlanInputs<'_> {
|
||||
fn map_mcp_tools_for_plan(mcp_tools: &HashMap<String, McpToolInput>) -> McpToolPlanInputs<'_> {
|
||||
McpToolPlanInputs {
|
||||
mcp_tools: mcp_tools
|
||||
.values()
|
||||
.map(|tool| ToolRegistryPlanMcpTool {
|
||||
name: tool.canonical_tool_name(),
|
||||
tool: &tool.tool,
|
||||
.map(|exposed_tool| {
|
||||
let tool = &exposed_tool.tool_info;
|
||||
ToolRegistryPlanMcpTool {
|
||||
name: tool.canonical_tool_name(),
|
||||
tool: &tool.tool,
|
||||
server_name: tool.server_name.as_str(),
|
||||
connector_name: tool.connector_name.as_deref(),
|
||||
connector_description: tool.connector_description.as_deref(),
|
||||
defer_loading: exposed_tool.defer_loading,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
tool_namespaces: mcp_tools
|
||||
.values()
|
||||
.map(|tool| {
|
||||
.map(|exposed_tool| {
|
||||
let tool = &exposed_tool.tool_info;
|
||||
(
|
||||
tool.callable_namespace.clone(),
|
||||
ToolNamespace {
|
||||
@@ -70,8 +77,7 @@ fn map_mcp_tools_for_plan(mcp_tools: &HashMap<String, ToolInfo>) -> McpToolPlanI
|
||||
|
||||
pub(crate) fn build_specs_with_discoverable_tools(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
deferred_mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
mcp_tools: Option<HashMap<String, McpToolInput>>,
|
||||
unavailable_called_tools: Vec<ToolName>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
@@ -111,17 +117,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
|
||||
let mut builder = ToolRegistryBuilder::new();
|
||||
let mcp_tool_plan_inputs = mcp_tools.as_ref().map(map_mcp_tools_for_plan);
|
||||
let deferred_mcp_tool_sources = deferred_mcp_tools.as_ref().map(|tools| {
|
||||
tools
|
||||
.values()
|
||||
.map(|tool| ToolRegistryPlanDeferredTool {
|
||||
name: tool.canonical_tool_name(),
|
||||
server_name: tool.server_name.as_str(),
|
||||
connector_name: tool.connector_name.as_deref(),
|
||||
connector_description: tool.connector_description.as_deref(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let default_agent_type_description =
|
||||
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
|
||||
let plan = build_tool_registry_plan(
|
||||
@@ -130,7 +125,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
mcp_tools: mcp_tool_plan_inputs
|
||||
.as_ref()
|
||||
.map(|inputs| inputs.mcp_tools.as_slice()),
|
||||
deferred_mcp_tools: deferred_mcp_tool_sources.as_deref(),
|
||||
tool_namespaces: mcp_tool_plan_inputs
|
||||
.as_ref()
|
||||
.map(|inputs| &inputs.tool_namespaces),
|
||||
@@ -260,10 +254,8 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
}
|
||||
ToolHandlerKind::ToolSearch => {
|
||||
if tool_search_handler.is_none() {
|
||||
let entries = build_tool_search_entries(
|
||||
deferred_mcp_tools.as_ref(),
|
||||
&deferred_dynamic_tools,
|
||||
);
|
||||
let entries =
|
||||
build_tool_search_entries(mcp_tools.as_ref(), &deferred_dynamic_tools);
|
||||
tool_search_handler = Some(Arc::new(ToolSearchHandler::new(entries)));
|
||||
}
|
||||
if let Some(tool_search_handler) = tool_search_handler.as_ref() {
|
||||
@@ -287,16 +279,6 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(deferred_mcp_tools) = deferred_mcp_tools.as_ref() {
|
||||
for (name, _) in deferred_mcp_tools.iter().filter(|(name, _)| {
|
||||
!mcp_tools
|
||||
.as_ref()
|
||||
.is_some_and(|tools| tools.contains_key(*name))
|
||||
}) {
|
||||
builder.register_handler(name.clone(), mcp_handler.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for unavailable_tool in unavailable_called_tools {
|
||||
let tool_name = unavailable_tool.display();
|
||||
if existing_spec_names.insert(tool_name.clone()) {
|
||||
|
||||
@@ -3,11 +3,13 @@ use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
use crate::test_support::construct_model_info_offline;
|
||||
use crate::tools::ToolRouter;
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
use crate::tools::router::ToolRouterParams;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_models_manager::model_info::with_config_overrides;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
@@ -32,7 +34,7 @@ use codex_tools::ToolsConfigParams;
|
||||
use codex_tools::UnifiedExecShellMode;
|
||||
use codex_tools::ZshForkConfig;
|
||||
use codex_tools::mcp_call_tool_result_output_schema;
|
||||
use codex_tools::mcp_tool_to_deferred_responses_api_tool;
|
||||
use codex_tools::mcp_tool_to_responses_api_tool;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::assert_regex_match;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -130,9 +132,10 @@ fn deferred_responses_api_tool_serializes_with_defer_loading() {
|
||||
);
|
||||
|
||||
let serialized = serde_json::to_value(ToolSpec::Function(
|
||||
mcp_tool_to_deferred_responses_api_tool(
|
||||
mcp_tool_to_responses_api_tool(
|
||||
&ToolName::namespaced("mcp__codex_apps__", "lookup_order"),
|
||||
&tool,
|
||||
/*defer_loading*/ true,
|
||||
)
|
||||
.expect("convert deferred tool"),
|
||||
))
|
||||
@@ -237,13 +240,7 @@ async fn multi_agent_v2_tools_config() -> ToolsConfig {
|
||||
}
|
||||
|
||||
fn multi_agent_v2_spawn_agent_description(tools_config: &ToolsConfig) -> String {
|
||||
let (tools, _) = build_specs(
|
||||
tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let (tools, _) = build_specs(tools_config, /*mcp_tools*/ None, &[]).build();
|
||||
let spawn_agent = find_tool(&tools, "spawn_agent");
|
||||
let ToolSpec::Function(ResponsesApiTool { description, .. }) = &spawn_agent.spec else {
|
||||
panic!("spawn_agent should be a function tool");
|
||||
@@ -266,36 +263,45 @@ async fn model_info_from_models_json(slug: &str) -> ModelInfo {
|
||||
/// Builds the tool registry builder while collecting tool specs for later serialization.
|
||||
fn build_specs(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
deferred_mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
mcp_tools: Option<HashMap<String, McpToolInput>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> ToolRegistryBuilder {
|
||||
build_specs_with_unavailable_tools(
|
||||
config,
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
Vec::new(),
|
||||
dynamic_tools,
|
||||
)
|
||||
build_specs_with_unavailable_tools(config, mcp_tools, Vec::new(), dynamic_tools)
|
||||
}
|
||||
|
||||
fn build_specs_with_unavailable_tools(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
deferred_mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
mcp_tools: Option<HashMap<String, McpToolInput>>,
|
||||
unavailable_called_tools: Vec<ToolName>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> ToolRegistryBuilder {
|
||||
build_specs_with_discoverable_tools(
|
||||
config,
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
unavailable_called_tools,
|
||||
/*discoverable_tools*/ None,
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
|
||||
fn mcp_tool_inputs(
|
||||
mcp_tools: HashMap<String, ToolInfo>,
|
||||
defer_loading: bool,
|
||||
) -> HashMap<String, McpToolInput> {
|
||||
mcp_tools
|
||||
.into_iter()
|
||||
.map(|(name, tool_info)| {
|
||||
(
|
||||
name,
|
||||
McpToolInput {
|
||||
tool_info,
|
||||
defer_loading,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_provided_unified_exec_is_blocked_for_windows_sandboxed_policies() {
|
||||
let mut model_info = model_info_from_models_json("gpt-5.4").await;
|
||||
@@ -338,13 +344,7 @@ async fn get_memory_requires_feature_flag() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]).build();
|
||||
assert!(
|
||||
!tools.iter().any(|t| t.spec.name() == "get_memory"),
|
||||
"get_memory should be disabled when memory_tool feature is off"
|
||||
@@ -374,7 +374,6 @@ async fn assert_model_tools(
|
||||
&tools_config,
|
||||
ToolRouterParams {
|
||||
mcp_tools: None,
|
||||
deferred_mcp_tools: None,
|
||||
unavailable_called_tools: Vec::new(),
|
||||
parallel_mcp_server_names: std::collections::HashSet::new(),
|
||||
discoverable_tools: None,
|
||||
@@ -653,13 +652,7 @@ async fn test_build_specs_default_shell_present() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::new()),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let (tools, _) = build_specs(&tools_config, Some(HashMap::new()), &[]).build();
|
||||
|
||||
// Only check the shell variant and a couple of core tools.
|
||||
let mut subset = vec!["exec_command", "write_stdin", "update_plan"];
|
||||
@@ -815,7 +808,6 @@ async fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Vec::new(),
|
||||
discoverable_tools.clone(),
|
||||
&[],
|
||||
@@ -832,7 +824,7 @@ async fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn search_tool_description_handles_no_enabled_mcp_tools() {
|
||||
async fn search_tool_is_not_registered_without_deferred_tools() {
|
||||
let model_info = search_capable_model_info().await;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
@@ -849,20 +841,12 @@ async fn search_tool_description_handles_no_enabled_mcp_tools() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ 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");
|
||||
};
|
||||
|
||||
assert!(description.contains("None currently enabled."));
|
||||
assert!(!description.contains("{{source_descriptions}}"));
|
||||
let (tools, _) = build_specs(&tools_config, Some(HashMap::new()), &[]).build();
|
||||
assert!(
|
||||
!tools
|
||||
.iter()
|
||||
.any(|tool| tool.name() == TOOL_SEARCH_TOOL_NAME)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -885,25 +869,27 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
Some(HashMap::from([(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
callable_name: "_create_event".to_string(),
|
||||
callable_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar_create_event",
|
||||
"Create calendar event",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
plugin_display_names: Vec::new(),
|
||||
connector_description: None,
|
||||
},
|
||||
)])),
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
callable_name: "_create_event".to_string(),
|
||||
callable_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar_create_event",
|
||||
"Create calendar event",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
plugin_display_names: Vec::new(),
|
||||
connector_description: None,
|
||||
},
|
||||
)]),
|
||||
/*defer_loading*/ true,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -936,59 +922,61 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() {
|
||||
|
||||
let (_, registry) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
Some(HashMap::from([
|
||||
(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
callable_name: "_create_event".to_string(),
|
||||
callable_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar-create-event",
|
||||
"Create calendar event",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
connector_description: None,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"mcp__codex_apps__calendar_list_events".to_string(),
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
callable_name: "_list_events".to_string(),
|
||||
callable_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar-list-events",
|
||||
"List calendar events",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
connector_description: None,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"mcp__rmcp__echo".to_string(),
|
||||
ToolInfo {
|
||||
server_name: "rmcp".to_string(),
|
||||
callable_name: "echo".to_string(),
|
||||
callable_namespace: "mcp__rmcp__".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool("echo", "Echo", serde_json::json!({"type": "object"})),
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
),
|
||||
])),
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([
|
||||
(
|
||||
"mcp__codex_apps__calendar_create_event".to_string(),
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
callable_name: "_create_event".to_string(),
|
||||
callable_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar-create-event",
|
||||
"Create calendar event",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
connector_description: None,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"mcp__codex_apps__calendar_list_events".to_string(),
|
||||
ToolInfo {
|
||||
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
callable_name: "_list_events".to_string(),
|
||||
callable_namespace: "mcp__codex_apps__calendar".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool(
|
||||
"calendar-list-events",
|
||||
"List calendar events",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
connector_description: None,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"mcp__rmcp__echo".to_string(),
|
||||
ToolInfo {
|
||||
server_name: "rmcp".to_string(),
|
||||
callable_name: "echo".to_string(),
|
||||
callable_namespace: "mcp__rmcp__".to_string(),
|
||||
server_instructions: None,
|
||||
tool: mcp_tool("echo", "Echo", serde_json::json!({"type": "object"})),
|
||||
connector_id: None,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
),
|
||||
]),
|
||||
/*defer_loading*/ true,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1021,15 +1009,17 @@ async fn direct_mcp_tools_register_namespaced_handlers() {
|
||||
|
||||
let (_, registry) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
"mcp__test_server__echo".to_string(),
|
||||
mcp_tool_info(mcp_tool(
|
||||
"echo",
|
||||
"Echo",
|
||||
serde_json::json!({"type": "object"}),
|
||||
)),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
"mcp__test_server__echo".to_string(),
|
||||
mcp_tool_info(mcp_tool(
|
||||
"echo",
|
||||
"Echo",
|
||||
serde_json::json!({"type": "object"}),
|
||||
)),
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1060,7 +1050,6 @@ async fn unavailable_mcp_tools_are_exposed_as_dummy_function_tools() {
|
||||
let (tools, registry) = build_specs_with_unavailable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
vec![unavailable_tool],
|
||||
&[],
|
||||
)
|
||||
@@ -1107,23 +1096,25 @@ async fn test_mcp_tool_property_missing_type_defaults_to_string() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
"dash/search".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/search",
|
||||
mcp_tool(
|
||||
"search",
|
||||
"Search docs",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"description": "search query"}
|
||||
}
|
||||
}),
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
"dash/search".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/search",
|
||||
mcp_tool(
|
||||
"search",
|
||||
"Search docs",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"description": "search query"}
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1170,21 +1161,23 @@ async fn test_mcp_tool_preserves_integer_schema() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
"dash/paginate".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/paginate",
|
||||
mcp_tool(
|
||||
"paginate",
|
||||
"Pagination",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {"page": {"type": "integer"}}
|
||||
}),
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
"dash/paginate".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/paginate",
|
||||
mcp_tool(
|
||||
"paginate",
|
||||
"Pagination",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {"page": {"type": "integer"}}
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1232,21 +1225,23 @@ async fn test_mcp_tool_array_without_items_gets_default_string_items() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
"dash/tags".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/tags",
|
||||
mcp_tool(
|
||||
"tags",
|
||||
"Tags",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {"tags": {"type": "array"}}
|
||||
}),
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
"dash/tags".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/tags",
|
||||
mcp_tool(
|
||||
"tags",
|
||||
"Tags",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {"tags": {"type": "array"}}
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1296,23 +1291,25 @@ async fn test_mcp_tool_anyof_defaults_to_string() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
"dash/value".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/value",
|
||||
mcp_tool(
|
||||
"value",
|
||||
"AnyOf Value",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {"anyOf": [{"type": "string"}, {"type": "number"}]}
|
||||
}
|
||||
}),
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
"dash/value".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"dash/value",
|
||||
mcp_tool(
|
||||
"value",
|
||||
"AnyOf Value",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {"anyOf": [{"type": "string"}, {"type": "number"}]}
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
@@ -1364,40 +1361,42 @@ async fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
"test_server/do_something_cool".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"test_server/do_something_cool",
|
||||
mcp_tool(
|
||||
"do_something_cool",
|
||||
"Do something cool",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_argument": {"type": "string"},
|
||||
"number_argument": {"type": "number"},
|
||||
"object_argument": {
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
"test_server/do_something_cool".to_string(),
|
||||
mcp_tool_info_with_display_name(
|
||||
"test_server/do_something_cool",
|
||||
mcp_tool(
|
||||
"do_something_cool",
|
||||
"Do something cool",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_property": {"type": "string"},
|
||||
"number_property": {"type": "number"}
|
||||
},
|
||||
"required": ["string_property", "number_property"],
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"addtl_prop": {"type": "string"}
|
||||
},
|
||||
"required": ["addtl_prop"],
|
||||
"additionalProperties": false
|
||||
"string_argument": {"type": "string"},
|
||||
"number_argument": {"type": "number"},
|
||||
"object_argument": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_property": {"type": "string"},
|
||||
"number_property": {"type": "number"}
|
||||
},
|
||||
"required": ["string_property", "number_property"],
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"addtl_prop": {"type": "string"}
|
||||
},
|
||||
"required": ["addtl_prop"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::tools::mcp_tool_input::McpToolInput;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tools::LoadableToolSpec;
|
||||
@@ -14,13 +15,19 @@ pub(crate) struct ToolSearchEntry {
|
||||
}
|
||||
|
||||
pub(crate) fn build_tool_search_entries(
|
||||
mcp_tools: Option<&HashMap<String, ToolInfo>>,
|
||||
mcp_tools: Option<&HashMap<String, McpToolInput>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> Vec<ToolSearchEntry> {
|
||||
let mut entries = Vec::new();
|
||||
|
||||
let mut mcp_tools = mcp_tools
|
||||
.map(|tools| tools.values().collect::<Vec<_>>())
|
||||
.map(|tools| {
|
||||
tools
|
||||
.values()
|
||||
.filter(|tool| tool.defer_loading)
|
||||
.map(|tool| &tool.tool_info)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
mcp_tools.sort_by_key(|info| info.canonical_tool_name().display());
|
||||
for info in mcp_tools {
|
||||
|
||||
@@ -37,7 +37,6 @@ schema and Responses API tool primitives that no longer need to live in
|
||||
- `dynamic_tool_to_loadable_tool_spec()`
|
||||
- `dynamic_tool_to_responses_api_tool()`
|
||||
- `mcp_tool_to_responses_api_tool()`
|
||||
- `mcp_tool_to_deferred_responses_api_tool()`
|
||||
- `augment_tool_spec_for_code_mode()`
|
||||
- `tool_spec_to_code_mode_tool_definition()`
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ pub fn collect_code_mode_exec_prompt_tool_definitions<'a>(
|
||||
) -> Vec<CodeModeToolDefinition> {
|
||||
let mut tool_definitions = specs
|
||||
.into_iter()
|
||||
.flat_map(code_mode_tool_definitions_for_spec)
|
||||
.flat_map(code_mode_exec_prompt_tool_definitions_for_spec)
|
||||
.filter(|definition| codex_code_mode::is_code_mode_nested_tool(&definition.name))
|
||||
.collect::<Vec<_>>();
|
||||
tool_definitions.sort_by(|left, right| left.name.cmp(&right.name));
|
||||
@@ -178,8 +178,22 @@ fn code_mode_tool_definition_for_spec(spec: &ToolSpec) -> Option<CodeModeToolDef
|
||||
}
|
||||
|
||||
fn code_mode_tool_definitions_for_spec(spec: &ToolSpec) -> Vec<CodeModeToolDefinition> {
|
||||
code_mode_tool_definitions_for_spec_with_deferred(spec, /*include_deferred*/ true)
|
||||
}
|
||||
|
||||
fn code_mode_exec_prompt_tool_definitions_for_spec(spec: &ToolSpec) -> Vec<CodeModeToolDefinition> {
|
||||
code_mode_tool_definitions_for_spec_with_deferred(spec, /*include_deferred*/ false)
|
||||
}
|
||||
|
||||
fn code_mode_tool_definitions_for_spec_with_deferred(
|
||||
spec: &ToolSpec,
|
||||
include_deferred: bool,
|
||||
) -> Vec<CodeModeToolDefinition> {
|
||||
match spec {
|
||||
ToolSpec::Function(tool) => {
|
||||
if !include_deferred && tool.defer_loading == Some(true) {
|
||||
return Vec::new();
|
||||
}
|
||||
let name = tool.name.clone();
|
||||
vec![CodeModeToolDefinition {
|
||||
tool_name: ToolName::plain(name.clone()),
|
||||
@@ -204,17 +218,20 @@ fn code_mode_tool_definitions_for_spec(spec: &ToolSpec) -> Vec<CodeModeToolDefin
|
||||
ToolSpec::Namespace(namespace) => namespace
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| match tool {
|
||||
.filter_map(|tool| match tool {
|
||||
ResponsesApiNamespaceTool::Function(tool) => {
|
||||
if !include_deferred && tool.defer_loading == Some(true) {
|
||||
return None;
|
||||
}
|
||||
let tool_name = ToolName::namespaced(namespace.name.clone(), tool.name.clone());
|
||||
CodeModeToolDefinition {
|
||||
Some(CodeModeToolDefinition {
|
||||
name: code_mode_name_for_tool_name(&tool_name),
|
||||
tool_name,
|
||||
description: tool.description.clone(),
|
||||
kind: CodeModeToolKind::Function,
|
||||
input_schema: serde_json::to_value(&tool.parameters).ok(),
|
||||
output_schema: tool.output_schema.clone(),
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -95,7 +95,6 @@ pub use responses_api::coalesce_loadable_tool_specs;
|
||||
pub(crate) use responses_api::default_namespace_description;
|
||||
pub use responses_api::dynamic_tool_to_loadable_tool_spec;
|
||||
pub use responses_api::dynamic_tool_to_responses_api_tool;
|
||||
pub use responses_api::mcp_tool_to_deferred_responses_api_tool;
|
||||
pub use responses_api::mcp_tool_to_responses_api_tool;
|
||||
pub use responses_api::tool_definition_to_responses_api_tool;
|
||||
pub use tool_config::ShellCommandBackendConfig;
|
||||
@@ -127,7 +126,6 @@ pub use tool_registry_plan_types::ToolHandlerKind;
|
||||
pub use tool_registry_plan_types::ToolHandlerSpec;
|
||||
pub use tool_registry_plan_types::ToolNamespace;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlan;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanDeferredTool;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanMcpTool;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanParams;
|
||||
pub use tool_spec::ConfiguredToolSpec;
|
||||
|
||||
@@ -122,21 +122,11 @@ pub fn coalesce_loadable_tool_specs(
|
||||
pub fn mcp_tool_to_responses_api_tool(
|
||||
tool_name: &ToolName,
|
||||
tool: &rmcp::model::Tool,
|
||||
defer_loading: bool,
|
||||
) -> Result<ResponsesApiTool, serde_json::Error> {
|
||||
Ok(tool_definition_to_responses_api_tool(
|
||||
parse_mcp_tool(tool)?.renamed(tool_name.name.clone()),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn mcp_tool_to_deferred_responses_api_tool(
|
||||
tool_name: &ToolName,
|
||||
tool: &rmcp::model::Tool,
|
||||
) -> Result<ResponsesApiTool, serde_json::Error> {
|
||||
Ok(tool_definition_to_responses_api_tool(
|
||||
parse_mcp_tool(tool)?
|
||||
.renamed(tool_name.name.clone())
|
||||
.into_deferred(),
|
||||
))
|
||||
let mut tool_definition = parse_mcp_tool(tool)?.renamed(tool_name.name.clone());
|
||||
tool_definition.defer_loading = defer_loading;
|
||||
Ok(tool_definition_to_responses_api_tool(tool_definition))
|
||||
}
|
||||
|
||||
pub fn tool_definition_to_responses_api_tool(tool_definition: ToolDefinition) -> ResponsesApiTool {
|
||||
|
||||
@@ -3,11 +3,12 @@ use super::ResponsesApiNamespace;
|
||||
use super::ResponsesApiNamespaceTool;
|
||||
use super::ResponsesApiTool;
|
||||
use super::dynamic_tool_to_responses_api_tool;
|
||||
use super::mcp_tool_to_deferred_responses_api_tool;
|
||||
use super::mcp_tool_to_responses_api_tool;
|
||||
use super::tool_definition_to_responses_api_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolName;
|
||||
use crate::mcp_call_tool_result_output_schema;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@@ -86,7 +87,7 @@ fn dynamic_tool_to_responses_api_tool_preserves_defer_loading() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_to_deferred_responses_api_tool_sets_defer_loading() {
|
||||
fn mcp_tool_to_responses_api_tool_sets_defer_loading_from_argument() {
|
||||
let tool = rmcp::model::Tool {
|
||||
name: "lookup_order".to_string().into(),
|
||||
title: None,
|
||||
@@ -107,9 +108,10 @@ fn mcp_tool_to_deferred_responses_api_tool_sets_defer_loading() {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
mcp_tool_to_deferred_responses_api_tool(
|
||||
mcp_tool_to_responses_api_tool(
|
||||
&ToolName::namespaced("mcp__codex_apps__", "lookup_order"),
|
||||
&tool,
|
||||
/*defer_loading*/ true,
|
||||
)
|
||||
.expect("convert deferred tool"),
|
||||
ResponsesApiTool {
|
||||
@@ -125,7 +127,7 @@ fn mcp_tool_to_deferred_responses_api_tool_sets_defer_loading() {
|
||||
Some(vec!["order_id".to_string()]),
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(json!({}))),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,12 +17,6 @@ impl ToolDefinition {
|
||||
self.name = name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_deferred(mut self) -> Self {
|
||||
self.output_schema = None;
|
||||
self.defer_loading = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -29,15 +29,3 @@ fn renamed_overrides_name_only() {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_deferred_drops_output_schema_and_sets_defer_loading() {
|
||||
assert_eq!(
|
||||
tool_definition().into_deferred(),
|
||||
ToolDefinition {
|
||||
output_schema: None,
|
||||
defer_loading: true,
|
||||
..tool_definition()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::ResponsesApiTool;
|
||||
use crate::ToolName;
|
||||
use crate::ToolSpec;
|
||||
use crate::default_namespace_description;
|
||||
use crate::mcp_tool_to_deferred_responses_api_tool;
|
||||
use crate::mcp_tool_to_responses_api_tool;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -233,7 +233,7 @@ fn tool_search_result_source_to_namespace_tool(
|
||||
source: ToolSearchResultSource<'_>,
|
||||
) -> Result<ResponsesApiNamespaceTool, serde_json::Error> {
|
||||
let tool_name = ToolName::namespaced(source.tool_namespace, source.tool_name);
|
||||
mcp_tool_to_deferred_responses_api_tool(&tool_name, source.tool)
|
||||
mcp_tool_to_responses_api_tool(&tool_name, source.tool, /*defer_loading*/ true)
|
||||
.map(ResponsesApiNamespaceTool::Function)
|
||||
}
|
||||
|
||||
|
||||
@@ -112,10 +112,7 @@ pub fn build_tool_registry_plan(
|
||||
&enabled_tools,
|
||||
&namespace_descriptions,
|
||||
config.code_mode_only_enabled,
|
||||
config.search_tool
|
||||
&& params
|
||||
.deferred_mcp_tools
|
||||
.is_some_and(|tools| !tools.is_empty()),
|
||||
config.search_tool && deferred_tools_available(params),
|
||||
),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
@@ -260,29 +257,27 @@ pub fn build_tool_registry_plan(
|
||||
plan.register_handler("request_permissions", ToolHandlerKind::RequestPermissions);
|
||||
}
|
||||
|
||||
let deferred_dynamic_tools = params
|
||||
.dynamic_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.defer_loading)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if config.search_tool
|
||||
&& (params.deferred_mcp_tools.is_some() || !deferred_dynamic_tools.is_empty())
|
||||
{
|
||||
let deferred_dynamic_tools_available =
|
||||
params.dynamic_tools.iter().any(|tool| tool.defer_loading);
|
||||
if config.search_tool && deferred_tools_available(params) {
|
||||
let mut search_source_infos = params
|
||||
.deferred_mcp_tools
|
||||
.map(|deferred_mcp_tools| {
|
||||
collect_tool_search_source_infos(deferred_mcp_tools.iter().map(|tool| {
|
||||
ToolSearchSource {
|
||||
.mcp_tools
|
||||
.map(|mcp_tools| {
|
||||
collect_tool_search_source_infos(mcp_tools.iter().filter_map(|tool| {
|
||||
if !tool.defer_loading {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ToolSearchSource {
|
||||
server_name: tool.server_name,
|
||||
connector_name: tool.connector_name,
|
||||
connector_description: tool.connector_description,
|
||||
}
|
||||
})
|
||||
}))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if !deferred_dynamic_tools.is_empty() {
|
||||
if deferred_dynamic_tools_available {
|
||||
search_source_infos.push(ToolSearchSourceInfo {
|
||||
name: "Dynamic tools".to_string(),
|
||||
description: Some("Tools provided by the current Codex thread.".to_string()),
|
||||
@@ -295,12 +290,6 @@ pub fn build_tool_registry_plan(
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
plan.register_handler(TOOL_SEARCH_TOOL_NAME, ToolHandlerKind::ToolSearch);
|
||||
|
||||
if let Some(deferred_mcp_tools) = params.deferred_mcp_tools {
|
||||
for tool in deferred_mcp_tools {
|
||||
plan.register_handler(tool.name.clone(), ToolHandlerKind::Mcp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.tool_suggest
|
||||
@@ -537,7 +526,11 @@ pub fn build_tool_registry_plan(
|
||||
});
|
||||
let mut tools = Vec::new();
|
||||
for tool in entries {
|
||||
match mcp_tool_to_responses_api_tool(&tool.name, tool.tool) {
|
||||
match mcp_tool_to_responses_api_tool(
|
||||
&tool.name,
|
||||
tool.tool,
|
||||
/*defer_loading*/ tool.defer_loading,
|
||||
) {
|
||||
Ok(converted_tool) => {
|
||||
tools.push(ResponsesApiNamespaceTool::Function(converted_tool));
|
||||
plan.register_handler(tool.name, ToolHandlerKind::Mcp);
|
||||
@@ -592,6 +585,13 @@ pub fn build_tool_registry_plan(
|
||||
plan
|
||||
}
|
||||
|
||||
fn deferred_tools_available(params: ToolRegistryPlanParams<'_>) -> bool {
|
||||
params
|
||||
.mcp_tools
|
||||
.is_some_and(|tools| tools.iter().any(|tool| tool.defer_loading))
|
||||
|| params.dynamic_tools.iter().any(|tool| tool.defer_loading)
|
||||
}
|
||||
|
||||
fn compare_code_mode_tools(
|
||||
left: &codex_code_mode::ToolDefinition,
|
||||
right: &codex_code_mode::ToolDefinition,
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::ResponsesApiWebSearchUserLocation;
|
||||
use crate::ToolHandlerSpec;
|
||||
use crate::ToolName;
|
||||
use crate::ToolNamespace;
|
||||
use crate::ToolRegistryPlanDeferredTool;
|
||||
use crate::ToolRegistryPlanMcpTool;
|
||||
use crate::ToolsConfigParams;
|
||||
use crate::WaitAgentTimeoutOptions;
|
||||
@@ -60,12 +59,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
let mut actual = BTreeMap::new();
|
||||
let mut duplicate_names = Vec::new();
|
||||
@@ -172,12 +166,7 @@ fn test_build_specs_collab_tools_enabled() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
@@ -210,12 +199,7 @@ fn goal_tools_require_goals_feature() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
assert_lacks_tool_name(&tools, "get_goal");
|
||||
assert_lacks_tool_name(&tools, "create_goal");
|
||||
assert_lacks_tool_name(&tools, "update_goal");
|
||||
@@ -231,12 +215,7 @@ fn goal_tools_require_goals_feature() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
assert_contains_tool_names(&tools, &["get_goal", "create_goal", "update_goal"]);
|
||||
}
|
||||
|
||||
@@ -257,12 +236,7 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
@@ -400,12 +374,7 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
@@ -435,12 +404,7 @@ fn view_image_tool_omits_detail_without_original_detail_support() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let view_image = find_tool(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &view_image.spec else {
|
||||
panic!("view_image should be a function tool");
|
||||
@@ -465,12 +429,7 @@ fn view_image_tool_includes_detail_with_original_detail_support() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let view_image = find_tool(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &view_image.spec else {
|
||||
panic!("view_image should be a function tool");
|
||||
@@ -506,12 +465,7 @@ fn disabled_environment_omits_environment_backed_tools() {
|
||||
tools_config
|
||||
.experimental_supported_tools
|
||||
.push("list_dir".to_string());
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert_lacks_tool_name(&tools, "exec_command");
|
||||
assert_lacks_tool_name(&tools, "write_stdin");
|
||||
@@ -540,12 +494,7 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
@@ -577,12 +526,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let request_user_input_tool = find_tool(&tools, REQUEST_USER_INPUT_TOOL_NAME);
|
||||
assert_eq!(
|
||||
request_user_input_tool.spec,
|
||||
@@ -600,12 +544,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let request_user_input_tool = find_tool(&tools, REQUEST_USER_INPUT_TOOL_NAME);
|
||||
assert_eq!(
|
||||
request_user_input_tool.spec,
|
||||
@@ -628,12 +567,7 @@ fn request_permissions_requires_feature_flag() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
assert_lacks_tool_name(&tools, "request_permissions");
|
||||
|
||||
let mut features = Features::with_defaults();
|
||||
@@ -648,12 +582,7 @@ fn request_permissions_requires_feature_flag() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let request_permissions_tool = find_tool(&tools, "request_permissions");
|
||||
assert_eq!(
|
||||
request_permissions_tool.spec,
|
||||
@@ -677,12 +606,7 @@ fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert_lacks_tool_name(&tools, "request_permissions");
|
||||
}
|
||||
@@ -708,12 +632,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (default_tools, _) = build_specs(
|
||||
&default_tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (default_tools, _) = build_specs(&default_tools_config, /*mcp_tools*/ None, &[]);
|
||||
assert!(
|
||||
!default_tools
|
||||
.iter()
|
||||
@@ -731,12 +650,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (supported_tools, _) = build_specs(
|
||||
&supported_tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (supported_tools, _) = build_specs(&supported_tools_config, /*mcp_tools*/ None, &[]);
|
||||
assert_contains_tool_names(&supported_tools, &["image_generation"]);
|
||||
let image_generation_tool = find_tool(&supported_tools, "image_generation");
|
||||
assert_eq!(
|
||||
@@ -757,12 +671,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
assert!(
|
||||
!tools
|
||||
.iter()
|
||||
@@ -787,12 +696,7 @@ fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -823,12 +727,7 @@ fn web_search_mode_live_sets_external_web_access_true() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -873,12 +772,7 @@ fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
.with_web_search_config(Some(web_search_config.clone()));
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -914,12 +808,7 @@ fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
let tool = find_tool(&tools, "web_search");
|
||||
assert_eq!(
|
||||
@@ -949,12 +838,7 @@ fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert!(
|
||||
!tools.iter().any(|tool| matches!(
|
||||
@@ -980,12 +864,7 @@ fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::new()),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, Some(Vec::new()), &[]);
|
||||
|
||||
assert_contains_tool_names(
|
||||
&tools,
|
||||
@@ -1014,12 +893,7 @@ fn test_parallel_support_flags() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert!(find_tool(&tools, "exec_command").supports_parallel_tool_calls);
|
||||
assert!(!find_tool(&tools, "write_stdin").supports_parallel_tool_calls);
|
||||
@@ -1041,12 +915,7 @@ fn test_test_model_info_includes_sync_tool() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
|
||||
assert!(tools.iter().any(|tool| tool.name() == "test_sync_tool"));
|
||||
}
|
||||
@@ -1069,30 +938,32 @@ fn test_build_specs_mcp_tools_converted() {
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
ToolName::namespaced("test_server/", "do_something_cool"),
|
||||
mcp_tool(
|
||||
"do_something_cool",
|
||||
"Do something cool",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_argument": { "type": "string" },
|
||||
"number_argument": { "type": "number" },
|
||||
"object_argument": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_property": { "type": "string" },
|
||||
"number_property": { "type": "number" },
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
ToolName::namespaced("test_server/", "do_something_cool"),
|
||||
mcp_tool(
|
||||
"do_something_cool",
|
||||
"Do something cool",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_argument": { "type": "string" },
|
||||
"number_argument": { "type": "number" },
|
||||
"object_argument": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"string_property": { "type": "string" },
|
||||
"number_property": { "type": "number" },
|
||||
},
|
||||
"required": ["string_property", "number_property"],
|
||||
"additionalProperties": false,
|
||||
},
|
||||
"required": ["string_property", "number_property"],
|
||||
"additionalProperties": false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
}),
|
||||
),
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1161,15 +1032,17 @@ fn test_build_specs_mcp_namespace_description_falls_back_when_missing() {
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
ToolName::namespaced("test_server/", "do_something_cool"),
|
||||
mcp_tool(
|
||||
"do_something_cool",
|
||||
"Do something cool",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
ToolName::namespaced("test_server/", "do_something_cool"),
|
||||
mcp_tool(
|
||||
"do_something_cool",
|
||||
"Do something cool",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1217,8 +1090,7 @@ fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(tools_map),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(mcp_tool_inputs(tools_map, /*defer_loading*/ false)),
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1250,9 +1122,8 @@ fn search_tool_description_lists_each_mcp_source_once() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, handlers) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([
|
||||
let mut mcp_tools = mcp_tool_inputs(
|
||||
HashMap::from([
|
||||
(
|
||||
ToolName::namespaced("mcp__codex_apps__calendar", "_create_event"),
|
||||
mcp_tool(
|
||||
@@ -1265,39 +1136,37 @@ fn search_tool_description_lists_each_mcp_source_once() {
|
||||
ToolName::namespaced("mcp__rmcp__", "echo"),
|
||||
mcp_tool("echo", "Echo", serde_json::json!({"type": "object"})),
|
||||
),
|
||||
])),
|
||||
Some(vec![
|
||||
deferred_mcp_tool(
|
||||
"_create_event",
|
||||
"mcp__codex_apps__calendar",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Calendar"),
|
||||
Some("Plan events and manage your calendar."),
|
||||
),
|
||||
deferred_mcp_tool(
|
||||
"_list_events",
|
||||
"mcp__codex_apps__calendar",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Calendar"),
|
||||
Some("Plan events and manage your calendar."),
|
||||
),
|
||||
deferred_mcp_tool(
|
||||
"_search_threads",
|
||||
"mcp__codex_apps__gmail",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Gmail"),
|
||||
Some("Find and summarize email threads."),
|
||||
),
|
||||
deferred_mcp_tool(
|
||||
"echo",
|
||||
"mcp__rmcp__",
|
||||
"rmcp",
|
||||
/*connector_name*/ None,
|
||||
/*connector_description*/ None,
|
||||
),
|
||||
]),
|
||||
&[],
|
||||
/*defer_loading*/ false,
|
||||
);
|
||||
mcp_tools.extend([
|
||||
mcp_tool_input(
|
||||
"_list_events",
|
||||
"mcp__codex_apps__calendar",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Calendar"),
|
||||
Some("Plan events and manage your calendar."),
|
||||
/*defer_loading*/ true,
|
||||
),
|
||||
mcp_tool_input(
|
||||
"_search_threads",
|
||||
"mcp__codex_apps__gmail",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Gmail"),
|
||||
Some("Find and summarize email threads."),
|
||||
/*defer_loading*/ true,
|
||||
),
|
||||
mcp_tool_input(
|
||||
"deferred_echo",
|
||||
"mcp__rmcp__",
|
||||
"rmcp",
|
||||
/*connector_name*/ None,
|
||||
/*connector_description*/ None,
|
||||
/*defer_loading*/ true,
|
||||
),
|
||||
]);
|
||||
|
||||
let (tools, handlers) = build_specs(&tools_config, Some(mcp_tools), &[]);
|
||||
|
||||
let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
let ToolSpec::ToolSearch { description, .. } = &search_tool.spec else {
|
||||
@@ -1328,13 +1197,14 @@ fn search_tool_description_lists_each_mcp_source_once() {
|
||||
#[test]
|
||||
fn search_tool_requires_model_capability_and_enabled_feature() {
|
||||
let model_info = search_capable_model_info();
|
||||
let deferred_mcp_tools = Some(vec![deferred_mcp_tool(
|
||||
let mcp_tools = vec![mcp_tool_input(
|
||||
"_create_event",
|
||||
"mcp__codex_apps__calendar",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Calendar"),
|
||||
/*connector_description*/ None,
|
||||
)]);
|
||||
/*defer_loading*/ true,
|
||||
)];
|
||||
|
||||
let features = Features::with_defaults();
|
||||
let available_models = Vec::new();
|
||||
@@ -1351,12 +1221,7 @@ fn search_tool_requires_model_capability_and_enabled_feature() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
deferred_mcp_tools.clone(),
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, Some(mcp_tools.clone()), &[]);
|
||||
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
|
||||
let mut features_without_tool_search = Features::with_defaults();
|
||||
@@ -1371,12 +1236,7 @@ fn search_tool_requires_model_capability_and_enabled_feature() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
deferred_mcp_tools.clone(),
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, Some(mcp_tools.clone()), &[]);
|
||||
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
@@ -1389,12 +1249,7 @@ fn search_tool_requires_model_capability_and_enabled_feature() {
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
deferred_mcp_tools,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, Some(mcp_tools), &[]);
|
||||
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME]);
|
||||
}
|
||||
|
||||
@@ -1439,12 +1294,7 @@ fn search_tool_registers_for_deferred_dynamic_tools() {
|
||||
},
|
||||
];
|
||||
|
||||
let (tools, handlers) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&dynamic_tools,
|
||||
);
|
||||
let (tools, handlers) = build_specs(&tools_config, /*mcp_tools*/ None, &dynamic_tools);
|
||||
|
||||
let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
let ToolSpec::ToolSearch { description, .. } = &search_tool.spec else {
|
||||
@@ -1506,7 +1356,6 @@ fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1546,7 +1395,6 @@ fn tool_suggest_can_be_registered_without_search_tool() {
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1614,7 +1462,6 @@ fn tool_suggest_description_lists_discoverable_tools() {
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(discoverable_tools),
|
||||
&[],
|
||||
);
|
||||
@@ -1694,22 +1541,24 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
ToolName::namespaced("mcp__sample__", "echo"),
|
||||
mcp_tool(
|
||||
"echo",
|
||||
"Echo text",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string"}
|
||||
},
|
||||
"required": ["message"],
|
||||
"additionalProperties": false
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
ToolName::namespaced("mcp__sample__", "echo"),
|
||||
mcp_tool(
|
||||
"echo",
|
||||
"Echo text",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string"}
|
||||
},
|
||||
"required": ["message"],
|
||||
"additionalProperties": false
|
||||
}),
|
||||
),
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1747,55 +1596,57 @@ fn code_mode_preserves_nullable_and_literal_mcp_input_shapes() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
ToolName::namespaced("mcp__sample__", "fn"),
|
||||
mcp_tool(
|
||||
"fn",
|
||||
"Sample fn",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"open": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ref_id": {"type": "string"},
|
||||
"lineno": {"anyOf": [{"type": "integer"}, {"type": "null"}]}
|
||||
},
|
||||
"required": ["ref_id"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{"type": "null"}
|
||||
]
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(
|
||||
ToolName::namespaced("mcp__sample__", "fn"),
|
||||
mcp_tool(
|
||||
"fn",
|
||||
"Sample fn",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"open": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ref_id": {"type": "string"},
|
||||
"lineno": {"anyOf": [{"type": "integer"}, {"type": "null"}]}
|
||||
},
|
||||
"required": ["ref_id"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"tagged_list": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {"type": "const", "const": "tagged"},
|
||||
"variant": {"type": "enum", "enum": ["alpha", "beta"]},
|
||||
"scope": {"type": "enum", "enum": ["one", "two"]}
|
||||
},
|
||||
"required": ["kind", "variant", "scope"]
|
||||
}
|
||||
},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"response_length": {"type": "enum", "enum": ["short", "medium", "long"]}
|
||||
},
|
||||
"tagged_list": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {"type": "const", "const": "tagged"},
|
||||
"variant": {"type": "enum", "enum": ["alpha", "beta"]},
|
||||
"scope": {"type": "enum", "enum": ["one", "two"]}
|
||||
},
|
||||
"required": ["kind", "variant", "scope"]
|
||||
}
|
||||
},
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"response_length": {"type": "enum", "enum": ["short", "medium", "long"]}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
"additionalProperties": false
|
||||
}),
|
||||
),
|
||||
)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1828,12 +1679,7 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let ToolSpec::Function(ResponsesApiTool { description, .. }) =
|
||||
&find_tool(&tools, VIEW_IMAGE_TOOL_NAME).spec
|
||||
else {
|
||||
@@ -1864,12 +1710,7 @@ fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let ToolSpec::Freeform(FreeformTool { description, .. }) = &find_tool(&tools, "exec").spec
|
||||
else {
|
||||
panic!("expected freeform tool");
|
||||
@@ -1901,12 +1742,7 @@ fn code_mode_exec_description_omits_nested_tool_details_when_not_code_mode_only(
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, &[]);
|
||||
let ToolSpec::Freeform(FreeformTool { description, .. }) = &find_tool(&tools, "exec").spec
|
||||
else {
|
||||
panic!("expected freeform tool");
|
||||
@@ -1961,60 +1797,51 @@ fn search_capable_model_info() -> ModelInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_specs<'a>(
|
||||
fn build_specs(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<ToolName, rmcp::model::Tool>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolRegistryPlanDeferredTool<'a>>>,
|
||||
mcp_tools: Option<Vec<TestMcpTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
build_specs_with_discoverable_tools(
|
||||
config,
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
/*discoverable_tools*/ None,
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_specs_with_discoverable_tools<'a>(
|
||||
fn build_specs_with_discoverable_tools(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<ToolName, rmcp::model::Tool>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolRegistryPlanDeferredTool<'a>>>,
|
||||
mcp_tools: Option<Vec<TestMcpTool>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
build_specs_with_optional_tool_namespaces(
|
||||
config,
|
||||
mcp_tools,
|
||||
deferred_mcp_tools,
|
||||
/*tool_namespaces*/ None,
|
||||
discoverable_tools,
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_specs_with_optional_tool_namespaces<'a>(
|
||||
fn build_specs_with_optional_tool_namespaces(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<ToolName, rmcp::model::Tool>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolRegistryPlanDeferredTool<'a>>>,
|
||||
mcp_tools: Option<Vec<TestMcpTool>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
let mcp_tool_inputs = mcp_tools.as_ref().map(|mcp_tools| {
|
||||
mcp_tools
|
||||
.iter()
|
||||
.map(|(name, tool)| ToolRegistryPlanMcpTool {
|
||||
name: name.clone(),
|
||||
tool,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let mcp_tool_inputs = mcp_tools
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(TestMcpTool::as_plan_tool)
|
||||
.collect::<Vec<_>>();
|
||||
let plan = build_tool_registry_plan(
|
||||
config,
|
||||
ToolRegistryPlanParams {
|
||||
mcp_tools: mcp_tool_inputs.as_deref(),
|
||||
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
|
||||
mcp_tools: mcp_tools.as_ref().map(|_| mcp_tool_inputs.as_slice()),
|
||||
tool_namespaces: tool_namespaces.as_ref(),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
dynamic_tools,
|
||||
@@ -2025,6 +1852,29 @@ fn build_specs_with_optional_tool_namespaces<'a>(
|
||||
(plan.specs, plan.handlers)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestMcpTool {
|
||||
name: ToolName,
|
||||
tool: rmcp::model::Tool,
|
||||
server_name: String,
|
||||
connector_name: Option<String>,
|
||||
connector_description: Option<String>,
|
||||
defer_loading: bool,
|
||||
}
|
||||
|
||||
impl TestMcpTool {
|
||||
fn as_plan_tool(&self) -> ToolRegistryPlanMcpTool<'_> {
|
||||
ToolRegistryPlanMcpTool {
|
||||
name: self.name.clone(),
|
||||
tool: &self.tool,
|
||||
server_name: self.server_name.as_str(),
|
||||
connector_name: self.connector_name.as_deref(),
|
||||
connector_description: self.connector_description.as_deref(),
|
||||
defer_loading: self.defer_loading,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> rmcp::model::Tool {
|
||||
rmcp::model::Tool {
|
||||
name: name.to_string().into(),
|
||||
@@ -2089,11 +1939,10 @@ fn code_mode_augments_mcp_tool_descriptions_with_structured_output_sample() {
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([(
|
||||
ToolName::namespaced("mcp__sample__", "echo"),
|
||||
tool,
|
||||
)])),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(mcp_tool_inputs(
|
||||
HashMap::from([(ToolName::namespaced("mcp__sample__", "echo"), tool)]),
|
||||
/*defer_loading*/ false,
|
||||
)),
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -2130,21 +1979,45 @@ fn discoverable_connector(id: &str, name: &str, description: &str) -> Discoverab
|
||||
}))
|
||||
}
|
||||
|
||||
fn deferred_mcp_tool<'a>(
|
||||
tool_name: &'a str,
|
||||
tool_namespace: &'a str,
|
||||
server_name: &'a str,
|
||||
connector_name: Option<&'a str>,
|
||||
connector_description: Option<&'a str>,
|
||||
) -> ToolRegistryPlanDeferredTool<'a> {
|
||||
ToolRegistryPlanDeferredTool {
|
||||
fn mcp_tool_input(
|
||||
tool_name: &str,
|
||||
tool_namespace: &str,
|
||||
server_name: &str,
|
||||
connector_name: Option<&str>,
|
||||
connector_description: Option<&str>,
|
||||
defer_loading: bool,
|
||||
) -> TestMcpTool {
|
||||
TestMcpTool {
|
||||
name: ToolName::namespaced(tool_namespace, tool_name),
|
||||
server_name,
|
||||
connector_name,
|
||||
connector_description,
|
||||
tool: mcp_tool(
|
||||
tool_name,
|
||||
"Deferred MCP test tool",
|
||||
serde_json::json!({"type": "object"}),
|
||||
),
|
||||
server_name: server_name.to_string(),
|
||||
connector_name: connector_name.map(str::to_string),
|
||||
connector_description: connector_description.map(str::to_string),
|
||||
defer_loading,
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_tool_inputs(
|
||||
mcp_tools: HashMap<ToolName, rmcp::model::Tool>,
|
||||
defer_loading: bool,
|
||||
) -> Vec<TestMcpTool> {
|
||||
mcp_tools
|
||||
.into_iter()
|
||||
.map(|(name, tool)| TestMcpTool {
|
||||
server_name: name.namespace.as_deref().unwrap_or("mcp").to_string(),
|
||||
name,
|
||||
tool,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
defer_loading,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn assert_contains_tool_names(tools: &[ConfiguredToolSpec], expected_subset: &[&str]) {
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ pub struct ToolRegistryPlan {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ToolRegistryPlanParams<'a> {
|
||||
pub mcp_tools: Option<&'a [ToolRegistryPlanMcpTool<'a>]>,
|
||||
pub deferred_mcp_tools: Option<&'a [ToolRegistryPlanDeferredTool<'a>]>,
|
||||
pub tool_namespaces: Option<&'a HashMap<String, ToolNamespace>>,
|
||||
pub discoverable_tools: Option<&'a [DiscoverableTool]>,
|
||||
pub dynamic_tools: &'a [DynamicToolSpec],
|
||||
@@ -71,21 +70,17 @@ pub struct ToolNamespace {
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
/// Direct MCP tool metadata needed to expose the Responses API namespace tool
|
||||
/// while registering its runtime handler with the canonical namespace/name
|
||||
/// identity.
|
||||
/// MCP tool metadata needed to expose the Responses API namespace tool, mark
|
||||
/// deferred-loading tools, and register runtime handlers with the canonical
|
||||
/// namespace/name identity.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToolRegistryPlanMcpTool<'a> {
|
||||
pub name: ToolName,
|
||||
pub tool: &'a rmcp::model::Tool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToolRegistryPlanDeferredTool<'a> {
|
||||
pub name: ToolName,
|
||||
pub server_name: &'a str,
|
||||
pub connector_name: Option<&'a str>,
|
||||
pub connector_description: Option<&'a str>,
|
||||
pub defer_loading: bool,
|
||||
}
|
||||
|
||||
impl ToolRegistryPlan {
|
||||
|
||||
Reference in New Issue
Block a user