diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 132083d32caf..9667c1ddd58c 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; @@ -78,7 +79,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 +111,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,29 +425,44 @@ fn code_mode_namespace_descriptions( namespace_descriptions } -fn register_tool_runtimes( - turn_context: &TurnContext, - params: ToolSetBuildParams<'_>, - tool_set: &mut ToolSetBuilder, -) { +fn add_tool_sources(context: &CoreToolPlanContext<'_>, tool_set: &mut ToolSetBuilder) { + 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)); +} + +fn add_shell_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(); 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, + 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), }, ); +} - if params.mcp_tools.is_some() { +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); } +} + +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(); tool_set.add_runtime(PlanHandler); if turn_context.goal_tools_enabled() { @@ -467,7 +481,7 @@ fn register_tool_runtimes( if tool_suggest_enabled(turn_context) && let Some(discoverable_tools) = - params.discoverable_tools.filter(|tools| !tools.is_empty()) + context.discoverable_tools.filter(|tools| !tools.is_empty()) { tool_set.add_runtime(RequestPluginInstallHandler::new(discoverable_tools)); } @@ -496,7 +510,10 @@ fn register_tool_runtimes( include_environment_id, })); } +} +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 { @@ -505,7 +522,7 @@ fn register_tool_runtimes( ToolExposure::Direct }; let agent_type_description = - agent_type_description(turn_context, params.default_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(), @@ -525,14 +542,14 @@ fn register_tool_runtimes( 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), + 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, params.default_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, @@ -548,7 +565,7 @@ fn register_tool_runtimes( })); tool_set.add_runtime(SendInputHandler); tool_set.add_runtime(ResumeAgentHandler); - tool_set.add_runtime(WaitAgentHandler::new(params.wait_agent_timeouts)); + tool_set.add_runtime(WaitAgentHandler::new(context.wait_agent_timeouts)); tool_set.add_runtime(CloseAgentHandler); } } @@ -559,14 +576,16 @@ fn register_tool_runtimes( tool_set.add_runtime(ReportAgentJobResultHandler); } } +} - if let Some(mcp_tools) = params.mcp_tools { +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())); } } - if let Some(deferred_mcp_tools) = params.deferred_mcp_tools { + 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(), @@ -574,8 +593,10 @@ fn register_tool_runtimes( )); } } +} - for tool in params.dynamic_tools { +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", @@ -586,11 +607,20 @@ fn register_tool_runtimes( tool_set.add_runtime(handler); } +} - append_extension_tool_executors(turn_context, params.extension_tool_executors, tool_set); +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(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 +638,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..230e8bf57146 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`, + `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..3d8df9ed8e15 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,8 @@ 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_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..d1487a725ab2 --- /dev/null +++ b/codex-rs/tools/src/tool_set.rs @@ -0,0 +1,67 @@ +use crate::ToolSpec; + +/// 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) + } +}