Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 67 additions & 36 deletions codex-rs/core/src/tools/spec_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]>,
Expand Down Expand Up @@ -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())
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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));
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
Expand All @@ -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,
Expand All @@ -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);
}
}
Expand All @@ -559,23 +576,27 @@ 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(),
ToolExposure::Deferred,
));
}
}
}

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",
Expand All @@ -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;
}
Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/tools/tool_family/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
71 changes: 20 additions & 51 deletions codex-rs/core/src/tools/tool_set.rs
Original file line number Diff line number Diff line change
@@ -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<Arc<dyn CoreToolRuntime>>,
hosted_specs: Vec<ToolSpec>,
}

impl ToolSetBuilder {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) type ToolSet = codex_tools::ToolSet<Arc<dyn CoreToolRuntime>>;
pub(crate) type ToolSetBuilder = codex_tools::ToolSetBuilder<Arc<dyn CoreToolRuntime>>;

pub(crate) fn add_runtime<T>(&mut self, runtime: T)
pub(crate) trait CoreToolSetBuilderExt {
fn add_runtime<T>(&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<dyn CoreToolRuntime>) {
self.runtimes.push(runtime);
}
fn add_runtime_arc(&mut self, runtime: Arc<dyn CoreToolRuntime>);

pub(crate) fn prepend_runtime_arcs<I>(&mut self, runtimes: I)
fn prepend_runtime_arcs<I>(&mut self, runtimes: I)
where
I: IntoIterator<Item = Arc<dyn CoreToolRuntime>>,
{
self.runtimes.splice(0..0, runtimes);
}

pub(crate) fn runtimes(&self) -> &[Arc<dyn CoreToolRuntime>] {
&self.runtimes
}
I: IntoIterator<Item = Arc<dyn CoreToolRuntime>>;
}

pub(crate) fn extend_hosted_specs<I>(&mut self, specs: I)
impl CoreToolSetBuilderExt for ToolSetBuilder {
fn add_runtime<T>(&mut self, runtime: T)
where
I: IntoIterator<Item = ToolSpec>,
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<dyn CoreToolRuntime>) {
self.push_runtime(runtime);
}
}

pub(crate) struct ToolSet {
runtimes: Vec<Arc<dyn CoreToolRuntime>>,
hosted_specs: Vec<ToolSpec>,
}

impl ToolSet {
pub(crate) fn into_parts(self) -> (Vec<Arc<dyn CoreToolRuntime>>, Vec<ToolSpec>) {
(self.runtimes, self.hosted_specs)
fn prepend_runtime_arcs<I>(&mut self, runtimes: I)
where
I: IntoIterator<Item = Arc<dyn CoreToolRuntime>>,
{
self.prepend_runtimes(runtimes);
}
}
4 changes: 2 additions & 2 deletions codex-rs/tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/tools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading