Files
codex/codex-rs/core/src/tools/spec_plan.rs
2026-05-18 21:49:50 -04:00

733 lines
26 KiB
Rust

use crate::config::DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
use crate::tools::code_mode::execute_spec::create_code_mode_tool;
use crate::tools::context::ToolInvocation;
use crate::tools::handlers::ApplyPatchHandler;
use crate::tools::handlers::CodeModeExecuteHandler;
use crate::tools::handlers::CodeModeWaitHandler;
use crate::tools::handlers::CreateGoalHandler;
use crate::tools::handlers::DynamicToolHandler;
use crate::tools::handlers::ExecCommandHandler;
use crate::tools::handlers::ExecCommandHandlerOptions;
use crate::tools::handlers::GetGoalHandler;
use crate::tools::handlers::ListMcpResourceTemplatesHandler;
use crate::tools::handlers::ListMcpResourcesHandler;
use crate::tools::handlers::McpHandler;
use crate::tools::handlers::PlanHandler;
use crate::tools::handlers::ReadMcpResourceHandler;
use crate::tools::handlers::ReloadPluginsHandler;
use crate::tools::handlers::RequestPermissionsHandler;
use crate::tools::handlers::RequestPluginInstallHandler;
use crate::tools::handlers::RequestUserInputHandler;
use crate::tools::handlers::ShellCommandHandler;
use crate::tools::handlers::ShellCommandHandlerOptions;
use crate::tools::handlers::TestSyncHandler;
use crate::tools::handlers::ToolSearchHandler;
use crate::tools::handlers::UpdateGoalHandler;
use crate::tools::handlers::ViewImageHandler;
use crate::tools::handlers::WriteStdinHandler;
use crate::tools::handlers::agent_jobs::ReportAgentJobResultHandler;
use crate::tools::handlers::agent_jobs::SpawnAgentsOnCsvHandler;
use crate::tools::handlers::extension_tools::ExtensionToolAdapter;
use crate::tools::handlers::multi_agents::CloseAgentHandler;
use crate::tools::handlers::multi_agents::ResumeAgentHandler;
use crate::tools::handlers::multi_agents::SendInputHandler;
use crate::tools::handlers::multi_agents::SpawnAgentHandler;
use crate::tools::handlers::multi_agents::WaitAgentHandler;
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::handlers::multi_agents_spec::SpawnAgentToolOptions;
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
use crate::tools::handlers::multi_agents_v2::CloseAgentHandler as CloseAgentHandlerV2;
use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2;
use crate::tools::handlers::multi_agents_v2::ListAgentsHandler as ListAgentsHandlerV2;
use crate::tools::handlers::multi_agents_v2::SendMessageHandler as SendMessageHandlerV2;
use crate::tools::handlers::multi_agents_v2::SpawnAgentHandler as SpawnAgentHandlerV2;
use crate::tools::handlers::multi_agents_v2::WaitAgentHandler as WaitAgentHandlerV2;
use crate::tools::handlers::reload_plugins_spec::RELOAD_PLUGINS_TOOL_NAME;
use crate::tools::handlers::view_image_spec::ViewImageToolOptions;
use crate::tools::hosted_spec::WebSearchToolOptions;
use crate::tools::hosted_spec::create_image_generation_tool;
use crate::tools::hosted_spec::create_web_search_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExposure;
use crate::tools::registry::ToolRegistry;
use crate::tools::registry::override_tool_exposure;
use crate::tools::router::ToolRouter;
use crate::tools::router::ToolRouterParams;
use codex_mcp::ToolInfo;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_tools::DiscoverableTool;
use codex_tools::ResponsesApiNamespace;
use codex_tools::ResponsesApiNamespaceTool;
use codex_tools::TOOL_SEARCH_TOOL_NAME;
use codex_tools::ToolCall as ExtensionToolCall;
use codex_tools::ToolEnvironmentMode;
use codex_tools::ToolExecutor;
use codex_tools::ToolName;
use codex_tools::ToolOutput;
use codex_tools::ToolSpec;
use codex_tools::ToolsConfig;
use codex_tools::collect_code_mode_exec_prompt_tool_definitions;
use codex_tools::default_namespace_description;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::sync::Arc;
use tracing::warn;
const MULTI_AGENT_V2_NAMESPACE_DESCRIPTION: &str = "Tools for spawning and managing sub-agents.";
#[derive(Clone, Copy)]
struct ToolRegistryBuildParams<'a> {
mcp_tools: Option<&'a [ToolInfo]>,
deferred_mcp_tools: Option<&'a [ToolInfo]>,
discoverable_tools: Option<&'a [DiscoverableTool]>,
extension_tool_executors: &'a [Arc<dyn ToolExecutor<ExtensionToolCall>>],
dynamic_tools: &'a [DynamicToolSpec],
default_agent_type_description: &'a str,
wait_agent_timeouts: WaitAgentTimeoutOptions,
}
pub(crate) fn build_tool_router(config: &ToolsConfig, params: ToolRouterParams<'_>) -> ToolRouter {
let (model_visible_specs, registry) = build_tool_specs_and_registry(config, params);
ToolRouter::from_parts(registry, model_visible_specs)
}
fn build_tool_specs_and_registry(
config: &ToolsConfig,
params: ToolRouterParams<'_>,
) -> (Vec<ToolSpec>, ToolRegistry) {
let ToolRouterParams {
mcp_tools,
deferred_mcp_tools,
discoverable_tools,
extension_tool_executors,
dynamic_tools,
} = params;
let default_agent_type_description =
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
let mut executors = collect_tool_executors(
config,
ToolRegistryBuildParams {
mcp_tools: mcp_tools.as_deref(),
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
discoverable_tools: discoverable_tools.as_deref(),
extension_tool_executors: &extension_tool_executors,
dynamic_tools,
default_agent_type_description: &default_agent_type_description,
wait_agent_timeouts: wait_agent_timeout_options(config),
},
);
append_tool_search_executor(config, &mut executors);
prepend_code_mode_executors(config, &mut executors);
build_model_visible_specs_and_registry(config, executors, hosted_model_tool_specs(config))
}
fn build_model_visible_specs_and_registry(
config: &ToolsConfig,
executors: Vec<Arc<dyn CoreToolRuntime>>,
hosted_specs: Vec<ToolSpec>,
) -> (Vec<ToolSpec>, ToolRegistry) {
let mut specs = Vec::new();
let mut seen_tool_names = HashSet::new();
for executor in &executors {
let tool_name = executor.tool_name();
if !seen_tool_names.insert(tool_name.clone()) {
continue;
}
let exposure = executor.exposure();
if exposure.is_direct()
&& !is_hidden_by_code_mode_only(config, &tool_name, exposure)
&& let Some(spec) = executor.spec()
{
specs.push(spec_for_model_request(config, exposure, spec));
}
}
for spec in hosted_specs {
if !is_hidden_by_code_mode_only(config, &ToolName::plain(spec.name()), ToolExposure::Direct)
{
specs.push(spec);
}
}
let registry = ToolRegistry::from_tools(executors);
let model_visible_specs = merge_into_namespaces(specs)
.into_iter()
.filter(|spec| config.namespace_tools || !matches!(spec, ToolSpec::Namespace(_)))
.collect();
(model_visible_specs, registry)
}
fn spec_for_model_request(
config: &ToolsConfig,
exposure: ToolExposure,
spec: ToolSpec,
) -> ToolSpec {
if config.code_mode_enabled
&& exposure != ToolExposure::DirectModelOnly
&& codex_code_mode::is_code_mode_nested_tool(spec.name())
{
codex_tools::augment_tool_spec_for_code_mode(spec)
} else {
spec
}
}
pub(crate) fn hosted_model_tool_specs(config: &ToolsConfig) -> Vec<ToolSpec> {
let mut specs = Vec::new();
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
web_search_mode: config.web_search_mode,
web_search_config: config.web_search_config.as_ref(),
web_search_tool_type: config.web_search_tool_type,
}) {
specs.push(web_search_tool);
}
if config.image_gen_tool {
specs.push(create_image_generation_tool("png"));
}
specs
}
fn wait_agent_timeout_options(config: &ToolsConfig) -> WaitAgentTimeoutOptions {
if config.multi_agent_v2 {
return WaitAgentTimeoutOptions {
default_timeout_ms: config
.wait_agent_default_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS),
min_timeout_ms: config
.wait_agent_min_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS),
max_timeout_ms: config
.wait_agent_max_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS),
};
}
WaitAgentTimeoutOptions {
default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS,
min_timeout_ms: MIN_WAIT_TIMEOUT_MS,
max_timeout_ms: MAX_WAIT_TIMEOUT_MS,
}
}
fn agent_type_description(config: &ToolsConfig, default_agent_type_description: &str) -> String {
if config.agent_type_description.is_empty() {
default_agent_type_description.to_string()
} else {
config.agent_type_description.clone()
}
}
fn is_hidden_by_code_mode_only(
config: &ToolsConfig,
tool_name: &ToolName,
exposure: ToolExposure,
) -> bool {
config.code_mode_only_enabled
&& exposure != ToolExposure::DirectModelOnly
&& codex_code_mode::is_code_mode_nested_tool(&codex_tools::code_mode_name_for_tool_name(
tool_name,
))
}
fn build_code_mode_executors(
config: &ToolsConfig,
executors: &[Arc<dyn CoreToolRuntime>],
deferred_tools_available: bool,
) -> Vec<Arc<dyn CoreToolRuntime>> {
if !config.code_mode_enabled {
return vec![];
}
let code_mode_nested_tool_specs = executors
.iter()
.filter_map(|executor| {
if executor.exposure() == ToolExposure::DirectModelOnly {
return None;
}
executor.spec()
})
.collect::<Vec<_>>();
let namespace_descriptions = code_mode_namespace_descriptions(&code_mode_nested_tool_specs);
let mut enabled_tools =
collect_code_mode_exec_prompt_tool_definitions(code_mode_nested_tool_specs.iter());
enabled_tools
.sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions));
vec![
Arc::new(CodeModeExecuteHandler::new(
create_code_mode_tool(
&enabled_tools,
&namespace_descriptions,
config.code_mode_only_enabled,
deferred_tools_available,
),
code_mode_nested_tool_specs,
)),
Arc::new(CodeModeWaitHandler),
]
}
fn merge_into_namespaces(specs: Vec<ToolSpec>) -> Vec<ToolSpec> {
let mut merged_specs = Vec::with_capacity(specs.len());
let mut namespace_indices = BTreeMap::<String, usize>::new();
for spec in specs {
match spec {
ToolSpec::Namespace(mut namespace) => {
if let Some(index) = namespace_indices.get(&namespace.name).copied() {
let ToolSpec::Namespace(existing_namespace) = &mut merged_specs[index] else {
unreachable!("namespace index must point to a namespace spec");
};
if existing_namespace.description.trim().is_empty()
&& !namespace.description.trim().is_empty()
{
existing_namespace.description = namespace.description;
}
existing_namespace.tools.append(&mut namespace.tools);
continue;
}
namespace_indices.insert(namespace.name.clone(), merged_specs.len());
merged_specs.push(ToolSpec::Namespace(namespace));
}
spec => merged_specs.push(spec),
}
}
for spec in &mut merged_specs {
let ToolSpec::Namespace(namespace) = spec else {
continue;
};
namespace.tools.sort_by(|left, right| match (left, right) {
(
ResponsesApiNamespaceTool::Function(left),
ResponsesApiNamespaceTool::Function(right),
) => left.name.cmp(&right.name),
});
if namespace.description.trim().is_empty() {
namespace.description = default_namespace_description(&namespace.name);
}
}
merged_specs
}
fn code_mode_namespace_descriptions(
specs: &[ToolSpec],
) -> BTreeMap<String, codex_code_mode::ToolNamespaceDescription> {
let mut namespace_descriptions = BTreeMap::new();
for spec in specs {
let ToolSpec::Namespace(namespace) = spec else {
continue;
};
let entry = namespace_descriptions
.entry(namespace.name.clone())
.or_insert_with(|| codex_code_mode::ToolNamespaceDescription {
name: namespace.name.clone(),
description: namespace.description.clone(),
});
if entry.description.trim().is_empty() && !namespace.description.trim().is_empty() {
entry.description = namespace.description.clone();
}
}
namespace_descriptions
}
fn collect_tool_executors(
config: &ToolsConfig,
params: ToolRegistryBuildParams<'_>,
) -> Vec<Arc<dyn CoreToolRuntime>> {
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
let mut executors = Vec::<Arc<dyn CoreToolRuntime>>::new();
if config.environment_mode.has_environment() {
let include_environment_id =
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
match &config.shell_type {
ConfigShellToolType::UnifiedExec => {
executors.push(Arc::new(ExecCommandHandler::new(
ExecCommandHandlerOptions {
allow_login_shell: config.allow_login_shell,
exec_permission_approvals_enabled,
include_environment_id,
},
)));
executors.push(Arc::new(WriteStdinHandler));
}
ConfigShellToolType::Disabled => {}
ConfigShellToolType::Default
| ConfigShellToolType::Local
| ConfigShellToolType::ShellCommand => {
executors.push(Arc::new(ShellCommandHandler::new(
ShellCommandHandlerOptions {
backend_config: config.shell_command_backend,
allow_login_shell: config.allow_login_shell,
exec_permission_approvals_enabled,
},
)));
}
}
}
if config.environment_mode.has_environment()
&& config.shell_type != ConfigShellToolType::Disabled
{
match &config.shell_type {
ConfigShellToolType::UnifiedExec => {
executors.push(Arc::new(ShellCommandHandler::from(
config.shell_command_backend,
)));
}
ConfigShellToolType::Default
| ConfigShellToolType::Local
| ConfigShellToolType::ShellCommand
| ConfigShellToolType::Disabled => {}
}
}
if params.mcp_tools.is_some() {
executors.push(Arc::new(ListMcpResourcesHandler));
executors.push(Arc::new(ListMcpResourceTemplatesHandler));
executors.push(Arc::new(ReadMcpResourceHandler));
}
executors.push(Arc::new(PlanHandler));
if config.goal_tools {
executors.push(Arc::new(GetGoalHandler));
executors.push(Arc::new(CreateGoalHandler));
executors.push(Arc::new(UpdateGoalHandler));
}
executors.push(Arc::new(RequestUserInputHandler {
available_modes: config.request_user_input_available_modes.clone(),
}));
if config.request_permissions_tool_enabled {
executors.push(Arc::new(RequestPermissionsHandler));
}
if config.tool_suggest
&& let Some(discoverable_tools) =
params.discoverable_tools.filter(|tools| !tools.is_empty())
{
executors.push(Arc::new(RequestPluginInstallHandler::new(
discoverable_tools,
)));
}
if config.environment_mode.has_environment() && config.apply_patch_tool_type.is_some() {
let include_environment_id =
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
executors.push(Arc::new(ApplyPatchHandler::new(include_environment_id)));
}
if config
.experimental_supported_tools
.iter()
.any(|tool| tool == "test_sync_tool")
{
executors.push(Arc::new(TestSyncHandler));
}
if config
.experimental_supported_tools
.iter()
.any(|tool| tool == RELOAD_PLUGINS_TOOL_NAME)
{
executors.push(Arc::new(ReloadPluginsHandler));
}
if config.environment_mode.has_environment() {
let include_environment_id =
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
executors.push(Arc::new(ViewImageHandler::new(ViewImageToolOptions {
can_request_original_image_detail: config.can_request_original_image_detail,
include_environment_id,
})));
}
if config.collab_tools {
if config.multi_agent_v2 {
let exposure = if config.multi_agent_v2_non_code_mode_only {
ToolExposure::DirectModelOnly
} else {
ToolExposure::Direct
};
let tool_namespace = config
.namespace_tools
.then_some(config.multi_agent_v2_tool_namespace.as_deref())
.flatten();
let agent_type_description =
agent_type_description(config, params.default_agent_type_description);
executors.push(multi_agent_v2_handler(
SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
available_models: config.available_models.clone(),
agent_type_description,
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
include_usage_hint: config.spawn_agent_usage_hint,
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
}),
exposure,
tool_namespace,
));
executors.push(multi_agent_v2_handler(
SendMessageHandlerV2,
exposure,
tool_namespace,
));
executors.push(multi_agent_v2_handler(
FollowupTaskHandlerV2,
exposure,
tool_namespace,
));
executors.push(multi_agent_v2_handler(
WaitAgentHandlerV2::new(params.wait_agent_timeouts),
exposure,
tool_namespace,
));
executors.push(multi_agent_v2_handler(
CloseAgentHandlerV2,
exposure,
tool_namespace,
));
executors.push(multi_agent_v2_handler(
ListAgentsHandlerV2,
exposure,
tool_namespace,
));
} else {
let agent_type_description =
agent_type_description(config, params.default_agent_type_description);
executors.push(Arc::new(SpawnAgentHandler::new(SpawnAgentToolOptions {
available_models: config.available_models.clone(),
agent_type_description,
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
include_usage_hint: config.spawn_agent_usage_hint,
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
})));
executors.push(Arc::new(SendInputHandler));
executors.push(Arc::new(ResumeAgentHandler));
executors.push(Arc::new(WaitAgentHandler::new(params.wait_agent_timeouts)));
executors.push(Arc::new(CloseAgentHandler));
}
}
if config.agent_jobs_tools {
executors.push(Arc::new(SpawnAgentsOnCsvHandler));
if config.agent_jobs_worker_tools {
executors.push(Arc::new(ReportAgentJobResultHandler));
}
}
if let Some(mcp_tools) = params.mcp_tools {
for tool in mcp_tools {
executors.push(Arc::new(McpHandler::new(tool.clone())));
}
}
if let Some(deferred_mcp_tools) = params.deferred_mcp_tools {
for tool in deferred_mcp_tools {
executors.push(Arc::new(McpHandler::with_exposure(
tool.clone(),
ToolExposure::Deferred,
)));
}
}
for tool in params.dynamic_tools {
let Some(handler) = DynamicToolHandler::new(tool).map(Arc::new) else {
tracing::error!(
"Failed to convert dynamic tool {:?} to OpenAI tool",
tool.name
);
continue;
};
executors.push(handler);
}
append_extension_tool_executors(config, params.extension_tool_executors, &mut executors);
executors
}
fn append_tool_search_executor(
config: &ToolsConfig,
executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
) {
if !(config.search_tool && config.namespace_tools) {
return;
}
let search_infos = executors
.iter()
.filter(|executor| executor.exposure() == ToolExposure::Deferred)
.filter_map(|executor| executor.search_info())
.collect::<Vec<_>>();
if search_infos.is_empty() {
return;
}
executors.push(Arc::new(ToolSearchHandler::new(search_infos)));
}
fn prepend_code_mode_executors(
config: &ToolsConfig,
executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
) {
let deferred_tools_available = config.search_tool
&& executors
.iter()
.any(|executor| executor.exposure() == ToolExposure::Deferred);
let code_mode_executors =
build_code_mode_executors(config, executors, deferred_tools_available);
executors.splice(0..0, code_mode_executors);
}
fn append_extension_tool_executors(
config: &ToolsConfig,
executors: &[Arc<dyn ToolExecutor<ExtensionToolCall>>],
registered_executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
) {
if executors.is_empty() {
return;
}
let mut reserved_tool_names = registered_executors
.iter()
.map(|executor| executor.tool_name())
.collect::<HashSet<_>>();
if config.code_mode_enabled {
reserved_tool_names.insert(ToolName::plain(codex_code_mode::PUBLIC_TOOL_NAME));
reserved_tool_names.insert(ToolName::plain(codex_code_mode::WAIT_TOOL_NAME));
}
if config.search_tool
&& config.namespace_tools
&& registered_executors
.iter()
.any(|executor| executor.exposure() == ToolExposure::Deferred)
{
reserved_tool_names.insert(ToolName::plain(TOOL_SEARCH_TOOL_NAME));
}
for executor in executors.iter().cloned() {
let tool_name = executor.tool_name();
if !reserved_tool_names.insert(tool_name.clone()) {
warn!("Skipping extension tool `{tool_name}`: tool already registered");
continue;
}
registered_executors.push(Arc::new(ExtensionToolAdapter::new(executor)));
}
}
fn multi_agent_v2_handler(
handler: impl CoreToolRuntime + 'static,
exposure: ToolExposure,
namespace: Option<&str>,
) -> Arc<dyn CoreToolRuntime> {
let handler: Arc<dyn CoreToolRuntime> = match namespace {
Some(namespace) => Arc::new(MultiAgentV2NamespaceOverride {
handler: Arc::new(handler),
namespace: namespace.to_string(),
}),
None => Arc::new(handler),
};
override_tool_exposure(handler, exposure)
}
struct MultiAgentV2NamespaceOverride {
handler: Arc<dyn CoreToolRuntime>,
namespace: String,
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for MultiAgentV2NamespaceOverride {
fn tool_name(&self) -> ToolName {
ToolName::namespaced(self.namespace.clone(), self.handler.tool_name().name)
}
fn spec(&self) -> Option<ToolSpec> {
match self.handler.spec()? {
ToolSpec::Function(tool) => Some(ToolSpec::Namespace(ResponsesApiNamespace {
name: self.namespace.clone(),
description: MULTI_AGENT_V2_NAMESPACE_DESCRIPTION.to_string(),
tools: vec![ResponsesApiNamespaceTool::Function(tool)],
})),
spec => Some(spec),
}
}
fn exposure(&self) -> ToolExposure {
self.handler.exposure()
}
fn supports_parallel_tool_calls(&self) -> bool {
self.handler.supports_parallel_tool_calls()
}
async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn ToolOutput>, codex_tools::FunctionCallError> {
self.handler.handle(invocation).await
}
}
impl CoreToolRuntime for MultiAgentV2NamespaceOverride {
fn matches_kind(&self, payload: &crate::tools::context::ToolPayload) -> bool {
self.handler.matches_kind(payload)
}
fn search_info(&self) -> Option<crate::tools::tool_search_entry::ToolSearchInfo> {
self.handler.search_info()
}
fn create_diff_consumer(
&self,
) -> Option<Box<dyn crate::tools::registry::ToolArgumentDiffConsumer>> {
self.handler.create_diff_consumer()
}
}
fn compare_code_mode_tools(
left: &codex_code_mode::ToolDefinition,
right: &codex_code_mode::ToolDefinition,
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
) -> std::cmp::Ordering {
let left_namespace = code_mode_namespace_name(left, namespace_descriptions);
let right_namespace = code_mode_namespace_name(right, namespace_descriptions);
left_namespace
.cmp(&right_namespace)
.then_with(|| left.tool_name.name.cmp(&right.tool_name.name))
.then_with(|| left.name.cmp(&right.name))
}
fn code_mode_namespace_name<'a>(
tool: &codex_code_mode::ToolDefinition,
namespace_descriptions: &'a BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
) -> Option<&'a str> {
tool.tool_name
.namespace
.as_ref()
.and_then(|namespace| namespace_descriptions.get(namespace))
.map(|namespace_description| namespace_description.name.as_str())
}
#[cfg(test)]
#[path = "spec_plan_model_tests.rs"]
mod model_tests;
#[cfg(test)]
#[path = "spec_plan_tests.rs"]
mod tests;