From 171cabf386368d74e99645a3b35e199d479b6c24 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 15 May 2026 16:47:12 +0200 Subject: [PATCH 1/2] Introduce declarative tool sources --- codex-rs/core/src/tools/spec_plan.rs | 363 +++++++++++-------- codex-rs/core/src/tools/tool_family/shell.rs | 1 + codex-rs/core/src/tools/tool_set.rs | 71 +--- codex-rs/tools/README.md | 4 +- codex-rs/tools/src/lib.rs | 4 + codex-rs/tools/src/tool_set.rs | 76 ++++ 6 files changed, 322 insertions(+), 197 deletions(-) create mode 100644 codex-rs/tools/src/tool_set.rs diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 132083d32caf..88793f71bd1a 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -49,6 +49,7 @@ use crate::tools::router::ToolRouter; use crate::tools::router::ToolRouterParams; use crate::tools::tool_family::shell::ShellToolsOptions; use crate::tools::tool_family::shell::register_shell_tools; +use crate::tools::tool_set::CoreToolSetBuilderExt; use crate::tools::tool_set::ToolSet; use crate::tools::tool_set::ToolSetBuilder; use codex_features::Feature; @@ -65,6 +66,7 @@ use codex_tools::ToolCall as ExtensionToolCall; use codex_tools::ToolEnvironmentMode; use codex_tools::ToolExecutor; use codex_tools::ToolName; +use codex_tools::ToolSource; use codex_tools::ToolSpec; use codex_tools::can_request_original_image_detail; use codex_tools::collect_code_mode_exec_prompt_tool_definitions; @@ -78,7 +80,8 @@ use std::sync::Arc; use tracing::warn; #[derive(Clone, Copy)] -struct ToolSetBuildParams<'a> { +struct CoreToolPlanContext<'a> { + turn_context: &'a TurnContext, mcp_tools: Option<&'a [ToolInfo]>, deferred_mcp_tools: Option<&'a [ToolInfo]>, discoverable_tools: Option<&'a [DiscoverableTool]>, @@ -109,23 +112,20 @@ fn build_tool_specs_and_registry( } = params; let default_agent_type_description = crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new()); - let mut tool_set = ToolSetBuilder::new(); - register_tool_runtimes( + let context = CoreToolPlanContext { turn_context, - ToolSetBuildParams { - 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(turn_context), - }, - &mut tool_set, - ); - tool_set.extend_hosted_specs(hosted_model_tool_specs(turn_context)); - append_tool_search_executor(turn_context, &mut tool_set); - prepend_code_mode_executors(turn_context, &mut tool_set); + 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(turn_context), + }; + let mut tool_set = ToolSetBuilder::new(); + add_tool_sources(&context, &mut tool_set); + append_tool_search_executor(&context, &mut tool_set); + prepend_code_mode_executors(&context, &mut tool_set); build_model_visible_specs_and_registry(turn_context, tool_set.finish()) } @@ -426,88 +426,154 @@ fn code_mode_namespace_descriptions( namespace_descriptions } -fn register_tool_runtimes( - turn_context: &TurnContext, - params: ToolSetBuildParams<'_>, - tool_set: &mut ToolSetBuilder, -) { - let features = turn_context.features.get(); - let environment_mode = turn_context.tool_environment_mode(); - register_shell_tools( - tool_set, - ShellToolsOptions { - shell_type: shell_type_for_model_and_features(&turn_context.model_info, features), - shell_command_backend: shell_command_backend_for_features(features), - environment_mode, - allow_login_shell: turn_context.config.permissions.allow_login_shell, - exec_permission_approvals_enabled: features.enabled(Feature::ExecPermissionApprovals), - }, - ); - - if params.mcp_tools.is_some() { - tool_set.add_runtime(ListMcpResourcesHandler); - tool_set.add_runtime(ListMcpResourceTemplatesHandler); - tool_set.add_runtime(ReadMcpResourceHandler); +fn add_tool_sources(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + let sources: [&dyn ToolSource, Arc>; 8] = [ + &ShellToolSource, + &McpResourceToolSource, + &CoreUtilityToolSource, + &CollaborationToolSource, + &McpRuntimeToolSource, + &DynamicToolSource, + &ExtensionToolSource, + &HostedModelToolSource, + ]; + for source in sources { + source.add_tools(context, tool_set); } +} - tool_set.add_runtime(PlanHandler); - if turn_context.goal_tools_enabled() { - tool_set.add_runtime(GetGoalHandler); - tool_set.add_runtime(CreateGoalHandler); - tool_set.add_runtime(UpdateGoalHandler); +struct ShellToolSource; + +impl<'a> ToolSource, Arc> for ShellToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; + let features = turn_context.features.get(); + register_shell_tools( + tool_set, + ShellToolsOptions { + shell_type: shell_type_for_model_and_features(&turn_context.model_info, features), + shell_command_backend: shell_command_backend_for_features(features), + environment_mode: turn_context.tool_environment_mode(), + allow_login_shell: turn_context.config.permissions.allow_login_shell, + exec_permission_approvals_enabled: features + .enabled(Feature::ExecPermissionApprovals), + }, + ); } +} - tool_set.add_runtime(RequestUserInputHandler { - available_modes: request_user_input_available_modes(features), - }); +struct McpResourceToolSource; - if features.enabled(Feature::RequestPermissionsTool) { - tool_set.add_runtime(RequestPermissionsHandler); +impl<'a> ToolSource, Arc> for McpResourceToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + if context.mcp_tools.is_some() { + tool_set.add_runtime(ListMcpResourcesHandler); + tool_set.add_runtime(ListMcpResourceTemplatesHandler); + tool_set.add_runtime(ReadMcpResourceHandler); + } } +} - if tool_suggest_enabled(turn_context) - && let Some(discoverable_tools) = - params.discoverable_tools.filter(|tools| !tools.is_empty()) - { - tool_set.add_runtime(RequestPluginInstallHandler::new(discoverable_tools)); - } +struct CoreUtilityToolSource; - if environment_mode.has_environment() && turn_context.model_info.apply_patch_tool_type.is_some() - { - let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); - tool_set.add_runtime(ApplyPatchHandler::new(include_environment_id)); - } +impl<'a> ToolSource, Arc> for CoreUtilityToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; + let features = turn_context.features.get(); + let environment_mode = turn_context.tool_environment_mode(); - if turn_context - .model_info - .experimental_supported_tools - .iter() - .any(|tool| tool == "test_sync_tool") - { - tool_set.add_runtime(TestSyncHandler); - } + tool_set.add_runtime(PlanHandler); + if turn_context.goal_tools_enabled() { + tool_set.add_runtime(GetGoalHandler); + tool_set.add_runtime(CreateGoalHandler); + tool_set.add_runtime(UpdateGoalHandler); + } - if environment_mode.has_environment() { - let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); - tool_set.add_runtime(ViewImageHandler::new(ViewImageToolOptions { - can_request_original_image_detail: can_request_original_image_detail( - &turn_context.model_info, - ), - include_environment_id, - })); + tool_set.add_runtime(RequestUserInputHandler { + available_modes: request_user_input_available_modes(features), + }); + + if features.enabled(Feature::RequestPermissionsTool) { + tool_set.add_runtime(RequestPermissionsHandler); + } + + if tool_suggest_enabled(turn_context) + && let Some(discoverable_tools) = + context.discoverable_tools.filter(|tools| !tools.is_empty()) + { + tool_set.add_runtime(RequestPluginInstallHandler::new(discoverable_tools)); + } + + if environment_mode.has_environment() + && turn_context.model_info.apply_patch_tool_type.is_some() + { + let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); + tool_set.add_runtime(ApplyPatchHandler::new(include_environment_id)); + } + + if turn_context + .model_info + .experimental_supported_tools + .iter() + .any(|tool| tool == "test_sync_tool") + { + tool_set.add_runtime(TestSyncHandler); + } + + if environment_mode.has_environment() { + let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); + tool_set.add_runtime(ViewImageHandler::new(ViewImageToolOptions { + can_request_original_image_detail: can_request_original_image_detail( + &turn_context.model_info, + ), + include_environment_id, + })); + } } +} - if collab_tools_enabled(turn_context) { - if multi_agent_v2_enabled(turn_context) { - let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only { - ToolExposure::DirectModelOnly +struct CollaborationToolSource; + +impl<'a> ToolSource, Arc> for CollaborationToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; + if collab_tools_enabled(turn_context) { + if multi_agent_v2_enabled(turn_context) { + let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only { + ToolExposure::DirectModelOnly + } else { + ToolExposure::Direct + }; + let agent_type_description = + agent_type_description(turn_context, context.default_agent_type_description); + tool_set.add_runtime_arc(multi_agent_v2_handler( + SpawnAgentHandlerV2::new(SpawnAgentToolOptions { + available_models: turn_context.available_models.clone(), + agent_type_description, + hide_agent_type_model_reasoning: turn_context + .config + .multi_agent_v2 + .hide_spawn_agent_metadata, + include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled, + usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(), + max_concurrent_threads_per_session: max_concurrent_threads_per_session( + turn_context, + ), + }), + exposure, + )); + tool_set.add_runtime_arc(multi_agent_v2_handler(SendMessageHandlerV2, exposure)); + tool_set.add_runtime_arc(multi_agent_v2_handler(FollowupTaskHandlerV2, exposure)); + tool_set.add_runtime_arc(multi_agent_v2_handler( + WaitAgentHandlerV2::new(context.wait_agent_timeouts), + exposure, + )); + tool_set.add_runtime_arc(multi_agent_v2_handler(CloseAgentHandlerV2, exposure)); + tool_set.add_runtime_arc(multi_agent_v2_handler(ListAgentsHandlerV2, exposure)); } else { - ToolExposure::Direct - }; - let agent_type_description = - agent_type_description(turn_context, params.default_agent_type_description); - tool_set.add_runtime_arc(multi_agent_v2_handler( - SpawnAgentHandlerV2::new(SpawnAgentToolOptions { + let agent_type_description = + agent_type_description(turn_context, context.default_agent_type_description); + tool_set.add_runtime(SpawnAgentHandler::new(SpawnAgentToolOptions { available_models: turn_context.available_models.clone(), agent_type_description, hide_agent_type_model_reasoning: turn_context @@ -519,78 +585,86 @@ fn register_tool_runtimes( max_concurrent_threads_per_session: max_concurrent_threads_per_session( turn_context, ), - }), - exposure, - )); - tool_set.add_runtime_arc(multi_agent_v2_handler(SendMessageHandlerV2, exposure)); - tool_set.add_runtime_arc(multi_agent_v2_handler(FollowupTaskHandlerV2, exposure)); - tool_set.add_runtime_arc(multi_agent_v2_handler( - WaitAgentHandlerV2::new(params.wait_agent_timeouts), - exposure, - )); - tool_set.add_runtime_arc(multi_agent_v2_handler(CloseAgentHandlerV2, exposure)); - tool_set.add_runtime_arc(multi_agent_v2_handler(ListAgentsHandlerV2, exposure)); - } else { - let agent_type_description = - agent_type_description(turn_context, params.default_agent_type_description); - tool_set.add_runtime(SpawnAgentHandler::new(SpawnAgentToolOptions { - available_models: turn_context.available_models.clone(), - agent_type_description, - hide_agent_type_model_reasoning: turn_context - .config - .multi_agent_v2 - .hide_spawn_agent_metadata, - include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled, - usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(), - max_concurrent_threads_per_session: max_concurrent_threads_per_session( - turn_context, - ), - })); - tool_set.add_runtime(SendInputHandler); - tool_set.add_runtime(ResumeAgentHandler); - tool_set.add_runtime(WaitAgentHandler::new(params.wait_agent_timeouts)); - tool_set.add_runtime(CloseAgentHandler); + })); + tool_set.add_runtime(SendInputHandler); + tool_set.add_runtime(ResumeAgentHandler); + tool_set.add_runtime(WaitAgentHandler::new(context.wait_agent_timeouts)); + tool_set.add_runtime(CloseAgentHandler); + } } - } - if agent_jobs_tools_enabled(turn_context) { - tool_set.add_runtime(SpawnAgentsOnCsvHandler); - if agent_jobs_worker_tools_enabled(turn_context) { - tool_set.add_runtime(ReportAgentJobResultHandler); + if agent_jobs_tools_enabled(turn_context) { + tool_set.add_runtime(SpawnAgentsOnCsvHandler); + if agent_jobs_worker_tools_enabled(turn_context) { + tool_set.add_runtime(ReportAgentJobResultHandler); + } } } +} - if let Some(mcp_tools) = params.mcp_tools { - for tool in mcp_tools { - tool_set.add_runtime(McpHandler::new(tool.clone())); +struct McpRuntimeToolSource; + +impl<'a> ToolSource, Arc> for McpRuntimeToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + if let Some(mcp_tools) = context.mcp_tools { + for tool in mcp_tools { + tool_set.add_runtime(McpHandler::new(tool.clone())); + } } - } - if let Some(deferred_mcp_tools) = params.deferred_mcp_tools { - for tool in deferred_mcp_tools { - tool_set.add_runtime(McpHandler::with_exposure( - tool.clone(), - ToolExposure::Deferred, - )); + if let Some(deferred_mcp_tools) = context.deferred_mcp_tools { + for tool in deferred_mcp_tools { + tool_set.add_runtime(McpHandler::with_exposure( + tool.clone(), + ToolExposure::Deferred, + )); + } } } +} - for tool in params.dynamic_tools { - let Some(handler) = DynamicToolHandler::new(tool) else { - tracing::error!( - "Failed to convert dynamic tool {:?} to OpenAI tool", - tool.name - ); - continue; - }; +struct DynamicToolSource; + +impl<'a> ToolSource, Arc> for DynamicToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + for tool in context.dynamic_tools { + let Some(handler) = DynamicToolHandler::new(tool) else { + tracing::error!( + "Failed to convert dynamic tool {:?} to OpenAI tool", + tool.name + ); + continue; + }; + + tool_set.add_runtime(handler); + } + } +} - tool_set.add_runtime(handler); +struct ExtensionToolSource; + +impl<'a> ToolSource, Arc> for ExtensionToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + // Extension ToolContributor implementations are resolved into executors + // before planning. Core only adapts those executors into its runtime set. + append_extension_tool_executors( + context.turn_context, + context.extension_tool_executors, + tool_set, + ); } +} - append_extension_tool_executors(turn_context, params.extension_tool_executors, tool_set); +struct HostedModelToolSource; + +impl<'a> ToolSource, Arc> for HostedModelToolSource { + fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { + tool_set.extend_hosted_specs(hosted_model_tool_specs(context.turn_context)); + } } -fn append_tool_search_executor(turn_context: &TurnContext, tool_set: &mut ToolSetBuilder) { +fn append_tool_search_executor(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; if !(search_tool_enabled(turn_context) && namespace_tools_enabled(turn_context)) { return; } @@ -608,7 +682,8 @@ fn append_tool_search_executor(turn_context: &TurnContext, tool_set: &mut ToolSe tool_set.add_runtime(ToolSearchHandler::new(search_infos)); } -fn prepend_code_mode_executors(turn_context: &TurnContext, tool_set: &mut ToolSetBuilder) { +fn prepend_code_mode_executors(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; let deferred_tools_available = search_tool_enabled(turn_context) && tool_set .runtimes() diff --git a/codex-rs/core/src/tools/tool_family/shell.rs b/codex-rs/core/src/tools/tool_family/shell.rs index 981b50b207c3..55c7e78bf7af 100644 --- a/codex-rs/core/src/tools/tool_family/shell.rs +++ b/codex-rs/core/src/tools/tool_family/shell.rs @@ -7,6 +7,7 @@ use crate::tools::handlers::ExecCommandHandlerOptions; use crate::tools::handlers::ShellCommandHandler; use crate::tools::handlers::ShellCommandHandlerOptions; use crate::tools::handlers::WriteStdinHandler; +use crate::tools::tool_set::CoreToolSetBuilderExt; use crate::tools::tool_set::ToolSetBuilder; #[derive(Clone, Copy, Debug)] diff --git a/codex-rs/core/src/tools/tool_set.rs b/codex-rs/core/src/tools/tool_set.rs index 6f24269d7937..315759b1d8b7 100644 --- a/codex-rs/core/src/tools/tool_set.rs +++ b/codex-rs/core/src/tools/tool_set.rs @@ -1,69 +1,38 @@ use std::sync::Arc; -use codex_tools::ToolSpec; - use crate::tools::registry::CoreToolRuntime; -/// Accumulates the concrete tools available for one model request. -/// -/// Runtime tools carry both their executable handler and model-visible spec. -/// Hosted model tools have no local runtime, so they are tracked beside the -/// runtimes until the final model-visible spec list is assembled. -#[derive(Default)] -pub(crate) struct ToolSetBuilder { - runtimes: Vec>, - hosted_specs: Vec, -} - -impl ToolSetBuilder { - pub(crate) fn new() -> Self { - Self::default() - } +pub(crate) type ToolSet = codex_tools::ToolSet>; +pub(crate) type ToolSetBuilder = codex_tools::ToolSetBuilder>; - pub(crate) fn add_runtime(&mut self, runtime: T) +pub(crate) trait CoreToolSetBuilderExt { + fn add_runtime(&mut self, runtime: T) where - T: CoreToolRuntime + 'static, - { - self.runtimes.push(Arc::new(runtime)); - } + T: CoreToolRuntime + 'static; - pub(crate) fn add_runtime_arc(&mut self, runtime: Arc) { - self.runtimes.push(runtime); - } + fn add_runtime_arc(&mut self, runtime: Arc); - pub(crate) fn prepend_runtime_arcs(&mut self, runtimes: I) + fn prepend_runtime_arcs(&mut self, runtimes: I) where - I: IntoIterator>, - { - self.runtimes.splice(0..0, runtimes); - } - - pub(crate) fn runtimes(&self) -> &[Arc] { - &self.runtimes - } + I: IntoIterator>; +} - pub(crate) fn extend_hosted_specs(&mut self, specs: I) +impl CoreToolSetBuilderExt for ToolSetBuilder { + fn add_runtime(&mut self, runtime: T) where - I: IntoIterator, + T: CoreToolRuntime + 'static, { - self.hosted_specs.extend(specs); + self.push_runtime(Arc::new(runtime)); } - pub(crate) fn finish(self) -> ToolSet { - ToolSet { - runtimes: self.runtimes, - hosted_specs: self.hosted_specs, - } + fn add_runtime_arc(&mut self, runtime: Arc) { + self.push_runtime(runtime); } -} -pub(crate) struct ToolSet { - runtimes: Vec>, - hosted_specs: Vec, -} - -impl ToolSet { - pub(crate) fn into_parts(self) -> (Vec>, Vec) { - (self.runtimes, self.hosted_specs) + fn prepend_runtime_arcs(&mut self, runtimes: I) + where + I: IntoIterator>, + { + self.prepend_runtimes(runtimes); } } diff --git a/codex-rs/tools/README.md b/codex-rs/tools/README.md index 1c950b6203c5..b26e7599bbbc 100644 --- a/codex-rs/tools/README.md +++ b/codex-rs/tools/README.md @@ -13,8 +13,8 @@ need to live in `core/src/tools/spec.rs` or `core/src/client_common.rs`: discoverable-tool models and request-plugin-install helpers - host adapters such as schema sanitization, MCP/dynamic conversion, code-mode augmentation, and image-detail normalization -- shared executable-tool contracts such as `ToolExecutor`, `ToolCall`, and - `ToolOutput` +- shared executable-tool and planning contracts such as `ToolExecutor`, + `ToolSource`, `ToolSetBuilder`, `ToolCall`, and `ToolOutput` That extraction is the first step in a longer migration. The goal is not to move all of `core/src/tools` into this crate in one shot. Instead, the plan is diff --git a/codex-rs/tools/src/lib.rs b/codex-rs/tools/src/lib.rs index 4d972af2cfea..6b7ba91c8d1b 100644 --- a/codex-rs/tools/src/lib.rs +++ b/codex-rs/tools/src/lib.rs @@ -16,6 +16,7 @@ mod tool_discovery; mod tool_executor; mod tool_output; mod tool_payload; +mod tool_set; mod tool_spec; pub use code_mode::augment_tool_spec_for_code_mode; @@ -83,6 +84,9 @@ pub use tool_executor::ToolExposure; pub use tool_output::JsonToolOutput; pub use tool_output::ToolOutput; pub use tool_payload::ToolPayload; +pub use tool_set::ToolSet; +pub use tool_set::ToolSetBuilder; +pub use tool_set::ToolSource; pub use tool_spec::ResponsesApiWebSearchFilters; pub use tool_spec::ResponsesApiWebSearchUserLocation; pub use tool_spec::ToolSpec; diff --git a/codex-rs/tools/src/tool_set.rs b/codex-rs/tools/src/tool_set.rs new file mode 100644 index 000000000000..57b77d7d8eac --- /dev/null +++ b/codex-rs/tools/src/tool_set.rs @@ -0,0 +1,76 @@ +use crate::ToolSpec; + +/// Source of tools for one host-specific planning context. +/// +/// Implementations add runtime tools, hosted model tools, or both to the +/// supplied tool set. The context and runtime type are host-owned so this +/// abstraction can stay independent of `codex-core`. +pub trait ToolSource: Send + Sync { + fn add_tools(&self, ctx: &C, tools: &mut ToolSetBuilder); +} + +/// Accumulates the concrete tools available for one model request. +/// +/// Runtime tools carry both their executable handler and model-visible spec. +/// Hosted model tools have no local runtime, so they are tracked beside the +/// runtimes until the final model-visible spec list is assembled. +pub struct ToolSetBuilder { + runtimes: Vec, + hosted_specs: Vec, +} + +impl Default for ToolSetBuilder { + fn default() -> Self { + Self { + runtimes: Vec::new(), + hosted_specs: Vec::new(), + } + } +} + +impl ToolSetBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn push_runtime(&mut self, runtime: R) { + self.runtimes.push(runtime); + } + + pub fn prepend_runtimes(&mut self, runtimes: I) + where + I: IntoIterator, + { + self.runtimes.splice(0..0, runtimes); + } + + pub fn runtimes(&self) -> &[R] { + &self.runtimes + } + + pub fn extend_hosted_specs(&mut self, specs: I) + where + I: IntoIterator, + { + self.hosted_specs.extend(specs); + } + + pub fn finish(self) -> ToolSet { + ToolSet { + runtimes: self.runtimes, + hosted_specs: self.hosted_specs, + } + } +} + +/// Concrete tool set produced by a `ToolSetBuilder`. +pub struct ToolSet { + runtimes: Vec, + hosted_specs: Vec, +} + +impl ToolSet { + pub fn into_parts(self) -> (Vec, Vec) { + (self.runtimes, self.hosted_specs) + } +} From eff6732afafac768614d82e1597049f1d960bc84 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Fri, 15 May 2026 17:23:25 +0200 Subject: [PATCH 2/2] Simplify tool planner sources --- codex-rs/core/src/tools/spec_plan.rs | 336 ++++++++++++--------------- codex-rs/tools/README.md | 2 +- codex-rs/tools/src/lib.rs | 1 - codex-rs/tools/src/tool_set.rs | 9 - 4 files changed, 147 insertions(+), 201 deletions(-) diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 88793f71bd1a..9667c1ddd58c 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -66,7 +66,6 @@ use codex_tools::ToolCall as ExtensionToolCall; use codex_tools::ToolEnvironmentMode; use codex_tools::ToolExecutor; use codex_tools::ToolName; -use codex_tools::ToolSource; use codex_tools::ToolSpec; use codex_tools::can_request_original_image_detail; use codex_tools::collect_code_mode_exec_prompt_tool_definitions; @@ -427,153 +426,105 @@ fn code_mode_namespace_descriptions( } fn add_tool_sources(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { - let sources: [&dyn ToolSource, Arc>; 8] = [ - &ShellToolSource, - &McpResourceToolSource, - &CoreUtilityToolSource, - &CollaborationToolSource, - &McpRuntimeToolSource, - &DynamicToolSource, - &ExtensionToolSource, - &HostedModelToolSource, - ]; - for source in sources { - source.add_tools(context, tool_set); - } + add_shell_tools(context, tool_set); + add_mcp_resource_tools(context, tool_set); + add_core_utility_tools(context, tool_set); + add_collaboration_tools(context, tool_set); + add_mcp_runtime_tools(context, tool_set); + add_dynamic_tools(context, tool_set); + add_extension_tools(context, tool_set); + tool_set.extend_hosted_specs(hosted_model_tool_specs(context.turn_context)); } -struct ShellToolSource; - -impl<'a> ToolSource, Arc> for ShellToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - let turn_context = context.turn_context; - let features = turn_context.features.get(); - register_shell_tools( - tool_set, - ShellToolsOptions { - shell_type: shell_type_for_model_and_features(&turn_context.model_info, features), - shell_command_backend: shell_command_backend_for_features(features), - environment_mode: turn_context.tool_environment_mode(), - allow_login_shell: turn_context.config.permissions.allow_login_shell, - exec_permission_approvals_enabled: features - .enabled(Feature::ExecPermissionApprovals), - }, - ); +fn add_shell_tools(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; + let features = turn_context.features.get(); + register_shell_tools( + tool_set, + ShellToolsOptions { + shell_type: shell_type_for_model_and_features(&turn_context.model_info, features), + shell_command_backend: shell_command_backend_for_features(features), + environment_mode: turn_context.tool_environment_mode(), + allow_login_shell: turn_context.config.permissions.allow_login_shell, + exec_permission_approvals_enabled: features.enabled(Feature::ExecPermissionApprovals), + }, + ); +} + +fn add_mcp_resource_tools(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + if context.mcp_tools.is_some() { + tool_set.add_runtime(ListMcpResourcesHandler); + tool_set.add_runtime(ListMcpResourceTemplatesHandler); + tool_set.add_runtime(ReadMcpResourceHandler); } } -struct McpResourceToolSource; +fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; + let features = turn_context.features.get(); + let environment_mode = turn_context.tool_environment_mode(); -impl<'a> ToolSource, Arc> for McpResourceToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - if context.mcp_tools.is_some() { - tool_set.add_runtime(ListMcpResourcesHandler); - tool_set.add_runtime(ListMcpResourceTemplatesHandler); - tool_set.add_runtime(ReadMcpResourceHandler); - } + tool_set.add_runtime(PlanHandler); + if turn_context.goal_tools_enabled() { + tool_set.add_runtime(GetGoalHandler); + tool_set.add_runtime(CreateGoalHandler); + tool_set.add_runtime(UpdateGoalHandler); } -} -struct CoreUtilityToolSource; + tool_set.add_runtime(RequestUserInputHandler { + available_modes: request_user_input_available_modes(features), + }); -impl<'a> ToolSource, Arc> for CoreUtilityToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - let turn_context = context.turn_context; - let features = turn_context.features.get(); - let environment_mode = turn_context.tool_environment_mode(); - - tool_set.add_runtime(PlanHandler); - if turn_context.goal_tools_enabled() { - tool_set.add_runtime(GetGoalHandler); - tool_set.add_runtime(CreateGoalHandler); - tool_set.add_runtime(UpdateGoalHandler); - } - - tool_set.add_runtime(RequestUserInputHandler { - available_modes: request_user_input_available_modes(features), - }); - - if features.enabled(Feature::RequestPermissionsTool) { - tool_set.add_runtime(RequestPermissionsHandler); - } + if features.enabled(Feature::RequestPermissionsTool) { + tool_set.add_runtime(RequestPermissionsHandler); + } - if tool_suggest_enabled(turn_context) - && let Some(discoverable_tools) = - context.discoverable_tools.filter(|tools| !tools.is_empty()) - { - tool_set.add_runtime(RequestPluginInstallHandler::new(discoverable_tools)); - } + if tool_suggest_enabled(turn_context) + && let Some(discoverable_tools) = + context.discoverable_tools.filter(|tools| !tools.is_empty()) + { + tool_set.add_runtime(RequestPluginInstallHandler::new(discoverable_tools)); + } - if environment_mode.has_environment() - && turn_context.model_info.apply_patch_tool_type.is_some() - { - let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); - tool_set.add_runtime(ApplyPatchHandler::new(include_environment_id)); - } + if environment_mode.has_environment() && turn_context.model_info.apply_patch_tool_type.is_some() + { + let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); + tool_set.add_runtime(ApplyPatchHandler::new(include_environment_id)); + } - if turn_context - .model_info - .experimental_supported_tools - .iter() - .any(|tool| tool == "test_sync_tool") - { - tool_set.add_runtime(TestSyncHandler); - } + if turn_context + .model_info + .experimental_supported_tools + .iter() + .any(|tool| tool == "test_sync_tool") + { + tool_set.add_runtime(TestSyncHandler); + } - if environment_mode.has_environment() { - let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); - tool_set.add_runtime(ViewImageHandler::new(ViewImageToolOptions { - can_request_original_image_detail: can_request_original_image_detail( - &turn_context.model_info, - ), - include_environment_id, - })); - } + if environment_mode.has_environment() { + let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple); + tool_set.add_runtime(ViewImageHandler::new(ViewImageToolOptions { + can_request_original_image_detail: can_request_original_image_detail( + &turn_context.model_info, + ), + include_environment_id, + })); } } -struct CollaborationToolSource; - -impl<'a> ToolSource, Arc> for CollaborationToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - let turn_context = context.turn_context; - if collab_tools_enabled(turn_context) { - if multi_agent_v2_enabled(turn_context) { - let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only { - ToolExposure::DirectModelOnly - } else { - ToolExposure::Direct - }; - let agent_type_description = - agent_type_description(turn_context, context.default_agent_type_description); - tool_set.add_runtime_arc(multi_agent_v2_handler( - SpawnAgentHandlerV2::new(SpawnAgentToolOptions { - available_models: turn_context.available_models.clone(), - agent_type_description, - hide_agent_type_model_reasoning: turn_context - .config - .multi_agent_v2 - .hide_spawn_agent_metadata, - include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled, - usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(), - max_concurrent_threads_per_session: max_concurrent_threads_per_session( - turn_context, - ), - }), - exposure, - )); - tool_set.add_runtime_arc(multi_agent_v2_handler(SendMessageHandlerV2, exposure)); - tool_set.add_runtime_arc(multi_agent_v2_handler(FollowupTaskHandlerV2, exposure)); - tool_set.add_runtime_arc(multi_agent_v2_handler( - WaitAgentHandlerV2::new(context.wait_agent_timeouts), - exposure, - )); - tool_set.add_runtime_arc(multi_agent_v2_handler(CloseAgentHandlerV2, exposure)); - tool_set.add_runtime_arc(multi_agent_v2_handler(ListAgentsHandlerV2, exposure)); +fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + let turn_context = context.turn_context; + if collab_tools_enabled(turn_context) { + if multi_agent_v2_enabled(turn_context) { + let exposure = if turn_context.config.multi_agent_v2.non_code_mode_only { + ToolExposure::DirectModelOnly } else { - let agent_type_description = - agent_type_description(turn_context, context.default_agent_type_description); - tool_set.add_runtime(SpawnAgentHandler::new(SpawnAgentToolOptions { + ToolExposure::Direct + }; + let agent_type_description = + agent_type_description(turn_context, context.default_agent_type_description); + tool_set.add_runtime_arc(multi_agent_v2_handler( + SpawnAgentHandlerV2::new(SpawnAgentToolOptions { available_models: turn_context.available_models.clone(), agent_type_description, hide_agent_type_model_reasoning: turn_context @@ -585,82 +536,87 @@ impl<'a> ToolSource, Arc> for Colla max_concurrent_threads_per_session: max_concurrent_threads_per_session( turn_context, ), - })); - tool_set.add_runtime(SendInputHandler); - tool_set.add_runtime(ResumeAgentHandler); - tool_set.add_runtime(WaitAgentHandler::new(context.wait_agent_timeouts)); - tool_set.add_runtime(CloseAgentHandler); - } + }), + exposure, + )); + tool_set.add_runtime_arc(multi_agent_v2_handler(SendMessageHandlerV2, exposure)); + tool_set.add_runtime_arc(multi_agent_v2_handler(FollowupTaskHandlerV2, exposure)); + tool_set.add_runtime_arc(multi_agent_v2_handler( + WaitAgentHandlerV2::new(context.wait_agent_timeouts), + exposure, + )); + tool_set.add_runtime_arc(multi_agent_v2_handler(CloseAgentHandlerV2, exposure)); + tool_set.add_runtime_arc(multi_agent_v2_handler(ListAgentsHandlerV2, exposure)); + } else { + let agent_type_description = + agent_type_description(turn_context, context.default_agent_type_description); + tool_set.add_runtime(SpawnAgentHandler::new(SpawnAgentToolOptions { + available_models: turn_context.available_models.clone(), + agent_type_description, + hide_agent_type_model_reasoning: turn_context + .config + .multi_agent_v2 + .hide_spawn_agent_metadata, + include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled, + usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(), + max_concurrent_threads_per_session: max_concurrent_threads_per_session( + turn_context, + ), + })); + tool_set.add_runtime(SendInputHandler); + tool_set.add_runtime(ResumeAgentHandler); + tool_set.add_runtime(WaitAgentHandler::new(context.wait_agent_timeouts)); + tool_set.add_runtime(CloseAgentHandler); } + } - if agent_jobs_tools_enabled(turn_context) { - tool_set.add_runtime(SpawnAgentsOnCsvHandler); - if agent_jobs_worker_tools_enabled(turn_context) { - tool_set.add_runtime(ReportAgentJobResultHandler); - } + if agent_jobs_tools_enabled(turn_context) { + tool_set.add_runtime(SpawnAgentsOnCsvHandler); + if agent_jobs_worker_tools_enabled(turn_context) { + tool_set.add_runtime(ReportAgentJobResultHandler); } } } -struct McpRuntimeToolSource; - -impl<'a> ToolSource, Arc> for McpRuntimeToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - if let Some(mcp_tools) = context.mcp_tools { - for tool in mcp_tools { - tool_set.add_runtime(McpHandler::new(tool.clone())); - } - } - - if let Some(deferred_mcp_tools) = context.deferred_mcp_tools { - for tool in deferred_mcp_tools { - tool_set.add_runtime(McpHandler::with_exposure( - tool.clone(), - ToolExposure::Deferred, - )); - } +fn add_mcp_runtime_tools(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + if let Some(mcp_tools) = context.mcp_tools { + for tool in mcp_tools { + tool_set.add_runtime(McpHandler::new(tool.clone())); } } -} - -struct DynamicToolSource; -impl<'a> ToolSource, Arc> for DynamicToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - for tool in context.dynamic_tools { - let Some(handler) = DynamicToolHandler::new(tool) else { - tracing::error!( - "Failed to convert dynamic tool {:?} to OpenAI tool", - tool.name - ); - continue; - }; - - tool_set.add_runtime(handler); + if let Some(deferred_mcp_tools) = context.deferred_mcp_tools { + for tool in deferred_mcp_tools { + tool_set.add_runtime(McpHandler::with_exposure( + tool.clone(), + ToolExposure::Deferred, + )); } } } -struct ExtensionToolSource; +fn add_dynamic_tools(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + for tool in context.dynamic_tools { + let Some(handler) = DynamicToolHandler::new(tool) else { + tracing::error!( + "Failed to convert dynamic tool {:?} to OpenAI tool", + tool.name + ); + continue; + }; -impl<'a> ToolSource, Arc> for ExtensionToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - // Extension ToolContributor implementations are resolved into executors - // before planning. Core only adapts those executors into its runtime set. - append_extension_tool_executors( - context.turn_context, - context.extension_tool_executors, - tool_set, - ); + tool_set.add_runtime(handler); } } -struct HostedModelToolSource; - -impl<'a> ToolSource, Arc> for HostedModelToolSource { - fn add_tools(&self, context: &CoreToolPlanContext<'a>, tool_set: &mut ToolSetBuilder) { - tool_set.extend_hosted_specs(hosted_model_tool_specs(context.turn_context)); - } +fn add_extension_tools(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + // Extension ToolContributor implementations are resolved into executors + // before planning. Core only adapts those executors into its runtime set. + append_extension_tool_executors( + context.turn_context, + context.extension_tool_executors, + tool_set, + ); } fn append_tool_search_executor(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { diff --git a/codex-rs/tools/README.md b/codex-rs/tools/README.md index b26e7599bbbc..230e8bf57146 100644 --- a/codex-rs/tools/README.md +++ b/codex-rs/tools/README.md @@ -14,7 +14,7 @@ need to live in `core/src/tools/spec.rs` or `core/src/client_common.rs`: - host adapters such as schema sanitization, MCP/dynamic conversion, code-mode augmentation, and image-detail normalization - shared executable-tool and planning contracts such as `ToolExecutor`, - `ToolSource`, `ToolSetBuilder`, `ToolCall`, and `ToolOutput` + `ToolSetBuilder`, `ToolCall`, and `ToolOutput` That extraction is the first step in a longer migration. The goal is not to move all of `core/src/tools` into this crate in one shot. Instead, the plan is diff --git a/codex-rs/tools/src/lib.rs b/codex-rs/tools/src/lib.rs index 6b7ba91c8d1b..3d8df9ed8e15 100644 --- a/codex-rs/tools/src/lib.rs +++ b/codex-rs/tools/src/lib.rs @@ -86,7 +86,6 @@ pub use tool_output::ToolOutput; pub use tool_payload::ToolPayload; pub use tool_set::ToolSet; pub use tool_set::ToolSetBuilder; -pub use tool_set::ToolSource; pub use tool_spec::ResponsesApiWebSearchFilters; pub use tool_spec::ResponsesApiWebSearchUserLocation; pub use tool_spec::ToolSpec; diff --git a/codex-rs/tools/src/tool_set.rs b/codex-rs/tools/src/tool_set.rs index 57b77d7d8eac..d1487a725ab2 100644 --- a/codex-rs/tools/src/tool_set.rs +++ b/codex-rs/tools/src/tool_set.rs @@ -1,14 +1,5 @@ use crate::ToolSpec; -/// Source of tools for one host-specific planning context. -/// -/// Implementations add runtime tools, hosted model tools, or both to the -/// supplied tool set. The context and runtime type are host-owned so this -/// abstraction can stay independent of `codex-core`. -pub trait ToolSource: Send + Sync { - fn add_tools(&self, ctx: &C, tools: &mut ToolSetBuilder); -} - /// Accumulates the concrete tools available for one model request. /// /// Runtime tools carry both their executable handler and model-visible spec.