Simplify tool executor and registry plumbing (#22636)

## Why

The tool runtime path still had a typed output associated type on
`ToolExecutor`, plus a core-only `RegisteredTool` adapter and
extension-only executor aliases. That made every new shared tool runtime
carry extra adapter plumbing before it could participate in core
dispatch, extension tools, hook payloads, telemetry, and model-visible
spec generation.

This PR moves output erasure to the shared executor boundary so core and
extension tools can use the same execution contract directly.

## What Changed

- Changed `codex_tools::ToolExecutor` to return `Box<dyn ToolOutput>`
instead of an associated `Output` type.
- Removed the extension-specific `ExtensionToolExecutor` /
`ExtensionToolOutput` aliases and exposed `ToolExecutor<ToolCall>` plus
`ToolOutput` through `codex-extension-api`.
- Reworked core tool registration around `CoreToolRuntime` and
`ToolRegistry::from_tools`, removing the extra `RegisteredTool` /
`ToolRegistryBuilder` layer.
- Consolidated model-visible spec planning and registry construction in
`core/src/tools/spec_plan.rs`, including deferred tool search and
code-mode-only filtering.
- Added `ToolOutput` helpers for post-tool-use hook ids and inputs so
MCP, unified exec, extension, and other boxed outputs preserve the same
hook payload behavior.
- Updated core handlers, memories tools, and the related
registry/spec/router tests to use the simplified contract.

## Test Coverage

- Updated coverage for tool spec planning, registry lookup, deferred
tool search registration, extension tool routing, post-tool-use hook
payloads, dispatch tracing, guardian output extraction, and memories
extension tool execution.
This commit is contained in:
jif-oai authored and GitHub committed 2026-05-15 11:47:54 +02:00
1 parent 0322ac3df8
commit 6f1a01fbdd
65 files changed
+830 -1008

No files matched your search

@@ -5,8 +5,9 @@ use crate::exec_policy::ExecPolicyManager;
use crate::guardian::GUARDIAN_REVIEWER_NAME; use crate::guardian::GUARDIAN_REVIEWER_NAME;
use crate::sandboxing::SandboxPermissions; use crate::sandboxing::SandboxPermissions;
use crate::test_support::models_manager_with_provider; use crate::test_support::models_manager_with_provider;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolCallSource; use crate::tools::context::ToolCallSource;
use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload;
use crate::turn_diff_tracker::TurnDiffTracker; use crate::turn_diff_tracker::TurnDiffTracker;
use codex_app_server_protocol::ConfigLayerSource; use codex_app_server_protocol::ConfigLayerSource;
use codex_config::ConfigLayerEntry; use codex_config::ConfigLayerEntry;
@@ -22,8 +23,8 @@ use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile; use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile;
use codex_protocol::models::ContentItem; use codex_protocol::models::ContentItem;
use codex_protocol::models::NetworkPermissions; use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem; use codex_protocol::models::ResponseItem;
use codex_protocol::models::function_call_output_content_items_to_text;
use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::AskForApproval;
use codex_protocol::request_permissions::PermissionGrantScope; use codex_protocol::request_permissions::PermissionGrantScope;
use codex_protocol::request_permissions::RequestPermissionProfile; use codex_protocol::request_permissions::RequestPermissionProfile;
@@ -48,8 +49,23 @@ use tempfile::tempdir;
use tokio::time::timeout; use tokio::time::timeout;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
fn expect_text_output(output: &FunctionToolOutput) -> String { fn expect_text_output<T>(output: &T) -> String
function_call_output_content_items_to_text(&output.body).unwrap_or_default() where
T: ToolOutput + ?Sized,
{
let response = output.to_response_item(
"call-guardian",
&ToolPayload::Function {
arguments: "{}".to_string(),
},
);
match response {
ResponseInputItem::FunctionCallOutput { output, .. }
| ResponseInputItem::CustomToolCallOutput { output, .. } => {
output.body.to_text().unwrap_or_default()
}
other => panic!("expected function output, got {other:?}"),
}
} }
#[tokio::test] #[tokio::test]
@@ -2,8 +2,9 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -89,8 +90,6 @@ impl CodeModeExecuteHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler { impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(PUBLIC_TOOL_NAME) ToolName::plain(PUBLIC_TOOL_NAME)
} }
@@ -99,7 +98,10 @@ impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler {
Some(self.spec.clone()) Some(self.spec.clone())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -110,9 +112,10 @@ impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler {
} = invocation; } = invocation;
match payload { match payload {
ToolPayload::Custom { input } if is_exec_tool_name(&tool_name) => { ToolPayload::Custom { input } if is_exec_tool_name(&tool_name) => self
self.execute(session, turn, call_id, input).await .execute(session, turn, call_id, input)
} .await
.map(boxed_tool_output),
_ => Err(FunctionCallError::RespondToModel(format!( _ => Err(FunctionCallError::RespondToModel(format!(
"{PUBLIC_TOOL_NAME} expects raw JavaScript source text" "{PUBLIC_TOOL_NAME} expects raw JavaScript source text"
))), ))),
@@ -120,7 +123,7 @@ impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler {
} }
} }
impl ToolHandler for CodeModeExecuteHandler { impl CoreToolRuntime for CodeModeExecuteHandler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Custom { .. }) matches!(payload, ToolPayload::Custom { .. })
} }
@@ -1,11 +1,11 @@
use serde::Deserialize; use serde::Deserialize;
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -43,8 +43,6 @@ where
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler { impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(WAIT_TOOL_NAME) ToolName::plain(WAIT_TOOL_NAME)
} }
@@ -53,7 +51,10 @@ impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler {
Some(create_wait_tool()) Some(create_wait_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -99,6 +100,7 @@ impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler {
} }
handle_runtime_response(&exec, wait_response.into(), args.max_tokens, started_at) handle_runtime_response(&exec, wait_response.into(), args.max_tokens, started_at)
.await .await
.map(boxed_tool_output)
.map_err(FunctionCallError::RespondToModel) .map_err(FunctionCallError::RespondToModel)
} }
_ => Err(FunctionCallError::RespondToModel(format!( _ => Err(FunctionCallError::RespondToModel(format!(
@@ -108,4 +110,4 @@ impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler {
} }
} }
impl ToolHandler for CodeModeWaitHandler {} impl CoreToolRuntime for CodeModeWaitHandler {}
+25
View File
@@ -28,6 +28,13 @@ use tokio_util::sync::CancellationToken;
pub use codex_tools::ToolOutput; pub use codex_tools::ToolOutput;
pub use codex_tools::ToolPayload; pub use codex_tools::ToolPayload;
pub(crate) fn boxed_tool_output<T>(output: T) -> Box<dyn ToolOutput>
where
T: ToolOutput + 'static,
{
Box::new(output)
}
pub type SharedTurnDiffTracker = Arc<Mutex<TurnDiffTracker>>; pub type SharedTurnDiffTracker = Arc<Mutex<TurnDiffTracker>>;
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@@ -91,6 +98,10 @@ impl ToolOutput for McpToolOutput {
}) })
} }
fn post_tool_use_input(&self, _payload: &ToolPayload) -> Option<JsonValue> {
Some(self.tool_input.clone())
}
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue> { fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue> {
serde_json::to_value(&self.result).ok() serde_json::to_value(&self.result).ok()
} }
@@ -327,6 +338,20 @@ impl ToolOutput for ExecCommandToolOutput {
) )
} }
fn post_tool_use_id(&self, call_id: &str) -> String {
if self.event_call_id.is_empty() {
call_id.to_string()
} else {
self.event_call_id.clone()
}
}
fn post_tool_use_input(&self, _payload: &ToolPayload) -> Option<JsonValue> {
self.hook_command
.as_ref()
.map(|command| serde_json::json!({ "command": command }))
}
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue> { fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue> {
if self.process_id.is_some() || self.hook_command.is_none() { if self.process_id.is_some() || self.hook_command.is_none() {
return None; return None;
@@ -2,9 +2,10 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::agent_jobs_spec::create_report_agent_job_result_tool; use crate::tools::handlers::agent_jobs_spec::create_report_agent_job_result_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -14,8 +15,6 @@ pub struct ReportAgentJobResultHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ReportAgentJobResultHandler { impl ToolExecutor<ToolInvocation> for ReportAgentJobResultHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("report_agent_job_result") ToolName::plain("report_agent_job_result")
} }
@@ -24,7 +23,10 @@ impl ToolExecutor<ToolInvocation> for ReportAgentJobResultHandler {
Some(create_report_agent_job_result_tool()) Some(create_report_agent_job_result_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, payload, .. session, payload, ..
} = invocation; } = invocation;
@@ -38,11 +40,11 @@ impl ToolExecutor<ToolInvocation> for ReportAgentJobResultHandler {
} }
}; };
handle(session, arguments).await handle(session, arguments).await.map(boxed_tool_output)
} }
} }
impl ToolHandler for ReportAgentJobResultHandler { impl CoreToolRuntime for ReportAgentJobResultHandler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -2,9 +2,10 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::agent_jobs_spec::create_spawn_agents_on_csv_tool; use crate::tools::handlers::agent_jobs_spec::create_spawn_agents_on_csv_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBuf;
@@ -15,8 +16,6 @@ pub struct SpawnAgentsOnCsvHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for SpawnAgentsOnCsvHandler { impl ToolExecutor<ToolInvocation> for SpawnAgentsOnCsvHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("spawn_agents_on_csv") ToolName::plain("spawn_agents_on_csv")
} }
@@ -25,7 +24,10 @@ impl ToolExecutor<ToolInvocation> for SpawnAgentsOnCsvHandler {
Some(create_spawn_agents_on_csv_tool()) Some(create_spawn_agents_on_csv_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -42,11 +44,13 @@ impl ToolExecutor<ToolInvocation> for SpawnAgentsOnCsvHandler {
} }
}; };
handle(session, turn, arguments).await handle(session, turn, arguments)
.await
.map(boxed_tool_output)
} }
} }
impl ToolHandler for SpawnAgentsOnCsvHandler { impl CoreToolRuntime for SpawnAgentsOnCsvHandler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -17,8 +17,8 @@ use crate::tools::context::ApplyPatchToolOutput;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::SharedTurnDiffTracker; use crate::tools::context::SharedTurnDiffTracker;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::events::ToolEmitter; use crate::tools::events::ToolEmitter;
use crate::tools::events::ToolEventCtx; use crate::tools::events::ToolEventCtx;
use crate::tools::handlers::apply_granted_turn_permissions; use crate::tools::handlers::apply_granted_turn_permissions;
@@ -27,11 +27,11 @@ use crate::tools::handlers::resolve_tool_environment;
use crate::tools::handlers::updated_hook_command; use crate::tools::handlers::updated_hook_command;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::orchestrator::ToolOrchestrator; use crate::tools::orchestrator::ToolOrchestrator;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PostToolUsePayload;
use crate::tools::registry::PreToolUsePayload; use crate::tools::registry::PreToolUsePayload;
use crate::tools::registry::ToolArgumentDiffConsumer; use crate::tools::registry::ToolArgumentDiffConsumer;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use crate::tools::runtimes::apply_patch::ApplyPatchRequest; use crate::tools::runtimes::apply_patch::ApplyPatchRequest;
use crate::tools::runtimes::apply_patch::ApplyPatchRuntime; use crate::tools::runtimes::apply_patch::ApplyPatchRuntime;
use crate::tools::sandboxing::ToolCtx; use crate::tools::sandboxing::ToolCtx;
@@ -299,8 +299,6 @@ async fn effective_patch_permissions(
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ApplyPatchHandler { impl ToolExecutor<ToolInvocation> for ApplyPatchHandler {
type Output = ApplyPatchToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("apply_patch") ToolName::plain("apply_patch")
} }
@@ -309,7 +307,10 @@ impl ToolExecutor<ToolInvocation> for ApplyPatchHandler {
Some(create_apply_patch_freeform_tool(self.multi_environment)) Some(create_apply_patch_freeform_tool(self.multi_environment))
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -359,7 +360,7 @@ impl ToolExecutor<ToolInvocation> for ApplyPatchHandler {
{ {
InternalApplyPatchInvocation::Output(item) => { InternalApplyPatchInvocation::Output(item) => {
let content = item?; let content = item?;
Ok(ApplyPatchToolOutput::from_text(content)) Ok(boxed_tool_output(ApplyPatchToolOutput::from_text(content)))
} }
InternalApplyPatchInvocation::DelegateToRuntime(apply) => { InternalApplyPatchInvocation::DelegateToRuntime(apply) => {
let changes = convert_apply_patch_to_protocol(&apply.action); let changes = convert_apply_patch_to_protocol(&apply.action);
@@ -414,7 +415,7 @@ impl ToolExecutor<ToolInvocation> for ApplyPatchHandler {
Some(&tracker), Some(&tracker),
); );
let content = emitter.finish(event_ctx, out, delta.as_ref()).await?; let content = emitter.finish(event_ctx, out, delta.as_ref()).await?;
Ok(ApplyPatchToolOutput::from_text(content)) Ok(boxed_tool_output(ApplyPatchToolOutput::from_text(content)))
} }
} }
} }
@@ -438,7 +439,7 @@ impl ToolExecutor<ToolInvocation> for ApplyPatchHandler {
} }
} }
impl ToolHandler for ApplyPatchHandler { impl CoreToolRuntime for ApplyPatchHandler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Custom { .. }) matches!(payload, ToolPayload::Custom { .. })
} }
@@ -472,7 +473,7 @@ impl ToolHandler for ApplyPatchHandler {
fn post_tool_use_payload( fn post_tool_use_payload(
&self, &self,
invocation: &ToolInvocation, invocation: &ToolInvocation,
result: &Self::Output, result: &dyn crate::tools::context::ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
let tool_response = let tool_response =
result.post_tool_use_response(&invocation.call_id, &invocation.payload)?; result.post_tool_use_response(&invocation.call_id, &invocation.payload)?;
+11 -6
View File
@@ -4,10 +4,11 @@ use crate::session::turn_context::TurnContext;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolExposure; use crate::tools::registry::ToolExposure;
use crate::tools::registry::ToolHandler;
use crate::tools::tool_search_entry::ToolSearchInfo; use crate::tools::tool_search_entry::ToolSearchInfo;
use crate::turn_timing::now_unix_timestamp_ms; use crate::turn_timing::now_unix_timestamp_ms;
use codex_protocol::dynamic_tools::DynamicToolCallRequest; use codex_protocol::dynamic_tools::DynamicToolCallRequest;
@@ -62,8 +63,6 @@ impl DynamicToolHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for DynamicToolHandler { impl ToolExecutor<ToolInvocation> for DynamicToolHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
self.tool_name.clone() self.tool_name.clone()
} }
@@ -76,7 +75,10 @@ impl ToolExecutor<ToolInvocation> for DynamicToolHandler {
self.exposure self.exposure
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -117,11 +119,14 @@ impl ToolExecutor<ToolInvocation> for DynamicToolHandler {
.into_iter() .into_iter()
.map(FunctionCallOutputContentItem::from) .map(FunctionCallOutputContentItem::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(FunctionToolOutput::from_content(body, Some(success))) Ok(boxed_tool_output(FunctionToolOutput::from_content(
body,
Some(success),
)))
} }
} }
impl ToolHandler for DynamicToolHandler { impl CoreToolRuntime for DynamicToolHandler {
fn search_info(&self) -> Option<ToolSearchInfo> { fn search_info(&self) -> Option<ToolSearchInfo> {
ToolSearchInfo::from_spec( ToolSearchInfo::from_spec(
self.search_text.clone(), self.search_text.clone(),
@@ -1,7 +1,5 @@
use std::sync::Arc; use std::sync::Arc;
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ExtensionToolOutput;
use codex_tools::ToolCall as ExtensionToolCall; use codex_tools::ToolCall as ExtensionToolCall;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -13,18 +11,16 @@ use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::flat_tool_name; use crate::tools::flat_tool_name;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PostToolUsePayload;
use crate::tools::registry::PreToolUsePayload; use crate::tools::registry::PreToolUsePayload;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
pub(crate) struct ExtensionToolHandler { pub(crate) struct ExtensionToolAdapter(Arc<dyn codex_tools::ToolExecutor<ExtensionToolCall>>);
executor: Arc<dyn ExtensionToolExecutor>,
}
impl ExtensionToolHandler { impl ExtensionToolAdapter {
pub(crate) fn new(executor: Arc<dyn ExtensionToolExecutor>) -> Self { pub(crate) fn new(executor: Arc<dyn codex_tools::ToolExecutor<ExtensionToolCall>>) -> Self {
Self { executor } Self(executor)
} }
fn arguments_from_payload<'a>(&self, payload: &'a ToolPayload) -> Option<&'a str> { fn arguments_from_payload<'a>(&self, payload: &'a ToolPayload) -> Option<&'a str> {
@@ -36,23 +32,32 @@ impl ExtensionToolHandler {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ExtensionToolHandler { impl ToolExecutor<ToolInvocation> for ExtensionToolAdapter {
type Output = ExtensionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
self.executor.tool_name() self.0.tool_name()
} }
fn spec(&self) -> Option<ToolSpec> { fn spec(&self) -> Option<ToolSpec> {
self.executor.spec() self.0.spec()
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { fn exposure(&self) -> crate::tools::registry::ToolExposure {
self.executor.handle(to_extension_call(&invocation)).await self.0.exposure()
}
fn supports_parallel_tool_calls(&self) -> bool {
self.0.supports_parallel_tool_calls()
}
async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn ToolOutput>, FunctionCallError> {
self.0.handle(to_extension_call(&invocation)).await
} }
} }
impl ToolHandler for ExtensionToolHandler { impl CoreToolRuntime for ExtensionToolAdapter {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
self.arguments_from_payload(payload).is_some() self.arguments_from_payload(payload).is_some()
} }
@@ -68,7 +73,7 @@ impl ToolHandler for ExtensionToolHandler {
fn post_tool_use_payload( fn post_tool_use_payload(
&self, &self,
invocation: &ToolInvocation, invocation: &ToolInvocation,
result: &Self::Output, result: &dyn ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
let arguments = self.arguments_from_payload(&invocation.payload)?; let arguments = self.arguments_from_payload(&invocation.payload)?;
Some(PostToolUsePayload { Some(PostToolUsePayload {
@@ -104,22 +109,20 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serde_json::json; use serde_json::json;
use super::ExtensionToolHandler; use super::ExtensionToolAdapter;
use crate::tools::context::ToolCallSource; use crate::tools::context::ToolCallSource;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PostToolUsePayload;
use crate::tools::registry::PreToolUsePayload; use crate::tools::registry::PreToolUsePayload;
use crate::tools::registry::ToolHandler;
use crate::turn_diff_tracker::TurnDiffTracker; use crate::turn_diff_tracker::TurnDiffTracker;
struct StubExtensionExecutor; struct StubExtensionExecutor;
#[async_trait::async_trait] #[async_trait::async_trait]
impl codex_extension_api::ToolExecutor<codex_tools::ToolCall> for StubExtensionExecutor { impl codex_extension_api::ToolExecutor<codex_tools::ToolCall> for StubExtensionExecutor {
type Output = codex_tools::JsonToolOutput;
fn tool_name(&self) -> codex_tools::ToolName { fn tool_name(&self) -> codex_tools::ToolName {
codex_tools::ToolName::plain("extension_echo") codex_tools::ToolName::plain("extension_echo")
} }
@@ -148,14 +151,16 @@ mod tests {
async fn handle( async fn handle(
&self, &self,
_call: codex_tools::ToolCall, _call: codex_tools::ToolCall,
) -> Result<Self::Output, codex_tools::FunctionCallError> { ) -> Result<Box<dyn codex_tools::ToolOutput>, codex_tools::FunctionCallError> {
Ok(codex_tools::JsonToolOutput::new(json!({ "ok": true }))) Ok(Box::new(codex_tools::JsonToolOutput::new(
json!({ "ok": true }),
)))
} }
} }
#[tokio::test] #[tokio::test]
async fn exposes_generic_hook_payloads() { async fn exposes_generic_hook_payloads() {
let handler = ExtensionToolHandler::new(Arc::new(StubExtensionExecutor)); let handler = ExtensionToolAdapter::new(Arc::new(StubExtensionExecutor));
let (session, turn) = crate::session::tests::make_session_and_context().await; let (session, turn) = crate::session::tests::make_session_and_context().await;
let invocation = ToolInvocation { let invocation = ToolInvocation {
session: session.into(), session: session.into(),
@@ -172,14 +177,14 @@ mod tests {
let output = codex_tools::JsonToolOutput::new(json!({ "ok": true })); let output = codex_tools::JsonToolOutput::new(json!({ "ok": true }));
assert_eq!( assert_eq!(
ToolHandler::pre_tool_use_payload(&handler, &invocation), CoreToolRuntime::pre_tool_use_payload(&handler, &invocation),
Some(PreToolUsePayload { Some(PreToolUsePayload {
tool_name: HookToolName::new("extension_echo"), tool_name: HookToolName::new("extension_echo"),
tool_input: json!({ "message": "hello" }), tool_input: json!({ "message": "hello" }),
}) })
); );
assert_eq!( assert_eq!(
ToolHandler::post_tool_use_payload(&handler, &invocation, &output), CoreToolRuntime::post_tool_use_payload(&handler, &invocation, &output),
Some(PostToolUsePayload { Some(PostToolUsePayload {
tool_name: HookToolName::new("extension_echo"), tool_name: HookToolName::new("extension_echo"),
tool_use_id: "call-extension".to_string(), tool_use_id: "call-extension".to_string(),
@@ -1,13 +1,13 @@
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::goals::CreateGoalRequest; use crate::goals::CreateGoalRequest;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::goal_spec::CREATE_GOAL_TOOL_NAME; use crate::tools::handlers::goal_spec::CREATE_GOAL_TOOL_NAME;
use crate::tools::handlers::goal_spec::create_create_goal_tool; use crate::tools::handlers::goal_spec::create_create_goal_tool;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -20,8 +20,6 @@ pub struct CreateGoalHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for CreateGoalHandler { impl ToolExecutor<ToolInvocation> for CreateGoalHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(CREATE_GOAL_TOOL_NAME) ToolName::plain(CREATE_GOAL_TOOL_NAME)
} }
@@ -30,7 +28,10 @@ impl ToolExecutor<ToolInvocation> for CreateGoalHandler {
Some(create_create_goal_tool()) Some(create_create_goal_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -70,8 +71,8 @@ impl ToolExecutor<ToolInvocation> for CreateGoalHandler {
FunctionCallError::RespondToModel(format_goal_error(err)) FunctionCallError::RespondToModel(format_goal_error(err))
} }
})?; })?;
goal_response(Some(goal), CompletionBudgetReport::Omit) goal_response(Some(goal), CompletionBudgetReport::Omit).map(boxed_tool_output)
} }
} }
impl ToolHandler for CreateGoalHandler {} impl CoreToolRuntime for CreateGoalHandler {}
@@ -1,11 +1,11 @@
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::goal_spec::GET_GOAL_TOOL_NAME; use crate::tools::handlers::goal_spec::GET_GOAL_TOOL_NAME;
use crate::tools::handlers::goal_spec::create_get_goal_tool; use crate::tools::handlers::goal_spec::create_get_goal_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -17,8 +17,6 @@ pub struct GetGoalHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for GetGoalHandler { impl ToolExecutor<ToolInvocation> for GetGoalHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(GET_GOAL_TOOL_NAME) ToolName::plain(GET_GOAL_TOOL_NAME)
} }
@@ -27,7 +25,10 @@ impl ToolExecutor<ToolInvocation> for GetGoalHandler {
Some(create_get_goal_tool()) Some(create_get_goal_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, payload, .. session, payload, ..
} = invocation; } = invocation;
@@ -38,7 +39,7 @@ impl ToolExecutor<ToolInvocation> for GetGoalHandler {
.get_thread_goal() .get_thread_goal()
.await .await
.map_err(|err| FunctionCallError::RespondToModel(format_goal_error(err)))?; .map_err(|err| FunctionCallError::RespondToModel(format_goal_error(err)))?;
goal_response(goal, CompletionBudgetReport::Omit) goal_response(goal, CompletionBudgetReport::Omit).map(boxed_tool_output)
} }
_ => Err(FunctionCallError::RespondToModel( _ => Err(FunctionCallError::RespondToModel(
"get_goal handler received unsupported payload".to_string(), "get_goal handler received unsupported payload".to_string(),
@@ -47,4 +48,4 @@ impl ToolExecutor<ToolInvocation> for GetGoalHandler {
} }
} }
impl ToolHandler for GetGoalHandler {} impl CoreToolRuntime for GetGoalHandler {}
@@ -1,14 +1,14 @@
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::goals::GoalRuntimeEvent; use crate::goals::GoalRuntimeEvent;
use crate::goals::SetGoalRequest; use crate::goals::SetGoalRequest;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::goal_spec::UPDATE_GOAL_TOOL_NAME; use crate::tools::handlers::goal_spec::UPDATE_GOAL_TOOL_NAME;
use crate::tools::handlers::goal_spec::create_update_goal_tool; use crate::tools::handlers::goal_spec::create_update_goal_tool;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::protocol::ThreadGoalStatus; use codex_protocol::protocol::ThreadGoalStatus;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -22,8 +22,6 @@ pub struct UpdateGoalHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for UpdateGoalHandler { impl ToolExecutor<ToolInvocation> for UpdateGoalHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(UPDATE_GOAL_TOOL_NAME) ToolName::plain(UPDATE_GOAL_TOOL_NAME)
} }
@@ -32,7 +30,10 @@ impl ToolExecutor<ToolInvocation> for UpdateGoalHandler {
Some(create_update_goal_tool()) Some(create_update_goal_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -73,8 +74,8 @@ impl ToolExecutor<ToolInvocation> for UpdateGoalHandler {
) )
.await .await
.map_err(|err| FunctionCallError::RespondToModel(format_goal_error(err)))?; .map_err(|err| FunctionCallError::RespondToModel(format_goal_error(err)))?;
goal_response(Some(goal), CompletionBudgetReport::Include) goal_response(Some(goal), CompletionBudgetReport::Include).map(boxed_tool_output)
} }
} }
impl ToolHandler for UpdateGoalHandler {} impl CoreToolRuntime for UpdateGoalHandler {}
+22 -16
View File
@@ -6,15 +6,15 @@ use crate::mcp_tool_call::handle_mcp_tool_call;
use crate::original_image_detail::can_request_original_image_detail; use crate::original_image_detail::can_request_original_image_detail;
use crate::tools::context::McpToolOutput; use crate::tools::context::McpToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::flat_tool_name; use crate::tools::flat_tool_name;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PostToolUsePayload;
use crate::tools::registry::PreToolUsePayload; use crate::tools::registry::PreToolUsePayload;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolExposure; use crate::tools::registry::ToolExposure;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolTelemetryTags; use crate::tools::registry::ToolTelemetryTags;
use crate::tools::tool_search_entry::ToolSearchInfo; use crate::tools::tool_search_entry::ToolSearchInfo;
use codex_mcp::ToolInfo; use codex_mcp::ToolInfo;
@@ -47,8 +47,6 @@ impl McpHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for McpHandler { impl ToolExecutor<ToolInvocation> for McpHandler {
type Output = McpToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
self.tool_info.canonical_tool_name() self.tool_info.canonical_tool_name()
} }
@@ -89,7 +87,10 @@ impl ToolExecutor<ToolInvocation> for McpHandler {
self.tool_info.supports_parallel_tool_calls self.tool_info.supports_parallel_tool_calls
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -119,17 +120,17 @@ impl ToolExecutor<ToolInvocation> for McpHandler {
) )
.await; .await;
Ok(McpToolOutput { Ok(boxed_tool_output(McpToolOutput {
result: result.result, result: result.result,
tool_input: result.tool_input, tool_input: result.tool_input,
wall_time: started.elapsed(), wall_time: started.elapsed(),
original_image_detail_supported: can_request_original_image_detail(&turn.model_info), original_image_detail_supported: can_request_original_image_detail(&turn.model_info),
truncation_policy: turn.truncation_policy, truncation_policy: turn.truncation_policy,
}) }))
} }
} }
impl ToolHandler for McpHandler { impl CoreToolRuntime for McpHandler {
fn search_info(&self) -> Option<ToolSearchInfo> { fn search_info(&self) -> Option<ToolSearchInfo> {
let source_name = self let source_name = self
.tool_info .tool_info
@@ -156,12 +157,17 @@ impl ToolHandler for McpHandler {
) )
} }
async fn telemetry_tags(&self, _invocation: &ToolInvocation) -> ToolTelemetryTags { fn telemetry_tags<'a>(
let mut tags = vec![("mcp_server", self.tool_info.server_name.clone())]; &'a self,
if let Some(origin) = &self.tool_info.server_origin { _invocation: &'a ToolInvocation,
tags.push(("mcp_server_origin", origin.clone())); ) -> futures::future::BoxFuture<'a, ToolTelemetryTags> {
} Box::pin(async {
tags let mut tags = vec![("mcp_server", self.tool_info.server_name.clone())];
if let Some(origin) = &self.tool_info.server_origin {
tags.push(("mcp_server_origin", origin.clone()));
}
tags
})
} }
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> { fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
@@ -200,7 +206,7 @@ impl ToolHandler for McpHandler {
fn post_tool_use_payload( fn post_tool_use_payload(
&self, &self,
invocation: &ToolInvocation, invocation: &ToolInvocation,
result: &Self::Output, result: &dyn crate::tools::context::ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
let ToolPayload::Function { .. } = &invocation.payload else { let ToolPayload::Function { .. } = &invocation.payload else {
return None; return None;
@@ -211,7 +217,7 @@ impl ToolHandler for McpHandler {
Some(PostToolUsePayload { Some(PostToolUsePayload {
tool_name: HookToolName::new(self.tool_name().to_string()), tool_name: HookToolName::new(self.tool_name().to_string()),
tool_use_id: invocation.call_id.clone(), tool_use_id: invocation.call_id.clone(),
tool_input: result.tool_input.clone(), tool_input: result.post_tool_use_input(&invocation.payload)?,
tool_response, tool_response,
}) })
} }
@@ -1,12 +1,12 @@
use std::time::Instant; use std::time::Instant;
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resource_templates_tool; use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resource_templates_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::models::function_call_output_content_items_to_text; use codex_protocol::models::function_call_output_content_items_to_text;
use codex_protocol::protocol::McpInvocation; use codex_protocol::protocol::McpInvocation;
use codex_tools::ToolName; use codex_tools::ToolName;
@@ -28,8 +28,6 @@ pub struct ListMcpResourceTemplatesHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ListMcpResourceTemplatesHandler { impl ToolExecutor<ToolInvocation> for ListMcpResourceTemplatesHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("list_mcp_resource_templates") ToolName::plain("list_mcp_resource_templates")
} }
@@ -46,7 +44,10 @@ impl ToolExecutor<ToolInvocation> for ListMcpResourceTemplatesHandler {
clippy::await_holding_invalid_type, clippy::await_holding_invalid_type,
reason = "MCP resource template listing reads through the session-owned manager guard" reason = "MCP resource template listing reads through the session-owned manager guard"
)] )]
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -131,7 +132,7 @@ impl ToolExecutor<ToolInvocation> for ListMcpResourceTemplatesHandler {
Ok(call_tool_result_from_content(&content, output.success)), Ok(call_tool_result_from_content(&content, output.success)),
) )
.await; .await;
Ok(output) Ok(boxed_tool_output(output))
} }
Err(err) => { Err(err) => {
let duration = start.elapsed(); let duration = start.elapsed();
@@ -166,4 +167,4 @@ impl ToolExecutor<ToolInvocation> for ListMcpResourceTemplatesHandler {
} }
} }
impl ToolHandler for ListMcpResourceTemplatesHandler {} impl CoreToolRuntime for ListMcpResourceTemplatesHandler {}
@@ -1,12 +1,12 @@
use std::time::Instant; use std::time::Instant;
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resources_tool; use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resources_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::models::function_call_output_content_items_to_text; use codex_protocol::models::function_call_output_content_items_to_text;
use codex_protocol::protocol::McpInvocation; use codex_protocol::protocol::McpInvocation;
use codex_tools::ToolName; use codex_tools::ToolName;
@@ -28,8 +28,6 @@ pub struct ListMcpResourcesHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ListMcpResourcesHandler { impl ToolExecutor<ToolInvocation> for ListMcpResourcesHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("list_mcp_resources") ToolName::plain("list_mcp_resources")
} }
@@ -46,7 +44,10 @@ impl ToolExecutor<ToolInvocation> for ListMcpResourcesHandler {
clippy::await_holding_invalid_type, clippy::await_holding_invalid_type,
reason = "MCP resource listing reads through the session-owned manager guard" reason = "MCP resource listing reads through the session-owned manager guard"
)] )]
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -129,7 +130,7 @@ impl ToolExecutor<ToolInvocation> for ListMcpResourcesHandler {
Ok(call_tool_result_from_content(&content, output.success)), Ok(call_tool_result_from_content(&content, output.success)),
) )
.await; .await;
Ok(output) Ok(boxed_tool_output(output))
} }
Err(err) => { Err(err) => {
let duration = start.elapsed(); let duration = start.elapsed();
@@ -164,4 +165,4 @@ impl ToolExecutor<ToolInvocation> for ListMcpResourcesHandler {
} }
} }
impl ToolHandler for ListMcpResourcesHandler {} impl CoreToolRuntime for ListMcpResourcesHandler {}
@@ -1,12 +1,12 @@
use std::time::Instant; use std::time::Instant;
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::mcp_resource_spec::create_read_mcp_resource_tool; use crate::tools::handlers::mcp_resource_spec::create_read_mcp_resource_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::models::function_call_output_content_items_to_text; use codex_protocol::models::function_call_output_content_items_to_text;
use codex_protocol::protocol::McpInvocation; use codex_protocol::protocol::McpInvocation;
use codex_tools::ToolName; use codex_tools::ToolName;
@@ -28,8 +28,6 @@ pub struct ReadMcpResourceHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler { impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("read_mcp_resource") ToolName::plain("read_mcp_resource")
} }
@@ -42,7 +40,10 @@ impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler {
true true
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -112,7 +113,7 @@ impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler {
Ok(call_tool_result_from_content(&content, output.success)), Ok(call_tool_result_from_content(&content, output.success)),
) )
.await; .await;
Ok(output) Ok(boxed_tool_output(output))
} }
Err(err) => { Err(err) => {
let duration = start.elapsed(); let duration = start.elapsed();
@@ -147,4 +148,4 @@ impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler {
} }
} }
impl ToolHandler for ReadMcpResourceHandler {} impl CoreToolRuntime for ReadMcpResourceHandler {}
@@ -13,10 +13,11 @@ use crate::session::turn_context::TurnContext;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput; use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
pub(crate) use crate::tools::handlers::multi_agents_common::*; pub(crate) use crate::tools::handlers::multi_agents_common::*;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::ThreadId; use codex_protocol::ThreadId;
use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseInputItem;
use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffort;
@@ -7,8 +7,6 @@ pub(crate) struct Handler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = CloseAgentResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("close_agent") ToolName::plain("close_agent")
} }
@@ -17,8 +15,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_close_agent_tool_v1()) Some(create_close_agent_tool_v1())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
handle_close_agent(invocation).await &self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
handle_close_agent(invocation).await.map(boxed_tool_output)
} }
} }
@@ -105,7 +106,7 @@ async fn handle_close_agent(
}) })
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -9,8 +9,6 @@ pub(crate) struct Handler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = ResumeAgentResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("resume_agent") ToolName::plain("resume_agent")
} }
@@ -19,8 +17,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_resume_agent_tool()) Some(create_resume_agent_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
handle_resume_agent(invocation).await &self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
handle_resume_agent(invocation).await.map(boxed_tool_output)
} }
} }
@@ -133,7 +134,7 @@ async fn handle_resume_agent(
Ok(ResumeAgentResult { status }) Ok(ResumeAgentResult { status })
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -8,8 +8,6 @@ pub(crate) struct Handler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = SendInputResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("send_input") ToolName::plain("send_input")
} }
@@ -18,7 +16,10 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_send_input_tool_v1()) Some(create_send_input_tool_v1())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -85,11 +86,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
.await; .await;
let submission_id = result?; let submission_id = result?;
Ok(SendInputResult { submission_id }) Ok(boxed_tool_output(SendInputResult { submission_id }))
} }
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -24,8 +24,6 @@ impl Handler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = SpawnAgentResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("spawn_agent") ToolName::plain("spawn_agent")
} }
@@ -34,8 +32,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_spawn_agent_tool_v1(self.options.clone())) Some(create_spawn_agent_tool_v1(self.options.clone()))
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
handle_spawn_agent(invocation).await &self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
handle_spawn_agent(invocation).await.map(boxed_tool_output)
} }
} }
@@ -198,7 +199,7 @@ async fn handle_spawn_agent(
}) })
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -29,8 +29,6 @@ impl Handler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = WaitAgentResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("wait_agent") ToolName::plain("wait_agent")
} }
@@ -39,7 +37,10 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_wait_agent_tool_v1(self.options)) Some(create_wait_agent_tool_v1(self.options))
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -197,11 +198,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
) )
.await; .await;
Ok(result) Ok(boxed_tool_output(result))
} }
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -317,7 +317,8 @@ async fn spawn_agent_fork_context_rejects_agent_type_override() {
})), })),
)) ))
.await .await
.expect_err("fork_context should reject agent_type overrides"); .err()
.expect("fork_context should reject agent_type overrides");
assert_eq!( assert_eq!(
err, err,
@@ -351,7 +352,8 @@ async fn spawn_agent_fork_context_rejects_child_model_overrides() {
})), })),
)) ))
.await .await
.expect_err("forked spawn should reject child model overrides"); .err()
.expect("forked spawn should reject child model overrides");
assert_eq!( assert_eq!(
err, err,
@@ -395,7 +397,8 @@ async fn multi_agent_v2_spawn_fork_turns_all_rejects_agent_type_override() {
})), })),
)) ))
.await .await
.expect_err("fork_turns=all should reject agent_type overrides"); .err()
.expect("fork_turns=all should reject agent_type overrides");
assert_eq!( assert_eq!(
err, err,
@@ -435,7 +438,8 @@ async fn multi_agent_v2_spawn_defaults_to_full_fork_and_rejects_child_model_over
})), })),
)) ))
.await .await
.expect_err("default full fork should reject child model overrides"); .err()
.expect("default full fork should reject child model overrides");
assert_eq!( assert_eq!(
err, err,
@@ -505,7 +509,8 @@ async fn spawn_agent_service_tier_override_validates_the_effective_child_model()
})), })),
)) ))
.await .await
.expect_err("unknown service tier should be rejected"); .err()
.expect("unknown service tier should be rejected");
assert_eq!( assert_eq!(
err, err,
@@ -530,7 +535,8 @@ async fn spawn_agent_service_tier_override_validates_the_effective_child_model()
})), })),
)) ))
.await .await
.expect_err("tier unsupported by the final child model should be rejected"); .err()
.expect("tier unsupported by the final child model should be rejected");
assert_eq!( assert_eq!(
err, err,
@@ -1116,7 +1122,8 @@ async fn multi_agent_v2_spawn_rejects_legacy_fork_context() {
})), })),
)) ))
.await .await
.expect_err("legacy fork_context should be rejected"); .err()
.expect("legacy fork_context should be rejected");
assert_eq!( assert_eq!(
err, err,
@@ -1155,7 +1162,8 @@ async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string() {
})), })),
)) ))
.await .await
.expect_err("invalid fork_turns should be rejected"); .err()
.expect("invalid fork_turns should be rejected");
assert_eq!( assert_eq!(
err, err,
@@ -1194,7 +1202,8 @@ async fn multi_agent_v2_spawn_rejects_zero_fork_turns() {
})), })),
)) ))
.await .await
.expect_err("zero turn count should be rejected"); .err()
.expect("zero turn count should be rejected");
assert_eq!( assert_eq!(
err, err,
@@ -3630,7 +3639,8 @@ async fn multi_agent_v2_close_agent_rejects_root_target_and_id() {
function_payload(json!({"target": "/root"})), function_payload(json!({"target": "/root"})),
)) ))
.await .await
.expect_err("close_agent should reject the root path"); .err()
.expect("close_agent should reject the root path");
assert_eq!( assert_eq!(
root_path_error, root_path_error,
FunctionCallError::RespondToModel("root is not a spawned agent".to_string()) FunctionCallError::RespondToModel("root is not a spawned agent".to_string())
@@ -3644,7 +3654,8 @@ async fn multi_agent_v2_close_agent_rejects_root_target_and_id() {
function_payload(json!({"target": root.thread_id.to_string()})), function_payload(json!({"target": root.thread_id.to_string()})),
)) ))
.await .await
.expect_err("close_agent should reject the root thread id"); .err()
.expect("close_agent should reject the root thread id");
assert_eq!( assert_eq!(
root_id_error, root_id_error,
FunctionCallError::RespondToModel("root is not a spawned agent".to_string()) FunctionCallError::RespondToModel("root is not a spawned agent".to_string())
@@ -6,10 +6,11 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput; use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::multi_agents_common::*; use crate::tools::handlers::multi_agents_common::*;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::AgentPath; use codex_protocol::AgentPath;
use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseInputItem;
use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::openai_models::ReasoningEffort;
@@ -7,8 +7,6 @@ pub(crate) struct Handler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = CloseAgentResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("close_agent") ToolName::plain("close_agent")
} }
@@ -17,8 +15,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_close_agent_tool_v2()) Some(create_close_agent_tool_v2())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
handle_close_agent(invocation).await &self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
handle_close_agent(invocation).await.map(boxed_tool_output)
} }
} }
@@ -117,7 +118,7 @@ async fn handle_close_agent(
}) })
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -2,7 +2,6 @@ use super::message_tool::FollowupTaskArgs;
use super::message_tool::MessageDeliveryMode; use super::message_tool::MessageDeliveryMode;
use super::message_tool::handle_message_string_tool; use super::message_tool::handle_message_string_tool;
use super::*; use super::*;
use crate::tools::context::FunctionToolOutput;
use crate::tools::handlers::multi_agents_spec::create_followup_task_tool; use crate::tools::handlers::multi_agents_spec::create_followup_task_tool;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -10,8 +9,6 @@ pub(crate) struct Handler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("followup_task") ToolName::plain("followup_task")
} }
@@ -20,7 +17,10 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_followup_task_tool()) Some(create_followup_task_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let arguments = function_arguments(invocation.payload.clone())?; let arguments = function_arguments(invocation.payload.clone())?;
let args: FollowupTaskArgs = parse_arguments(&arguments)?; let args: FollowupTaskArgs = parse_arguments(&arguments)?;
handle_message_string_tool( handle_message_string_tool(
@@ -30,10 +30,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
args.message, args.message,
) )
.await .await
.map(boxed_tool_output)
} }
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -7,8 +7,6 @@ pub(crate) struct Handler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = ListAgentsResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("list_agents") ToolName::plain("list_agents")
} }
@@ -17,7 +15,10 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_list_agents_tool()) Some(create_list_agents_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -37,11 +38,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
.await .await
.map_err(collab_spawn_error)?; .map_err(collab_spawn_error)?;
Ok(ListAgentsResult { agents }) Ok(boxed_tool_output(ListAgentsResult { agents }))
} }
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -2,7 +2,6 @@ use super::message_tool::MessageDeliveryMode;
use super::message_tool::SendMessageArgs; use super::message_tool::SendMessageArgs;
use super::message_tool::handle_message_string_tool; use super::message_tool::handle_message_string_tool;
use super::*; use super::*;
use crate::tools::context::FunctionToolOutput;
use crate::tools::handlers::multi_agents_spec::create_send_message_tool; use crate::tools::handlers::multi_agents_spec::create_send_message_tool;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -10,8 +9,6 @@ pub(crate) struct Handler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("send_message") ToolName::plain("send_message")
} }
@@ -20,7 +17,10 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_send_message_tool()) Some(create_send_message_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let arguments = function_arguments(invocation.payload.clone())?; let arguments = function_arguments(invocation.payload.clone())?;
let args: SendMessageArgs = parse_arguments(&arguments)?; let args: SendMessageArgs = parse_arguments(&arguments)?;
handle_message_string_tool( handle_message_string_tool(
@@ -30,10 +30,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
args.message, args.message,
) )
.await .await
.map(boxed_tool_output)
} }
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -26,8 +26,6 @@ impl Handler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = SpawnAgentResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("spawn_agent") ToolName::plain("spawn_agent")
} }
@@ -36,8 +34,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_spawn_agent_tool_v2(self.options.clone())) Some(create_spawn_agent_tool_v2(self.options.clone()))
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
handle_spawn_agent(invocation).await &self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
handle_spawn_agent(invocation).await.map(boxed_tool_output)
} }
} }
@@ -229,7 +230,7 @@ async fn handle_spawn_agent(
} }
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -21,8 +21,6 @@ impl Handler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler { impl ToolExecutor<ToolInvocation> for Handler {
type Output = WaitAgentResult;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("wait_agent") ToolName::plain("wait_agent")
} }
@@ -31,7 +29,10 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_wait_agent_tool_v2(self.options)) Some(create_wait_agent_tool_v2(self.options))
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -97,11 +98,11 @@ impl ToolExecutor<ToolInvocation> for Handler {
) )
.await; .await;
Ok(result) Ok(boxed_tool_output(result))
} }
} }
impl ToolHandler for Handler { impl CoreToolRuntime for Handler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
+8 -6
View File
@@ -2,9 +2,10 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput; use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::plan_spec::create_update_plan_tool; use crate::tools::handlers::plan_spec::create_update_plan_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::ModeKind;
use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseInputItem;
@@ -46,8 +47,6 @@ impl ToolOutput for PlanToolOutput {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for PlanHandler { impl ToolExecutor<ToolInvocation> for PlanHandler {
type Output = PlanToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("update_plan") ToolName::plain("update_plan")
} }
@@ -56,7 +55,10 @@ impl ToolExecutor<ToolInvocation> for PlanHandler {
Some(create_update_plan_tool()) Some(create_update_plan_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -85,11 +87,11 @@ impl ToolExecutor<ToolInvocation> for PlanHandler {
.send_event(turn.as_ref(), EventMsg::PlanUpdate(args)) .send_event(turn.as_ref(), EventMsg::PlanUpdate(args))
.await; .await;
Ok(PlanToolOutput) Ok(boxed_tool_output(PlanToolOutput))
} }
} }
impl ToolHandler for PlanHandler {} impl CoreToolRuntime for PlanHandler {}
fn parse_update_plan_arguments(arguments: &str) -> Result<UpdatePlanArgs, FunctionCallError> { fn parse_update_plan_arguments(arguments: &str) -> Result<UpdatePlanArgs, FunctionCallError> {
serde_json::from_str::<UpdatePlanArgs>(arguments).map_err(|e| { serde_json::from_str::<UpdatePlanArgs>(arguments).map_err(|e| {
@@ -5,11 +5,12 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments_with_base_path; use crate::tools::handlers::parse_arguments_with_base_path;
use crate::tools::handlers::shell_spec::create_request_permissions_tool; use crate::tools::handlers::shell_spec::create_request_permissions_tool;
use crate::tools::handlers::shell_spec::request_permissions_tool_description; use crate::tools::handlers::shell_spec::request_permissions_tool_description;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -17,8 +18,6 @@ pub struct RequestPermissionsHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for RequestPermissionsHandler { impl ToolExecutor<ToolInvocation> for RequestPermissionsHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("request_permissions") ToolName::plain("request_permissions")
} }
@@ -29,7 +28,10 @@ impl ToolExecutor<ToolInvocation> for RequestPermissionsHandler {
)) ))
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -75,8 +77,11 @@ impl ToolExecutor<ToolInvocation> for RequestPermissionsHandler {
)) ))
})?; })?;
Ok(FunctionToolOutput::from_text(content, Some(true))) Ok(boxed_tool_output(FunctionToolOutput::from_text(
content,
Some(true),
)))
} }
} }
impl ToolHandler for RequestPermissionsHandler {} impl CoreToolRuntime for RequestPermissionsHandler {}
@@ -32,10 +32,11 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::handlers::request_plugin_install_spec::create_request_plugin_install_tool; use crate::tools::handlers::request_plugin_install_spec::create_request_plugin_install_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
#[derive(Default)] #[derive(Default)]
pub struct RequestPluginInstallHandler { pub struct RequestPluginInstallHandler {
@@ -52,8 +53,6 @@ impl RequestPluginInstallHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for RequestPluginInstallHandler { impl ToolExecutor<ToolInvocation> for RequestPluginInstallHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(REQUEST_PLUGIN_INSTALL_TOOL_NAME) ToolName::plain(REQUEST_PLUGIN_INSTALL_TOOL_NAME)
} }
@@ -70,7 +69,10 @@ impl ToolExecutor<ToolInvocation> for RequestPluginInstallHandler {
clippy::await_holding_invalid_type, clippy::await_holding_invalid_type,
reason = "plugin install discovery reads through the session-owned manager guard" reason = "plugin install discovery reads through the session-owned manager guard"
)] )]
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
payload, payload,
session, session,
@@ -190,11 +192,14 @@ impl ToolExecutor<ToolInvocation> for RequestPluginInstallHandler {
)) ))
})?; })?;
Ok(FunctionToolOutput::from_text(content, Some(true))) Ok(boxed_tool_output(FunctionToolOutput::from_text(
content,
Some(true),
)))
} }
} }
impl ToolHandler for RequestPluginInstallHandler {} impl CoreToolRuntime for RequestPluginInstallHandler {}
async fn maybe_persist_disabled_install_request( async fn maybe_persist_disabled_install_request(
session: &crate::session::session::Session, session: &crate::session::session::Session,
@@ -2,14 +2,15 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::handlers::request_user_input_spec::REQUEST_USER_INPUT_TOOL_NAME; use crate::tools::handlers::request_user_input_spec::REQUEST_USER_INPUT_TOOL_NAME;
use crate::tools::handlers::request_user_input_spec::create_request_user_input_tool; use crate::tools::handlers::request_user_input_spec::create_request_user_input_tool;
use crate::tools::handlers::request_user_input_spec::normalize_request_user_input_args; use crate::tools::handlers::request_user_input_spec::normalize_request_user_input_args;
use crate::tools::handlers::request_user_input_spec::request_user_input_tool_description; use crate::tools::handlers::request_user_input_spec::request_user_input_tool_description;
use crate::tools::handlers::request_user_input_spec::request_user_input_unavailable_message; use crate::tools::handlers::request_user_input_spec::request_user_input_unavailable_message;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::ModeKind;
use codex_protocol::request_user_input::RequestUserInputArgs; use codex_protocol::request_user_input::RequestUserInputArgs;
use codex_tools::ToolName; use codex_tools::ToolName;
@@ -21,8 +22,6 @@ pub struct RequestUserInputHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for RequestUserInputHandler { impl ToolExecutor<ToolInvocation> for RequestUserInputHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME) ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME)
} }
@@ -33,7 +32,10 @@ impl ToolExecutor<ToolInvocation> for RequestUserInputHandler {
)) ))
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -80,11 +82,14 @@ impl ToolExecutor<ToolInvocation> for RequestUserInputHandler {
)) ))
})?; })?;
Ok(FunctionToolOutput::from_text(content, Some(true))) Ok(boxed_tool_output(FunctionToolOutput::from_text(
content,
Some(true),
)))
} }
} }
impl ToolHandler for RequestUserInputHandler {} impl CoreToolRuntime for RequestUserInputHandler {}
#[cfg(test)] #[cfg(test)]
#[path = "request_user_input_tests.rs"] #[path = "request_user_input_tests.rs"]
@@ -10,19 +10,18 @@ use crate::function_tool::FunctionCallError;
use crate::maybe_emit_implicit_skill_invocation; use crate::maybe_emit_implicit_skill_invocation;
use crate::session::turn_context::TurnContext; use crate::session::turn_context::TurnContext;
use crate::shell::Shell; use crate::shell::Shell;
use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments_with_base_path; use crate::tools::handlers::parse_arguments_with_base_path;
use crate::tools::handlers::resolve_workdir_base_path; use crate::tools::handlers::resolve_workdir_base_path;
use crate::tools::handlers::rewrite_function_string_argument; use crate::tools::handlers::rewrite_function_string_argument;
use crate::tools::handlers::updated_hook_command; use crate::tools::handlers::updated_hook_command;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PostToolUsePayload;
use crate::tools::registry::PreToolUsePayload; use crate::tools::registry::PreToolUsePayload;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use crate::tools::runtimes::shell::ShellRuntimeBackend; use crate::tools::runtimes::shell::ShellRuntimeBackend;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -129,8 +128,6 @@ impl From<ShellCommandBackendConfig> for ShellCommandHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ShellCommandHandler { impl ToolExecutor<ToolInvocation> for ShellCommandHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("shell_command") ToolName::plain("shell_command")
} }
@@ -148,7 +145,10 @@ impl ToolExecutor<ToolInvocation> for ShellCommandHandler {
self.options.is_some() self.options.is_some()
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -201,10 +201,11 @@ impl ToolExecutor<ToolInvocation> for ShellCommandHandler {
shell_runtime_backend: self.shell_runtime_backend(), shell_runtime_backend: self.shell_runtime_backend(),
}) })
.await .await
.map(boxed_tool_output)
} }
} }
impl ToolHandler for ShellCommandHandler { impl CoreToolRuntime for ShellCommandHandler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -240,7 +241,7 @@ impl ToolHandler for ShellCommandHandler {
fn post_tool_use_payload( fn post_tool_use_payload(
&self, &self,
invocation: &ToolInvocation, invocation: &ToolInvocation,
result: &Self::Output, result: &dyn crate::tools::context::ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
let tool_response = let tool_response =
result.post_tool_use_response(&invocation.call_id, &invocation.payload)?; result.post_tool_use_response(&invocation.call_id, &invocation.payload)?;
@@ -18,7 +18,7 @@ use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::handlers::ShellCommandHandler; use crate::tools::handlers::ShellCommandHandler;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::registry::ToolHandler; use crate::tools::registry::CoreToolRuntime;
use crate::turn_diff_tracker::TurnDiffTracker; use crate::turn_diff_tracker::TurnDiffTracker;
use codex_shell_command::is_safe_command::is_known_safe_command; use codex_shell_command::is_safe_command::is_known_safe_command;
use codex_shell_command::powershell::try_find_powershell_executable_blocking; use codex_shell_command::powershell::try_find_powershell_executable_blocking;
+11 -6
View File
@@ -12,10 +12,11 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::FunctionToolOutput; use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::handlers::test_sync_spec::create_test_sync_tool; use crate::tools::handlers::test_sync_spec::create_test_sync_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -58,8 +59,6 @@ fn barrier_map() -> &'static tokio::sync::Mutex<HashMap<String, BarrierState>> {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for TestSyncHandler { impl ToolExecutor<ToolInvocation> for TestSyncHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("test_sync_tool") ToolName::plain("test_sync_tool")
} }
@@ -72,7 +71,10 @@ impl ToolExecutor<ToolInvocation> for TestSyncHandler {
true true
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { payload, .. } = invocation; let ToolInvocation { payload, .. } = invocation;
let arguments = match payload { let arguments = match payload {
@@ -102,11 +104,14 @@ impl ToolExecutor<ToolInvocation> for TestSyncHandler {
sleep(Duration::from_millis(delay)).await; sleep(Duration::from_millis(delay)).await;
} }
Ok(FunctionToolOutput::from_text("ok".to_string(), Some(true))) Ok(boxed_tool_output(FunctionToolOutput::from_text(
"ok".to_string(),
Some(true),
)))
} }
} }
impl ToolHandler for TestSyncHandler {} impl CoreToolRuntime for TestSyncHandler {}
async fn wait_on_barrier(args: BarrierArgs) -> Result<(), FunctionCallError> { async fn wait_on_barrier(args: BarrierArgs) -> Result<(), FunctionCallError> {
if args.participants == 0 { if args.participants == 0 {
@@ -2,9 +2,10 @@ use crate::function_tool::FunctionCallError;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::ToolSearchOutput; use crate::tools::context::ToolSearchOutput;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::tool_search_spec::create_tool_search_tool; use crate::tools::handlers::tool_search_spec::create_tool_search_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use crate::tools::tool_search_entry::ToolSearchEntry; use crate::tools::tool_search_entry::ToolSearchEntry;
use crate::tools::tool_search_entry::ToolSearchInfo; use crate::tools::tool_search_entry::ToolSearchInfo;
use bm25::Document; use bm25::Document;
@@ -54,8 +55,6 @@ impl ToolSearchHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ToolSearchHandler { impl ToolExecutor<ToolInvocation> for ToolSearchHandler {
type Output = ToolSearchOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(TOOL_SEARCH_TOOL_NAME) ToolName::plain(TOOL_SEARCH_TOOL_NAME)
} }
@@ -74,7 +73,7 @@ impl ToolExecutor<ToolInvocation> for ToolSearchHandler {
async fn handle( async fn handle(
&self, &self,
invocation: ToolInvocation, invocation: ToolInvocation,
) -> Result<ToolSearchOutput, FunctionCallError> { ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { payload, .. } = invocation; let ToolInvocation { payload, .. } = invocation;
let args = match payload { let args = match payload {
@@ -101,16 +100,16 @@ impl ToolExecutor<ToolInvocation> for ToolSearchHandler {
} }
if self.entries.is_empty() { if self.entries.is_empty() {
return Ok(ToolSearchOutput { tools: Vec::new() }); return Ok(boxed_tool_output(ToolSearchOutput { tools: Vec::new() }));
} }
let tools = self.search(query, limit)?; let tools = self.search(query, limit)?;
Ok(ToolSearchOutput { tools }) Ok(boxed_tool_output(ToolSearchOutput { tools }))
} }
} }
impl ToolHandler for ToolSearchHandler {} impl CoreToolRuntime for ToolSearchHandler {}
impl ToolSearchHandler { impl ToolSearchHandler {
fn search( fn search(
@@ -2,7 +2,6 @@ use crate::sandboxing::SandboxPermissions;
use crate::shell::Shell; use crate::shell::Shell;
use crate::shell::ShellType; use crate::shell::ShellType;
use crate::shell::get_shell_by_model_provided_path; use crate::shell::get_shell_by_model_provided_path;
use crate::tools::context::ExecCommandToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput; use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
@@ -88,23 +87,19 @@ pub(crate) struct ResolvedCommand {
fn post_unified_exec_tool_use_payload( fn post_unified_exec_tool_use_payload(
invocation: &ToolInvocation, invocation: &ToolInvocation,
result: &ExecCommandToolOutput, result: &dyn ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
let ToolPayload::Function { .. } = &invocation.payload else { let ToolPayload::Function { .. } = &invocation.payload else {
return None; return None;
}; };
let command = result.hook_command.clone()?; let tool_input = result.post_tool_use_input(&invocation.payload)?;
let tool_use_id = if result.event_call_id.is_empty() { let tool_use_id = result.post_tool_use_id(&invocation.call_id);
invocation.call_id.clone()
} else {
result.event_call_id.clone()
};
let tool_response = result.post_tool_use_response(&tool_use_id, &invocation.payload)?; let tool_response = result.post_tool_use_response(&tool_use_id, &invocation.payload)?;
Some(PostToolUsePayload { Some(PostToolUsePayload {
tool_name: HookToolName::bash(), tool_name: HookToolName::bash(),
tool_use_id, tool_use_id,
tool_input: serde_json::json!({ "command": command }), tool_input,
tool_response, tool_response,
}) })
} }
@@ -5,6 +5,7 @@ use crate::maybe_emit_implicit_skill_invocation;
use crate::tools::context::ExecCommandToolOutput; use crate::tools::context::ExecCommandToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::apply_granted_turn_permissions; use crate::tools::handlers::apply_granted_turn_permissions;
use crate::tools::handlers::apply_patch::intercept_apply_patch; use crate::tools::handlers::apply_patch::intercept_apply_patch;
use crate::tools::handlers::implicit_granted_permissions; use crate::tools::handlers::implicit_granted_permissions;
@@ -15,10 +16,10 @@ use crate::tools::handlers::resolve_tool_environment;
use crate::tools::handlers::rewrite_function_string_argument; use crate::tools::handlers::rewrite_function_string_argument;
use crate::tools::handlers::updated_hook_command; use crate::tools::handlers::updated_hook_command;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PostToolUsePayload;
use crate::tools::registry::PreToolUsePayload; use crate::tools::registry::PreToolUsePayload;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use crate::unified_exec::ExecCommandRequest; use crate::unified_exec::ExecCommandRequest;
use crate::unified_exec::UnifiedExecContext; use crate::unified_exec::UnifiedExecContext;
use crate::unified_exec::UnifiedExecError; use crate::unified_exec::UnifiedExecError;
@@ -70,8 +71,6 @@ impl ExecCommandHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ExecCommandHandler { impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
type Output = ExecCommandToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("exec_command") ToolName::plain("exec_command")
} }
@@ -90,7 +89,10 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
true true
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -234,7 +236,7 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
.await? .await?
{ {
manager.release_process_id(process_id).await; manager.release_process_id(process_id).await;
return Ok(ExecCommandToolOutput { return Ok(boxed_tool_output(ExecCommandToolOutput {
event_call_id: String::new(), event_call_id: String::new(),
chunk_id: String::new(), chunk_id: String::new(),
wall_time: std::time::Duration::ZERO, wall_time: std::time::Duration::ZERO,
@@ -244,7 +246,7 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
exit_code: None, exit_code: None,
original_token_count: None, original_token_count: None,
hook_command: None, hook_command: None,
}); }));
} }
emit_unified_exec_tty_metric(&turn.session_telemetry, tty); emit_unified_exec_tty_metric(&turn.session_telemetry, tty);
@@ -273,11 +275,11 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
) )
.await .await
{ {
Ok(response) => Ok(response), Ok(response) => Ok(boxed_tool_output(response)),
Err(UnifiedExecError::SandboxDenied { output, .. }) => { Err(UnifiedExecError::SandboxDenied { output, .. }) => {
let output_text = output.aggregated_output.text; let output_text = output.aggregated_output.text;
let original_token_count = approx_token_count(&output_text); let original_token_count = approx_token_count(&output_text);
Ok(ExecCommandToolOutput { Ok(boxed_tool_output(ExecCommandToolOutput {
event_call_id: context.call_id.clone(), event_call_id: context.call_id.clone(),
chunk_id: generate_chunk_id(), chunk_id: generate_chunk_id(),
wall_time: output.duration, wall_time: output.duration,
@@ -289,7 +291,7 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
exit_code: Some(output.exit_code), exit_code: Some(output.exit_code),
original_token_count: Some(original_token_count), original_token_count: Some(original_token_count),
hook_command: Some(hook_command), hook_command: Some(hook_command),
}) }))
} }
Err(err) => Err(FunctionCallError::RespondToModel(format!( Err(err) => Err(FunctionCallError::RespondToModel(format!(
"exec_command failed for `{command_for_display}`: {err:?}" "exec_command failed for `{command_for_display}`: {err:?}"
@@ -298,7 +300,7 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
} }
} }
impl ToolHandler for ExecCommandHandler { impl CoreToolRuntime for ExecCommandHandler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -340,7 +342,7 @@ impl ToolHandler for ExecCommandHandler {
fn post_tool_use_payload( fn post_tool_use_payload(
&self, &self,
invocation: &ToolInvocation, invocation: &ToolInvocation,
result: &Self::Output, result: &dyn crate::tools::context::ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
post_unified_exec_tool_use_payload(invocation, result) post_unified_exec_tool_use_payload(invocation, result)
} }
@@ -1,11 +1,11 @@
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::tools::context::ExecCommandToolOutput;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::PostToolUsePayload; use crate::tools::registry::PostToolUsePayload;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use crate::unified_exec::WriteStdinRequest; use crate::unified_exec::WriteStdinRequest;
use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::TerminalInteractionEvent; use codex_protocol::protocol::TerminalInteractionEvent;
@@ -33,8 +33,6 @@ pub struct WriteStdinHandler;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for WriteStdinHandler { impl ToolExecutor<ToolInvocation> for WriteStdinHandler {
type Output = ExecCommandToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("write_stdin") ToolName::plain("write_stdin")
} }
@@ -43,7 +41,10 @@ impl ToolExecutor<ToolInvocation> for WriteStdinHandler {
Some(create_write_stdin_tool()) Some(create_write_stdin_tool())
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let ToolInvocation { let ToolInvocation {
session, session,
turn, turn,
@@ -86,11 +87,11 @@ impl ToolExecutor<ToolInvocation> for WriteStdinHandler {
.send_event(turn.as_ref(), EventMsg::TerminalInteraction(interaction)) .send_event(turn.as_ref(), EventMsg::TerminalInteraction(interaction))
.await; .await;
Ok(response) Ok(boxed_tool_output(response))
} }
} }
impl ToolHandler for WriteStdinHandler { impl CoreToolRuntime for WriteStdinHandler {
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
matches!(payload, ToolPayload::Function { .. }) matches!(payload, ToolPayload::Function { .. })
} }
@@ -98,7 +99,7 @@ impl ToolHandler for WriteStdinHandler {
fn post_tool_use_payload( fn post_tool_use_payload(
&self, &self,
invocation: &ToolInvocation, invocation: &ToolInvocation,
result: &Self::Output, result: &dyn crate::tools::context::ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
post_unified_exec_tool_use_payload(invocation, result) post_unified_exec_tool_use_payload(invocation, result)
} }
@@ -13,7 +13,7 @@ use crate::tools::context::ToolCallSource;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::hook_names::HookToolName; use crate::tools::hook_names::HookToolName;
use crate::tools::registry::ToolHandler; use crate::tools::registry::CoreToolRuntime;
use crate::turn_diff_tracker::TurnDiffTracker; use crate::turn_diff_tracker::TurnDiffTracker;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@@ -16,12 +16,13 @@ use crate::original_image_detail::can_request_original_image_detail;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput; use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::context::boxed_tool_output;
use crate::tools::handlers::parse_arguments; use crate::tools::handlers::parse_arguments;
use crate::tools::handlers::resolve_tool_environment; use crate::tools::handlers::resolve_tool_environment;
use crate::tools::handlers::view_image_spec::ViewImageToolOptions; use crate::tools::handlers::view_image_spec::ViewImageToolOptions;
use crate::tools::handlers::view_image_spec::create_view_image_tool; use crate::tools::handlers::view_image_spec::create_view_image_tool;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
@@ -64,8 +65,6 @@ enum ViewImageDetail {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ViewImageHandler { impl ToolExecutor<ToolInvocation> for ViewImageHandler {
type Output = ViewImageOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain("view_image") ToolName::plain("view_image")
} }
@@ -78,7 +77,10 @@ impl ToolExecutor<ToolInvocation> for ViewImageHandler {
true true
} }
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
if !invocation if !invocation
.turn .turn
.model_info .model_info
@@ -195,14 +197,14 @@ impl ToolExecutor<ToolInvocation> for ViewImageHandler {
session.emit_turn_item_started(turn.as_ref(), &item).await; session.emit_turn_item_started(turn.as_ref(), &item).await;
session.emit_turn_item_completed(turn.as_ref(), item).await; session.emit_turn_item_completed(turn.as_ref(), item).await;
Ok(ViewImageOutput { Ok(boxed_tool_output(ViewImageOutput {
image_url, image_url,
image_detail, image_detail,
}) }))
} }
} }
impl ToolHandler for ViewImageHandler {} impl CoreToolRuntime for ViewImageHandler {}
pub struct ViewImageOutput { pub struct ViewImageOutput {
image_url: String, image_url: String,
-1
View File
@@ -13,7 +13,6 @@ pub(crate) mod runtimes;
pub(crate) mod sandboxing; pub(crate) mod sandboxing;
pub(crate) mod spec; pub(crate) mod spec;
pub(crate) mod spec_plan; pub(crate) mod spec_plan;
pub(crate) mod spec_plan_types;
pub(crate) mod tool_dispatch_trace; pub(crate) mod tool_dispatch_trace;
pub(crate) mod tool_search_entry; pub(crate) mod tool_search_entry;
+86 -197
View File
@@ -1,5 +1,4 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@@ -35,7 +34,11 @@ pub(crate) type ToolTelemetryTags = Vec<(&'static str, String)>;
pub use codex_tools::ToolExecutor; pub use codex_tools::ToolExecutor;
pub use codex_tools::ToolExposure; pub use codex_tools::ToolExposure;
pub trait ToolHandler: ToolExecutor<ToolInvocation> { /// Typed runtime contract for locally executed tools.
///
/// Implementers provide the shared `ToolExecutor` behavior plus optional
/// core-owned metadata for hooks, telemetry, tool search, and argument diffs.
pub(crate) trait CoreToolRuntime: ToolExecutor<ToolInvocation> {
fn search_info(&self) -> Option<ToolSearchInfo> { fn search_info(&self) -> Option<ToolSearchInfo> {
None None
} }
@@ -47,17 +50,17 @@ pub trait ToolHandler: ToolExecutor<ToolInvocation> {
) )
} }
fn telemetry_tags( fn telemetry_tags<'a>(
&self, &'a self,
_invocation: &ToolInvocation, _invocation: &'a ToolInvocation,
) -> impl Future<Output = ToolTelemetryTags> + Send { ) -> BoxFuture<'a, ToolTelemetryTags> {
async { Vec::new() } Box::pin(async { Vec::new() })
} }
fn post_tool_use_payload( fn post_tool_use_payload(
&self, &self,
_invocation: &ToolInvocation, _invocation: &ToolInvocation,
_result: &Self::Output, _result: &dyn ToolOutput,
) -> Option<PostToolUsePayload> { ) -> Option<PostToolUsePayload> {
None None
} }
@@ -154,117 +157,10 @@ pub(crate) struct PostToolUsePayload {
pub(crate) tool_response: Value, pub(crate) tool_response: Value,
} }
/// Object-safe registry entry for heterogeneous tool handlers.
///
/// Concrete handlers keep their typed `ToolExecutor::Output`; the registry
/// boxes that output only after typed hooks have run.
pub(crate) trait RegisteredTool: Send + Sync {
fn tool_name(&self) -> ToolName;
fn spec(&self) -> Option<ToolSpec>;
fn exposure(&self) -> ToolExposure;
fn search_info(&self) -> Option<ToolSearchInfo>;
fn supports_parallel_tool_calls(&self) -> bool;
fn matches_kind(&self, payload: &ToolPayload) -> bool;
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload>;
fn with_updated_hook_input(
&self,
invocation: ToolInvocation,
updated_input: Value,
) -> Result<ToolInvocation, FunctionCallError>;
fn telemetry_tags<'a>(
&'a self,
invocation: &'a ToolInvocation,
) -> BoxFuture<'a, ToolTelemetryTags>;
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>>;
fn handle_any<'a>(
&'a self,
invocation: ToolInvocation,
) -> BoxFuture<'a, Result<AnyToolResult, FunctionCallError>>;
}
impl<T> RegisteredTool for T
where
T: ToolHandler,
{
fn tool_name(&self) -> ToolName {
ToolExecutor::tool_name(self)
}
fn spec(&self) -> Option<ToolSpec> {
ToolExecutor::spec(self)
}
fn exposure(&self) -> ToolExposure {
ToolExecutor::exposure(self)
}
fn search_info(&self) -> Option<ToolSearchInfo> {
ToolHandler::search_info(self)
}
fn supports_parallel_tool_calls(&self) -> bool {
ToolExecutor::supports_parallel_tool_calls(self)
}
fn matches_kind(&self, payload: &ToolPayload) -> bool {
ToolHandler::matches_kind(self, payload)
}
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
ToolHandler::pre_tool_use_payload(self, invocation)
}
fn with_updated_hook_input(
&self,
invocation: ToolInvocation,
updated_input: Value,
) -> Result<ToolInvocation, FunctionCallError> {
ToolHandler::with_updated_hook_input(self, invocation, updated_input)
}
fn telemetry_tags<'a>(
&'a self,
invocation: &'a ToolInvocation,
) -> BoxFuture<'a, ToolTelemetryTags> {
Box::pin(ToolHandler::telemetry_tags(self, invocation))
}
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
ToolHandler::create_diff_consumer(self)
}
fn handle_any<'a>(
&'a self,
invocation: ToolInvocation,
) -> BoxFuture<'a, Result<AnyToolResult, FunctionCallError>> {
Box::pin(async move {
let call_id = invocation.call_id.clone();
let payload = invocation.payload.clone();
let output = ToolExecutor::handle(self, invocation.clone()).await?;
let post_tool_use_payload =
ToolHandler::post_tool_use_payload(self, &invocation, &output);
Ok(AnyToolResult {
call_id,
payload,
result: Box::new(output),
post_tool_use_payload,
})
})
}
}
pub(crate) fn override_tool_exposure( pub(crate) fn override_tool_exposure(
handler: Arc<dyn RegisteredTool>, handler: Arc<dyn CoreToolRuntime>,
exposure: ToolExposure, exposure: ToolExposure,
) -> Arc<dyn RegisteredTool> { ) -> Arc<dyn CoreToolRuntime> {
if handler.exposure() == exposure { if handler.exposure() == exposure {
return handler; return handler;
} }
@@ -273,11 +169,12 @@ pub(crate) fn override_tool_exposure(
} }
struct ExposureOverride { struct ExposureOverride {
handler: Arc<dyn RegisteredTool>, handler: Arc<dyn CoreToolRuntime>,
exposure: ToolExposure, exposure: ToolExposure,
} }
impl RegisteredTool for ExposureOverride { #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ExposureOverride {
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
self.handler.tool_name() self.handler.tool_name()
} }
@@ -290,14 +187,23 @@ impl RegisteredTool for ExposureOverride {
self.exposure self.exposure
} }
fn search_info(&self) -> Option<ToolSearchInfo> {
self.handler.search_info()
}
fn supports_parallel_tool_calls(&self) -> bool { fn supports_parallel_tool_calls(&self) -> bool {
self.handler.supports_parallel_tool_calls() self.handler.supports_parallel_tool_calls()
} }
async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn ToolOutput>, FunctionCallError> {
self.handler.handle(invocation).await
}
}
impl CoreToolRuntime for ExposureOverride {
fn search_info(&self) -> Option<ToolSearchInfo> {
self.handler.search_info()
}
fn matches_kind(&self, payload: &ToolPayload) -> bool { fn matches_kind(&self, payload: &ToolPayload) -> bool {
self.handler.matches_kind(payload) self.handler.matches_kind(payload)
} }
@@ -306,6 +212,14 @@ impl RegisteredTool for ExposureOverride {
self.handler.pre_tool_use_payload(invocation) self.handler.pre_tool_use_payload(invocation)
} }
fn post_tool_use_payload(
&self,
invocation: &ToolInvocation,
result: &dyn ToolOutput,
) -> Option<PostToolUsePayload> {
self.handler.post_tool_use_payload(invocation, result)
}
fn with_updated_hook_input( fn with_updated_hook_input(
&self, &self,
invocation: ToolInvocation, invocation: ToolInvocation,
@@ -325,22 +239,28 @@ impl RegisteredTool for ExposureOverride {
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> { fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
self.handler.create_diff_consumer() self.handler.create_diff_consumer()
} }
fn handle_any<'a>(
&'a self,
invocation: ToolInvocation,
) -> BoxFuture<'a, Result<AnyToolResult, FunctionCallError>> {
self.handler.handle_any(invocation)
}
} }
pub struct ToolRegistry { pub struct ToolRegistry {
handlers: HashMap<ToolName, Arc<dyn RegisteredTool>>, tools: HashMap<ToolName, Arc<dyn CoreToolRuntime>>,
} }
impl ToolRegistry { impl ToolRegistry {
fn new(handlers: HashMap<ToolName, Arc<dyn RegisteredTool>>) -> Self { fn new(tools: HashMap<ToolName, Arc<dyn CoreToolRuntime>>) -> Self {
Self { handlers } Self { tools }
}
pub(crate) fn from_tools(tools: impl IntoIterator<Item = Arc<dyn CoreToolRuntime>>) -> Self {
let mut tools_by_name = HashMap::new();
for tool in tools {
let name = tool.tool_name();
if tools_by_name.contains_key(&name) {
error_or_panic(format!("tool {name} already registered"));
continue;
}
tools_by_name.insert(name, tool);
}
Self::new(tools_by_name)
} }
#[cfg(test)] #[cfg(test)]
@@ -351,35 +271,35 @@ impl ToolRegistry {
#[cfg(test)] #[cfg(test)]
pub(crate) fn with_handler_for_test<T>(handler: Arc<T>) -> Self pub(crate) fn with_handler_for_test<T>(handler: Arc<T>) -> Self
where where
T: ToolHandler + 'static, T: CoreToolRuntime + 'static,
{ {
let name = handler.tool_name(); let name = handler.tool_name();
Self::new(HashMap::from([(name, handler as Arc<dyn RegisteredTool>)])) Self::new(HashMap::from([(name, handler as Arc<dyn CoreToolRuntime>)]))
} }
fn handler(&self, name: &ToolName) -> Option<Arc<dyn RegisteredTool>> { fn tool(&self, name: &ToolName) -> Option<Arc<dyn CoreToolRuntime>> {
self.handlers.get(name).map(Arc::clone) self.tools.get(name).map(Arc::clone)
} }
pub(crate) fn tool_exposure(&self, name: &ToolName) -> Option<ToolExposure> { pub(crate) fn tool_exposure(&self, name: &ToolName) -> Option<ToolExposure> {
self.handlers.get(name).map(|handler| handler.exposure()) self.tools.get(name).map(|tool| tool.exposure())
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn has_handler(&self, name: &ToolName) -> bool { pub(crate) fn has_tool(&self, name: &ToolName) -> bool {
self.handler(name).is_some() self.tool(name).is_some()
} }
pub(crate) fn create_diff_consumer( pub(crate) fn create_diff_consumer(
&self, &self,
name: &ToolName, name: &ToolName,
) -> Option<Box<dyn ToolArgumentDiffConsumer>> { ) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
self.handler(name)?.create_diff_consumer() self.tool(name)?.create_diff_consumer()
} }
pub(crate) fn supports_parallel_tool_calls(&self, name: &ToolName) -> Option<bool> { pub(crate) fn supports_parallel_tool_calls(&self, name: &ToolName) -> Option<bool> {
let handler = self.handler(name)?; let tool = self.tool(name)?;
Some(handler.supports_parallel_tool_calls()) Some(tool.supports_parallel_tool_calls())
} }
#[expect( #[expect(
@@ -422,8 +342,8 @@ impl ToolRegistry {
} }
let dispatch_trace = ToolDispatchTrace::start(&invocation); let dispatch_trace = ToolDispatchTrace::start(&invocation);
let handler = match self.handler(&tool_name) { let tool = match self.tool(&tool_name) {
Some(handler) => handler, Some(tool) => tool,
None => { None => {
let message = unsupported_tool_call_message(&invocation.payload, &tool_name); let message = unsupported_tool_call_message(&invocation.payload, &tool_name);
let log_payload = invocation.payload.log_payload(); let log_payload = invocation.payload.log_payload();
@@ -443,7 +363,7 @@ impl ToolRegistry {
} }
}; };
let telemetry_tags = handler.telemetry_tags(&invocation).await; let telemetry_tags = tool.telemetry_tags(&invocation).await;
let mut tool_result_tags = let mut tool_result_tags =
Vec::with_capacity(base_tool_result_tags.len() + telemetry_tags.len()); Vec::with_capacity(base_tool_result_tags.len() + telemetry_tags.len());
let mut extra_trace_fields = Vec::new(); let mut extra_trace_fields = Vec::new();
@@ -455,7 +375,7 @@ impl ToolRegistry {
tool_result_tags.push((*key, value.as_str())); tool_result_tags.push((*key, value.as_str()));
} }
} }
if !handler.matches_kind(&invocation.payload) { if !tool.matches_kind(&invocation.payload) {
let message = format!("tool {tool_name} invoked with incompatible payload"); let message = format!("tool {tool_name} invoked with incompatible payload");
let log_payload = invocation.payload.log_payload(); let log_payload = invocation.payload.log_payload();
otel.tool_result_with_tags( otel.tool_result_with_tags(
@@ -473,7 +393,7 @@ impl ToolRegistry {
return Err(err); return Err(err);
} }
if let Some(pre_tool_use_payload) = handler.pre_tool_use_payload(&invocation) { if let Some(pre_tool_use_payload) = tool.pre_tool_use_payload(&invocation) {
match run_pre_tool_use_hooks( match run_pre_tool_use_hooks(
&invocation.session, &invocation.session,
&invocation.turn, &invocation.turn,
@@ -491,7 +411,7 @@ impl ToolRegistry {
PreToolUseHookResult::Continue { PreToolUseHookResult::Continue {
updated_input: Some(updated_input), updated_input: Some(updated_input),
} => { } => {
invocation = handler.with_updated_hook_input(invocation, updated_input)?; invocation = tool.with_updated_hook_input(invocation, updated_input)?;
} }
PreToolUseHookResult::Continue { PreToolUseHookResult::Continue {
updated_input: None, updated_input: None,
@@ -511,10 +431,10 @@ impl ToolRegistry {
&tool_result_tags, &tool_result_tags,
&extra_trace_fields, &extra_trace_fields,
|| { || {
let handler = handler.clone(); let tool = tool.clone();
let response_cell = &response_cell; let response_cell = &response_cell;
async move { async move {
match handler.handle_any(invocation_for_tool).await { match handle_any_tool(tool.as_ref(), invocation_for_tool).await {
Ok(result) => { Ok(result) => {
let preview = result.result.log_preview(); let preview = result.result.log_preview();
let success = result.result.success_for_logging(); let success = result.result.success_for_logging();
@@ -620,52 +540,21 @@ impl ToolRegistry {
} }
} }
pub struct ToolRegistryBuilder { async fn handle_any_tool(
handlers: HashMap<ToolName, Arc<dyn RegisteredTool>>, tool: &dyn CoreToolRuntime,
specs: Vec<ToolSpec>, invocation: ToolInvocation,
} ) -> Result<AnyToolResult, FunctionCallError> {
let call_id = invocation.call_id.clone();
impl ToolRegistryBuilder { let payload = invocation.payload.clone();
pub fn new() -> Self { let output = tool.handle(invocation.clone()).await?;
Self { let post_tool_use_payload =
handlers: HashMap::new(), CoreToolRuntime::post_tool_use_payload(tool, &invocation, output.as_ref());
specs: Vec::new(), Ok(AnyToolResult {
} call_id,
} payload,
result: output,
pub(crate) fn push_spec(&mut self, spec: ToolSpec) { post_tool_use_payload,
self.specs.push(spec); })
}
pub(crate) fn register_tool(&mut self, handler: Arc<dyn RegisteredTool>) {
self.register_tool_internal(handler, /*include_spec*/ true);
}
pub(crate) fn register_tool_without_spec(&mut self, handler: Arc<dyn RegisteredTool>) {
self.register_tool_internal(handler, /*include_spec*/ false);
}
fn register_tool_internal(&mut self, handler: Arc<dyn RegisteredTool>, include_spec: bool) {
let name = handler.tool_name();
if self.handlers.contains_key(&name) {
error_or_panic(format!("handler for tool {name} already registered"));
return;
}
if include_spec
&& handler.exposure().is_direct()
&& let Some(spec) = handler.spec()
{
self.push_spec(spec);
}
self.handlers.insert(name, handler);
}
pub fn build(self) -> (Vec<ToolSpec>, ToolRegistry) {
let registry = ToolRegistry::new(self.handlers);
(self.specs, registry)
}
} }
fn unsupported_tool_call_message(payload: &ToolPayload, tool_name: &ToolName) -> String { fn unsupported_tool_call_message(payload: &ToolPayload, tool_name: &ToolName) -> String {
+12 -27
View File
@@ -1,7 +1,4 @@
use super::*; use super::*;
use crate::tools::handlers::GetGoalHandler;
use crate::tools::handlers::goal_spec::GET_GOAL_TOOL_NAME;
use crate::tools::handlers::goal_spec::create_get_goal_tool;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
struct TestHandler { struct TestHandler {
@@ -10,21 +7,21 @@ struct TestHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for TestHandler { impl ToolExecutor<ToolInvocation> for TestHandler {
type Output = crate::tools::context::FunctionToolOutput;
fn tool_name(&self) -> codex_tools::ToolName { fn tool_name(&self) -> codex_tools::ToolName {
self.tool_name.clone() self.tool_name.clone()
} }
async fn handle(&self, _invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
Ok(crate::tools::context::FunctionToolOutput::from_text( &self,
"ok".to_string(), _invocation: ToolInvocation,
Some(true), ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
Ok(Box::new(
crate::tools::context::FunctionToolOutput::from_text("ok".to_string(), Some(true)),
)) ))
} }
} }
impl ToolHandler for TestHandler {} impl CoreToolRuntime for TestHandler {}
#[test] #[test]
fn handler_looks_up_namespaced_aliases_explicitly() { fn handler_looks_up_namespaced_aliases_explicitly() {
@@ -34,18 +31,18 @@ fn handler_looks_up_namespaced_aliases_explicitly() {
let namespaced_name = codex_tools::ToolName::namespaced(namespace, tool_name); let namespaced_name = codex_tools::ToolName::namespaced(namespace, tool_name);
let plain_handler = Arc::new(TestHandler { let plain_handler = Arc::new(TestHandler {
tool_name: plain_name.clone(), tool_name: plain_name.clone(),
}) as Arc<dyn RegisteredTool>; }) as Arc<dyn CoreToolRuntime>;
let namespaced_handler = Arc::new(TestHandler { let namespaced_handler = Arc::new(TestHandler {
tool_name: namespaced_name.clone(), tool_name: namespaced_name.clone(),
}) as Arc<dyn RegisteredTool>; }) as Arc<dyn CoreToolRuntime>;
let registry = ToolRegistry::new(HashMap::from([ let registry = ToolRegistry::new(HashMap::from([
(plain_name.clone(), Arc::clone(&plain_handler)), (plain_name.clone(), Arc::clone(&plain_handler)),
(namespaced_name.clone(), Arc::clone(&namespaced_handler)), (namespaced_name.clone(), Arc::clone(&namespaced_handler)),
])); ]));
let plain = registry.handler(&plain_name); let plain = registry.tool(&plain_name);
let namespaced = registry.handler(&namespaced_name); let namespaced = registry.tool(&namespaced_name);
let missing_namespaced = registry.handler(&codex_tools::ToolName::namespaced( let missing_namespaced = registry.tool(&codex_tools::ToolName::namespaced(
"mcp__codex_apps__calendar", "mcp__codex_apps__calendar",
tool_name, tool_name,
)); ));
@@ -64,15 +61,3 @@ fn handler_looks_up_namespaced_aliases_explicitly() {
.is_some_and(|handler| Arc::ptr_eq(handler, &namespaced_handler)) .is_some_and(|handler| Arc::ptr_eq(handler, &namespaced_handler))
); );
} }
#[test]
fn register_tool_adds_executor_and_spec() {
let mut builder = ToolRegistryBuilder::new();
builder.register_tool(Arc::new(GetGoalHandler));
let (specs, registry) = builder.build();
assert_eq!(specs.len(), 1);
assert_eq!(specs[0], create_get_goal_tool());
assert!(registry.has_handler(&codex_tools::ToolName::plain(GET_GOAL_TOOL_NAME)));
}
+9 -50
View File
@@ -5,18 +5,16 @@ use crate::tools::context::SharedTurnDiffTracker;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::registry::AnyToolResult; use crate::tools::registry::AnyToolResult;
use crate::tools::registry::RegisteredTool;
use crate::tools::registry::ToolArgumentDiffConsumer; use crate::tools::registry::ToolArgumentDiffConsumer;
use crate::tools::registry::ToolExposure;
use crate::tools::registry::ToolRegistry; use crate::tools::registry::ToolRegistry;
use crate::tools::spec::collect_tool_router_parts; use crate::tools::spec_plan::build_tool_router;
use crate::tools::spec_plan::build_tool_registry_builder_from_executors;
use codex_extension_api::ExtensionToolExecutor;
use codex_mcp::ToolInfo; use codex_mcp::ToolInfo;
use codex_protocol::dynamic_tools::DynamicToolSpec; use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::models::ResponseItem; use codex_protocol::models::ResponseItem;
use codex_protocol::models::SearchToolCallParams; use codex_protocol::models::SearchToolCallParams;
use codex_tools::DiscoverableTool; use codex_tools::DiscoverableTool;
use codex_tools::ToolCall as ExtensionToolCall;
use codex_tools::ToolExecutor;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
use codex_tools::ToolsConfig; use codex_tools::ToolsConfig;
@@ -42,42 +40,16 @@ pub(crate) struct ToolRouterParams<'a> {
pub(crate) mcp_tools: Option<Vec<ToolInfo>>, pub(crate) mcp_tools: Option<Vec<ToolInfo>>,
pub(crate) deferred_mcp_tools: Option<Vec<ToolInfo>>, pub(crate) deferred_mcp_tools: Option<Vec<ToolInfo>>,
pub(crate) discoverable_tools: Option<Vec<DiscoverableTool>>, pub(crate) discoverable_tools: Option<Vec<DiscoverableTool>>,
pub(crate) extension_tool_executors: Vec<Arc<dyn ExtensionToolExecutor>>, pub(crate) extension_tool_executors: Vec<Arc<dyn ToolExecutor<ExtensionToolCall>>>,
pub(crate) dynamic_tools: &'a [DynamicToolSpec], pub(crate) dynamic_tools: &'a [DynamicToolSpec],
} }
impl ToolRouter { impl ToolRouter {
pub fn from_config(config: &ToolsConfig, params: ToolRouterParams<'_>) -> Self { pub fn from_config(config: &ToolsConfig, params: ToolRouterParams<'_>) -> Self {
let ToolRouterParams { build_tool_router(config, params)
mcp_tools,
deferred_mcp_tools,
discoverable_tools,
extension_tool_executors,
dynamic_tools,
} = params;
let parts = collect_tool_router_parts(
config,
mcp_tools,
deferred_mcp_tools,
discoverable_tools,
&extension_tool_executors,
dynamic_tools,
);
Self::from_executors(config, parts.executors, parts.hosted_specs)
} }
pub(crate) fn from_executors( pub(crate) fn from_parts(registry: ToolRegistry, model_visible_specs: Vec<ToolSpec>) -> Self {
config: &ToolsConfig,
executors: Vec<Arc<dyn RegisteredTool>>,
hosted_specs: Vec<ToolSpec>,
) -> Self {
let builder = build_tool_registry_builder_from_executors(config, executors, hosted_specs);
let (specs, registry) = builder.build();
let model_visible_specs = specs
.into_iter()
.filter(|spec| !is_hidden_by_code_mode_only(config, &registry, spec))
.collect();
Self { Self {
registry, registry,
model_visible_specs, model_visible_specs,
@@ -182,22 +154,9 @@ impl ToolRouter {
} }
} }
fn is_hidden_by_code_mode_only( pub(crate) fn extension_tool_executors(
config: &ToolsConfig, session: &Session,
registry: &ToolRegistry, ) -> Vec<Arc<dyn ToolExecutor<ExtensionToolCall>>> {
spec: &ToolSpec,
) -> bool {
if !config.code_mode_only_enabled || !codex_code_mode::is_code_mode_nested_tool(spec.name()) {
return false;
}
let exposure = registry
.tool_exposure(&ToolName::plain(spec.name()))
.unwrap_or(ToolExposure::Direct);
exposure != ToolExposure::DirectModelOnly
}
pub(crate) fn extension_tool_executors(session: &Session) -> Vec<Arc<dyn ExtensionToolExecutor>> {
session session
.services .services
.extensions .extensions
+4 -7
View File
@@ -7,7 +7,6 @@ use crate::turn_diff_tracker::TurnDiffTracker;
use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionRegistry; use codex_extension_api::ExtensionRegistry;
use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ResponsesApiTool; use codex_extension_api::ResponsesApiTool;
use codex_extension_api::ToolCall as ExtensionToolCall; use codex_extension_api::ToolCall as ExtensionToolCall;
use codex_extension_api::ToolExecutor; use codex_extension_api::ToolExecutor;
@@ -37,7 +36,7 @@ impl codex_extension_api::ToolContributor for ExtensionEchoContributor {
&self, &self,
_session_store: &ExtensionData, _session_store: &ExtensionData,
_thread_store: &ExtensionData, _thread_store: &ExtensionData,
) -> Vec<Arc<dyn ExtensionToolExecutor>> { ) -> Vec<Arc<dyn ToolExecutor<ExtensionToolCall>>> {
vec![Arc::new(ExtensionEchoExecutor)] vec![Arc::new(ExtensionEchoExecutor)]
} }
} }
@@ -46,8 +45,6 @@ struct ExtensionEchoExecutor;
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ExtensionToolCall> for ExtensionEchoExecutor { impl ToolExecutor<ExtensionToolCall> for ExtensionEchoExecutor {
type Output = codex_tools::JsonToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::namespaced("extension/", "echo") ToolName::namespaced("extension/", "echo")
} }
@@ -78,14 +75,14 @@ impl ToolExecutor<ExtensionToolCall> for ExtensionEchoExecutor {
async fn handle( async fn handle(
&self, &self,
call: ExtensionToolCall, call: ExtensionToolCall,
) -> Result<Self::Output, codex_tools::FunctionCallError> { ) -> Result<Box<dyn codex_tools::ToolOutput>, codex_tools::FunctionCallError> {
let arguments: serde_json::Value = let arguments: serde_json::Value =
serde_json::from_str(call.function_arguments()?).expect("test arguments should parse"); serde_json::from_str(call.function_arguments()?).expect("test arguments should parse");
Ok(codex_tools::JsonToolOutput::new(json!({ Ok(Box::new(codex_tools::JsonToolOutput::new(json!({
"arguments": arguments, "arguments": arguments,
"callId": call.call_id, "callId": call.call_id,
"ok": true, "ok": true,
}))) }))))
} }
} }
-77
View File
@@ -1,23 +1,6 @@
use crate::config::DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
use crate::shell::Shell; use crate::shell::Shell;
use crate::shell::ShellType; use crate::shell::ShellType;
use crate::tools::handlers::multi_agents_common::DEFAULT_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_common::MAX_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_common::MIN_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
use crate::tools::registry::RegisteredTool;
use crate::tools::spec_plan::collect_tool_executors;
use crate::tools::spec_plan::hosted_model_tool_specs;
use crate::tools::spec_plan_types::ToolRegistryBuildParams;
use codex_extension_api::ExtensionToolExecutor;
use codex_mcp::ToolInfo;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_tools::DiscoverableTool;
use codex_tools::ToolUserShellType; use codex_tools::ToolUserShellType;
use codex_tools::ToolsConfig;
use std::sync::Arc;
pub(crate) fn tool_user_shell_type(user_shell: &Shell) -> ToolUserShellType { pub(crate) fn tool_user_shell_type(user_shell: &Shell) -> ToolUserShellType {
match user_shell.shell_type { match user_shell.shell_type {
@@ -29,66 +12,6 @@ pub(crate) fn tool_user_shell_type(user_shell: &Shell) -> ToolUserShellType {
} }
} }
pub(crate) struct ToolRouterParts {
pub(crate) executors: Vec<Arc<dyn RegisteredTool>>,
pub(crate) hosted_specs: Vec<codex_tools::ToolSpec>,
}
pub(crate) fn collect_tool_router_parts(
config: &ToolsConfig,
mcp_tools: Option<Vec<ToolInfo>>,
deferred_mcp_tools: Option<Vec<ToolInfo>>,
discoverable_tools: Option<Vec<DiscoverableTool>>,
extension_tool_executors: &[Arc<dyn ExtensionToolExecutor>],
dynamic_tools: &[DynamicToolSpec],
) -> ToolRouterParts {
let default_agent_type_description =
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
let (min_wait_timeout_ms, max_wait_timeout_ms, default_wait_timeout_ms) =
if config.multi_agent_v2 {
let min_wait_timeout_ms = config
.wait_agent_min_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS);
let max_wait_timeout_ms = config
.wait_agent_max_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS);
let default_wait_timeout_ms = config
.wait_agent_default_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS);
(
min_wait_timeout_ms,
max_wait_timeout_ms,
default_wait_timeout_ms,
)
} else {
(
MIN_WAIT_TIMEOUT_MS,
MAX_WAIT_TIMEOUT_MS,
DEFAULT_WAIT_TIMEOUT_MS,
)
};
let executors = collect_tool_executors(
config,
ToolRegistryBuildParams {
mcp_tools: mcp_tools.as_deref(),
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
discoverable_tools: discoverable_tools.as_deref(),
extension_tool_executors,
dynamic_tools,
default_agent_type_description: &default_agent_type_description,
wait_agent_timeouts: WaitAgentTimeoutOptions {
default_timeout_ms: default_wait_timeout_ms,
min_timeout_ms: min_wait_timeout_ms,
max_timeout_ms: max_wait_timeout_ms,
},
},
);
ToolRouterParts {
executors,
hosted_specs: hosted_model_tool_specs(config),
}
}
#[cfg(test)] #[cfg(test)]
#[path = "spec_tests.rs"] #[path = "spec_tests.rs"]
mod tests; mod tests;
+185 -76
View File
@@ -1,3 +1,6 @@
use crate::config::DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
use crate::tools::code_mode::execute_spec::create_code_mode_tool; use crate::tools::code_mode::execute_spec::create_code_mode_tool;
use crate::tools::handlers::ApplyPatchHandler; use crate::tools::handlers::ApplyPatchHandler;
use crate::tools::handlers::CodeModeExecuteHandler; use crate::tools::handlers::CodeModeExecuteHandler;
@@ -24,13 +27,17 @@ use crate::tools::handlers::ViewImageHandler;
use crate::tools::handlers::WriteStdinHandler; use crate::tools::handlers::WriteStdinHandler;
use crate::tools::handlers::agent_jobs::ReportAgentJobResultHandler; use crate::tools::handlers::agent_jobs::ReportAgentJobResultHandler;
use crate::tools::handlers::agent_jobs::SpawnAgentsOnCsvHandler; use crate::tools::handlers::agent_jobs::SpawnAgentsOnCsvHandler;
use crate::tools::handlers::extension_tools::ExtensionToolHandler; use crate::tools::handlers::extension_tools::ExtensionToolAdapter;
use crate::tools::handlers::multi_agents::CloseAgentHandler; use crate::tools::handlers::multi_agents::CloseAgentHandler;
use crate::tools::handlers::multi_agents::ResumeAgentHandler; use crate::tools::handlers::multi_agents::ResumeAgentHandler;
use crate::tools::handlers::multi_agents::SendInputHandler; use crate::tools::handlers::multi_agents::SendInputHandler;
use crate::tools::handlers::multi_agents::SpawnAgentHandler; use crate::tools::handlers::multi_agents::SpawnAgentHandler;
use crate::tools::handlers::multi_agents::WaitAgentHandler; use crate::tools::handlers::multi_agents::WaitAgentHandler;
use crate::tools::handlers::multi_agents_common::DEFAULT_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_common::MAX_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_common::MIN_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions; use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
use crate::tools::handlers::multi_agents_v2::CloseAgentHandler as CloseAgentHandlerV2; use crate::tools::handlers::multi_agents_v2::CloseAgentHandler as CloseAgentHandlerV2;
use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2; use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2;
use crate::tools::handlers::multi_agents_v2::ListAgentsHandler as ListAgentsHandlerV2; use crate::tools::handlers::multi_agents_v2::ListAgentsHandler as ListAgentsHandlerV2;
@@ -41,17 +48,21 @@ use crate::tools::handlers::view_image_spec::ViewImageToolOptions;
use crate::tools::hosted_spec::WebSearchToolOptions; use crate::tools::hosted_spec::WebSearchToolOptions;
use crate::tools::hosted_spec::create_image_generation_tool; use crate::tools::hosted_spec::create_image_generation_tool;
use crate::tools::hosted_spec::create_web_search_tool; use crate::tools::hosted_spec::create_web_search_tool;
use crate::tools::registry::RegisteredTool; use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExposure; use crate::tools::registry::ToolExposure;
use crate::tools::registry::ToolRegistryBuilder; use crate::tools::registry::ToolRegistry;
use crate::tools::registry::override_tool_exposure; use crate::tools::registry::override_tool_exposure;
use crate::tools::spec_plan_types::ToolRegistryBuildParams; use crate::tools::router::ToolRouter;
use crate::tools::spec_plan_types::agent_type_description; use crate::tools::router::ToolRouterParams;
use codex_extension_api::ExtensionToolExecutor; use codex_mcp::ToolInfo;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::ConfigShellToolType;
use codex_tools::DiscoverableTool;
use codex_tools::ResponsesApiNamespaceTool; use codex_tools::ResponsesApiNamespaceTool;
use codex_tools::TOOL_SEARCH_TOOL_NAME; use codex_tools::TOOL_SEARCH_TOOL_NAME;
use codex_tools::ToolCall as ExtensionToolCall;
use codex_tools::ToolEnvironmentMode; use codex_tools::ToolEnvironmentMode;
use codex_tools::ToolExecutor;
use codex_tools::ToolName; use codex_tools::ToolName;
use codex_tools::ToolSpec; use codex_tools::ToolSpec;
use codex_tools::ToolsConfig; use codex_tools::ToolsConfig;
@@ -62,74 +73,94 @@ use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use tracing::warn; use tracing::warn;
pub(crate) fn build_tool_registry_builder_from_executors( #[derive(Clone, Copy)]
struct ToolRegistryBuildParams<'a> {
mcp_tools: Option<&'a [ToolInfo]>,
deferred_mcp_tools: Option<&'a [ToolInfo]>,
discoverable_tools: Option<&'a [DiscoverableTool]>,
extension_tool_executors: &'a [Arc<dyn ToolExecutor<ExtensionToolCall>>],
dynamic_tools: &'a [DynamicToolSpec],
default_agent_type_description: &'a str,
wait_agent_timeouts: WaitAgentTimeoutOptions,
}
pub(crate) fn build_tool_router(config: &ToolsConfig, params: ToolRouterParams<'_>) -> ToolRouter {
let (model_visible_specs, registry) = build_tool_specs_and_registry(config, params);
ToolRouter::from_parts(registry, model_visible_specs)
}
fn build_tool_specs_and_registry(
config: &ToolsConfig, config: &ToolsConfig,
executors: Vec<Arc<dyn RegisteredTool>>, params: ToolRouterParams<'_>,
hosted_specs: Vec<ToolSpec>, ) -> (Vec<ToolSpec>, ToolRegistry) {
) -> ToolRegistryBuilder { let ToolRouterParams {
let mut builder = ToolRegistryBuilder::new(); mcp_tools,
let deferred_tools_available = executors deferred_mcp_tools,
.iter() discoverable_tools,
.any(|executor| executor.exposure() == ToolExposure::Deferred); extension_tool_executors,
dynamic_tools,
for executor in build_code_mode_executors( } = params;
let default_agent_type_description =
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
let mut executors = collect_tool_executors(
config, config,
&executors, ToolRegistryBuildParams {
config.search_tool && deferred_tools_available, mcp_tools: mcp_tools.as_deref(),
) { deferred_mcp_tools: deferred_mcp_tools.as_deref(),
builder.register_tool(executor); discoverable_tools: discoverable_tools.as_deref(),
} extension_tool_executors: &extension_tool_executors,
dynamic_tools,
let mut non_deferred_specs = Vec::new(); default_agent_type_description: &default_agent_type_description,
let mut deferred_search_infos = Vec::new(); wait_agent_timeouts: wait_agent_timeout_options(config),
for executor in &executors { },
match executor.exposure() {
ToolExposure::Direct | ToolExposure::DirectModelOnly => {
if let Some(spec) = executor.spec() {
non_deferred_specs.push((spec, executor.exposure()));
}
}
ToolExposure::Deferred => {
if let Some(search_info) = executor.search_info() {
deferred_search_infos.push(search_info);
}
}
}
}
non_deferred_specs.extend(
hosted_specs
.into_iter()
.map(|spec| (spec, ToolExposure::Direct)),
); );
append_tool_search_executor(config, &mut executors);
prepend_code_mode_executors(config, &mut executors);
build_model_visible_specs_and_registry(config, executors, hosted_model_tool_specs(config))
}
let non_deferred_specs = non_deferred_specs fn build_model_visible_specs_and_registry(
.into_iter() config: &ToolsConfig,
.map(|(spec, exposure)| { executors: Vec<Arc<dyn CoreToolRuntime>>,
if config.code_mode_enabled && exposure != ToolExposure::DirectModelOnly { hosted_specs: Vec<ToolSpec>,
codex_tools::augment_tool_spec_for_code_mode(spec) ) -> (Vec<ToolSpec>, ToolRegistry) {
} else { let mut specs = Vec::new();
spec let mut seen_tool_names = HashSet::new();
} for executor in &executors {
}) if !seen_tool_names.insert(executor.tool_name()) {
.collect();
for spec in merge_into_namespaces(non_deferred_specs) {
if !config.namespace_tools && matches!(spec, ToolSpec::Namespace(_)) {
continue; continue;
} }
builder.push_spec(spec); if executor.exposure().is_direct()
&& let Some(spec) = executor.spec()
{
specs.push(spec_for_model_request(config, executor.exposure(), spec));
}
} }
specs.extend(hosted_specs);
for executor in executors { let registry = ToolRegistry::from_tools(executors);
builder.register_tool_without_spec(executor); let model_visible_specs = merge_into_namespaces(specs)
.into_iter()
.filter(|spec| config.namespace_tools || !matches!(spec, ToolSpec::Namespace(_)))
.filter(|spec| !is_hidden_by_code_mode_only(config, &registry, spec))
.collect();
(model_visible_specs, registry)
}
fn spec_for_model_request(
config: &ToolsConfig,
exposure: ToolExposure,
spec: ToolSpec,
) -> ToolSpec {
if config.code_mode_enabled
&& exposure != ToolExposure::DirectModelOnly
&& codex_code_mode::is_code_mode_nested_tool(spec.name())
{
codex_tools::augment_tool_spec_for_code_mode(spec)
} else {
spec
} }
if config.search_tool && config.namespace_tools && !deferred_search_infos.is_empty() {
builder.register_tool(Arc::new(ToolSearchHandler::new(deferred_search_infos)));
}
builder
} }
pub(crate) fn hosted_model_tool_specs(config: &ToolsConfig) -> Vec<ToolSpec> { pub(crate) fn hosted_model_tool_specs(config: &ToolsConfig) -> Vec<ToolSpec> {
@@ -147,11 +178,56 @@ pub(crate) fn hosted_model_tool_specs(config: &ToolsConfig) -> Vec<ToolSpec> {
specs specs
} }
fn wait_agent_timeout_options(config: &ToolsConfig) -> WaitAgentTimeoutOptions {
if config.multi_agent_v2 {
return WaitAgentTimeoutOptions {
default_timeout_ms: config
.wait_agent_default_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS),
min_timeout_ms: config
.wait_agent_min_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS),
max_timeout_ms: config
.wait_agent_max_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS),
};
}
WaitAgentTimeoutOptions {
default_timeout_ms: DEFAULT_WAIT_TIMEOUT_MS,
min_timeout_ms: MIN_WAIT_TIMEOUT_MS,
max_timeout_ms: MAX_WAIT_TIMEOUT_MS,
}
}
fn agent_type_description(config: &ToolsConfig, default_agent_type_description: &str) -> String {
if config.agent_type_description.is_empty() {
default_agent_type_description.to_string()
} else {
config.agent_type_description.clone()
}
}
fn is_hidden_by_code_mode_only(
config: &ToolsConfig,
registry: &ToolRegistry,
spec: &ToolSpec,
) -> bool {
if !config.code_mode_only_enabled || !codex_code_mode::is_code_mode_nested_tool(spec.name()) {
return false;
}
let exposure = registry
.tool_exposure(&ToolName::plain(spec.name()))
.unwrap_or(ToolExposure::Direct);
exposure != ToolExposure::DirectModelOnly
}
fn build_code_mode_executors( fn build_code_mode_executors(
config: &ToolsConfig, config: &ToolsConfig,
executors: &[Arc<dyn RegisteredTool>], executors: &[Arc<dyn CoreToolRuntime>],
deferred_tools_available: bool, deferred_tools_available: bool,
) -> Vec<Arc<dyn RegisteredTool>> { ) -> Vec<Arc<dyn CoreToolRuntime>> {
if !config.code_mode_enabled { if !config.code_mode_enabled {
return vec![]; return vec![];
} }
@@ -254,12 +330,12 @@ fn code_mode_namespace_descriptions(
namespace_descriptions namespace_descriptions
} }
pub(crate) fn collect_tool_executors( fn collect_tool_executors(
config: &ToolsConfig, config: &ToolsConfig,
params: ToolRegistryBuildParams<'_>, params: ToolRegistryBuildParams<'_>,
) -> Vec<Arc<dyn RegisteredTool>> { ) -> Vec<Arc<dyn CoreToolRuntime>> {
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled; let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
let mut executors = Vec::<Arc<dyn RegisteredTool>>::new(); let mut executors = Vec::<Arc<dyn CoreToolRuntime>>::new();
if config.environment_mode.has_environment() { if config.environment_mode.has_environment() {
let include_environment_id = let include_environment_id =
@@ -444,10 +520,43 @@ pub(crate) fn collect_tool_executors(
executors executors
} }
fn append_tool_search_executor(
config: &ToolsConfig,
executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
) {
if !(config.search_tool && config.namespace_tools) {
return;
}
let search_infos = executors
.iter()
.filter(|executor| executor.exposure() == ToolExposure::Deferred)
.filter_map(|executor| executor.search_info())
.collect::<Vec<_>>();
if search_infos.is_empty() {
return;
}
executors.push(Arc::new(ToolSearchHandler::new(search_infos)));
}
fn prepend_code_mode_executors(
config: &ToolsConfig,
executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
) {
let deferred_tools_available = config.search_tool
&& executors
.iter()
.any(|executor| executor.exposure() == ToolExposure::Deferred);
let code_mode_executors =
build_code_mode_executors(config, executors, deferred_tools_available);
executors.splice(0..0, code_mode_executors);
}
fn append_extension_tool_executors( fn append_extension_tool_executors(
config: &ToolsConfig, config: &ToolsConfig,
executors: &[Arc<dyn ExtensionToolExecutor>], executors: &[Arc<dyn ToolExecutor<ExtensionToolCall>>],
registered_executors: &mut Vec<Arc<dyn RegisteredTool>>, registered_executors: &mut Vec<Arc<dyn CoreToolRuntime>>,
) { ) {
if executors.is_empty() { if executors.is_empty() {
return; return;
@@ -473,17 +582,17 @@ fn append_extension_tool_executors(
for executor in executors.iter().cloned() { for executor in executors.iter().cloned() {
let tool_name = executor.tool_name(); let tool_name = executor.tool_name();
if !reserved_tool_names.insert(tool_name.clone()) { if !reserved_tool_names.insert(tool_name.clone()) {
warn!("Skipping extension tool `{tool_name}`: handler already registered"); warn!("Skipping extension tool `{tool_name}`: tool already registered");
continue; continue;
} }
registered_executors.push(Arc::new(ExtensionToolHandler::new(executor))); registered_executors.push(Arc::new(ExtensionToolAdapter::new(executor)));
} }
} }
fn multi_agent_v2_handler( fn multi_agent_v2_handler(
handler: impl RegisteredTool + 'static, handler: impl CoreToolRuntime + 'static,
exposure: ToolExposure, exposure: ToolExposure,
) -> Arc<dyn RegisteredTool> { ) -> Arc<dyn CoreToolRuntime> {
override_tool_exposure(Arc::new(handler), exposure) override_tool_exposure(Arc::new(handler), exposure)
} }
+35 -36
View File
@@ -26,7 +26,6 @@ use crate::tools::handlers::view_image_spec::ViewImageToolOptions;
use crate::tools::handlers::view_image_spec::create_view_image_tool; use crate::tools::handlers::view_image_spec::create_view_image_tool;
use crate::tools::registry::ToolRegistry; use crate::tools::registry::ToolRegistry;
use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::AppInfo;
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ToolCall as ExtensionToolCall; use codex_extension_api::ToolCall as ExtensionToolCall;
use codex_extension_api::ToolExecutor; use codex_extension_api::ToolExecutor;
use codex_features::Feature; use codex_features::Feature;
@@ -74,7 +73,10 @@ const DEFAULT_WAIT_TIMEOUT_MS: i64 = 30_000;
const MIN_WAIT_TIMEOUT_MS: i64 = 10_000; const MIN_WAIT_TIMEOUT_MS: i64 = 10_000;
const MAX_WAIT_TIMEOUT_MS: i64 = 3_600_000; const MAX_WAIT_TIMEOUT_MS: i64 = 3_600_000;
fn extension_tool_executor(name: &str, description: &str) -> Arc<dyn ExtensionToolExecutor> { fn extension_tool_executor(
name: &str,
description: &str,
) -> Arc<dyn ToolExecutor<ExtensionToolCall>> {
struct SpecOnlyExtensionExecutor { struct SpecOnlyExtensionExecutor {
name: String, name: String,
description: String, description: String,
@@ -82,8 +84,6 @@ fn extension_tool_executor(name: &str, description: &str) -> Arc<dyn ExtensionTo
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ExtensionToolCall> for SpecOnlyExtensionExecutor { impl ToolExecutor<ExtensionToolCall> for SpecOnlyExtensionExecutor {
type Output = codex_tools::JsonToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
ToolName::plain(self.name.as_str()) ToolName::plain(self.name.as_str())
} }
@@ -109,7 +109,7 @@ fn extension_tool_executor(name: &str, description: &str) -> Arc<dyn ExtensionTo
async fn handle( async fn handle(
&self, &self,
_call: ExtensionToolCall, _call: ExtensionToolCall,
) -> Result<Self::Output, codex_tools::FunctionCallError> { ) -> Result<Box<dyn codex_tools::ToolOutput>, codex_tools::FunctionCallError> {
panic!("spec planning should not execute extension tools") panic!("spec planning should not execute extension tools")
} }
} }
@@ -1440,7 +1440,7 @@ fn namespace_specs_are_hidden_when_namespace_tools_are_disabled() {
); );
assert_lacks_tool_name(&tools, "mcp__sample__"); assert_lacks_tool_name(&tools, "mcp__sample__");
assert!(registry.has_handler(&ToolName::namespaced("mcp__sample__", "echo"))); assert!(registry.has_tool(&ToolName::namespaced("mcp__sample__", "echo")));
} }
#[test] #[test]
@@ -1646,11 +1646,11 @@ fn search_tool_description_lists_each_mcp_source_once() {
assert!(description.contains("- rmcp: Remote memory tools.")); assert!(description.contains("- rmcp: Remote memory tools."));
assert!(!description.contains("mcp__rmcp__echo")); assert!(!description.contains("mcp__rmcp__echo"));
assert!(registry.has_handler(&ToolName::namespaced( assert!(registry.has_tool(&ToolName::namespaced(
"mcp__codex_apps__calendar", "mcp__codex_apps__calendar",
"_create_event", "_create_event",
))); )));
assert!(registry.has_handler(&ToolName::namespaced("mcp__rmcp__", "echo"))); assert!(registry.has_tool(&ToolName::namespaced("mcp__rmcp__", "echo")));
} }
#[test] #[test]
@@ -1758,7 +1758,7 @@ fn no_search_tool_when_namespaces_disabled() {
); );
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME); assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
assert!(!registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME))); assert!(!registry.has_tool(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
} }
#[test] #[test]
@@ -1816,9 +1816,9 @@ fn search_tool_registers_for_deferred_dynamic_tools() {
assert!(description.contains("- Dynamic tools: Tools provided by the current Codex thread.")); assert!(description.contains("- Dynamic tools: Tools provided by the current Codex thread."));
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME]); assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME]);
assert_lacks_tool_name(&tools, "codex_app"); assert_lacks_tool_name(&tools, "codex_app");
assert!(registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME))); assert!(registry.has_tool(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
assert!(registry.has_handler(&ToolName::namespaced("codex_app", "automation_update"))); assert!(registry.has_tool(&ToolName::namespaced("codex_app", "automation_update")));
assert!(registry.has_handler(&ToolName::namespaced("codex_app", "automation_list"))); assert!(registry.has_tool(&ToolName::namespaced("codex_app", "automation_list")));
} }
#[test] #[test]
@@ -1865,9 +1865,9 @@ fn search_tool_is_hidden_for_deferred_dynamic_tools_when_namespace_tools_are_dis
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME); assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
assert_lacks_tool_name(&tools, "codex_app"); assert_lacks_tool_name(&tools, "codex_app");
assert_lacks_tool_name(&tools, "plain_dynamic"); assert_lacks_tool_name(&tools, "plain_dynamic");
assert!(!registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME))); assert!(!registry.has_tool(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
assert!(registry.has_handler(&ToolName::namespaced("codex_app", "automation_update"))); assert!(registry.has_tool(&ToolName::namespaced("codex_app", "automation_update")));
assert!(registry.has_handler(&ToolName::plain("plain_dynamic"))); assert!(registry.has_tool(&ToolName::plain("plain_dynamic")));
} }
#[test] #[test]
@@ -2007,7 +2007,7 @@ fn request_plugin_install_description_lists_discoverable_tools() {
/*extension_tool_executors*/ &[], /*extension_tool_executors*/ &[],
&[], &[],
); );
assert!(registry.has_handler(&ToolName::plain(REQUEST_PLUGIN_INSTALL_TOOL_NAME))); assert!(registry.has_tool(&ToolName::plain(REQUEST_PLUGIN_INSTALL_TOOL_NAME)));
let request_plugin_install = find_tool(&tools, REQUEST_PLUGIN_INSTALL_TOOL_NAME); let request_plugin_install = find_tool(&tools, REQUEST_PLUGIN_INSTALL_TOOL_NAME);
let ToolSpec::Function(ResponsesApiTool { let ToolSpec::Function(ResponsesApiTool {
@@ -2109,18 +2109,19 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
&[], &[],
); );
let ResponsesApiTool { description, .. } = let ToolSpec::Freeform(FreeformTool { description, .. }) = find_tool(&tools, "exec") else {
find_namespace_function_tool(&tools, "mcp__sample__", "echo"); panic!("expected freeform tool");
};
assert_eq!( assert!(description.contains(
description, r#"### `mcp__sample__echo`
r#"Echo text Echo text
exec tool declaration: exec tool declaration:
```ts ```ts
declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<CallToolResult>; }; declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<CallToolResult>; };
```"# ```"#
); ));
} }
#[test] #[test]
@@ -2413,7 +2414,7 @@ fn build_specs_with_inputs_for_test(
mcp_tools: Option<HashMap<ToolName, rmcp::model::Tool>>, mcp_tools: Option<HashMap<ToolName, rmcp::model::Tool>>,
deferred_mcp_tools: Option<Vec<ToolInfo>>, deferred_mcp_tools: Option<Vec<ToolInfo>>,
discoverable_tools: Option<Vec<DiscoverableTool>>, discoverable_tools: Option<Vec<DiscoverableTool>>,
extension_tool_executors: &[Arc<dyn ExtensionToolExecutor>], extension_tool_executors: &[Arc<dyn ToolExecutor<ExtensionToolCall>>],
dynamic_tools: &[DynamicToolSpec], dynamic_tools: &[DynamicToolSpec],
) -> (Vec<ToolSpec>, ToolRegistry) { ) -> (Vec<ToolSpec>, ToolRegistry) {
let mcp_tool_inputs = mcp_tools.as_ref().map(|mcp_tools| { let mcp_tool_inputs = mcp_tools.as_ref().map(|mcp_tools| {
@@ -2431,13 +2432,10 @@ fn build_specs_with_inputs_for_test(
default_agent_type_description: DEFAULT_AGENT_TYPE_DESCRIPTION, default_agent_type_description: DEFAULT_AGENT_TYPE_DESCRIPTION,
wait_agent_timeouts: wait_agent_timeout_options(), wait_agent_timeouts: wait_agent_timeout_options(),
}; };
let executors = collect_tool_executors(config, params); let mut executors = collect_tool_executors(config, params);
let builder = build_tool_registry_builder_from_executors( append_tool_search_executor(config, &mut executors);
config, prepend_code_mode_executors(config, &mut executors);
executors, build_model_visible_specs_and_registry(config, executors, hosted_model_tool_specs(config))
hosted_model_tool_specs(config),
);
builder.build()
} }
fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> rmcp::model::Tool { fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> rmcp::model::Tool {
@@ -2539,18 +2537,19 @@ fn code_mode_augments_mcp_tool_descriptions_with_structured_output_sample() {
&[], &[],
); );
let ResponsesApiTool { description, .. } = let ToolSpec::Freeform(FreeformTool { description, .. }) = find_tool(&tools, "exec") else {
find_namespace_function_tool(&tools, "mcp__sample__", "echo"); panic!("expected freeform tool");
};
assert_eq!( assert!(description.contains(
description, r#"### `mcp__sample__echo`
r#"Echo text Echo text
exec tool declaration: exec tool declaration:
```ts ```ts
declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; }; declare const tools: { mcp__sample__echo(args: { message: string; }): Promise<CallToolResult<{ echo: string; env: string | null; }>>; };
```"# ```"#
); ));
} }
fn discoverable_connector(id: &str, name: &str, description: &str) -> DiscoverableTool { fn discoverable_connector(id: &str, name: &str, description: &str) -> DiscoverableTool {
@@ -1,29 +0,0 @@
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
use codex_extension_api::ExtensionToolExecutor;
use codex_mcp::ToolInfo;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_tools::DiscoverableTool;
use codex_tools::ToolsConfig;
use std::sync::Arc;
#[derive(Clone, Copy)]
pub struct ToolRegistryBuildParams<'a> {
pub mcp_tools: Option<&'a [ToolInfo]>,
pub deferred_mcp_tools: Option<&'a [ToolInfo]>,
pub discoverable_tools: Option<&'a [DiscoverableTool]>,
pub extension_tool_executors: &'a [Arc<dyn ExtensionToolExecutor>],
pub dynamic_tools: &'a [DynamicToolSpec],
pub default_agent_type_description: &'a str,
pub wait_agent_timeouts: WaitAgentTimeoutOptions,
}
pub(crate) fn agent_type_description(
config: &ToolsConfig,
default_agent_type_description: &str,
) -> String {
if config.agent_type_description.is_empty() {
default_agent_type_description.to_string()
} else {
config.agent_type_description.clone()
}
}
+42 -178
View File
@@ -3,17 +3,17 @@ use crate::shell::Shell;
use crate::shell::ShellType; use crate::shell::ShellType;
use crate::test_support::construct_model_info_offline; use crate::test_support::construct_model_info_offline;
use crate::tools::ToolRouter; use crate::tools::ToolRouter;
use crate::tools::registry::ToolRegistryBuilder;
use crate::tools::router::ToolRouterParams; use crate::tools::router::ToolRouterParams;
use crate::tools::spec_plan::build_tool_registry_builder_from_executors;
use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::AppInfo;
use codex_features::Feature; use codex_features::Feature;
use codex_features::Features; use codex_features::Features;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::ToolInfo;
use codex_models_manager::bundled_models_response; use codex_models_manager::bundled_models_response;
use codex_models_manager::model_info::with_config_overrides; use codex_models_manager::model_info::with_config_overrides;
use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ModelInfo;
@@ -56,21 +56,6 @@ fn mcp_tool(name: &str, description: &str, input_schema: serde_json::Value) -> r
} }
} }
fn mcp_tool_info(tool: rmcp::model::Tool) -> ToolInfo {
ToolInfo {
server_name: "test_server".to_string(),
supports_parallel_tool_calls: false,
server_origin: None,
callable_name: tool.name.to_string(),
callable_namespace: "mcp__test_server__".to_string(),
namespace_description: None,
tool,
connector_id: None,
connector_name: None,
plugin_display_names: Vec::new(),
}
}
fn mcp_tool_info_with_display_name(display_name: &str, tool: rmcp::model::Tool) -> ToolInfo { fn mcp_tool_info_with_display_name(display_name: &str, tool: rmcp::model::Tool) -> ToolInfo {
let (callable_namespace, callable_name) = display_name let (callable_namespace, callable_name) = display_name
.rsplit_once('/') .rsplit_once('/')
@@ -240,13 +225,12 @@ async fn multi_agent_v2_tools_config() -> ToolsConfig {
} }
fn multi_agent_v2_spawn_agent_description(tools_config: &ToolsConfig) -> String { fn multi_agent_v2_spawn_agent_description(tools_config: &ToolsConfig) -> String {
let (tools, _) = build_specs( let tools = build_specs(
tools_config, tools_config,
/*mcp_tools*/ None, /*mcp_tools*/ None,
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
let spawn_agent = find_tool(&tools, "spawn_agent"); let spawn_agent = find_tool(&tools, "spawn_agent");
let ToolSpec::Function(ResponsesApiTool { description, .. }) = spawn_agent else { let ToolSpec::Function(ResponsesApiTool { description, .. }) = spawn_agent else {
panic!("spawn_agent should be a function tool"); panic!("spawn_agent should be a function tool");
@@ -266,13 +250,13 @@ async fn model_info_from_models_json(slug: &str) -> ModelInfo {
with_config_overrides(model, &config.to_models_manager_config()) with_config_overrides(model, &config.to_models_manager_config())
} }
/// Builds the tool registry builder while collecting tool specs for later serialization. /// Builds tool specs and the matching registry from the same executor list.
fn build_specs( fn build_specs(
config: &ToolsConfig, config: &ToolsConfig,
mcp_tools: Option<Vec<ToolInfo>>, mcp_tools: Option<Vec<ToolInfo>>,
deferred_mcp_tools: Option<Vec<ToolInfo>>, deferred_mcp_tools: Option<Vec<ToolInfo>>,
dynamic_tools: &[DynamicToolSpec], dynamic_tools: &[DynamicToolSpec],
) -> ToolRegistryBuilder { ) -> Vec<ToolSpec> {
build_specs_with_inputs_for_test( build_specs_with_inputs_for_test(
config, config,
mcp_tools, mcp_tools,
@@ -288,18 +272,22 @@ fn build_specs_with_inputs_for_test(
mcp_tools: Option<Vec<ToolInfo>>, mcp_tools: Option<Vec<ToolInfo>>,
deferred_mcp_tools: Option<Vec<ToolInfo>>, deferred_mcp_tools: Option<Vec<ToolInfo>>,
discoverable_tools: Option<Vec<DiscoverableTool>>, discoverable_tools: Option<Vec<DiscoverableTool>>,
extension_tool_executors: &[Arc<dyn codex_extension_api::ExtensionToolExecutor>], extension_tool_executors: &[Arc<
dyn codex_extension_api::ToolExecutor<codex_extension_api::ToolCall>,
>],
dynamic_tools: &[DynamicToolSpec], dynamic_tools: &[DynamicToolSpec],
) -> ToolRegistryBuilder { ) -> Vec<ToolSpec> {
let parts = collect_tool_router_parts( ToolRouter::from_config(
config, config,
mcp_tools, ToolRouterParams {
deferred_mcp_tools, mcp_tools,
discoverable_tools, deferred_mcp_tools,
extension_tool_executors, discoverable_tools,
dynamic_tools, extension_tool_executors: extension_tool_executors.to_vec(),
); dynamic_tools,
build_tool_registry_builder_from_executors(config, parts.executors, parts.hosted_specs) },
)
.model_visible_specs()
} }
#[tokio::test] #[tokio::test]
@@ -319,13 +307,12 @@ async fn get_memory_requires_feature_flag() {
permission_profile: &PermissionProfile::Disabled, permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
/*mcp_tools*/ None, /*mcp_tools*/ None,
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
assert!( assert!(
!tools.iter().any(|t| t.name() == "get_memory"), !tools.iter().any(|t| t.name() == "get_memory"),
"get_memory should be disabled when memory_tool feature is off" "get_memory should be disabled when memory_tool feature is off"
@@ -633,13 +620,12 @@ async fn test_build_specs_default_shell_present() {
permission_profile: &PermissionProfile::Disabled, permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
Some(Vec::new()), Some(Vec::new()),
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
// Only check the shell variant and a couple of core tools. // Only check the shell variant and a couple of core tools.
let mut subset = vec!["exec_command", "write_stdin", "update_plan"]; let mut subset = vec!["exec_command", "write_stdin", "update_plan"];
@@ -774,13 +760,12 @@ async fn multi_agent_v2_wait_agent_schema_uses_configured_timeouts() {
.with_wait_agent_min_timeout_ms(wait_agent_min_timeout_ms) .with_wait_agent_min_timeout_ms(wait_agent_min_timeout_ms)
.with_wait_agent_max_timeout_ms(wait_agent_max_timeout_ms) .with_wait_agent_max_timeout_ms(wait_agent_max_timeout_ms)
.with_wait_agent_default_timeout_ms(wait_agent_default_timeout_ms); .with_wait_agent_default_timeout_ms(wait_agent_default_timeout_ms);
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
/*mcp_tools*/ None, /*mcp_tools*/ None,
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
let wait_agent = find_tool(&tools, "wait_agent"); let wait_agent = find_tool(&tools, "wait_agent");
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = wait_agent else { let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = wait_agent else {
panic!("wait_agent should be a function tool"); panic!("wait_agent should be a function tool");
@@ -825,15 +810,14 @@ async fn request_plugin_install_requires_apps_and_plugins_features() {
permission_profile: &PermissionProfile::Disabled, permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs_with_inputs_for_test( let tools = build_specs_with_inputs_for_test(
&tools_config, &tools_config,
/*mcp_tools*/ None, /*mcp_tools*/ None,
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
discoverable_tools.clone(), discoverable_tools.clone(),
/*extension_tool_executors*/ &[], /*extension_tool_executors*/ &[],
&[], &[],
) );
.build();
assert!( assert!(
!tools !tools
@@ -862,13 +846,12 @@ async fn search_tool_is_hidden_without_deferred_tools() {
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
/*mcp_tools*/ None, /*mcp_tools*/ None,
Some(Vec::new()), Some(Vec::new()),
&[], &[],
) );
.build();
assert!( assert!(
!tools !tools
.iter() .iter()
@@ -894,7 +877,7 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
/*mcp_tools*/ None, /*mcp_tools*/ None,
Some(vec![ToolInfo { Some(vec![ToolInfo {
@@ -914,8 +897,7 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio
plugin_display_names: Vec::new(), plugin_display_names: Vec::new(),
}]), }]),
&[], &[],
) );
.build();
let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME); let search_tool = find_tool(&tools, TOOL_SEARCH_TOOL_NAME);
let ToolSpec::ToolSearch { description, .. } = search_tool else { let ToolSpec::ToolSearch { description, .. } = search_tool else {
panic!("expected tool_search tool"); panic!("expected tool_search tool");
@@ -925,119 +907,6 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio
assert!(!description.contains("- Calendar:")); assert!(!description.contains("- Calendar:"));
} }
#[tokio::test]
async fn search_tool_registers_namespaced_mcp_tool_aliases() {
let model_info = search_capable_model_info().await;
let mut features = Features::with_defaults();
features.enable(Feature::Apps);
features.enable(Feature::ToolSearch);
let available_models = Vec::new();
let tools_config = ToolsConfig::new(&ToolsConfigParams {
model_info: &model_info,
available_models: &available_models,
features: &features,
image_generation_tool_auth_allowed: true,
web_search_mode: Some(WebSearchMode::Cached),
session_source: SessionSource::Cli,
permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
});
let (_, registry) = build_specs(
&tools_config,
/*mcp_tools*/ None,
Some(vec![
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
supports_parallel_tool_calls: false,
server_origin: None,
callable_name: "_create_event".to_string(),
callable_namespace: "mcp__codex_apps__calendar".to_string(),
namespace_description: None,
tool: mcp_tool(
"calendar-create-event",
"Create calendar event",
serde_json::json!({"type": "object"}),
),
connector_id: Some("calendar".to_string()),
connector_name: Some("Calendar".to_string()),
plugin_display_names: Vec::new(),
},
ToolInfo {
server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
supports_parallel_tool_calls: false,
server_origin: None,
callable_name: "_list_events".to_string(),
callable_namespace: "mcp__codex_apps__calendar".to_string(),
namespace_description: None,
tool: mcp_tool(
"calendar-list-events",
"List calendar events",
serde_json::json!({"type": "object"}),
),
connector_id: Some("calendar".to_string()),
connector_name: Some("Calendar".to_string()),
plugin_display_names: Vec::new(),
},
ToolInfo {
server_name: "rmcp".to_string(),
supports_parallel_tool_calls: false,
server_origin: None,
callable_name: "echo".to_string(),
callable_namespace: "mcp__rmcp__".to_string(),
namespace_description: None,
tool: mcp_tool("echo", "Echo", serde_json::json!({"type": "object"})),
connector_id: None,
connector_name: None,
plugin_display_names: Vec::new(),
},
]),
&[],
)
.build();
let app_alias = ToolName::namespaced("mcp__codex_apps__calendar", "_create_event");
let mcp_alias = ToolName::namespaced("mcp__rmcp__", "echo");
assert!(registry.has_handler(&ToolName::plain(TOOL_SEARCH_TOOL_NAME)));
assert!(registry.has_handler(&app_alias));
assert!(registry.has_handler(&mcp_alias));
}
#[tokio::test]
async fn direct_mcp_tools_register_namespaced_handlers() {
let config = test_config().await;
let model_info = construct_model_info_offline("gpt-5.4", &config);
let mut features = Features::with_defaults();
features.enable(Feature::UnifiedExec);
let available_models = Vec::new();
let tools_config = ToolsConfig::new(&ToolsConfigParams {
model_info: &model_info,
available_models: &available_models,
features: &features,
image_generation_tool_auth_allowed: true,
web_search_mode: Some(WebSearchMode::Cached),
session_source: SessionSource::Cli,
permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
});
let (_, registry) = build_specs(
&tools_config,
Some(vec![mcp_tool_info(mcp_tool(
"echo",
"Echo",
serde_json::json!({"type": "object"}),
))]),
/*deferred_mcp_tools*/ None,
&[],
)
.build();
assert!(registry.has_handler(&ToolName::namespaced("mcp__test_server__", "echo")));
assert!(!registry.has_handler(&ToolName::plain("mcp__test_server__echo")));
}
#[tokio::test] #[tokio::test]
async fn test_mcp_tool_property_missing_type_defaults_to_string() { async fn test_mcp_tool_property_missing_type_defaults_to_string() {
let config = test_config().await; let config = test_config().await;
@@ -1056,7 +925,7 @@ async fn test_mcp_tool_property_missing_type_defaults_to_string() {
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
Some(vec![mcp_tool_info_with_display_name( Some(vec![mcp_tool_info_with_display_name(
"dash/search", "dash/search",
@@ -1073,8 +942,7 @@ async fn test_mcp_tool_property_missing_type_defaults_to_string() {
)]), )]),
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
let tool = find_namespace_function_tool(&tools, "dash/", "search"); let tool = find_namespace_function_tool(&tools, "dash/", "search");
assert_eq!( assert_eq!(
@@ -1116,7 +984,7 @@ async fn test_mcp_tool_preserves_integer_schema() {
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
Some(vec![mcp_tool_info_with_display_name( Some(vec![mcp_tool_info_with_display_name(
"dash/paginate", "dash/paginate",
@@ -1131,8 +999,7 @@ async fn test_mcp_tool_preserves_integer_schema() {
)]), )]),
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
let tool = find_namespace_function_tool(&tools, "dash/", "paginate"); let tool = find_namespace_function_tool(&tools, "dash/", "paginate");
assert_eq!( assert_eq!(
@@ -1174,7 +1041,7 @@ async fn test_mcp_tool_array_without_items_gets_default_string_items() {
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
Some(vec![mcp_tool_info_with_display_name( Some(vec![mcp_tool_info_with_display_name(
"dash/tags", "dash/tags",
@@ -1189,8 +1056,7 @@ async fn test_mcp_tool_array_without_items_gets_default_string_items() {
)]), )]),
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
let tool = find_namespace_function_tool(&tools, "dash/", "tags"); let tool = find_namespace_function_tool(&tools, "dash/", "tags");
assert_eq!( assert_eq!(
@@ -1235,7 +1101,7 @@ async fn test_mcp_tool_anyof_defaults_to_string() {
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
Some(vec![mcp_tool_info_with_display_name( Some(vec![mcp_tool_info_with_display_name(
"dash/value", "dash/value",
@@ -1252,8 +1118,7 @@ async fn test_mcp_tool_anyof_defaults_to_string() {
)]), )]),
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
let tool = find_namespace_function_tool(&tools, "dash/", "value"); let tool = find_namespace_function_tool(&tools, "dash/", "value");
assert_eq!( assert_eq!(
@@ -1300,7 +1165,7 @@ async fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
permission_profile: &PermissionProfile::Disabled, permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
}); });
let (tools, _) = build_specs( let tools = build_specs(
&tools_config, &tools_config,
Some(vec![mcp_tool_info_with_display_name( Some(vec![mcp_tool_info_with_display_name(
"test_server/do_something_cool", "test_server/do_something_cool",
@@ -1334,8 +1199,7 @@ async fn test_get_openai_tools_mcp_tools_with_additional_properties_schema() {
)]), )]),
/*deferred_mcp_tools*/ None, /*deferred_mcp_tools*/ None,
&[], &[],
) );
.build();
let tool = find_namespace_function_tool(&tools, "test_server/", "do_something_cool"); let tool = find_namespace_function_tool(&tools, "test_server/", "do_something_cool");
assert_eq!( assert_eq!(
@@ -21,8 +21,8 @@ use crate::tools::context::FunctionToolOutput;
use crate::tools::context::ToolCallSource; use crate::tools::context::ToolCallSource;
use crate::tools::context::ToolInvocation; use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload; use crate::tools::context::ToolPayload;
use crate::tools::registry::CoreToolRuntime;
use crate::tools::registry::ToolExecutor; use crate::tools::registry::ToolExecutor;
use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolRegistry; use crate::tools::registry::ToolRegistry;
use crate::turn_diff_tracker::TurnDiffTracker; use crate::turn_diff_tracker::TurnDiffTracker;
@@ -32,18 +32,22 @@ struct TestHandler {
#[async_trait::async_trait] #[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for TestHandler { impl ToolExecutor<ToolInvocation> for TestHandler {
type Output = FunctionToolOutput;
fn tool_name(&self) -> codex_tools::ToolName { fn tool_name(&self) -> codex_tools::ToolName {
self.tool_name.clone() self.tool_name.clone()
} }
async fn handle(&self, _invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> { async fn handle(
Ok(FunctionToolOutput::from_text("ok".to_string(), Some(true))) &self,
_invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
Ok(Box::new(FunctionToolOutput::from_text(
"ok".to_string(),
Some(true),
)))
} }
} }
impl ToolHandler for TestHandler {} impl CoreToolRuntime for TestHandler {}
#[tokio::test] #[tokio::test]
async fn dispatch_lifecycle_trace_records_direct_and_code_mode_requesters() -> anyhow::Result<()> { async fn dispatch_lifecycle_trace_records_direct_and_code_mode_requesters() -> anyhow::Result<()> {
@@ -4,12 +4,13 @@ use std::sync::Arc;
use codex_protocol::items::TurnItem; use codex_protocol::items::TurnItem;
use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::TokenUsageInfo; use codex_protocol::protocol::TokenUsageInfo;
use codex_tools::ToolCall;
use codex_tools::ToolExecutor;
use crate::ExtensionData; use crate::ExtensionData;
mod prompt; mod prompt;
mod thread_lifecycle; mod thread_lifecycle;
mod tools;
mod turn_lifecycle; mod turn_lifecycle;
pub use prompt::PromptFragment; pub use prompt::PromptFragment;
@@ -17,8 +18,6 @@ pub use prompt::PromptSlot;
pub use thread_lifecycle::ThreadResumeInput; pub use thread_lifecycle::ThreadResumeInput;
pub use thread_lifecycle::ThreadStartInput; pub use thread_lifecycle::ThreadStartInput;
pub use thread_lifecycle::ThreadStopInput; pub use thread_lifecycle::ThreadStopInput;
pub use tools::ExtensionToolExecutor;
pub use tools::ExtensionToolOutput;
pub use turn_lifecycle::TurnAbortInput; pub use turn_lifecycle::TurnAbortInput;
pub use turn_lifecycle::TurnStartInput; pub use turn_lifecycle::TurnStartInput;
pub use turn_lifecycle::TurnStopInput; pub use turn_lifecycle::TurnStopInput;
@@ -106,7 +105,7 @@ pub trait ToolContributor: Send + Sync {
&self, &self,
session_store: &ExtensionData, session_store: &ExtensionData,
thread_store: &ExtensionData, thread_store: &ExtensionData,
) -> Vec<Arc<dyn ExtensionToolExecutor>>; ) -> Vec<Arc<dyn ToolExecutor<ToolCall>>>;
} }
/// Future returned by one claimed approval-review contribution. /// Future returned by one claimed approval-review contribution.
@@ -1,15 +0,0 @@
use codex_tools::JsonToolOutput;
use codex_tools::ToolCall;
use codex_tools::ToolExecutor;
/// Model-facing output returned by extension-owned tools.
pub type ExtensionToolOutput = JsonToolOutput;
/// Thin alias for extension-owned executable tools.
///
/// Extensions implement the shared `ToolExecutor<ToolCall>` contract directly;
/// the marker keeps contributor signatures readable while preserving one
/// executable-tool abstraction across host and extension tools.
pub trait ExtensionToolExecutor: ToolExecutor<ToolCall, Output = ExtensionToolOutput> {}
impl<T> ExtensionToolExecutor for T where T: ToolExecutor<ToolCall, Output = ExtensionToolOutput> {}
+1 -2
View File
@@ -11,6 +11,7 @@ pub use codex_tools::ResponsesApiTool;
pub use codex_tools::ToolCall; pub use codex_tools::ToolCall;
pub use codex_tools::ToolExecutor; pub use codex_tools::ToolExecutor;
pub use codex_tools::ToolName; pub use codex_tools::ToolName;
pub use codex_tools::ToolOutput;
pub use codex_tools::ToolPayload; pub use codex_tools::ToolPayload;
pub use codex_tools::ToolSpec; pub use codex_tools::ToolSpec;
pub use codex_tools::parse_tool_input_schema; pub use codex_tools::parse_tool_input_schema;
@@ -18,8 +19,6 @@ pub use contributors::ApprovalReviewContributor;
pub use contributors::ApprovalReviewFuture; pub use contributors::ApprovalReviewFuture;
pub use contributors::ConfigContributor; pub use contributors::ConfigContributor;
pub use contributors::ContextContributor; pub use contributors::ContextContributor;
pub use contributors::ExtensionToolExecutor;
pub use contributors::ExtensionToolOutput;
pub use contributors::PromptFragment; pub use contributors::PromptFragment;
pub use contributors::PromptSlot; pub use contributors::PromptSlot;
pub use contributors::ThreadLifecycleContributor; pub use contributors::ThreadLifecycleContributor;
+1 -1
View File
@@ -63,7 +63,7 @@ impl ToolContributor for MemoriesExtension {
&self, &self,
_session_store: &ExtensionData, _session_store: &ExtensionData,
thread_store: &ExtensionData, thread_store: &ExtensionData,
) -> Vec<Arc<dyn codex_extension_api::ExtensionToolExecutor>> { ) -> Vec<Arc<dyn codex_extension_api::ToolExecutor<codex_extension_api::ToolCall>>> {
let Some(config) = thread_store.get::<MemoriesExtensionConfig>() else { let Some(config) = thread_store.get::<MemoriesExtensionConfig>() else {
return Vec::new(); return Vec::new();
}; };
+8 -5
View File
@@ -3,10 +3,10 @@ use std::sync::Arc;
use codex_extension_api::ContextContributor; use codex_extension_api::ContextContributor;
use codex_extension_api::ExtensionData; use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::PromptSlot; use codex_extension_api::PromptSlot;
use codex_extension_api::ToolCall; use codex_extension_api::ToolCall;
use codex_extension_api::ToolContributor; use codex_extension_api::ToolContributor;
use codex_extension_api::ToolExecutor;
use codex_extension_api::ToolName; use codex_extension_api::ToolName;
use codex_extension_api::ToolPayload; use codex_extension_api::ToolPayload;
use codex_tools::ToolOutput; use codex_tools::ToolOutput;
@@ -290,20 +290,23 @@ async fn search_tool_rejects_legacy_single_query() {
.to_string(), .to_string(),
}; };
let err = tool let result = tool
.handle(ToolCall { .handle(ToolCall {
call_id: "call-1".to_string(), call_id: "call-1".to_string(),
tool_name: memory_tool_name(crate::SEARCH_TOOL_NAME), tool_name: memory_tool_name(crate::SEARCH_TOOL_NAME),
payload, payload,
}) })
.await .await;
.expect_err("legacy query field should be rejected"); let err = match result {
Ok(_) => panic!("legacy query field should be rejected"),
Err(err) => err,
};
assert!(err.to_string().contains("unknown field")); assert!(err.to_string().contains("unknown field"));
assert!(err.to_string().contains("query")); assert!(err.to_string().contains("query"));
} }
fn memory_tool(memory_root: &Path, tool_name: &str) -> Arc<dyn ExtensionToolExecutor> { fn memory_tool(memory_root: &Path, tool_name: &str) -> Arc<dyn ToolExecutor<ToolCall>> {
let expected_tool_name = memory_tool_name(tool_name); let expected_tool_name = memory_tool_name(tool_name);
crate::tools::memory_tools(LocalMemoriesBackend::from_memory_root(memory_root)) crate::tools::memory_tools(LocalMemoriesBackend::from_memory_root(memory_root))
.into_iter() .into_iter()
+3 -4
View File
@@ -39,8 +39,6 @@ impl<B> ToolExecutor<ToolCall> for ListTool<B>
where where
B: MemoriesBackend, B: MemoriesBackend,
{ {
type Output = JsonToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
memory_tool_name(LIST_TOOL_NAME) memory_tool_name(LIST_TOOL_NAME)
} }
@@ -55,7 +53,8 @@ where
async fn handle( async fn handle(
&self, &self,
call: ToolCall, call: ToolCall,
) -> Result<Self::Output, codex_extension_api::FunctionCallError> { ) -> Result<Box<dyn codex_extension_api::ToolOutput>, codex_extension_api::FunctionCallError>
{
let backend = self.backend.clone(); let backend = self.backend.clone();
let args: ListArgs = parse_args(&call)?; let args: ListArgs = parse_args(&call)?;
let response = backend let response = backend
@@ -70,6 +69,6 @@ where
}) })
.await .await
.map_err(backend_error_to_function_call)?; .map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response))) Ok(Box::new(JsonToolOutput::new(json!(response))))
} }
} }
+2 -2
View File
@@ -1,9 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::FunctionCallError; use codex_extension_api::FunctionCallError;
use codex_extension_api::ResponsesApiTool; use codex_extension_api::ResponsesApiTool;
use codex_extension_api::ToolCall; use codex_extension_api::ToolCall;
use codex_extension_api::ToolExecutor;
use codex_extension_api::ToolName; use codex_extension_api::ToolName;
use codex_extension_api::ToolSpec; use codex_extension_api::ToolSpec;
use codex_extension_api::parse_tool_input_schema; use codex_extension_api::parse_tool_input_schema;
@@ -23,7 +23,7 @@ mod list;
mod read; mod read;
mod search; mod search;
pub(crate) fn memory_tools<B>(backend: B) -> Vec<Arc<dyn ExtensionToolExecutor>> pub(crate) fn memory_tools<B>(backend: B) -> Vec<Arc<dyn ToolExecutor<ToolCall>>>
where where
B: MemoriesBackend, B: MemoriesBackend,
{ {
+3 -4
View File
@@ -38,8 +38,6 @@ impl<B> ToolExecutor<ToolCall> for ReadTool<B>
where where
B: MemoriesBackend, B: MemoriesBackend,
{ {
type Output = JsonToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
memory_tool_name(READ_TOOL_NAME) memory_tool_name(READ_TOOL_NAME)
} }
@@ -54,7 +52,8 @@ where
async fn handle( async fn handle(
&self, &self,
call: ToolCall, call: ToolCall,
) -> Result<Self::Output, codex_extension_api::FunctionCallError> { ) -> Result<Box<dyn codex_extension_api::ToolOutput>, codex_extension_api::FunctionCallError>
{
let backend = self.backend.clone(); let backend = self.backend.clone();
let args: ReadArgs = parse_args(&call)?; let args: ReadArgs = parse_args(&call)?;
let response = backend let response = backend
@@ -66,6 +65,6 @@ where
}) })
.await .await
.map_err(backend_error_to_function_call)?; .map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response))) Ok(Box::new(JsonToolOutput::new(json!(response))))
} }
} }
+3 -4
View File
@@ -47,8 +47,6 @@ impl<B> ToolExecutor<ToolCall> for SearchTool<B>
where where
B: MemoriesBackend, B: MemoriesBackend,
{ {
type Output = JsonToolOutput;
fn tool_name(&self) -> ToolName { fn tool_name(&self) -> ToolName {
memory_tool_name(SEARCH_TOOL_NAME) memory_tool_name(SEARCH_TOOL_NAME)
} }
@@ -63,14 +61,15 @@ where
async fn handle( async fn handle(
&self, &self,
call: ToolCall, call: ToolCall,
) -> Result<Self::Output, codex_extension_api::FunctionCallError> { ) -> Result<Box<dyn codex_extension_api::ToolOutput>, codex_extension_api::FunctionCallError>
{
let backend = self.backend.clone(); let backend = self.backend.clone();
let args: SearchArgs = parse_args(&call)?; let args: SearchArgs = parse_args(&call)?;
let response = backend let response = backend
.search(args.into_request()) .search(args.into_request())
.await .await
.map_err(backend_error_to_function_call)?; .map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response))) Ok(Box::new(JsonToolOutput::new(json!(response))))
} }
} }
+4 -3
View File
@@ -36,8 +36,6 @@ impl ToolExposure {
/// top without reopening the spec/runtime split. /// top without reopening the spec/runtime split.
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait ToolExecutor<Invocation>: Send + Sync { pub trait ToolExecutor<Invocation>: Send + Sync {
type Output: ToolOutput + 'static;
/// The concrete tool name handled by this runtime instance. /// The concrete tool name handled by this runtime instance.
fn tool_name(&self) -> ToolName; fn tool_name(&self) -> ToolName;
@@ -53,5 +51,8 @@ pub trait ToolExecutor<Invocation>: Send + Sync {
false false
} }
async fn handle(&self, invocation: Invocation) -> Result<Self::Output, FunctionCallError>; async fn handle(
&self,
invocation: Invocation,
) -> Result<Box<dyn ToolOutput>, FunctionCallError>;
} }
+18
View File
@@ -20,6 +20,16 @@ pub trait ToolOutput: Send {
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem; fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem;
/// Returns the tool call id exposed to `PostToolUse` hooks for this output.
fn post_tool_use_id(&self, call_id: &str) -> String {
call_id.to_string()
}
/// Returns the tool input exposed to `PostToolUse` hooks for this output.
fn post_tool_use_input(&self, _payload: &ToolPayload) -> Option<JsonValue> {
None
}
/// Returns the stable value exposed to `PostToolUse` hooks for this tool output. /// Returns the stable value exposed to `PostToolUse` hooks for this tool output.
/// ///
/// Tool handlers decide whether a tool participates in `PostToolUse`, but /// Tool handlers decide whether a tool participates in `PostToolUse`, but
@@ -52,6 +62,14 @@ where
(**self).to_response_item(call_id, payload) (**self).to_response_item(call_id, payload)
} }
fn post_tool_use_id(&self, call_id: &str) -> String {
(**self).post_tool_use_id(call_id)
}
fn post_tool_use_input(&self, payload: &ToolPayload) -> Option<JsonValue> {
(**self).post_tool_use_input(payload)
}
fn post_tool_use_response(&self, call_id: &str, payload: &ToolPayload) -> Option<JsonValue> { fn post_tool_use_response(&self, call_id: &str, payload: &ToolPayload) -> Option<JsonValue> {
(**self).post_tool_use_response(call_id, payload) (**self).post_tool_use_response(call_id, payload)
} }