mirror of
https://github.com/openai/codex.git
synced 2026-09-14 11:57:03 +00:00
6.6 KiB
6.6 KiB
Tool Handling Refactor Plan
Current Pain Points
- Tool specs and dispatch live in multiple files (
openai_tools.rs,codex.rs,shell.rs,apply_patch.rs,exec_command,plan_tool), so every tool is stitched together by hand with duplicated string matches. codex.rsmixes concerns: argument parsing, apply-patch verification, approval policy enforcement, telemetry, and response construction for Function/Custom/LocalShell calls are all intertwined.- Adding or adjusting a tool requires touching 5–7 sites and remembering subtle invariants (e.g., apply_patch needs extra verification, view_image requires path resolution), which increases review overhead and bug risk.
- Spec configuration (
ToolsConfig,get_openai_tools) is disconnected from runtime dispatch, so there is no single registry that knows which tools are active or how to invoke them.
Goals
- Centralize tool definitions, specs, and dispatching inside a dedicated
toolsmodule with clearly defined traits and types. - Unify handling for function calls, custom calls, and local shell calls, sharing approval checks, apply_patch guards, and telemetry.
- Slim down
codex.rsso it only convertsResponseIteminto aToolCalland delegates the rest. - Make new tool onboarding require (at most) one handler + registration entry + optional tests.
Core Traits & Types
-
pub trait ToolHandler: Send + Sync { fn spec(&self) -> Option<&ToolSpec>; fn kind(&self) -> ToolKind; async fn handle(&self, invocation: ToolInvocation<'_>) -> Result<ToolOutput, FunctionCallError>; }spec: supplies the OpenAI tool definition for function/custom tools (returnsNonefor implicit tools like local shell).kind: declares whether the handler servicesToolCall::Function,ToolCall::Custom,ToolCall::LocalShell, etc., for dispatch filtering.handle: runs the tool logic, returning structured output + optional streaming side effects.
-
pub struct ToolInvocation<'a> { pub session: &'a Session, pub turn: &'a TurnContext, pub tracker: &'a mut TurnDiffTracker, pub sub_id: &'a str, pub call_id: &'a str, pub payload: ToolPayload<'a>, }payloadcaptures parsed arguments (serde_json::Valuefor functions/custom,ShellToolCallParamsfor local shell, etc.).
-
pub enum ToolOutput { Function(FunctionCallOutputPayload), Custom(String), }- Helper constructors convert to
ResponseInputItemin one place.
- Helper constructors convert to
-
pub struct ToolRegistry { handlers: HashMap<ToolName, Arc<dyn ToolHandler>>, }- Provides
register_handler,iter_specs()for prompt assembly, anddispatch(call: ToolCall) -> Result<ResponseInputItem, FunctionCallError>.
- Provides
Proposed Refactor
-
Scaffold
core/src/toolsModulemod.rsre-exportscontext,registry,router,spec, andhandlers::*.context.rsdefinesToolInvocation,ToolPayload,ToolOutput, helper constructors forResponseInputItem.registry.rsimplementsToolRegistry+ToolKindenum and houses the instrumentation wrapper (otel.log_tool_result, approval policy checks).router.rsprovidesToolCallenum (variants:Function,Custom,LocalShell,UnifiedExec,Mcp), plusRouter::from_turn_context(&TurnContext)to build the registry + spec list together.
-
Move Spec Logic Into
tools::spec- Relocate
ToolsConfig,ToolsConfigParams,get_openai_tools,ConfigShellToolType, and tool builders (shell/unified exec/apply_patch/etc.) intospec.rs. - Add
ToolSpecstruct mirroring currentOpenAiTool, keeping existing tests but updated paths. - Expose
pub fn build_specs(config: &ToolsConfig, mcp: Option<HashMap<..>>) -> (Vec<ToolSpec>, ToolRegistryBuilder)to keep spec generation and handler registration in sync.
- Relocate
-
Implement Concrete Handlers
- Split existing logic into focused files:
handlers/shell.rs: wrapshandle_container_exec_with_params, approval policy enforcement, apply_patch verification, and streaming support.handlers/apply_patch.rs: retains apply_patch-specific parsing when invoked as a function/custom tool.handlers/unified_exec.rs: hostshandle_unified_exec_tool_calllogic.handlers/plan.rs: wrapshandle_update_plan.handlers/view_image.rs: handles local path resolution +inject_inputcall.handlers/exec_stream.rs: handlesexec_commandandwrite_stdin(for streamable exec shell variant).handlers/mcp.rs: optional adapter that calls through tohandle_mcp_tool_callwhile emitting consistent telemetry.
- Each handler implements
ToolHandlerwith explicit payload parsing (serde_json::from_valuehelpers) and reuses shared utilities (create_env,maybe_parse_apply_patch_verified).
- Split existing logic into focused files:
-
Registry Wiring
- Add
ToolRegistryBuilderhelper collecting(ToolName, Arc<dyn ToolHandler>)pairs. - Builder methods (e.g.,
with_shell_handler,with_apply_patch_handler) are invoked fromspec::build_specsbased onToolsConfigdecisions so that enabling a spec automatically registers the handler. - Integrate MCP tools by registering a generic
McpHandlerper tool when MCP discovery runs.
- Add
-
Update
codex.rs- Replace
handle_function_call,handle_custom_tool_call,to_exec_params, andhandle_container_exec_with_paramswith:let router = tools::router::Router::new(sess, turn_context, turn_diff_tracker);- Convert
ResponseItemintoToolCalland callrouter.dispatch(...).
- Remove duplicate instrumentation;
ToolRegistryhandleslog_tool_resultand error wrapping. - Keep only MCP discovery + fallback logic, delegating to
ToolRegistryfor execution.
- Replace
-
Tests & Validation
- Port existing unit tests in
openai_tools.rstotools::specand add new tests forToolRegistrycovering success, error, and approval-policy rejection paths. - Add smoke tests for converting
ResponseItemtoToolCallintools::router. - Ensure apply_patch verification works both for function and local shell flows via dedicated tests (mock
apply_patch::apply_patch).
- Port existing unit tests in
Testing & Rollout
- Run
cargo test -p codex-core tools::spec(once added) plus focused handler tests. - Execute existing integration suites that cover exec/apply_patch/unified_exec flows.
- No behavior change expected, but snapshot suites (esp. TUI if any output differs) should be re-run as a sanity check.
Follow-ups
- With registry centralization, expose structured telemetry (counts, durations) and hook into metrics.
- Evaluate deletion of legacy local shell pathway once unified_exec proves stable.