mirror of
https://github.com/openai/codex.git
synced 2026-05-02 02:17:22 +00:00
Compare commits
2 Commits
dev/rasmus
...
dev/sayan/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f53e6061a4 | ||
|
|
104a3f71a6 |
@@ -202,6 +202,8 @@ mod tests {
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolDefinition;
|
||||
use codex_tools::ToolLoadingPolicy;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::Tool;
|
||||
use std::sync::Arc;
|
||||
@@ -444,6 +446,21 @@ 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 mcp_tools = mcp_tools.map(|tools| {
|
||||
tools
|
||||
.values()
|
||||
.map(|tool| {
|
||||
crate::tools::mcp_tool_definition::mcp_tool_info_to_tool_definition(
|
||||
tool,
|
||||
ToolLoadingPolicy::Deferred,
|
||||
)
|
||||
.expect("convert deferred MCP test tool")
|
||||
})
|
||||
.collect::<Vec<ToolDefinition>>()
|
||||
});
|
||||
ToolSearchHandler::new(build_tool_search_entries(
|
||||
mcp_tools.as_deref(),
|
||||
dynamic_tools,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
84
codex-rs/core/src/tools/mcp_tool_definition.rs
Normal file
84
codex-rs/core/src/tools/mcp_tool_definition.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_tools::ToolDefinition;
|
||||
use codex_tools::ToolLoadingPolicy;
|
||||
use codex_tools::ToolPresentation;
|
||||
use codex_tools::ToolSearchMetadata;
|
||||
use codex_tools::mcp_tool_to_tool_definition;
|
||||
|
||||
pub(crate) fn mcp_tool_info_to_tool_definition(
|
||||
info: &ToolInfo,
|
||||
loading: ToolLoadingPolicy,
|
||||
) -> Result<ToolDefinition, serde_json::Error> {
|
||||
let mut definition = mcp_tool_to_tool_definition(&info.canonical_tool_name(), &info.tool)?;
|
||||
if loading.is_deferred() {
|
||||
definition = definition.into_deferred();
|
||||
definition.search = Some(mcp_tool_search_metadata(info));
|
||||
}
|
||||
definition.presentation = mcp_tool_presentation(info, loading);
|
||||
Ok(definition)
|
||||
}
|
||||
|
||||
fn mcp_tool_presentation(info: &ToolInfo, loading: ToolLoadingPolicy) -> Option<ToolPresentation> {
|
||||
let namespace_description = match loading {
|
||||
ToolLoadingPolicy::Eager => non_empty(info.connector_description.as_deref())
|
||||
.or_else(|| non_empty(info.server_instructions.as_deref())),
|
||||
ToolLoadingPolicy::Deferred => {
|
||||
non_empty(info.connector_description.as_deref()).or_else(|| {
|
||||
non_empty(info.connector_name.as_deref())
|
||||
.map(|name| format!("Tools for working with {name}."))
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
namespace_description.map(|namespace_description| ToolPresentation {
|
||||
namespace_display_name: None,
|
||||
namespace_description: Some(namespace_description),
|
||||
})
|
||||
}
|
||||
|
||||
fn mcp_tool_search_metadata(info: &ToolInfo) -> ToolSearchMetadata {
|
||||
let source_name = non_empty(info.connector_name.as_deref())
|
||||
.unwrap_or_else(|| info.server_name.trim().to_string());
|
||||
let source_description = non_empty(info.connector_description.as_deref());
|
||||
let mut extra_terms = Vec::new();
|
||||
|
||||
push_non_empty(&mut extra_terms, &info.callable_name);
|
||||
push_non_empty(&mut extra_terms, info.tool.name.as_ref());
|
||||
push_non_empty(&mut extra_terms, &info.server_name);
|
||||
if let Some(title) = info.tool.title.as_deref() {
|
||||
push_non_empty(&mut extra_terms, title);
|
||||
}
|
||||
if let Some(connector_name) = info.connector_name.as_deref() {
|
||||
push_non_empty(&mut extra_terms, connector_name);
|
||||
}
|
||||
if let Some(connector_description) = info.connector_description.as_deref() {
|
||||
push_non_empty(&mut extra_terms, connector_description);
|
||||
}
|
||||
for plugin_display_name in &info.plugin_display_names {
|
||||
push_non_empty(&mut extra_terms, plugin_display_name);
|
||||
}
|
||||
|
||||
ToolSearchMetadata {
|
||||
source_name,
|
||||
source_description,
|
||||
extra_terms,
|
||||
limit_bucket: Some(info.server_name.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_non_empty(parts: &mut Vec<String>, value: &str) {
|
||||
if let Some(value) = non_empty(Some(value)) {
|
||||
parts.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn non_empty(value: Option<&str>) -> Option<String> {
|
||||
value
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mcp_tool_definition_tests.rs"]
|
||||
mod tests;
|
||||
108
codex-rs/core/src/tools/mcp_tool_definition_tests.rs
Normal file
108
codex-rs/core/src/tools/mcp_tool_definition_tests.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use super::*;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::ToolExecution;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::mcp_call_tool_result_output_schema;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::Tool;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn tool_info() -> ToolInfo {
|
||||
ToolInfo {
|
||||
server_name: "codex_apps".to_string(),
|
||||
callable_name: "create_event".to_string(),
|
||||
callable_namespace: "mcp__calendar__".to_string(),
|
||||
server_instructions: Some("Use the calendar carefully.".to_string()),
|
||||
tool: Tool {
|
||||
name: "calendar-create-event".to_string().into(),
|
||||
title: None,
|
||||
description: Some("Create events".to_string().into()),
|
||||
input_schema: Arc::new(rmcp::model::object(serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}))),
|
||||
output_schema: None,
|
||||
annotations: None,
|
||||
execution: None,
|
||||
icons: None,
|
||||
meta: None,
|
||||
},
|
||||
connector_id: Some("calendar".to_string()),
|
||||
connector_name: Some("Calendar".to_string()),
|
||||
plugin_display_names: vec!["calendar-plugin".to_string()],
|
||||
connector_description: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eager_mcp_tool_info_to_tool_definition_uses_server_instructions_for_namespace() {
|
||||
assert_eq!(
|
||||
mcp_tool_info_to_tool_definition(&tool_info(), ToolLoadingPolicy::Eager)
|
||||
.expect("convert MCP tool info"),
|
||||
ToolDefinition {
|
||||
name: ToolName::namespaced("mcp__calendar__", "create_event"),
|
||||
description: "Create events".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
"title".to_string(),
|
||||
JsonSchema::string(/*description*/ None),
|
||||
)]),
|
||||
/*required*/ None,
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({}))),
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: Some(ToolPresentation {
|
||||
namespace_display_name: None,
|
||||
namespace_description: Some("Use the calendar carefully.".to_string()),
|
||||
}),
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deferred_mcp_tool_info_to_tool_definition_populates_search_metadata() {
|
||||
assert_eq!(
|
||||
mcp_tool_info_to_tool_definition(&tool_info(), ToolLoadingPolicy::Deferred)
|
||||
.expect("convert MCP tool info"),
|
||||
ToolDefinition {
|
||||
name: ToolName::namespaced("mcp__calendar__", "create_event"),
|
||||
description: "Create events".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
"title".to_string(),
|
||||
JsonSchema::string(/*description*/ None),
|
||||
)]),
|
||||
/*required*/ None,
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
loading: ToolLoadingPolicy::Deferred,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: Some(ToolPresentation {
|
||||
namespace_display_name: None,
|
||||
namespace_description: Some("Tools for working with Calendar.".to_string()),
|
||||
}),
|
||||
search: Some(ToolSearchMetadata {
|
||||
source_name: "Calendar".to_string(),
|
||||
source_description: None,
|
||||
extra_terms: vec![
|
||||
"create_event".to_string(),
|
||||
"calendar-create-event".to_string(),
|
||||
"codex_apps".to_string(),
|
||||
"Calendar".to_string(),
|
||||
"calendar-plugin".to_string(),
|
||||
],
|
||||
limit_bucket: Some("codex_apps".to_string()),
|
||||
}),
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub(crate) mod context;
|
||||
pub(crate) mod events;
|
||||
pub(crate) mod handlers;
|
||||
pub(crate) mod js_repl;
|
||||
pub(crate) mod mcp_tool_definition;
|
||||
pub(crate) mod network_approval;
|
||||
pub(crate) mod orchestrator;
|
||||
pub(crate) mod parallel;
|
||||
|
||||
@@ -11,11 +11,11 @@ use codex_tools::AdditionalProperties;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolDefinition;
|
||||
use codex_tools::ToolHandlerKind;
|
||||
use codex_tools::ToolLoadingPolicy;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolNamespace;
|
||||
use codex_tools::ToolRegistryPlanDeferredTool;
|
||||
use codex_tools::ToolRegistryPlanMcpTool;
|
||||
use codex_tools::ToolRegistryPlanParams;
|
||||
use codex_tools::ToolUserShellType;
|
||||
use codex_tools::ToolsConfig;
|
||||
@@ -36,38 +36,59 @@ pub(crate) fn tool_user_shell_type(user_shell: &Shell) -> ToolUserShellType {
|
||||
}
|
||||
}
|
||||
|
||||
struct McpToolPlanInputs<'a> {
|
||||
mcp_tools: Vec<ToolRegistryPlanMcpTool<'a>>,
|
||||
struct McpToolPlanInputs {
|
||||
mcp_tools: Vec<ToolDefinition>,
|
||||
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, ToolInfo>) -> McpToolPlanInputs {
|
||||
let mcp_tools = mcp_tool_definitions_for_plan(mcp_tools, ToolLoadingPolicy::Eager);
|
||||
let tool_namespaces = mcp_tools
|
||||
.iter()
|
||||
.filter_map(|tool| {
|
||||
let namespace = tool.name.namespace.clone()?;
|
||||
Some((
|
||||
namespace.clone(),
|
||||
ToolNamespace {
|
||||
name: namespace,
|
||||
description: tool
|
||||
.presentation
|
||||
.as_ref()
|
||||
.and_then(|presentation| presentation.namespace_description.clone()),
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
|
||||
McpToolPlanInputs {
|
||||
mcp_tools: mcp_tools
|
||||
.values()
|
||||
.map(|tool| ToolRegistryPlanMcpTool {
|
||||
name: tool.canonical_tool_name(),
|
||||
tool: &tool.tool,
|
||||
})
|
||||
.collect(),
|
||||
tool_namespaces: mcp_tools
|
||||
.values()
|
||||
.map(|tool| {
|
||||
(
|
||||
tool.callable_namespace.clone(),
|
||||
ToolNamespace {
|
||||
name: tool.callable_namespace.clone(),
|
||||
description: tool
|
||||
.connector_description
|
||||
.clone()
|
||||
.or_else(|| tool.server_instructions.clone()),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
mcp_tools,
|
||||
tool_namespaces,
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_tool_definitions_for_plan(
|
||||
mcp_tools: &HashMap<String, ToolInfo>,
|
||||
loading: ToolLoadingPolicy,
|
||||
) -> Vec<ToolDefinition> {
|
||||
use crate::tools::mcp_tool_definition::mcp_tool_info_to_tool_definition;
|
||||
|
||||
mcp_tools
|
||||
.values()
|
||||
.filter_map(
|
||||
|tool| match mcp_tool_info_to_tool_definition(tool, loading) {
|
||||
Ok(tool_definition) => Some(tool_definition),
|
||||
Err(error) => {
|
||||
let tool_name = tool.canonical_tool_name();
|
||||
tracing::error!(
|
||||
"Failed to convert MCP tool `{tool_name}` to tool definition: {error:?}"
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn build_specs_with_discoverable_tools(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, ToolInfo>>,
|
||||
@@ -112,17 +133,9 @@ 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 deferred_mcp_tool_definitions = deferred_mcp_tools
|
||||
.as_ref()
|
||||
.map(|tools| mcp_tool_definitions_for_plan(tools, ToolLoadingPolicy::Deferred));
|
||||
let default_agent_type_description =
|
||||
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
|
||||
let plan = build_tool_registry_plan(
|
||||
@@ -131,7 +144,7 @@ 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(),
|
||||
deferred_mcp_tools: deferred_mcp_tool_definitions.as_deref(),
|
||||
tool_namespaces: mcp_tool_plan_inputs
|
||||
.as_ref()
|
||||
.map(|inputs| &inputs.tool_namespaces),
|
||||
@@ -266,7 +279,7 @@ 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_mcp_tool_definitions.as_deref(),
|
||||
&deferred_dynamic_tools,
|
||||
);
|
||||
tool_search_handler = Some(Arc::new(ToolSearchHandler::new(entries)));
|
||||
|
||||
@@ -32,7 +32,8 @@ 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_tool_definition;
|
||||
use codex_tools::tool_definition_to_responses_api_tool;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::assert_regex_match;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -129,12 +130,14 @@ fn deferred_responses_api_tool_serializes_with_defer_loading() {
|
||||
}),
|
||||
);
|
||||
|
||||
let tool_definition = mcp_tool_to_tool_definition(
|
||||
&ToolName::namespaced("mcp__codex_apps__", "lookup_order"),
|
||||
&tool,
|
||||
)
|
||||
.expect("convert deferred tool")
|
||||
.into_deferred();
|
||||
let serialized = serde_json::to_value(ToolSpec::Function(
|
||||
mcp_tool_to_deferred_responses_api_tool(
|
||||
&ToolName::namespaced("mcp__codex_apps__", "lookup_order"),
|
||||
&tool,
|
||||
)
|
||||
.expect("convert deferred tool"),
|
||||
tool_definition_to_responses_api_tool(&tool_definition),
|
||||
))
|
||||
.expect("serialize deferred tool");
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_tools::ToolDefinition;
|
||||
use codex_tools::ToolSearchOutputTool;
|
||||
use codex_tools::ToolSearchResultSource;
|
||||
use codex_tools::dynamic_tool_to_responses_api_tool;
|
||||
use codex_tools::tool_search_result_source_to_output_tool;
|
||||
use std::collections::HashMap;
|
||||
use codex_tools::tool_definition_to_tool_search_output_tool;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ToolSearchEntry {
|
||||
@@ -14,25 +12,17 @@ pub(crate) struct ToolSearchEntry {
|
||||
}
|
||||
|
||||
pub(crate) fn build_tool_search_entries(
|
||||
mcp_tools: Option<&HashMap<String, ToolInfo>>,
|
||||
mcp_tools: Option<&[ToolDefinition]>,
|
||||
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.iter().collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
mcp_tools.sort_by_key(|info| info.canonical_tool_name().display());
|
||||
for info in mcp_tools {
|
||||
match mcp_tool_search_entry(info) {
|
||||
Ok(entry) => entries.push(entry),
|
||||
Err(error) => {
|
||||
let tool_name = info.canonical_tool_name();
|
||||
tracing::error!(
|
||||
"Failed to convert deferred MCP tool `{tool_name}` to OpenAI tool: {error:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
mcp_tools.sort_by_key(|tool| tool.name.display());
|
||||
for tool in mcp_tools {
|
||||
entries.push(tool_definition_search_entry(tool));
|
||||
}
|
||||
|
||||
let mut dynamic_tools = dynamic_tools.iter().collect::<Vec<_>>();
|
||||
@@ -52,19 +42,15 @@ pub(crate) fn build_tool_search_entries(
|
||||
entries
|
||||
}
|
||||
|
||||
fn mcp_tool_search_entry(info: &ToolInfo) -> Result<ToolSearchEntry, serde_json::Error> {
|
||||
Ok(ToolSearchEntry {
|
||||
search_text: build_mcp_search_text(info),
|
||||
output: tool_search_result_source_to_output_tool(ToolSearchResultSource {
|
||||
server_name: info.server_name.as_str(),
|
||||
tool_namespace: info.callable_namespace.as_str(),
|
||||
tool_name: info.callable_name.as_str(),
|
||||
tool: &info.tool,
|
||||
connector_name: info.connector_name.as_deref(),
|
||||
connector_description: info.connector_description.as_deref(),
|
||||
})?,
|
||||
limit_bucket: Some(info.server_name.clone()),
|
||||
})
|
||||
fn tool_definition_search_entry(tool: &ToolDefinition) -> ToolSearchEntry {
|
||||
ToolSearchEntry {
|
||||
search_text: build_tool_definition_search_text(tool),
|
||||
output: tool_definition_to_tool_search_output_tool(tool),
|
||||
limit_bucket: tool
|
||||
.search
|
||||
.as_ref()
|
||||
.and_then(|metadata| metadata.limit_bucket.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn dynamic_tool_search_entry(tool: &DynamicToolSpec) -> Result<ToolSearchEntry, serde_json::Error> {
|
||||
@@ -75,52 +61,25 @@ fn dynamic_tool_search_entry(tool: &DynamicToolSpec) -> Result<ToolSearchEntry,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_mcp_search_text(info: &ToolInfo) -> String {
|
||||
fn build_tool_definition_search_text(tool: &ToolDefinition) -> String {
|
||||
let mut parts = vec![
|
||||
info.canonical_tool_name().display(),
|
||||
info.callable_name.clone(),
|
||||
info.tool.name.to_string(),
|
||||
info.server_name.clone(),
|
||||
tool.name.display(),
|
||||
tool.name.name.clone(),
|
||||
tool.description.clone(),
|
||||
];
|
||||
|
||||
if let Some(title) = info.tool.title.as_deref()
|
||||
&& !title.trim().is_empty()
|
||||
{
|
||||
parts.push(title.to_string());
|
||||
}
|
||||
|
||||
if let Some(description) = info.tool.description.as_deref()
|
||||
&& !description.trim().is_empty()
|
||||
{
|
||||
parts.push(description.to_string());
|
||||
}
|
||||
|
||||
if let Some(connector_name) = info.connector_name.as_deref()
|
||||
&& !connector_name.trim().is_empty()
|
||||
{
|
||||
parts.push(connector_name.to_string());
|
||||
}
|
||||
|
||||
if let Some(connector_description) = info.connector_description.as_deref()
|
||||
&& !connector_description.trim().is_empty()
|
||||
{
|
||||
parts.push(connector_description.to_string());
|
||||
if let Some(search) = tool.search.as_ref() {
|
||||
parts.push(search.source_name.clone());
|
||||
if let Some(source_description) = search.source_description.as_ref() {
|
||||
parts.push(source_description.clone());
|
||||
}
|
||||
parts.extend(search.extra_terms.clone());
|
||||
}
|
||||
|
||||
parts.extend(
|
||||
info.plugin_display_names
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|name| !name.is_empty())
|
||||
.map(str::to_string),
|
||||
);
|
||||
|
||||
parts.extend(
|
||||
info.tool
|
||||
.input_schema
|
||||
.get("properties")
|
||||
.and_then(serde_json::Value::as_object)
|
||||
tool.input_schema
|
||||
.properties
|
||||
.as_ref()
|
||||
.map(|map| map.keys().cloned().collect::<Vec<_>>())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
@@ -11,6 +11,10 @@ schema and Responses API tool primitives that no longer need to live in
|
||||
- `JsonSchema`
|
||||
- `AdditionalProperties`
|
||||
- `ToolDefinition`
|
||||
- `ToolLoadingPolicy`
|
||||
- `ToolExecution`
|
||||
- `ToolPresentation`
|
||||
- `ToolSearchMetadata`
|
||||
- `ToolSpec`
|
||||
- `ConfiguredToolSpec`
|
||||
- `ResponsesApiTool`
|
||||
@@ -32,12 +36,13 @@ schema and Responses API tool primitives that no longer need to live in
|
||||
- `parse_tool_input_schema()`
|
||||
- `parse_dynamic_tool()`
|
||||
- `parse_mcp_tool()`
|
||||
- `dynamic_tool_to_tool_definition()`
|
||||
- `mcp_tool_to_tool_definition()`
|
||||
- `create_tools_json_for_responses_api()`
|
||||
- `mcp_call_tool_result_output_schema()`
|
||||
- `tool_definition_to_responses_api_tool()`
|
||||
- `tool_definition_to_tool_search_output_tool()`
|
||||
- `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()`
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolExecution;
|
||||
use crate::ToolLoadingPolicy;
|
||||
use crate::ToolName;
|
||||
use crate::parse_tool_input_schema;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
|
||||
pub fn parse_dynamic_tool(tool: &DynamicToolSpec) -> Result<ToolDefinition, serde_json::Error> {
|
||||
pub fn dynamic_tool_to_tool_definition(
|
||||
tool: &DynamicToolSpec,
|
||||
) -> Result<ToolDefinition, serde_json::Error> {
|
||||
let DynamicToolSpec {
|
||||
name,
|
||||
description,
|
||||
@@ -10,14 +15,28 @@ pub fn parse_dynamic_tool(tool: &DynamicToolSpec) -> Result<ToolDefinition, serd
|
||||
defer_loading,
|
||||
} = tool;
|
||||
Ok(ToolDefinition {
|
||||
name: name.clone(),
|
||||
name: ToolName::plain(name.clone()),
|
||||
description: description.clone(),
|
||||
input_schema: parse_tool_input_schema(input_schema)?,
|
||||
output_schema: None,
|
||||
defer_loading: *defer_loading,
|
||||
loading: if *defer_loading {
|
||||
ToolLoadingPolicy::Deferred
|
||||
} else {
|
||||
ToolLoadingPolicy::Eager
|
||||
},
|
||||
execution: ToolExecution::Dynamic,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(tool-definition-unification): migrate remaining callers to
|
||||
// `dynamic_tool_to_tool_definition` and remove this compatibility wrapper.
|
||||
pub fn parse_dynamic_tool(tool: &DynamicToolSpec) -> Result<ToolDefinition, serde_json::Error> {
|
||||
dynamic_tool_to_tool_definition(tool)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "dynamic_tool_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::parse_dynamic_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolExecution;
|
||||
use crate::ToolLoadingPolicy;
|
||||
use crate::ToolName;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
@@ -23,7 +26,7 @@ fn parse_dynamic_tool_sanitizes_input_schema() {
|
||||
assert_eq!(
|
||||
parse_dynamic_tool(&tool).expect("parse dynamic tool"),
|
||||
ToolDefinition {
|
||||
name: "lookup_ticket".to_string(),
|
||||
name: ToolName::plain("lookup_ticket"),
|
||||
description: "Fetch a ticket".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
@@ -34,7 +37,11 @@ fn parse_dynamic_tool_sanitizes_input_schema() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
defer_loading: false,
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Dynamic,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -54,7 +61,7 @@ fn parse_dynamic_tool_preserves_defer_loading() {
|
||||
assert_eq!(
|
||||
parse_dynamic_tool(&tool).expect("parse dynamic tool"),
|
||||
ToolDefinition {
|
||||
name: "lookup_ticket".to_string(),
|
||||
name: ToolName::plain("lookup_ticket"),
|
||||
description: "Fetch a ticket".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
@@ -62,7 +69,11 @@ fn parse_dynamic_tool_preserves_defer_loading() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
defer_loading: true,
|
||||
loading: ToolLoadingPolicy::Deferred,
|
||||
execution: ToolExecution::Dynamic,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ pub use code_mode::create_code_mode_tool;
|
||||
pub use code_mode::create_wait_tool;
|
||||
pub use code_mode::tool_spec_to_code_mode_tool_definition;
|
||||
pub use codex_protocol::ToolName;
|
||||
pub use dynamic_tool::dynamic_tool_to_tool_definition;
|
||||
pub use dynamic_tool::parse_dynamic_tool;
|
||||
pub use image_detail::can_request_original_image_detail;
|
||||
pub use image_detail::normalize_output_image_detail;
|
||||
@@ -73,6 +74,7 @@ pub use mcp_resource_tool::create_list_mcp_resource_templates_tool;
|
||||
pub use mcp_resource_tool::create_list_mcp_resources_tool;
|
||||
pub use mcp_resource_tool::create_read_mcp_resource_tool;
|
||||
pub use mcp_tool::mcp_call_tool_result_output_schema;
|
||||
pub use mcp_tool::mcp_tool_to_tool_definition;
|
||||
pub use mcp_tool::parse_mcp_tool;
|
||||
pub use plan_tool::create_update_plan_tool;
|
||||
pub use request_user_input_tool::REQUEST_USER_INPUT_TOOL_NAME;
|
||||
@@ -88,9 +90,8 @@ pub use responses_api::ResponsesApiTool;
|
||||
pub use responses_api::ToolSearchOutputTool;
|
||||
pub(crate) use responses_api::default_namespace_description;
|
||||
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 responses_api::tool_definition_to_tool_search_output_tool;
|
||||
pub use tool_config::ShellCommandBackendConfig;
|
||||
pub use tool_config::ToolUserShellType;
|
||||
pub use tool_config::ToolsConfig;
|
||||
@@ -98,6 +99,10 @@ pub use tool_config::ToolsConfigParams;
|
||||
pub use tool_config::UnifiedExecShellMode;
|
||||
pub use tool_config::ZshForkConfig;
|
||||
pub use tool_definition::ToolDefinition;
|
||||
pub use tool_definition::ToolExecution;
|
||||
pub use tool_definition::ToolLoadingPolicy;
|
||||
pub use tool_definition::ToolPresentation;
|
||||
pub use tool_definition::ToolSearchMetadata;
|
||||
pub use tool_discovery::DiscoverablePluginInfo;
|
||||
pub use tool_discovery::DiscoverableTool;
|
||||
pub use tool_discovery::DiscoverableToolAction;
|
||||
@@ -105,23 +110,17 @@ pub use tool_discovery::DiscoverableToolType;
|
||||
pub use tool_discovery::TOOL_SEARCH_DEFAULT_LIMIT;
|
||||
pub use tool_discovery::TOOL_SEARCH_TOOL_NAME;
|
||||
pub use tool_discovery::TOOL_SUGGEST_TOOL_NAME;
|
||||
pub use tool_discovery::ToolSearchResultSource;
|
||||
pub use tool_discovery::ToolSearchSource;
|
||||
pub use tool_discovery::ToolSearchSourceInfo;
|
||||
pub use tool_discovery::ToolSuggestEntry;
|
||||
pub use tool_discovery::collect_tool_search_source_infos;
|
||||
pub use tool_discovery::collect_tool_suggest_entries;
|
||||
pub use tool_discovery::create_tool_search_tool;
|
||||
pub use tool_discovery::create_tool_suggest_tool;
|
||||
pub use tool_discovery::filter_tool_suggest_discoverable_tools_for_client;
|
||||
pub use tool_discovery::tool_search_result_source_to_output_tool;
|
||||
pub use tool_registry_plan::build_tool_registry_plan;
|
||||
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;
|
||||
pub use tool_spec::ResponsesApiWebSearchFilters;
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolExecution;
|
||||
use crate::ToolLoadingPolicy;
|
||||
use crate::ToolName;
|
||||
use crate::parse_tool_input_schema;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
|
||||
pub fn parse_mcp_tool(tool: &rmcp::model::Tool) -> Result<ToolDefinition, serde_json::Error> {
|
||||
// TODO(tool-definition-unification): remove this incomplete raw MCP adapter
|
||||
// once callers can use a full `ToolInfo -> ToolDefinition` adapter that also
|
||||
// populates MCP presentation/search metadata.
|
||||
pub fn mcp_tool_to_tool_definition(
|
||||
tool_name: &ToolName,
|
||||
tool: &rmcp::model::Tool,
|
||||
) -> Result<ToolDefinition, serde_json::Error> {
|
||||
let mut serialized_input_schema = serde_json::Value::Object(tool.input_schema.as_ref().clone());
|
||||
|
||||
// OpenAI models mandate the "properties" field in the schema. Some MCP
|
||||
@@ -26,16 +35,26 @@ pub fn parse_mcp_tool(tool: &rmcp::model::Tool) -> Result<ToolDefinition, serde_
|
||||
.unwrap_or_else(|| JsonValue::Object(serde_json::Map::new()));
|
||||
|
||||
Ok(ToolDefinition {
|
||||
name: tool.name.to_string(),
|
||||
name: tool_name.clone(),
|
||||
description: tool.description.clone().map(Into::into).unwrap_or_default(),
|
||||
input_schema,
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(
|
||||
structured_content_schema,
|
||||
)),
|
||||
defer_loading: false,
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(tool-definition-unification): remove this compatibility wrapper once
|
||||
// callers can use the full `ToolInfo -> ToolDefinition` adapter.
|
||||
pub fn parse_mcp_tool(tool: &rmcp::model::Tool) -> Result<ToolDefinition, serde_json::Error> {
|
||||
mcp_tool_to_tool_definition(&ToolName::plain(tool.name.to_string()), tool)
|
||||
}
|
||||
|
||||
pub fn mcp_call_tool_result_output_schema(structured_content_schema: JsonValue) -> JsonValue {
|
||||
json!({
|
||||
"type": "object",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use super::mcp_call_tool_result_output_schema;
|
||||
use super::mcp_tool_to_tool_definition;
|
||||
use super::parse_mcp_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolExecution;
|
||||
use crate::ToolLoadingPolicy;
|
||||
use crate::ToolName;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -19,6 +23,40 @@ fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> r
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_to_tool_definition_uses_canonical_tool_name() {
|
||||
let tool = mcp_tool(
|
||||
"raw_lookup",
|
||||
"Look up an order",
|
||||
serde_json::json!({
|
||||
"type": "object"
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mcp_tool_to_tool_definition(
|
||||
&ToolName::namespaced("mcp__orders__", "lookup_order"),
|
||||
&tool,
|
||||
)
|
||||
.expect("convert MCP tool"),
|
||||
ToolDefinition {
|
||||
name: ToolName::namespaced("mcp__orders__", "lookup_order"),
|
||||
description: "Look up an order".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({}))),
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mcp_tool_inserts_empty_properties() {
|
||||
let tool = mcp_tool(
|
||||
@@ -32,7 +70,7 @@ fn parse_mcp_tool_inserts_empty_properties() {
|
||||
assert_eq!(
|
||||
parse_mcp_tool(&tool).expect("parse MCP tool"),
|
||||
ToolDefinition {
|
||||
name: "no_props".to_string(),
|
||||
name: ToolName::plain("no_props"),
|
||||
description: "No properties".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
@@ -40,7 +78,11 @@ fn parse_mcp_tool_inserts_empty_properties() {
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({}))),
|
||||
defer_loading: false,
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -70,7 +112,7 @@ fn parse_mcp_tool_preserves_top_level_output_schema() {
|
||||
assert_eq!(
|
||||
parse_mcp_tool(&tool).expect("parse MCP tool"),
|
||||
ToolDefinition {
|
||||
name: "with_output".to_string(),
|
||||
name: ToolName::plain("with_output"),
|
||||
description: "Has output schema".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
@@ -87,7 +129,11 @@ fn parse_mcp_tool_preserves_top_level_output_schema() {
|
||||
},
|
||||
"required": ["result"]
|
||||
}))),
|
||||
defer_loading: false,
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -110,7 +156,7 @@ fn parse_mcp_tool_preserves_output_schema_without_inferred_type() {
|
||||
assert_eq!(
|
||||
parse_mcp_tool(&tool).expect("parse MCP tool"),
|
||||
ToolDefinition {
|
||||
name: "with_enum_output".to_string(),
|
||||
name: ToolName::plain("with_enum_output"),
|
||||
description: "Has enum output schema".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
@@ -120,7 +166,11 @@ fn parse_mcp_tool_preserves_output_schema_without_inferred_type() {
|
||||
output_schema: Some(mcp_call_tool_result_output_schema(serde_json::json!({
|
||||
"enum": ["ok", "error"]
|
||||
}))),
|
||||
defer_loading: false,
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolName;
|
||||
use crate::parse_dynamic_tool;
|
||||
use crate::parse_mcp_tool;
|
||||
use crate::dynamic_tool_to_tool_definition;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -68,43 +66,51 @@ pub enum ResponsesApiNamespaceTool {
|
||||
|
||||
pub fn dynamic_tool_to_responses_api_tool(
|
||||
tool: &DynamicToolSpec,
|
||||
) -> Result<ResponsesApiTool, serde_json::Error> {
|
||||
Ok(tool_definition_to_responses_api_tool(parse_dynamic_tool(
|
||||
tool,
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn mcp_tool_to_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()),
|
||||
&dynamic_tool_to_tool_definition(tool)?,
|
||||
))
|
||||
}
|
||||
|
||||
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(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn tool_definition_to_responses_api_tool(tool_definition: ToolDefinition) -> ResponsesApiTool {
|
||||
/// Converts the leaf function shape of a canonical tool definition.
|
||||
///
|
||||
/// If the tool is namespaced, callers are still responsible for wrapping the
|
||||
/// returned function in a Responses API namespace tool.
|
||||
pub fn tool_definition_to_responses_api_tool(tool_definition: &ToolDefinition) -> ResponsesApiTool {
|
||||
ResponsesApiTool {
|
||||
name: tool_definition.name,
|
||||
description: tool_definition.description,
|
||||
name: tool_definition.name.name.clone(),
|
||||
description: tool_definition.description.clone(),
|
||||
strict: false,
|
||||
defer_loading: tool_definition.defer_loading.then_some(true),
|
||||
parameters: tool_definition.input_schema,
|
||||
output_schema: tool_definition.output_schema,
|
||||
defer_loading: tool_definition.defer_loading().then_some(true),
|
||||
parameters: tool_definition.input_schema.clone(),
|
||||
output_schema: tool_definition.output_schema.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool_definition_to_tool_search_output_tool(
|
||||
tool_definition: &ToolDefinition,
|
||||
) -> ToolSearchOutputTool {
|
||||
let function_tool = tool_definition_to_responses_api_tool(tool_definition);
|
||||
let Some(namespace) = tool_definition.name.namespace.as_ref() else {
|
||||
return ToolSearchOutputTool::Function(function_tool);
|
||||
};
|
||||
|
||||
let description = tool_definition
|
||||
.presentation
|
||||
.as_ref()
|
||||
.and_then(|presentation| presentation.namespace_description.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| default_namespace_description(namespace));
|
||||
|
||||
ToolSearchOutputTool::Namespace(ResponsesApiNamespace {
|
||||
name: namespace.clone(),
|
||||
description,
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(function_tool)],
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "responses_api_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -3,11 +3,14 @@ use super::ResponsesApiNamespaceTool;
|
||||
use super::ResponsesApiTool;
|
||||
use super::ToolSearchOutputTool;
|
||||
use super::dynamic_tool_to_responses_api_tool;
|
||||
use super::mcp_tool_to_deferred_responses_api_tool;
|
||||
use super::tool_definition_to_responses_api_tool;
|
||||
use super::tool_definition_to_tool_search_output_tool;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolExecution;
|
||||
use crate::ToolLoadingPolicy;
|
||||
use crate::ToolName;
|
||||
use crate::ToolPresentation;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@@ -16,8 +19,8 @@ use std::collections::BTreeMap;
|
||||
#[test]
|
||||
fn tool_definition_to_responses_api_tool_omits_false_defer_loading() {
|
||||
assert_eq!(
|
||||
tool_definition_to_responses_api_tool(ToolDefinition {
|
||||
name: "lookup_order".to_string(),
|
||||
tool_definition_to_responses_api_tool(&ToolDefinition {
|
||||
name: ToolName::plain("lookup_order"),
|
||||
description: "Look up an order".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
@@ -28,7 +31,11 @@ fn tool_definition_to_responses_api_tool_omits_false_defer_loading() {
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: Some(json!({"type": "object"})),
|
||||
defer_loading: false,
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Dynamic,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}),
|
||||
ResponsesApiTool {
|
||||
name: "lookup_order".to_string(),
|
||||
@@ -84,51 +91,6 @@ fn dynamic_tool_to_responses_api_tool_preserves_defer_loading() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_tool_to_deferred_responses_api_tool_sets_defer_loading() {
|
||||
let tool = rmcp::model::Tool {
|
||||
name: "lookup_order".to_string().into(),
|
||||
title: None,
|
||||
description: Some("Look up an order".to_string().into()),
|
||||
input_schema: std::sync::Arc::new(rmcp::model::object(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"order_id": {"type": "string"}
|
||||
},
|
||||
"required": ["order_id"],
|
||||
"additionalProperties": false,
|
||||
}))),
|
||||
output_schema: None,
|
||||
annotations: None,
|
||||
execution: None,
|
||||
icons: None,
|
||||
meta: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
mcp_tool_to_deferred_responses_api_tool(
|
||||
&ToolName::namespaced("mcp__codex_apps__", "lookup_order"),
|
||||
&tool,
|
||||
)
|
||||
.expect("convert deferred tool"),
|
||||
ResponsesApiTool {
|
||||
name: "lookup_order".to_string(),
|
||||
description: "Look up an order".to_string(),
|
||||
strict: false,
|
||||
defer_loading: Some(true),
|
||||
parameters: JsonSchema::object(
|
||||
BTreeMap::from([(
|
||||
"order_id".to_string(),
|
||||
JsonSchema::string(/*description*/ None),
|
||||
)]),
|
||||
Some(vec!["order_id".to_string()]),
|
||||
Some(false.into())
|
||||
),
|
||||
output_schema: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_search_output_namespace_serializes_with_deferred_child_tools() {
|
||||
let namespace = ToolSearchOutputTool::Namespace(ResponsesApiNamespace {
|
||||
@@ -172,3 +134,43 @@ fn tool_search_output_namespace_serializes_with_deferred_child_tools() {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_definition_to_tool_search_output_tool_wraps_namespaced_tools() {
|
||||
assert_eq!(
|
||||
tool_definition_to_tool_search_output_tool(&ToolDefinition {
|
||||
name: ToolName::namespaced("mcp__calendar__", "create_event"),
|
||||
description: "Create a calendar event.".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
Default::default(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None,
|
||||
),
|
||||
output_schema: None,
|
||||
loading: ToolLoadingPolicy::Deferred,
|
||||
execution: ToolExecution::Mcp,
|
||||
presentation: Some(ToolPresentation {
|
||||
namespace_display_name: None,
|
||||
namespace_description: Some("Calendar tools.".to_string()),
|
||||
}),
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}),
|
||||
ToolSearchOutputTool::Namespace(ResponsesApiNamespace {
|
||||
name: "mcp__calendar__".to_string(),
|
||||
description: "Calendar tools.".to_string(),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: "create_event".to_string(),
|
||||
description: "Create a calendar event.".to_string(),
|
||||
strict: false,
|
||||
defer_loading: Some(true),
|
||||
parameters: JsonSchema::object(
|
||||
Default::default(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None,
|
||||
),
|
||||
output_schema: None,
|
||||
})],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,76 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolName;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// Tool metadata and schemas that downstream crates can adapt into higher-level
|
||||
/// tool specs.
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Canonical metadata for JSON-schema function tools.
|
||||
///
|
||||
/// This intentionally models function-like tools only. If freeform tools need
|
||||
/// the same registry/search/code-mode lifecycle later, this can grow a
|
||||
/// function-vs-freeform input enum without changing the conversion boundary.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ToolDefinition {
|
||||
pub name: String,
|
||||
pub name: ToolName,
|
||||
pub description: String,
|
||||
pub input_schema: JsonSchema,
|
||||
pub output_schema: Option<JsonValue>,
|
||||
pub defer_loading: bool,
|
||||
pub loading: ToolLoadingPolicy,
|
||||
pub execution: ToolExecution,
|
||||
pub presentation: Option<ToolPresentation>,
|
||||
pub search: Option<ToolSearchMetadata>,
|
||||
pub supports_parallel_tool_calls: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ToolLoadingPolicy {
|
||||
Eager,
|
||||
Deferred,
|
||||
}
|
||||
|
||||
impl ToolLoadingPolicy {
|
||||
pub fn is_deferred(self) -> bool {
|
||||
matches!(self, Self::Deferred)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ToolExecution {
|
||||
/// Tool execution handled by an in-process Codex handler.
|
||||
Builtin,
|
||||
/// Tool registered dynamically by the caller for the current thread.
|
||||
Dynamic,
|
||||
/// Tool routed through the MCP connection manager.
|
||||
Mcp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolPresentation {
|
||||
pub namespace_display_name: Option<String>,
|
||||
pub namespace_description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolSearchMetadata {
|
||||
pub source_name: String,
|
||||
pub source_description: Option<String>,
|
||||
pub extra_terms: Vec<String>,
|
||||
pub limit_bucket: Option<String>,
|
||||
}
|
||||
|
||||
impl ToolDefinition {
|
||||
pub fn renamed(mut self, name: String) -> Self {
|
||||
self.name = name;
|
||||
pub fn renamed(mut self, name: impl Into<ToolName>) -> Self {
|
||||
self.name = name.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_deferred(mut self) -> Self {
|
||||
self.output_schema = None;
|
||||
self.defer_loading = true;
|
||||
self.loading = ToolLoadingPolicy::Deferred;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn defer_loading(&self) -> bool {
|
||||
self.loading.is_deferred()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use super::ToolDefinition;
|
||||
use crate::JsonSchema;
|
||||
use crate::ToolExecution;
|
||||
use crate::ToolLoadingPolicy;
|
||||
use crate::ToolName;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn tool_definition() -> ToolDefinition {
|
||||
ToolDefinition {
|
||||
name: "lookup_order".to_string(),
|
||||
name: ToolName::plain("lookup_order"),
|
||||
description: "Look up an order".to_string(),
|
||||
input_schema: JsonSchema::object(
|
||||
BTreeMap::new(),
|
||||
@@ -15,16 +18,20 @@ fn tool_definition() -> ToolDefinition {
|
||||
output_schema: Some(serde_json::json!({
|
||||
"type": "object",
|
||||
})),
|
||||
defer_loading: false,
|
||||
loading: ToolLoadingPolicy::Eager,
|
||||
execution: ToolExecution::Dynamic,
|
||||
presentation: None,
|
||||
search: None,
|
||||
supports_parallel_tool_calls: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renamed_overrides_name_only() {
|
||||
assert_eq!(
|
||||
tool_definition().renamed("mcp__orders__lookup_order".to_string()),
|
||||
tool_definition().renamed(ToolName::namespaced("mcp__orders__", "lookup_order")),
|
||||
ToolDefinition {
|
||||
name: "mcp__orders__lookup_order".to_string(),
|
||||
name: ToolName::namespaced("mcp__orders__", "lookup_order"),
|
||||
..tool_definition()
|
||||
}
|
||||
);
|
||||
@@ -36,7 +43,7 @@ fn into_deferred_drops_output_schema_and_sets_defer_loading() {
|
||||
tool_definition().into_deferred(),
|
||||
ToolDefinition {
|
||||
output_schema: None,
|
||||
defer_loading: true,
|
||||
loading: ToolLoadingPolicy::Deferred,
|
||||
..tool_definition()
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
use crate::JsonSchema;
|
||||
use crate::ResponsesApiNamespace;
|
||||
use crate::ResponsesApiNamespaceTool;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ToolName;
|
||||
use crate::ToolSearchOutputTool;
|
||||
use crate::ToolSpec;
|
||||
use crate::default_namespace_description;
|
||||
use crate::mcp_tool_to_deferred_responses_api_tool;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -23,23 +17,6 @@ pub struct ToolSearchSourceInfo {
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ToolSearchSource<'a> {
|
||||
pub server_name: &'a str,
|
||||
pub connector_name: Option<&'a str>,
|
||||
pub connector_description: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ToolSearchResultSource<'a> {
|
||||
pub server_name: &'a str,
|
||||
pub tool_namespace: &'a str,
|
||||
pub tool_name: &'a str,
|
||||
pub tool: &'a rmcp::model::Tool,
|
||||
pub connector_name: Option<&'a str>,
|
||||
pub connector_description: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DiscoverableToolType {
|
||||
@@ -203,74 +180,6 @@ pub fn create_tool_search_tool(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool_search_result_source_to_output_tool(
|
||||
source: ToolSearchResultSource<'_>,
|
||||
) -> Result<ToolSearchOutputTool, serde_json::Error> {
|
||||
Ok(ToolSearchOutputTool::Namespace(ResponsesApiNamespace {
|
||||
name: source.tool_namespace.to_string(),
|
||||
description: tool_search_result_source_namespace_description(source),
|
||||
tools: vec![tool_search_result_source_to_namespace_tool(source)?],
|
||||
}))
|
||||
}
|
||||
|
||||
fn tool_search_result_source_namespace_description(source: ToolSearchResultSource<'_>) -> String {
|
||||
source
|
||||
.connector_description
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.or_else(|| {
|
||||
source
|
||||
.connector_name
|
||||
.map(str::trim)
|
||||
.filter(|connector_name| !connector_name.is_empty())
|
||||
.map(|connector_name| format!("Tools for working with {connector_name}."))
|
||||
})
|
||||
.unwrap_or_else(|| default_namespace_description(source.tool_namespace))
|
||||
}
|
||||
|
||||
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)
|
||||
.map(ResponsesApiNamespaceTool::Function)
|
||||
}
|
||||
|
||||
pub fn collect_tool_search_source_infos<'a>(
|
||||
searchable_tools: impl IntoIterator<Item = ToolSearchSource<'a>>,
|
||||
) -> Vec<ToolSearchSourceInfo> {
|
||||
searchable_tools
|
||||
.into_iter()
|
||||
.filter_map(|tool| {
|
||||
if let Some(name) = tool
|
||||
.connector_name
|
||||
.map(str::trim)
|
||||
.filter(|connector_name| !connector_name.is_empty())
|
||||
{
|
||||
return Some(ToolSearchSourceInfo {
|
||||
name: name.to_string(),
|
||||
description: tool
|
||||
.connector_description
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string),
|
||||
});
|
||||
}
|
||||
|
||||
let name = tool.server_name.trim();
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ToolSearchSourceInfo {
|
||||
name: name.to_string(),
|
||||
description: None,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn create_tool_suggest_tool(discoverable_tools: &[ToolSuggestEntry]) -> ToolSpec {
|
||||
let discoverable_tool_ids = discoverable_tools
|
||||
.iter()
|
||||
|
||||
@@ -10,14 +10,12 @@ use crate::TOOL_SUGGEST_TOOL_NAME;
|
||||
use crate::ToolHandlerKind;
|
||||
use crate::ToolRegistryPlan;
|
||||
use crate::ToolRegistryPlanParams;
|
||||
use crate::ToolSearchSource;
|
||||
use crate::ToolSearchSourceInfo;
|
||||
use crate::ToolSpec;
|
||||
use crate::ToolsConfig;
|
||||
use crate::ViewImageToolOptions;
|
||||
use crate::WebSearchToolOptions;
|
||||
use crate::collect_code_mode_exec_prompt_tool_definitions;
|
||||
use crate::collect_tool_search_source_infos;
|
||||
use crate::collect_tool_suggest_entries;
|
||||
use crate::create_apply_patch_freeform_tool;
|
||||
use crate::create_apply_patch_json_tool;
|
||||
@@ -58,9 +56,9 @@ use crate::create_web_search_tool;
|
||||
use crate::create_write_stdin_tool;
|
||||
use crate::default_namespace_description;
|
||||
use crate::dynamic_tool_to_responses_api_tool;
|
||||
use crate::mcp_tool_to_responses_api_tool;
|
||||
use crate::request_permissions_tool_description;
|
||||
use crate::request_user_input_tool_description;
|
||||
use crate::tool_definition_to_responses_api_tool;
|
||||
use crate::tool_registry_plan_types::agent_type_description;
|
||||
use codex_protocol::openai_models::ApplyPatchToolType;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
@@ -261,16 +259,28 @@ pub fn build_tool_registry_plan(
|
||||
if config.search_tool
|
||||
&& (params.deferred_mcp_tools.is_some() || !deferred_dynamic_tools.is_empty())
|
||||
{
|
||||
let mut search_source_infos = params
|
||||
let mut search_source_infos: Vec<ToolSearchSourceInfo> = params
|
||||
.deferred_mcp_tools
|
||||
.map(|deferred_mcp_tools| {
|
||||
collect_tool_search_source_infos(deferred_mcp_tools.iter().map(|tool| {
|
||||
ToolSearchSource {
|
||||
server_name: tool.server_name,
|
||||
connector_name: tool.connector_name,
|
||||
connector_description: tool.connector_description,
|
||||
}
|
||||
}))
|
||||
deferred_mcp_tools
|
||||
.iter()
|
||||
.filter_map(|tool| {
|
||||
let search = tool.search.as_ref()?;
|
||||
let name = search.source_name.trim();
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(ToolSearchSourceInfo {
|
||||
name: name.to_string(),
|
||||
description: search
|
||||
.source_description
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -493,7 +503,7 @@ pub fn build_tool_registry_plan(
|
||||
}
|
||||
|
||||
if let Some(mcp_tools) = params.mcp_tools {
|
||||
let mut entries = mcp_tools.to_vec();
|
||||
let mut entries = mcp_tools.iter().collect::<Vec<_>>();
|
||||
entries.sort_by_key(|tool| tool.name.display());
|
||||
let mut namespace_entries = BTreeMap::new();
|
||||
|
||||
@@ -511,34 +521,20 @@ pub fn build_tool_registry_plan(
|
||||
|
||||
for (namespace, mut entries) in namespace_entries {
|
||||
entries.sort_by_key(|tool| tool.name.name.clone());
|
||||
let tool_namespace = params
|
||||
.tool_namespaces
|
||||
.and_then(|namespaces| namespaces.get(&namespace));
|
||||
let description = tool_namespace
|
||||
.and_then(|namespace| namespace.description.as_deref())
|
||||
let description = entries
|
||||
.iter()
|
||||
.filter_map(|tool| tool.presentation.as_ref())
|
||||
.filter_map(|presentation| presentation.namespace_description.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.find(|description| !description.is_empty())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| {
|
||||
let namespace_name = tool_namespace
|
||||
.map(|namespace| namespace.name.as_str())
|
||||
.unwrap_or(namespace.as_str());
|
||||
default_namespace_description(namespace_name)
|
||||
});
|
||||
.unwrap_or_else(|| default_namespace_description(&namespace));
|
||||
let mut tools = Vec::new();
|
||||
for tool in entries {
|
||||
match mcp_tool_to_responses_api_tool(&tool.name, tool.tool) {
|
||||
Ok(converted_tool) => {
|
||||
tools.push(ResponsesApiNamespaceTool::Function(converted_tool));
|
||||
plan.register_handler(tool.name, ToolHandlerKind::Mcp);
|
||||
}
|
||||
Err(error) => {
|
||||
let tool_name = &tool.name;
|
||||
tracing::error!(
|
||||
"Failed to convert `{tool_name}` MCP tool to OpenAI tool: {error:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
tools.push(ResponsesApiNamespaceTool::Function(
|
||||
tool_definition_to_responses_api_tool(tool),
|
||||
));
|
||||
plan.register_handler(tool.name.clone(), ToolHandlerKind::Mcp);
|
||||
}
|
||||
|
||||
if !tools.is_empty() {
|
||||
|
||||
@@ -11,14 +11,15 @@ use crate::ResponsesApiNamespaceTool;
|
||||
use crate::ResponsesApiTool;
|
||||
use crate::ResponsesApiWebSearchFilters;
|
||||
use crate::ResponsesApiWebSearchUserLocation;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolHandlerSpec;
|
||||
use crate::ToolName;
|
||||
use crate::ToolNamespace;
|
||||
use crate::ToolRegistryPlanDeferredTool;
|
||||
use crate::ToolRegistryPlanMcpTool;
|
||||
use crate::ToolSearchMetadata;
|
||||
use crate::ToolsConfigParams;
|
||||
use crate::WaitAgentTimeoutOptions;
|
||||
use crate::mcp_call_tool_result_output_schema;
|
||||
use crate::mcp_tool_to_tool_definition;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use codex_features::Feature;
|
||||
use codex_features::Features;
|
||||
@@ -1935,10 +1936,10 @@ 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>>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolDefinition>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
build_specs_with_discoverable_tools(
|
||||
@@ -1950,10 +1951,10 @@ fn build_specs<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
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>>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolDefinition>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
@@ -1967,10 +1968,10 @@ fn build_specs_with_discoverable_tools<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
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>>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolDefinition>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
@@ -1978,9 +1979,8 @@ fn build_specs_with_optional_tool_namespaces<'a>(
|
||||
let mcp_tool_inputs = mcp_tools.as_ref().map(|mcp_tools| {
|
||||
mcp_tools
|
||||
.iter()
|
||||
.map(|(name, tool)| ToolRegistryPlanMcpTool {
|
||||
name: name.clone(),
|
||||
tool,
|
||||
.map(|(name, tool)| {
|
||||
mcp_tool_to_tool_definition(name, tool).expect("convert MCP test tool")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
@@ -2104,19 +2104,26 @@ 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 {
|
||||
name: ToolName::namespaced(tool_namespace, tool_name),
|
||||
server_name,
|
||||
connector_name,
|
||||
connector_description,
|
||||
}
|
||||
fn deferred_mcp_tool(
|
||||
tool_name: &str,
|
||||
tool_namespace: &str,
|
||||
server_name: &str,
|
||||
connector_name: Option<&str>,
|
||||
connector_description: Option<&str>,
|
||||
) -> ToolDefinition {
|
||||
let mut definition = mcp_tool_to_tool_definition(
|
||||
&ToolName::namespaced(tool_namespace, tool_name),
|
||||
&mcp_tool(tool_name, "", serde_json::json!({"type": "object"})),
|
||||
)
|
||||
.expect("convert deferred MCP test tool")
|
||||
.into_deferred();
|
||||
definition.search = Some(ToolSearchMetadata {
|
||||
source_name: connector_name.unwrap_or(server_name).to_string(),
|
||||
source_description: connector_description.map(str::to_string),
|
||||
extra_terms: Vec::new(),
|
||||
limit_bucket: Some(server_name.to_string()),
|
||||
});
|
||||
definition
|
||||
}
|
||||
|
||||
fn assert_contains_tool_names(tools: &[ConfiguredToolSpec], expected_subset: &[&str]) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::ConfiguredToolSpec;
|
||||
use crate::DiscoverableTool;
|
||||
use crate::ToolDefinition;
|
||||
use crate::ToolName;
|
||||
use crate::ToolSpec;
|
||||
use crate::ToolsConfig;
|
||||
@@ -57,8 +58,8 @@ 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 mcp_tools: Option<&'a [ToolDefinition]>,
|
||||
pub deferred_mcp_tools: Option<&'a [ToolDefinition]>,
|
||||
pub tool_namespaces: Option<&'a HashMap<String, ToolNamespace>>,
|
||||
pub discoverable_tools: Option<&'a [DiscoverableTool]>,
|
||||
pub dynamic_tools: &'a [DynamicToolSpec],
|
||||
@@ -72,23 +73,6 @@ 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.
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl ToolRegistryPlan {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
|
||||
Reference in New Issue
Block a user