From 828b837235f0bf6adaf4f8648951bf60c722e86b Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 2 Apr 2026 00:18:18 -0700 Subject: [PATCH] Extract tool registry planning into codex-tools (#16513) ## Why This is a larger step in the `codex-core` -> `codex-tools` migration called out in `AGENTS.md`. `codex-rs/core/src/tools/spec.rs` had become mostly pure tool-spec assembly plus handler registration. That made it hard to move more of the tool-definition layer into `codex-tools`, because the runtime binding and the crate-independent planning logic were still interleaved in one function. Splitting those concerns gives `codex-tools` ownership of the declarative registry plan while keeping `codex-core` responsible for instantiating concrete handlers. ## What Changed - Add a `codex-tools` registry-plan layer in `codex-rs/tools/src/tool_registry_plan.rs` and `codex-rs/tools/src/tool_registry_plan_types.rs`. - Move feature-gated tool-spec assembly, MCP/dynamic tool conversion, tool-search aliases, and code-mode nested-plan expansion into `codex-tools`. - Keep `codex-rs/core/src/tools/spec.rs` as the core-side adapter that maps each planned handler kind to concrete runtime handler instances. - Update `spec_tests.rs` to import the moved `codex_tools` symbols directly instead of relying on top-level `spec.rs` re-exports. This is intended to be a straight refactor with no behavior change and no new test surface. ## Verification - `cargo test -p codex-tools` - `cargo test -p codex-core tools::spec::tests` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/16513). * #16521 * __->__ #16513 --- codex-rs/core/src/tools/spec.rs | 674 ++++-------------- codex-rs/core/src/tools/spec_tests.rs | 13 + codex-rs/tools/src/lib.rs | 8 + codex-rs/tools/src/tool_registry_plan.rs | 488 +++++++++++++ .../tools/src/tool_registry_plan_types.rs | 118 +++ 5 files changed, 768 insertions(+), 533 deletions(-) create mode 100644 codex-rs/tools/src/tool_registry_plan.rs create mode 100644 codex-rs/tools/src/tool_registry_plan_types.rs diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 1a9db07084..86f4840a43 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -1,79 +1,23 @@ use crate::shell::Shell; use crate::shell::ShellType; -use crate::tools::code_mode::PUBLIC_TOOL_NAME; -use crate::tools::code_mode::WAIT_TOOL_NAME; 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::registry::ToolRegistryBuilder; -use crate::tools::registry::tool_handler_key; use codex_mcp::mcp::CODEX_APPS_MCP_SERVER_NAME; use codex_mcp::mcp_connection_manager::ToolInfo; use codex_protocol::dynamic_tools::DynamicToolSpec; -use codex_protocol::openai_models::ApplyPatchToolType; -use codex_protocol::openai_models::ConfigShellToolType; -use codex_tools::CommandToolOptions; use codex_tools::DiscoverableTool; -use codex_tools::REQUEST_USER_INPUT_TOOL_NAME; -use codex_tools::ShellToolOptions; -use codex_tools::SpawnAgentToolOptions; -use codex_tools::TOOL_SEARCH_DEFAULT_LIMIT; -use codex_tools::TOOL_SEARCH_TOOL_NAME; -use codex_tools::TOOL_SUGGEST_TOOL_NAME; -use codex_tools::ToolSearchAppSource; -use codex_tools::ToolSpec; +use codex_tools::ToolHandlerKind; +use codex_tools::ToolRegistryPlanAppTool; +use codex_tools::ToolRegistryPlanParams; use codex_tools::ToolUserShellType; use codex_tools::ToolsConfig; -use codex_tools::ViewImageToolOptions; use codex_tools::WaitAgentTimeoutOptions; -use codex_tools::WebSearchToolOptions; -use codex_tools::augment_tool_spec_for_code_mode; -use codex_tools::collect_code_mode_tool_definitions; -use codex_tools::collect_tool_search_app_infos; -use codex_tools::collect_tool_suggest_entries; -use codex_tools::create_apply_patch_freeform_tool; -use codex_tools::create_apply_patch_json_tool; -use codex_tools::create_assign_task_tool; -use codex_tools::create_close_agent_tool_v1; -use codex_tools::create_close_agent_tool_v2; -use codex_tools::create_code_mode_tool; -use codex_tools::create_exec_command_tool; -use codex_tools::create_image_generation_tool; -use codex_tools::create_js_repl_reset_tool; -use codex_tools::create_js_repl_tool; -use codex_tools::create_list_agents_tool; -use codex_tools::create_list_dir_tool; -use codex_tools::create_list_mcp_resource_templates_tool; -use codex_tools::create_list_mcp_resources_tool; -use codex_tools::create_local_shell_tool; -use codex_tools::create_read_mcp_resource_tool; -use codex_tools::create_report_agent_job_result_tool; -use codex_tools::create_request_permissions_tool; -use codex_tools::create_request_user_input_tool; -use codex_tools::create_resume_agent_tool; -use codex_tools::create_send_input_tool_v1; -use codex_tools::create_send_message_tool; -use codex_tools::create_shell_command_tool; -use codex_tools::create_shell_tool; -use codex_tools::create_spawn_agent_tool_v1; -use codex_tools::create_spawn_agent_tool_v2; -use codex_tools::create_spawn_agents_on_csv_tool; -use codex_tools::create_test_sync_tool; -use codex_tools::create_tool_search_tool; -use codex_tools::create_tool_suggest_tool; -use codex_tools::create_update_plan_tool; -use codex_tools::create_view_image_tool; -use codex_tools::create_wait_agent_tool_v1; -use codex_tools::create_wait_agent_tool_v2; -use codex_tools::create_wait_tool; -use codex_tools::create_web_search_tool; -use codex_tools::create_write_stdin_tool; -use codex_tools::dynamic_tool_to_responses_api_tool; -use codex_tools::mcp_tool_to_responses_api_tool; -use codex_tools::request_permissions_tool_description; -use codex_tools::request_user_input_tool_description; +use codex_tools::build_tool_registry_plan; use std::collections::HashMap; +use std::sync::Arc; #[cfg(test)] pub(crate) use codex_tools::mcp_call_tool_result_output_schema; @@ -88,32 +32,6 @@ pub(crate) fn tool_user_shell_type(user_shell: &Shell) -> ToolUserShellType { } } -fn agent_type_description(config: &ToolsConfig) -> String { - if config.agent_type_description.is_empty() { - crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new()) - } else { - config.agent_type_description.clone() - } -} - -fn push_tool_spec( - builder: &mut ToolRegistryBuilder, - spec: ToolSpec, - supports_parallel_tool_calls: bool, - code_mode_enabled: bool, -) { - let spec = if code_mode_enabled { - augment_tool_spec_for_code_mode(spec) - } else { - spec - }; - if supports_parallel_tool_calls { - builder.push_spec_with_parallel_support(spec, /*supports_parallel_tool_calls*/ true); - } else { - builder.push_spec(spec); - } -} - /// Builds the tool registry builder while collecting tool specs for later serialization. #[cfg(test)] pub(crate) fn build_specs( @@ -168,10 +86,38 @@ pub(crate) fn build_specs_with_discoverable_tools( 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 std::sync::Arc; let mut builder = ToolRegistryBuilder::new(); - + let app_tool_sources = app_tools.as_ref().map(|app_tools| { + app_tools + .values() + .map(|tool| ToolRegistryPlanAppTool { + tool_name: tool.tool_name.as_str(), + tool_namespace: tool.tool_namespace.as_str(), + server_name: tool.server_name.as_str(), + connector_name: tool.connector_name.as_deref(), + connector_description: tool.connector_description.as_deref(), + }) + .collect::>() + }); + let default_agent_type_description = + crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new()); + let plan = build_tool_registry_plan( + config, + ToolRegistryPlanParams { + mcp_tools: mcp_tools.as_ref(), + app_tools: app_tool_sources.as_deref(), + discoverable_tools: discoverable_tools.as_deref(), + dynamic_tools, + default_agent_type_description: &default_agent_type_description, + wait_agent_timeouts: WaitAgentTimeoutOptions { + default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS, + min_timeout_ms: MIN_WAIT_TIMEOUT_MS, + max_timeout_ms: MAX_WAIT_TIMEOUT_MS, + }, + codex_apps_mcp_server_name: CODEX_APPS_MCP_SERVER_NAME, + }, + ); let shell_handler = Arc::new(ShellHandler); let unified_exec_handler = Arc::new(UnifiedExecHandler); let plan_handler = Arc::new(PlanHandler); @@ -185,468 +131,130 @@ pub(crate) fn build_specs_with_discoverable_tools( let request_user_input_handler = Arc::new(RequestUserInputHandler { default_mode_request_user_input: config.default_mode_request_user_input, }); + let mut tool_search_handler = None; let tool_suggest_handler = Arc::new(ToolSuggestHandler); let code_mode_handler = Arc::new(CodeModeExecuteHandler); let code_mode_wait_handler = Arc::new(CodeModeWaitHandler); let js_repl_handler = Arc::new(JsReplHandler); let js_repl_reset_handler = Arc::new(JsReplResetHandler); - let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled; - if config.code_mode_enabled { - let nested_config = config.for_code_mode_nested_tools(); - let (nested_specs, _) = build_specs_with_discoverable_tools( - &nested_config, - mcp_tools.clone(), - app_tools.clone(), - /*discoverable_tools*/ None, - dynamic_tools, - ) - .build(); - let enabled_tools = collect_code_mode_tool_definitions( - nested_specs - .iter() - .map(|configured_tool| &configured_tool.spec), - ) - .into_iter() - .map(|tool| (tool.name, tool.description)) - .collect::>(); - push_tool_spec( - &mut builder, - create_code_mode_tool(&enabled_tools, config.code_mode_only_enabled), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler(PUBLIC_TOOL_NAME, code_mode_handler); - push_tool_spec( - &mut builder, - create_wait_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler(WAIT_TOOL_NAME, code_mode_wait_handler); - } - - match &config.shell_type { - ConfigShellToolType::Default => { - push_tool_spec( - &mut builder, - create_shell_tool(ShellToolOptions { - exec_permission_approvals_enabled, - }), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, + for spec in plan.specs { + if spec.supports_parallel_tool_calls { + builder.push_spec_with_parallel_support( + spec.spec, /*supports_parallel_tool_calls*/ true, ); - } - ConfigShellToolType::Local => { - push_tool_spec( - &mut builder, - create_local_shell_tool(), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - } - ConfigShellToolType::UnifiedExec => { - push_tool_spec( - &mut builder, - create_exec_command_tool(CommandToolOptions { - allow_login_shell: config.allow_login_shell, - exec_permission_approvals_enabled, - }), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_write_stdin_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("exec_command", unified_exec_handler.clone()); - builder.register_handler("write_stdin", unified_exec_handler); - } - ConfigShellToolType::Disabled => { - // Do nothing. - } - ConfigShellToolType::ShellCommand => { - push_tool_spec( - &mut builder, - create_shell_command_tool(CommandToolOptions { - allow_login_shell: config.allow_login_shell, - exec_permission_approvals_enabled, - }), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - } - } - - if config.shell_type != ConfigShellToolType::Disabled { - // Always register shell aliases so older prompts remain compatible. - builder.register_handler("shell", shell_handler.clone()); - builder.register_handler("container.exec", shell_handler.clone()); - builder.register_handler("local_shell", shell_handler); - builder.register_handler("shell_command", shell_command_handler); - } - - if mcp_tools.is_some() { - push_tool_spec( - &mut builder, - create_list_mcp_resources_tool(), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_list_mcp_resource_templates_tool(), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_read_mcp_resource_tool(), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - builder.register_handler("list_mcp_resources", mcp_resource_handler.clone()); - builder.register_handler("list_mcp_resource_templates", mcp_resource_handler.clone()); - builder.register_handler("read_mcp_resource", mcp_resource_handler); - } - - push_tool_spec( - &mut builder, - create_update_plan_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("update_plan", plan_handler); - - if config.js_repl_enabled { - push_tool_spec( - &mut builder, - create_js_repl_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_js_repl_reset_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("js_repl", js_repl_handler); - builder.register_handler("js_repl_reset", js_repl_reset_handler); - } - - if config.request_user_input { - push_tool_spec( - &mut builder, - create_request_user_input_tool(request_user_input_tool_description( - config.default_mode_request_user_input, - )), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler(REQUEST_USER_INPUT_TOOL_NAME, request_user_input_handler); - } - - if config.request_permissions_tool_enabled { - push_tool_spec( - &mut builder, - create_request_permissions_tool(request_permissions_tool_description()), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("request_permissions", request_permissions_handler); - } - - if config.search_tool - && let Some(app_tools) = app_tools - { - let search_tool_handler = Arc::new(ToolSearchHandler::new(app_tools.clone())); - let search_app_infos = collect_tool_search_app_infos( - app_tools.values().map(|tool| ToolSearchAppSource { - server_name: &tool.server_name, - connector_name: tool.connector_name.as_deref(), - connector_description: tool.connector_description.as_deref(), - }), - CODEX_APPS_MCP_SERVER_NAME, - ); - push_tool_spec( - &mut builder, - create_tool_search_tool(&search_app_infos, TOOL_SEARCH_DEFAULT_LIMIT), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - builder.register_handler(TOOL_SEARCH_TOOL_NAME, search_tool_handler); - - for tool in app_tools.values() { - let alias_name = - tool_handler_key(tool.tool_name.as_str(), Some(tool.tool_namespace.as_str())); - - builder.register_handler(alias_name, mcp_handler.clone()); - } - } - - if config.tool_suggest - && let Some(discoverable_tools) = discoverable_tools - .as_ref() - .filter(|tools| !tools.is_empty()) - { - builder.push_spec_with_parallel_support( - create_tool_suggest_tool(&collect_tool_suggest_entries(discoverable_tools)), - /*supports_parallel_tool_calls*/ true, - ); - builder.register_handler(TOOL_SUGGEST_TOOL_NAME, tool_suggest_handler); - } - - if let Some(apply_patch_tool_type) = &config.apply_patch_tool_type { - match apply_patch_tool_type { - ApplyPatchToolType::Freeform => { - push_tool_spec( - &mut builder, - create_apply_patch_freeform_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - } - ApplyPatchToolType::Function => { - push_tool_spec( - &mut builder, - create_apply_patch_json_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - } - } - builder.register_handler("apply_patch", apply_patch_handler); - } - - if config - .experimental_supported_tools - .iter() - .any(|tool| tool == "list_dir") - { - let list_dir_handler = Arc::new(ListDirHandler); - push_tool_spec( - &mut builder, - create_list_dir_tool(), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - builder.register_handler("list_dir", list_dir_handler); - } - - if config - .experimental_supported_tools - .contains(&"test_sync_tool".to_string()) - { - let test_sync_handler = Arc::new(TestSyncHandler); - push_tool_spec( - &mut builder, - create_test_sync_tool(), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - builder.register_handler("test_sync_tool", test_sync_handler); - } - - 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, - }) { - push_tool_spec( - &mut builder, - web_search_tool, - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - } - - if config.image_gen_tool { - push_tool_spec( - &mut builder, - create_image_generation_tool("png"), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - } - - push_tool_spec( - &mut builder, - create_view_image_tool(ViewImageToolOptions { - can_request_original_image_detail: config.can_request_original_image_detail, - }), - /*supports_parallel_tool_calls*/ true, - config.code_mode_enabled, - ); - builder.register_handler("view_image", view_image_handler); - - if config.collab_tools { - if config.multi_agent_v2 { - let agent_type_description = agent_type_description(config); - push_tool_spec( - &mut builder, - create_spawn_agent_tool_v2(SpawnAgentToolOptions { - available_models: &config.available_models, - agent_type_description, - }), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_send_message_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_assign_task_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_wait_agent_tool_v2(WaitAgentTimeoutOptions { - default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS, - min_timeout_ms: MIN_WAIT_TIMEOUT_MS, - max_timeout_ms: MAX_WAIT_TIMEOUT_MS, - }), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_close_agent_tool_v2(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_list_agents_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("spawn_agent", Arc::new(SpawnAgentHandlerV2)); - builder.register_handler("send_message", Arc::new(SendMessageHandlerV2)); - builder.register_handler("assign_task", Arc::new(AssignTaskHandlerV2)); - builder.register_handler("wait_agent", Arc::new(WaitAgentHandlerV2)); - builder.register_handler("close_agent", Arc::new(CloseAgentHandlerV2)); - builder.register_handler("list_agents", Arc::new(ListAgentsHandlerV2)); } else { - let agent_type_description = agent_type_description(config); - push_tool_spec( - &mut builder, - create_spawn_agent_tool_v1(SpawnAgentToolOptions { - available_models: &config.available_models, - agent_type_description, - }), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_send_input_tool_v1(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_resume_agent_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("resume_agent", Arc::new(ResumeAgentHandler)); - push_tool_spec( - &mut builder, - create_wait_agent_tool_v1(WaitAgentTimeoutOptions { - default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS, - min_timeout_ms: MIN_WAIT_TIMEOUT_MS, - max_timeout_ms: MAX_WAIT_TIMEOUT_MS, - }), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - push_tool_spec( - &mut builder, - create_close_agent_tool_v1(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("spawn_agent", Arc::new(SpawnAgentHandler)); - builder.register_handler("send_input", Arc::new(SendInputHandler)); - builder.register_handler("wait_agent", Arc::new(WaitAgentHandler)); - builder.register_handler("close_agent", Arc::new(CloseAgentHandler)); + builder.push_spec(spec.spec); } } - if config.agent_jobs_tools { - let agent_jobs_handler = Arc::new(BatchJobHandler); - push_tool_spec( - &mut builder, - create_spawn_agents_on_csv_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("spawn_agents_on_csv", agent_jobs_handler.clone()); - if config.agent_jobs_worker_tools { - push_tool_spec( - &mut builder, - create_report_agent_job_result_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("report_agent_job_result", agent_jobs_handler); - } - } - - if let Some(mcp_tools) = mcp_tools { - let mut entries: Vec<(String, rmcp::model::Tool)> = mcp_tools.into_iter().collect(); - entries.sort_by(|a, b| a.0.cmp(&b.0)); - - for (name, tool) in entries.into_iter() { - match mcp_tool_to_responses_api_tool(name.clone(), &tool) { - Ok(converted_tool) => { - push_tool_spec( - &mut builder, - ToolSpec::Function(converted_tool), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler(name, mcp_handler.clone()); + for handler in plan.handlers { + match handler.kind { + ToolHandlerKind::AgentJobs => { + builder.register_handler(handler.name, Arc::new(BatchJobHandler)); + } + ToolHandlerKind::ApplyPatch => { + builder.register_handler(handler.name, apply_patch_handler.clone()); + } + ToolHandlerKind::AssignTaskV2 => { + builder.register_handler(handler.name, Arc::new(AssignTaskHandlerV2)); + } + ToolHandlerKind::CloseAgentV1 => { + builder.register_handler(handler.name, Arc::new(CloseAgentHandler)); + } + ToolHandlerKind::CloseAgentV2 => { + builder.register_handler(handler.name, Arc::new(CloseAgentHandlerV2)); + } + ToolHandlerKind::CodeModeExecute => { + builder.register_handler(handler.name, code_mode_handler.clone()); + } + ToolHandlerKind::CodeModeWait => { + builder.register_handler(handler.name, code_mode_wait_handler.clone()); + } + ToolHandlerKind::DynamicTool => { + builder.register_handler(handler.name, dynamic_tool_handler.clone()); + } + ToolHandlerKind::JsRepl => { + builder.register_handler(handler.name, js_repl_handler.clone()); + } + ToolHandlerKind::JsReplReset => { + builder.register_handler(handler.name, js_repl_reset_handler.clone()); + } + ToolHandlerKind::ListAgentsV2 => { + builder.register_handler(handler.name, Arc::new(ListAgentsHandlerV2)); + } + ToolHandlerKind::ListDir => { + builder.register_handler(handler.name, Arc::new(ListDirHandler)); + } + ToolHandlerKind::Mcp => { + builder.register_handler(handler.name, mcp_handler.clone()); + } + ToolHandlerKind::McpResource => { + builder.register_handler(handler.name, mcp_resource_handler.clone()); + } + ToolHandlerKind::Plan => { + builder.register_handler(handler.name, plan_handler.clone()); + } + ToolHandlerKind::RequestPermissions => { + builder.register_handler(handler.name, request_permissions_handler.clone()); + } + ToolHandlerKind::RequestUserInput => { + builder.register_handler(handler.name, request_user_input_handler.clone()); + } + ToolHandlerKind::ResumeAgentV1 => { + builder.register_handler(handler.name, Arc::new(ResumeAgentHandler)); + } + ToolHandlerKind::SendInputV1 => { + builder.register_handler(handler.name, Arc::new(SendInputHandler)); + } + ToolHandlerKind::SendMessageV2 => { + builder.register_handler(handler.name, Arc::new(SendMessageHandlerV2)); + } + ToolHandlerKind::Shell => { + builder.register_handler(handler.name, shell_handler.clone()); + } + ToolHandlerKind::ShellCommand => { + builder.register_handler(handler.name, shell_command_handler.clone()); + } + ToolHandlerKind::SpawnAgentV1 => { + builder.register_handler(handler.name, Arc::new(SpawnAgentHandler)); + } + ToolHandlerKind::SpawnAgentV2 => { + builder.register_handler(handler.name, Arc::new(SpawnAgentHandlerV2)); + } + ToolHandlerKind::TestSync => { + builder.register_handler(handler.name, Arc::new(TestSyncHandler)); + } + ToolHandlerKind::ToolSearch => { + if tool_search_handler.is_none() { + tool_search_handler = app_tools + .as_ref() + .map(|app_tools| Arc::new(ToolSearchHandler::new(app_tools.clone()))); } - Err(e) => { - tracing::error!("Failed to convert {name:?} MCP tool to OpenAI tool: {e:?}"); + if let Some(tool_search_handler) = tool_search_handler.as_ref() { + builder.register_handler(handler.name, tool_search_handler.clone()); } } + ToolHandlerKind::ToolSuggest => { + builder.register_handler(handler.name, tool_suggest_handler.clone()); + } + ToolHandlerKind::UnifiedExec => { + builder.register_handler(handler.name, unified_exec_handler.clone()); + } + ToolHandlerKind::ViewImage => { + builder.register_handler(handler.name, view_image_handler.clone()); + } + ToolHandlerKind::WaitAgentV1 => { + builder.register_handler(handler.name, Arc::new(WaitAgentHandler)); + } + ToolHandlerKind::WaitAgentV2 => { + builder.register_handler(handler.name, Arc::new(WaitAgentHandlerV2)); + } } } - - if !dynamic_tools.is_empty() { - for tool in dynamic_tools { - match dynamic_tool_to_responses_api_tool(tool) { - Ok(converted_tool) => { - push_tool_spec( - &mut builder, - ToolSpec::Function(converted_tool), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler(tool.name.clone(), dynamic_tool_handler.clone()); - } - Err(e) => { - tracing::error!( - "Failed to convert dynamic tool {:?} to OpenAI tool: {e:?}", - tool.name - ); - } - } - } - } - builder } + #[cfg(test)] #[path = "spec_tests.rs"] mod tests; diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index b4b68da25d..222d7e4c72 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -4,6 +4,7 @@ use crate::models_manager::model_info::with_config_overrides; use crate::shell::Shell; use crate::shell::ShellType; use crate::tools::ToolRouter; +use crate::tools::registry::tool_handler_key; use crate::tools::router::ToolRouterParams; use codex_app_server_protocol::AppInfo; use codex_features::Feature; @@ -13,6 +14,7 @@ use codex_protocol::config_types::WebSearchConfig; use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::VIEW_IMAGE_TOOL_NAME; +use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::InputModality; use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ModelsResponse; @@ -32,6 +34,9 @@ use codex_tools::ResponsesApiWebSearchFilters; use codex_tools::ResponsesApiWebSearchUserLocation; use codex_tools::ShellCommandBackendConfig; use codex_tools::SpawnAgentToolOptions; +use codex_tools::TOOL_SEARCH_TOOL_NAME; +use codex_tools::TOOL_SUGGEST_TOOL_NAME; +use codex_tools::ToolSpec; use codex_tools::ToolsConfig; use codex_tools::ToolsConfigParams; use codex_tools::UnifiedExecShellMode; @@ -203,6 +208,14 @@ fn spawn_agent_tool_options(config: &ToolsConfig) -> SpawnAgentToolOptions<'_> { } } +fn agent_type_description(config: &ToolsConfig) -> String { + if config.agent_type_description.is_empty() { + crate::agent::role::spawn_tool_spec::build(&BTreeMap::new()) + } else { + config.agent_type_description.clone() + } +} + fn wait_agent_timeout_options() -> WaitAgentTimeoutOptions { WaitAgentTimeoutOptions { default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS, diff --git a/codex-rs/tools/src/lib.rs b/codex-rs/tools/src/lib.rs index 3bce0fb107..91931e8944 100644 --- a/codex-rs/tools/src/lib.rs +++ b/codex-rs/tools/src/lib.rs @@ -18,6 +18,8 @@ mod responses_api; mod tool_config; mod tool_definition; mod tool_discovery; +mod tool_registry_plan; +mod tool_registry_plan_types; mod tool_spec; mod tool_suggest; mod utility_tool; @@ -107,6 +109,12 @@ 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_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::ToolRegistryPlan; +pub use tool_registry_plan_types::ToolRegistryPlanAppTool; +pub use tool_registry_plan_types::ToolRegistryPlanParams; pub use tool_spec::ConfiguredToolSpec; pub use tool_spec::ResponsesApiWebSearchFilters; pub use tool_spec::ResponsesApiWebSearchUserLocation; diff --git a/codex-rs/tools/src/tool_registry_plan.rs b/codex-rs/tools/src/tool_registry_plan.rs new file mode 100644 index 0000000000..01cda5d64e --- /dev/null +++ b/codex-rs/tools/src/tool_registry_plan.rs @@ -0,0 +1,488 @@ +use crate::CommandToolOptions; +use crate::REQUEST_USER_INPUT_TOOL_NAME; +use crate::ShellToolOptions; +use crate::SpawnAgentToolOptions; +use crate::TOOL_SEARCH_DEFAULT_LIMIT; +use crate::TOOL_SEARCH_TOOL_NAME; +use crate::TOOL_SUGGEST_TOOL_NAME; +use crate::ToolHandlerKind; +use crate::ToolRegistryPlan; +use crate::ToolRegistryPlanParams; +use crate::ToolSearchAppSource; +use crate::ToolSpec; +use crate::ToolsConfig; +use crate::ViewImageToolOptions; +use crate::WebSearchToolOptions; +use crate::collect_code_mode_tool_definitions; +use crate::collect_tool_search_app_infos; +use crate::collect_tool_suggest_entries; +use crate::create_apply_patch_freeform_tool; +use crate::create_apply_patch_json_tool; +use crate::create_assign_task_tool; +use crate::create_close_agent_tool_v1; +use crate::create_close_agent_tool_v2; +use crate::create_code_mode_tool; +use crate::create_exec_command_tool; +use crate::create_image_generation_tool; +use crate::create_js_repl_reset_tool; +use crate::create_js_repl_tool; +use crate::create_list_agents_tool; +use crate::create_list_dir_tool; +use crate::create_list_mcp_resource_templates_tool; +use crate::create_list_mcp_resources_tool; +use crate::create_local_shell_tool; +use crate::create_read_mcp_resource_tool; +use crate::create_report_agent_job_result_tool; +use crate::create_request_permissions_tool; +use crate::create_request_user_input_tool; +use crate::create_resume_agent_tool; +use crate::create_send_input_tool_v1; +use crate::create_send_message_tool; +use crate::create_shell_command_tool; +use crate::create_shell_tool; +use crate::create_spawn_agent_tool_v1; +use crate::create_spawn_agent_tool_v2; +use crate::create_spawn_agents_on_csv_tool; +use crate::create_test_sync_tool; +use crate::create_tool_search_tool; +use crate::create_tool_suggest_tool; +use crate::create_update_plan_tool; +use crate::create_view_image_tool; +use crate::create_wait_agent_tool_v1; +use crate::create_wait_agent_tool_v2; +use crate::create_wait_tool; +use crate::create_web_search_tool; +use crate::create_write_stdin_tool; +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_registry_plan_types::agent_type_description; +use codex_protocol::openai_models::ApplyPatchToolType; +use codex_protocol::openai_models::ConfigShellToolType; +use rmcp::model::Tool as McpTool; + +pub fn build_tool_registry_plan( + config: &ToolsConfig, + params: ToolRegistryPlanParams<'_>, +) -> ToolRegistryPlan { + let mut plan = ToolRegistryPlan::new(); + let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled; + + if config.code_mode_enabled { + let nested_config = config.for_code_mode_nested_tools(); + let nested_plan = build_tool_registry_plan( + &nested_config, + ToolRegistryPlanParams { + discoverable_tools: None, + ..params + }, + ); + let enabled_tools = collect_code_mode_tool_definitions( + nested_plan + .specs + .iter() + .map(|configured_tool| &configured_tool.spec), + ) + .into_iter() + .map(|tool| (tool.name, tool.description)) + .collect::>(); + plan.push_spec( + create_code_mode_tool(&enabled_tools, config.code_mode_only_enabled), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler( + codex_code_mode::PUBLIC_TOOL_NAME, + ToolHandlerKind::CodeModeExecute, + ); + plan.push_spec( + create_wait_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler( + codex_code_mode::WAIT_TOOL_NAME, + ToolHandlerKind::CodeModeWait, + ); + } + + match &config.shell_type { + ConfigShellToolType::Default => { + plan.push_spec( + create_shell_tool(ShellToolOptions { + exec_permission_approvals_enabled, + }), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + } + ConfigShellToolType::Local => { + plan.push_spec( + create_local_shell_tool(), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + } + ConfigShellToolType::UnifiedExec => { + plan.push_spec( + create_exec_command_tool(CommandToolOptions { + allow_login_shell: config.allow_login_shell, + exec_permission_approvals_enabled, + }), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.push_spec( + create_write_stdin_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("exec_command", ToolHandlerKind::UnifiedExec); + plan.register_handler("write_stdin", ToolHandlerKind::UnifiedExec); + } + ConfigShellToolType::Disabled => {} + ConfigShellToolType::ShellCommand => { + plan.push_spec( + create_shell_command_tool(CommandToolOptions { + allow_login_shell: config.allow_login_shell, + exec_permission_approvals_enabled, + }), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + } + } + + if config.shell_type != ConfigShellToolType::Disabled { + plan.register_handler("shell", ToolHandlerKind::Shell); + plan.register_handler("container.exec", ToolHandlerKind::Shell); + plan.register_handler("local_shell", ToolHandlerKind::Shell); + plan.register_handler("shell_command", ToolHandlerKind::ShellCommand); + } + + if params.mcp_tools.is_some() { + plan.push_spec( + create_list_mcp_resources_tool(), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.push_spec( + create_list_mcp_resource_templates_tool(), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.push_spec( + create_read_mcp_resource_tool(), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.register_handler("list_mcp_resources", ToolHandlerKind::McpResource); + plan.register_handler("list_mcp_resource_templates", ToolHandlerKind::McpResource); + plan.register_handler("read_mcp_resource", ToolHandlerKind::McpResource); + } + + plan.push_spec( + create_update_plan_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("update_plan", ToolHandlerKind::Plan); + + if config.js_repl_enabled { + plan.push_spec( + create_js_repl_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_js_repl_reset_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("js_repl", ToolHandlerKind::JsRepl); + plan.register_handler("js_repl_reset", ToolHandlerKind::JsReplReset); + } + + if config.request_user_input { + plan.push_spec( + create_request_user_input_tool(request_user_input_tool_description( + config.default_mode_request_user_input, + )), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler( + REQUEST_USER_INPUT_TOOL_NAME, + ToolHandlerKind::RequestUserInput, + ); + } + + if config.request_permissions_tool_enabled { + plan.push_spec( + create_request_permissions_tool(request_permissions_tool_description()), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("request_permissions", ToolHandlerKind::RequestPermissions); + } + + if config.search_tool + && let Some(app_tools) = params.app_tools + { + let search_app_infos = collect_tool_search_app_infos( + app_tools.iter().map(|tool| ToolSearchAppSource { + server_name: tool.server_name, + connector_name: tool.connector_name, + connector_description: tool.connector_description, + }), + params.codex_apps_mcp_server_name, + ); + plan.push_spec( + create_tool_search_tool(&search_app_infos, TOOL_SEARCH_DEFAULT_LIMIT), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.register_handler(TOOL_SEARCH_TOOL_NAME, ToolHandlerKind::ToolSearch); + + for tool in app_tools { + plan.register_handler( + format!("{}:{}", tool.tool_namespace, tool.tool_name), + ToolHandlerKind::Mcp, + ); + } + } + + if config.tool_suggest + && let Some(discoverable_tools) = + params.discoverable_tools.filter(|tools| !tools.is_empty()) + { + plan.push_spec( + create_tool_suggest_tool(&collect_tool_suggest_entries(discoverable_tools)), + /*supports_parallel_tool_calls*/ true, + /*code_mode_enabled*/ false, + ); + plan.register_handler(TOOL_SUGGEST_TOOL_NAME, ToolHandlerKind::ToolSuggest); + } + + if let Some(apply_patch_tool_type) = &config.apply_patch_tool_type { + match apply_patch_tool_type { + ApplyPatchToolType::Freeform => { + plan.push_spec( + create_apply_patch_freeform_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + } + ApplyPatchToolType::Function => { + plan.push_spec( + create_apply_patch_json_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + } + } + plan.register_handler("apply_patch", ToolHandlerKind::ApplyPatch); + } + + if config + .experimental_supported_tools + .iter() + .any(|tool| tool == "list_dir") + { + plan.push_spec( + create_list_dir_tool(), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.register_handler("list_dir", ToolHandlerKind::ListDir); + } + + if config + .experimental_supported_tools + .iter() + .any(|tool| tool == "test_sync_tool") + { + plan.push_spec( + create_test_sync_tool(), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.register_handler("test_sync_tool", ToolHandlerKind::TestSync); + } + + 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, + }) { + plan.push_spec( + web_search_tool, + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + } + + if config.image_gen_tool { + plan.push_spec( + create_image_generation_tool("png"), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + } + + plan.push_spec( + create_view_image_tool(ViewImageToolOptions { + can_request_original_image_detail: config.can_request_original_image_detail, + }), + /*supports_parallel_tool_calls*/ true, + config.code_mode_enabled, + ); + plan.register_handler("view_image", ToolHandlerKind::ViewImage); + + if config.collab_tools { + if config.multi_agent_v2 { + let agent_type_description = + agent_type_description(config, params.default_agent_type_description); + plan.push_spec( + create_spawn_agent_tool_v2(SpawnAgentToolOptions { + available_models: &config.available_models, + agent_type_description, + }), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_send_message_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_assign_task_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_wait_agent_tool_v2(params.wait_agent_timeouts), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_close_agent_tool_v2(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_list_agents_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("spawn_agent", ToolHandlerKind::SpawnAgentV2); + plan.register_handler("send_message", ToolHandlerKind::SendMessageV2); + plan.register_handler("assign_task", ToolHandlerKind::AssignTaskV2); + plan.register_handler("wait_agent", ToolHandlerKind::WaitAgentV2); + plan.register_handler("close_agent", ToolHandlerKind::CloseAgentV2); + plan.register_handler("list_agents", ToolHandlerKind::ListAgentsV2); + } else { + let agent_type_description = + agent_type_description(config, params.default_agent_type_description); + plan.push_spec( + create_spawn_agent_tool_v1(SpawnAgentToolOptions { + available_models: &config.available_models, + agent_type_description, + }), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_send_input_tool_v1(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_resume_agent_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("resume_agent", ToolHandlerKind::ResumeAgentV1); + plan.push_spec( + create_wait_agent_tool_v1(params.wait_agent_timeouts), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.push_spec( + create_close_agent_tool_v1(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("spawn_agent", ToolHandlerKind::SpawnAgentV1); + plan.register_handler("send_input", ToolHandlerKind::SendInputV1); + plan.register_handler("wait_agent", ToolHandlerKind::WaitAgentV1); + plan.register_handler("close_agent", ToolHandlerKind::CloseAgentV1); + } + } + + if config.agent_jobs_tools { + plan.push_spec( + create_spawn_agents_on_csv_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("spawn_agents_on_csv", ToolHandlerKind::AgentJobs); + if config.agent_jobs_worker_tools { + plan.push_spec( + create_report_agent_job_result_tool(), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler("report_agent_job_result", ToolHandlerKind::AgentJobs); + } + } + + if let Some(mcp_tools) = params.mcp_tools { + let mut entries: Vec<(String, &McpTool)> = mcp_tools + .iter() + .map(|(name, tool)| (name.clone(), tool)) + .collect(); + entries.sort_by(|left, right| left.0.cmp(&right.0)); + + for (name, tool) in entries { + match mcp_tool_to_responses_api_tool(name.clone(), tool) { + Ok(converted_tool) => { + plan.push_spec( + ToolSpec::Function(converted_tool), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler(name, ToolHandlerKind::Mcp); + } + Err(error) => { + tracing::error!( + "Failed to convert {name:?} MCP tool to OpenAI tool: {error:?}" + ); + } + } + } + } + + for tool in params.dynamic_tools { + match dynamic_tool_to_responses_api_tool(tool) { + Ok(converted_tool) => { + plan.push_spec( + ToolSpec::Function(converted_tool), + /*supports_parallel_tool_calls*/ false, + config.code_mode_enabled, + ); + plan.register_handler(tool.name.clone(), ToolHandlerKind::DynamicTool); + } + Err(error) => { + tracing::error!( + "Failed to convert dynamic tool {:?} to OpenAI tool: {error:?}", + tool.name + ); + } + } + } + + plan +} diff --git a/codex-rs/tools/src/tool_registry_plan_types.rs b/codex-rs/tools/src/tool_registry_plan_types.rs new file mode 100644 index 0000000000..bdcb75acd8 --- /dev/null +++ b/codex-rs/tools/src/tool_registry_plan_types.rs @@ -0,0 +1,118 @@ +use crate::ConfiguredToolSpec; +use crate::DiscoverableTool; +use crate::ToolSpec; +use crate::ToolsConfig; +use crate::WaitAgentTimeoutOptions; +use crate::augment_tool_spec_for_code_mode; +use codex_protocol::dynamic_tools::DynamicToolSpec; +use rmcp::model::Tool as McpTool; +use std::collections::HashMap; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ToolHandlerKind { + AgentJobs, + ApplyPatch, + AssignTaskV2, + CloseAgentV1, + CloseAgentV2, + CodeModeExecute, + CodeModeWait, + DynamicTool, + JsRepl, + JsReplReset, + ListAgentsV2, + ListDir, + Mcp, + McpResource, + Plan, + RequestPermissions, + RequestUserInput, + ResumeAgentV1, + SendInputV1, + SendMessageV2, + Shell, + ShellCommand, + SpawnAgentV1, + SpawnAgentV2, + TestSync, + ToolSearch, + ToolSuggest, + UnifiedExec, + ViewImage, + WaitAgentV1, + WaitAgentV2, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ToolHandlerSpec { + pub name: String, + pub kind: ToolHandlerKind, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ToolRegistryPlan { + pub specs: Vec, + pub handlers: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub struct ToolRegistryPlanParams<'a> { + pub mcp_tools: Option<&'a HashMap>, + pub app_tools: Option<&'a [ToolRegistryPlanAppTool<'a>]>, + pub discoverable_tools: Option<&'a [DiscoverableTool]>, + pub dynamic_tools: &'a [DynamicToolSpec], + pub default_agent_type_description: &'a str, + pub wait_agent_timeouts: WaitAgentTimeoutOptions, + pub codex_apps_mcp_server_name: &'a str, +} + +#[derive(Debug, Clone, Copy)] +pub struct ToolRegistryPlanAppTool<'a> { + pub tool_name: &'a str, + pub tool_namespace: &'a str, + 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 { + specs: Vec::new(), + handlers: Vec::new(), + } + } + + pub(crate) fn push_spec( + &mut self, + spec: ToolSpec, + supports_parallel_tool_calls: bool, + code_mode_enabled: bool, + ) { + let spec = if code_mode_enabled { + augment_tool_spec_for_code_mode(spec) + } else { + spec + }; + self.specs + .push(ConfiguredToolSpec::new(spec, supports_parallel_tool_calls)); + } + + pub(crate) fn register_handler(&mut self, name: impl Into, kind: ToolHandlerKind) { + self.handlers.push(ToolHandlerSpec { + name: name.into(), + kind, + }); + } +} + +pub(crate) 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() + } +}