[codex] Split tool handlers by tool name (#20687)

## Why

Tool registration used to bind a tool name to a handler externally,
which left ownership split between the registry plan and the handler
implementation. Some built-in handlers also multiplexed multiple in-core
tools by switching on the invoked tool name internally.

This moves the registry identity onto the handler itself and makes
built-in multi-tool areas use separate concrete handlers, so each
registered handler instance owns exactly one tool name and one dispatch
path.

## What Changed

- Added `ToolHandler::tool_name()` and changed
`ToolRegistryBuilder::register_handler` to derive the registry key from
the handler.
- Split built-in multiplexed handlers into concrete per-tool handlers
for unified exec, shell/local shell/container exec, MCP resources, goal
tools, and agent job tools.
- Kept name-carrying handler instances only where the runtime target is
inherently external or dynamic, such as MCP tools, dynamic tools, and
unavailable placeholders.
- Updated `ToolHandlerKind` and registry-plan construction so plan
entries map directly to concrete handler registrations.

## Verification

- `cargo test -p codex-tools tool_registry_plan`
- `cargo test -p codex-core --lib tools::registry_tests`
- `just fix -p codex-tools`
- `just fix -p codex-core`
This commit is contained in:
pakrym-oai
2026-05-05 13:46:45 -07:00
committed by GitHub
Unverified
parent 9cbef243b5
commit f593323ef1
43 changed files with 1383 additions and 952 deletions
+13 -10
View File
@@ -66,9 +66,10 @@ use crate::tasks::execute_user_shell_command;
use crate::tools::ToolRouter;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::handlers::GoalHandler;
use crate::tools::handlers::CreateGoalHandler;
use crate::tools::handlers::ExecCommandHandler;
use crate::tools::handlers::ShellHandler;
use crate::tools::handlers::UnifiedExecHandler;
use crate::tools::handlers::UpdateGoalHandler;
use crate::tools::registry::ToolHandler;
use crate::tools::router::ToolCallSource;
use crate::turn_diff_tracker::TurnDiffTracker;
@@ -8247,7 +8248,7 @@ async fn sample_rollout(
async fn create_goal_tool_rejects_existing_goal() {
let (session, turn_context, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let handler = GoalHandler;
let handler = CreateGoalHandler;
handler
.handle(ToolInvocation {
@@ -8309,9 +8310,10 @@ async fn create_goal_tool_rejects_existing_goal() {
async fn update_goal_tool_rejects_pausing_goal() {
let (session, turn_context, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let handler = GoalHandler;
let create_handler = CreateGoalHandler;
let update_handler = UpdateGoalHandler;
handler
create_handler
.handle(ToolInvocation {
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
@@ -8331,7 +8333,7 @@ async fn update_goal_tool_rejects_pausing_goal() {
.await
.expect("initial create_goal should succeed");
let response = handler
let response = update_handler
.handle(ToolInvocation {
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
@@ -8369,9 +8371,10 @@ async fn update_goal_tool_rejects_pausing_goal() {
async fn update_goal_tool_marks_goal_complete() {
let (session, turn_context, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let handler = GoalHandler;
let create_handler = CreateGoalHandler;
let update_handler = UpdateGoalHandler;
handler
create_handler
.handle(ToolInvocation {
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
@@ -8391,7 +8394,7 @@ async fn update_goal_tool_marks_goal_complete() {
.await
.expect("initial create_goal should succeed");
handler
update_handler
.handle(ToolInvocation {
session: Arc::clone(&session),
turn: Arc::clone(&turn_context),
@@ -8548,7 +8551,7 @@ async fn unified_exec_rejects_escalated_permissions_when_policy_not_on_request()
let turn_context = Arc::new(turn_context_raw);
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let handler = UnifiedExecHandler;
let handler = ExecCommandHandler;
let resp = handler
.handle(ToolInvocation {
session: Arc::clone(&session),
@@ -498,7 +498,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic
let turn_context = Arc::new(turn_context_raw);
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let handler = UnifiedExecHandler;
let handler = ExecCommandHandler;
let resp = handler
.handle(ToolInvocation {
session: Arc::clone(&session),