feat: make ToolExecutor an async trait (#22560)

## Why

`codex_tools::ToolExecutor` keeps a tool spec attached to its runtime
handler, but extension tools still carried a parallel
`ExtensionToolFuture` / `ExtensionToolExecutor` shape. That made
extension-owned tools look different from host tools even though
routing, registration, and execution need the same abstraction.

This PR makes the shared executor contract directly async and lets
extension tools implement it too, so host tools and extension tools can
move through the same registration path.

## What changed

- Changed `ToolExecutor::handle` to an `async fn` using `async-trait`,
and updated built-in tool handlers to implement the async trait
directly.
- Replaced the bespoke `ExtensionToolFuture` contract with a marker
`ExtensionToolExecutor` over `ToolExecutor<ToolCall, Output =
JsonToolOutput>`, re-exporting `ToolExecutor` from
`codex-extension-api`.
- Updated the memories extension tools to implement the shared executor
trait.
- Split tool-router construction into collected executors plus hosted
model specs, keeping hosted tools like web search and image generation
separate from executable handlers.
- Updated spec/router tests and extension-tool stubs for the new
executor shape.

## Verification

- Not run locally.
This commit is contained in:
jif-oai
2026-05-14 11:23:57 +02:00
committed by GitHub
Unverified
parent 6a225e4005
commit 6d65686313
54 changed files with 313 additions and 237 deletions
+2
View File
@@ -3137,6 +3137,7 @@ dependencies = [
name = "codex-memories-extension"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-core",
"codex-extension-api",
"codex-features",
@@ -3729,6 +3730,7 @@ dependencies = [
name = "codex-tools"
version = "0.0.0"
dependencies = [
"async-trait",
"codex-app-server-protocol",
"codex-code-mode",
"codex-features",
@@ -87,6 +87,7 @@ impl CodeModeExecuteHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler {
type Output = FunctionToolOutput;
@@ -41,6 +41,7 @@ where
})
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler {
type Output = FunctionToolOutput;
@@ -12,6 +12,7 @@ use super::*;
pub struct ReportAgentJobResultHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ReportAgentJobResultHandler {
type Output = FunctionToolOutput;
@@ -13,6 +13,7 @@ use super::*;
pub struct SpawnAgentsOnCsvHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for SpawnAgentsOnCsvHandler {
type Output = FunctionToolOutput;
@@ -297,6 +297,7 @@ async fn effective_patch_permissions(
)
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ApplyPatchHandler {
type Output = ApplyPatchToolOutput;
@@ -60,6 +60,7 @@ impl DynamicToolHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for DynamicToolHandler {
type Output = FunctionToolOutput;
@@ -35,6 +35,7 @@ impl ExtensionToolHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ExtensionToolHandler {
type Output = ExtensionToolOutput;
@@ -115,7 +116,10 @@ mod tests {
struct StubExtensionExecutor;
impl codex_extension_api::ExtensionToolExecutor for StubExtensionExecutor {
#[async_trait::async_trait]
impl codex_extension_api::ToolExecutor<codex_tools::ToolCall> for StubExtensionExecutor {
type Output = codex_tools::JsonToolOutput;
fn tool_name(&self) -> codex_tools::ToolName {
codex_tools::ToolName::plain("extension_echo")
}
@@ -141,11 +145,11 @@ mod tests {
))
}
fn handle(
async fn handle(
&self,
_call: codex_tools::ToolCall,
) -> codex_extension_api::ExtensionToolFuture<'_> {
Box::pin(async { Ok(codex_tools::JsonToolOutput::new(json!({ "ok": true }))) })
) -> Result<Self::Output, codex_tools::FunctionCallError> {
Ok(codex_tools::JsonToolOutput::new(json!({ "ok": true })))
}
}
@@ -18,6 +18,7 @@ use super::goal_response;
pub struct CreateGoalHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for CreateGoalHandler {
type Output = FunctionToolOutput;
@@ -15,6 +15,7 @@ use super::goal_response;
pub struct GetGoalHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for GetGoalHandler {
type Output = FunctionToolOutput;
@@ -20,6 +20,7 @@ use super::goal_response;
pub struct UpdateGoalHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for UpdateGoalHandler {
type Output = FunctionToolOutput;
+1
View File
@@ -45,6 +45,7 @@ impl McpHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for McpHandler {
type Output = McpToolOutput;
@@ -26,6 +26,7 @@ use super::serialize_function_output;
pub struct ListMcpResourceTemplatesHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ListMcpResourceTemplatesHandler {
type Output = FunctionToolOutput;
@@ -26,6 +26,7 @@ use super::serialize_function_output;
pub struct ListMcpResourcesHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ListMcpResourcesHandler {
type Output = FunctionToolOutput;
@@ -26,6 +26,7 @@ use super::serialize_function_output;
pub struct ReadMcpResourceHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler {
type Output = FunctionToolOutput;
@@ -5,6 +5,7 @@ use codex_tools::ToolSpec;
pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = CloseAgentResult;
@@ -16,11 +17,8 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_close_agent_tool_v1())
}
fn handle(
&self,
invocation: ToolInvocation,
) -> impl std::future::Future<Output = Result<Self::Output, FunctionCallError>> + Send {
Box::pin(handle_close_agent(invocation))
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
handle_close_agent(invocation).await
}
}
@@ -7,6 +7,7 @@ use std::sync::Arc;
pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = ResumeAgentResult;
@@ -18,11 +19,8 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_resume_agent_tool())
}
fn handle(
&self,
invocation: ToolInvocation,
) -> impl std::future::Future<Output = Result<Self::Output, FunctionCallError>> + Send {
Box::pin(handle_resume_agent(invocation))
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
handle_resume_agent(invocation).await
}
}
@@ -6,6 +6,7 @@ use codex_tools::ToolSpec;
pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = SendInputResult;
@@ -22,6 +22,7 @@ impl Handler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = SpawnAgentResult;
@@ -33,11 +34,8 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_spawn_agent_tool_v1(self.options.clone()))
}
fn handle(
&self,
invocation: ToolInvocation,
) -> impl std::future::Future<Output = Result<Self::Output, FunctionCallError>> + Send {
Box::pin(handle_spawn_agent(invocation))
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
handle_spawn_agent(invocation).await
}
}
@@ -27,6 +27,7 @@ impl Handler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = WaitAgentResult;
@@ -5,6 +5,7 @@ use codex_tools::ToolSpec;
pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = CloseAgentResult;
@@ -16,11 +17,8 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_close_agent_tool_v2())
}
fn handle(
&self,
invocation: ToolInvocation,
) -> impl std::future::Future<Output = Result<Self::Output, FunctionCallError>> + Send {
Box::pin(handle_close_agent(invocation))
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
handle_close_agent(invocation).await
}
}
@@ -8,6 +8,7 @@ use codex_tools::ToolSpec;
pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = FunctionToolOutput;
@@ -5,6 +5,7 @@ use codex_tools::ToolSpec;
pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = ListAgentsResult;
@@ -8,6 +8,7 @@ use codex_tools::ToolSpec;
pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = FunctionToolOutput;
@@ -24,6 +24,7 @@ impl Handler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = SpawnAgentResult;
@@ -35,11 +36,8 @@ impl ToolExecutor<ToolInvocation> for Handler {
Some(create_spawn_agent_tool_v2(self.options.clone()))
}
fn handle(
&self,
invocation: ToolInvocation,
) -> impl std::future::Future<Output = Result<Self::Output, FunctionCallError>> + Send {
Box::pin(handle_spawn_agent(invocation))
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
handle_spawn_agent(invocation).await
}
}
@@ -19,6 +19,7 @@ impl Handler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
type Output = WaitAgentResult;
+1
View File
@@ -44,6 +44,7 @@ impl ToolOutput for PlanToolOutput {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for PlanHandler {
type Output = PlanToolOutput;
@@ -15,6 +15,7 @@ use codex_tools::ToolSpec;
pub struct RequestPermissionsHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for RequestPermissionsHandler {
type Output = FunctionToolOutput;
@@ -50,6 +50,7 @@ impl RequestPluginInstallHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for RequestPluginInstallHandler {
type Output = FunctionToolOutput;
@@ -19,6 +19,7 @@ pub struct RequestUserInputHandler {
pub available_modes: Vec<ModeKind>,
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for RequestUserInputHandler {
type Output = FunctionToolOutput;
@@ -127,6 +127,7 @@ impl From<ShellCommandBackendConfig> for ShellCommandHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ShellCommandHandler {
type Output = FunctionToolOutput;
@@ -56,6 +56,7 @@ fn barrier_map() -> &'static tokio::sync::Mutex<HashMap<String, BarrierState>> {
BARRIERS.get_or_init(|| tokio::sync::Mutex::new(HashMap::new()))
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for TestSyncHandler {
type Output = FunctionToolOutput;
@@ -52,6 +52,7 @@ impl ToolSearchHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ToolSearchHandler {
type Output = ToolSearchOutput;
@@ -68,6 +68,7 @@ impl ExecCommandHandler {
}
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
type Output = ExecCommandToolOutput;
@@ -31,6 +31,7 @@ struct WriteStdinArgs {
pub struct WriteStdinHandler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for WriteStdinHandler {
type Output = ExecCommandToolOutput;
@@ -62,6 +62,7 @@ enum ViewImageDetail {
Original,
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for ViewImageHandler {
type Output = ViewImageOutput;
-7
View File
@@ -637,13 +637,6 @@ impl ToolRegistryBuilder {
self.specs.push(spec);
}
pub fn register_handler<H>(&mut self, handler: Arc<H>)
where
H: ToolHandler + 'static,
{
self.register_tool(handler);
}
pub(crate) fn register_tool(&mut self, handler: Arc<dyn RegisteredTool>) {
self.register_tool_internal(handler, /*include_spec*/ true);
}
+3 -2
View File
@@ -8,6 +8,7 @@ struct TestHandler {
tool_name: codex_tools::ToolName,
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for TestHandler {
type Output = crate::tools::context::FunctionToolOutput;
@@ -65,9 +66,9 @@ fn handler_looks_up_namespaced_aliases_explicitly() {
}
#[test]
fn register_handler_adds_handler_and_spec() {
fn register_tool_adds_executor_and_spec() {
let mut builder = ToolRegistryBuilder::new();
builder.register_handler(Arc::new(GetGoalHandler));
builder.register_tool(Arc::new(GetGoalHandler));
let (specs, registry) = builder.build();
+13 -2
View File
@@ -5,10 +5,12 @@ use crate::tools::context::SharedTurnDiffTracker;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::registry::AnyToolResult;
use crate::tools::registry::RegisteredTool;
use crate::tools::registry::ToolArgumentDiffConsumer;
use crate::tools::registry::ToolExposure;
use crate::tools::registry::ToolRegistry;
use crate::tools::spec::build_specs_with_discoverable_tools;
use crate::tools::spec::collect_tool_router_parts;
use crate::tools::spec_plan::build_tool_registry_builder_from_executors;
use codex_extension_api::ExtensionToolExecutor;
use codex_mcp::ToolInfo;
use codex_protocol::dynamic_tools::DynamicToolSpec;
@@ -53,7 +55,7 @@ impl ToolRouter {
extension_tool_executors,
dynamic_tools,
} = params;
let builder = build_specs_with_discoverable_tools(
let parts = collect_tool_router_parts(
config,
mcp_tools,
deferred_mcp_tools,
@@ -61,6 +63,15 @@ impl ToolRouter {
&extension_tool_executors,
dynamic_tools,
);
Self::from_executors(config, parts.executors, parts.hosted_specs)
}
pub(crate) fn from_executors(
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()
+16 -12
View File
@@ -8,9 +8,9 @@ use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionRegistry;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ExtensionToolOutput;
use codex_extension_api::ResponsesApiTool;
use codex_extension_api::ToolCall as ExtensionToolCall;
use codex_extension_api::ToolExecutor;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::ResponseInputItem;
@@ -44,7 +44,10 @@ impl codex_extension_api::ToolContributor for ExtensionEchoContributor {
struct ExtensionEchoExecutor;
impl ExtensionToolExecutor for ExtensionEchoExecutor {
#[async_trait::async_trait]
impl ToolExecutor<ExtensionToolCall> for ExtensionEchoExecutor {
type Output = codex_tools::JsonToolOutput;
fn tool_name(&self) -> ToolName {
ToolName::namespaced("extension/", "echo")
}
@@ -72,16 +75,17 @@ impl ExtensionToolExecutor for ExtensionEchoExecutor {
}))
}
fn handle(&self, call: ExtensionToolCall) -> codex_extension_api::ExtensionToolFuture<'_> {
Box::pin(async move {
let arguments: serde_json::Value = serde_json::from_str(call.function_arguments()?)
.expect("test arguments should parse");
Ok(ExtensionToolOutput::new(json!({
"arguments": arguments,
"callId": call.call_id.clone(),
"ok": true,
})))
})
async fn handle(
&self,
call: ExtensionToolCall,
) -> Result<Self::Output, codex_tools::FunctionCallError> {
let arguments: serde_json::Value =
serde_json::from_str(call.function_arguments()?).expect("test arguments should parse");
Ok(codex_tools::JsonToolOutput::new(json!({
"arguments": arguments,
"callId": call.call_id,
"ok": true,
})))
}
}
+16 -6
View File
@@ -7,8 +7,9 @@ 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::ToolRegistryBuilder;
use crate::tools::spec_plan::build_tool_registry_builder;
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;
@@ -28,14 +29,19 @@ pub(crate) fn tool_user_shell_type(user_shell: &Shell) -> ToolUserShellType {
}
}
pub(crate) fn build_specs_with_discoverable_tools(
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],
) -> ToolRegistryBuilder {
) -> 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) =
@@ -61,7 +67,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
DEFAULT_WAIT_TIMEOUT_MS,
)
};
build_tool_registry_builder(
let executors = collect_tool_executors(
config,
ToolRegistryBuildParams {
mcp_tools: mcp_tools.as_deref(),
@@ -76,7 +82,11 @@ pub(crate) fn build_specs_with_discoverable_tools(
max_timeout_ms: max_wait_timeout_ms,
},
},
)
);
ToolRouterParts {
executors,
hosted_specs: hosted_model_tool_specs(config),
}
}
#[cfg(test)]
+86 -76
View File
@@ -62,51 +62,46 @@ use std::collections::HashSet;
use std::sync::Arc;
use tracing::warn;
pub fn build_tool_registry_builder(
pub(crate) fn build_tool_registry_builder_from_executors(
config: &ToolsConfig,
params: ToolRegistryBuildParams<'_>,
executors: Vec<Arc<dyn RegisteredTool>>,
hosted_specs: Vec<ToolSpec>,
) -> ToolRegistryBuilder {
let mut builder = ToolRegistryBuilder::new();
let handlers = collect_handler_tools(config, params);
let deferred_tools_available = handlers
let deferred_tools_available = executors
.iter()
.any(|handler| handler.exposure() == ToolExposure::Deferred);
.any(|executor| executor.exposure() == ToolExposure::Deferred);
for handler in build_code_mode_handlers(
for executor in build_code_mode_executors(
config,
&handlers,
&executors,
config.search_tool && deferred_tools_available,
) {
builder.register_tool(handler);
builder.register_tool(executor);
}
let mut non_deferred_specs = Vec::new();
let mut deferred_search_infos = Vec::new();
for handler in &handlers {
match handler.exposure() {
for executor in &executors {
match executor.exposure() {
ToolExposure::Direct | ToolExposure::DirectModelOnly => {
if let Some(spec) = handler.spec() {
non_deferred_specs.push((spec, handler.exposure()));
if let Some(spec) = executor.spec() {
non_deferred_specs.push((spec, executor.exposure()));
}
}
ToolExposure::Deferred => {
if let Some(search_info) = handler.search_info() {
if let Some(search_info) = executor.search_info() {
deferred_search_infos.push(search_info);
}
}
}
}
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
web_search_mode: config.web_search_mode,
web_search_config: config.web_search_config.as_ref(),
web_search_tool_type: config.web_search_tool_type,
}) {
non_deferred_specs.push((web_search_tool, ToolExposure::Direct));
}
if config.image_gen_tool {
non_deferred_specs.push((create_image_generation_tool("png"), ToolExposure::Direct));
}
non_deferred_specs.extend(
hosted_specs
.into_iter()
.map(|spec| (spec, ToolExposure::Direct)),
);
let non_deferred_specs = non_deferred_specs
.into_iter()
@@ -126,34 +121,49 @@ pub fn build_tool_registry_builder(
builder.push_spec(spec);
}
for handler in handlers {
builder.register_tool_without_spec(handler);
for executor in executors {
builder.register_tool_without_spec(executor);
}
if config.search_tool && config.namespace_tools && !deferred_search_infos.is_empty() {
builder.register_handler(Arc::new(ToolSearchHandler::new(deferred_search_infos)));
builder.register_tool(Arc::new(ToolSearchHandler::new(deferred_search_infos)));
}
builder
}
fn build_code_mode_handlers(
pub(crate) fn hosted_model_tool_specs(config: &ToolsConfig) -> Vec<ToolSpec> {
let mut specs = Vec::new();
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
web_search_mode: config.web_search_mode,
web_search_config: config.web_search_config.as_ref(),
web_search_tool_type: config.web_search_tool_type,
}) {
specs.push(web_search_tool);
}
if config.image_gen_tool {
specs.push(create_image_generation_tool("png"));
}
specs
}
fn build_code_mode_executors(
config: &ToolsConfig,
handlers: &[Arc<dyn RegisteredTool>],
executors: &[Arc<dyn RegisteredTool>],
deferred_tools_available: bool,
) -> Vec<Arc<dyn RegisteredTool>> {
if !config.code_mode_enabled {
return vec![];
}
let code_mode_nested_tool_specs = handlers
let code_mode_nested_tool_specs = executors
.iter()
.filter_map(|handler| {
if handler.exposure() == ToolExposure::DirectModelOnly {
.filter_map(|executor| {
if executor.exposure() == ToolExposure::DirectModelOnly {
return None;
}
handler.spec()
executor.spec()
})
.collect::<Vec<_>>();
let namespace_descriptions = code_mode_namespace_descriptions(&code_mode_nested_tool_specs);
@@ -244,32 +254,32 @@ fn code_mode_namespace_descriptions(
namespace_descriptions
}
fn collect_handler_tools(
pub(crate) fn collect_tool_executors(
config: &ToolsConfig,
params: ToolRegistryBuildParams<'_>,
) -> Vec<Arc<dyn RegisteredTool>> {
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
let mut handlers = Vec::<Arc<dyn RegisteredTool>>::new();
let mut executors = Vec::<Arc<dyn RegisteredTool>>::new();
if config.environment_mode.has_environment() {
let include_environment_id =
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
match &config.shell_type {
ConfigShellToolType::UnifiedExec => {
handlers.push(Arc::new(ExecCommandHandler::new(
executors.push(Arc::new(ExecCommandHandler::new(
ExecCommandHandlerOptions {
allow_login_shell: config.allow_login_shell,
exec_permission_approvals_enabled,
include_environment_id,
},
)));
handlers.push(Arc::new(WriteStdinHandler));
executors.push(Arc::new(WriteStdinHandler));
}
ConfigShellToolType::Disabled => {}
ConfigShellToolType::Default
| ConfigShellToolType::Local
| ConfigShellToolType::ShellCommand => {
handlers.push(Arc::new(ShellCommandHandler::new(
executors.push(Arc::new(ShellCommandHandler::new(
ShellCommandHandlerOptions {
backend_config: config.shell_command_backend,
allow_login_shell: config.allow_login_shell,
@@ -285,7 +295,7 @@ fn collect_handler_tools(
{
match &config.shell_type {
ConfigShellToolType::UnifiedExec => {
handlers.push(Arc::new(ShellCommandHandler::from(
executors.push(Arc::new(ShellCommandHandler::from(
config.shell_command_backend,
)));
}
@@ -297,31 +307,31 @@ fn collect_handler_tools(
}
if params.mcp_tools.is_some() {
handlers.push(Arc::new(ListMcpResourcesHandler));
handlers.push(Arc::new(ListMcpResourceTemplatesHandler));
handlers.push(Arc::new(ReadMcpResourceHandler));
executors.push(Arc::new(ListMcpResourcesHandler));
executors.push(Arc::new(ListMcpResourceTemplatesHandler));
executors.push(Arc::new(ReadMcpResourceHandler));
}
handlers.push(Arc::new(PlanHandler));
executors.push(Arc::new(PlanHandler));
if config.goal_tools {
handlers.push(Arc::new(GetGoalHandler));
handlers.push(Arc::new(CreateGoalHandler));
handlers.push(Arc::new(UpdateGoalHandler));
executors.push(Arc::new(GetGoalHandler));
executors.push(Arc::new(CreateGoalHandler));
executors.push(Arc::new(UpdateGoalHandler));
}
handlers.push(Arc::new(RequestUserInputHandler {
executors.push(Arc::new(RequestUserInputHandler {
available_modes: config.request_user_input_available_modes.clone(),
}));
if config.request_permissions_tool_enabled {
handlers.push(Arc::new(RequestPermissionsHandler));
executors.push(Arc::new(RequestPermissionsHandler));
}
if config.tool_suggest
&& let Some(discoverable_tools) =
params.discoverable_tools.filter(|tools| !tools.is_empty())
{
handlers.push(Arc::new(RequestPluginInstallHandler::new(
executors.push(Arc::new(RequestPluginInstallHandler::new(
discoverable_tools,
)));
}
@@ -329,7 +339,7 @@ fn collect_handler_tools(
if config.environment_mode.has_environment() && config.apply_patch_tool_type.is_some() {
let include_environment_id =
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
handlers.push(Arc::new(ApplyPatchHandler::new(include_environment_id)));
executors.push(Arc::new(ApplyPatchHandler::new(include_environment_id)));
}
if config
@@ -337,13 +347,13 @@ fn collect_handler_tools(
.iter()
.any(|tool| tool == "test_sync_tool")
{
handlers.push(Arc::new(TestSyncHandler));
executors.push(Arc::new(TestSyncHandler));
}
if config.environment_mode.has_environment() {
let include_environment_id =
matches!(config.environment_mode, ToolEnvironmentMode::Multiple);
handlers.push(Arc::new(ViewImageHandler::new(ViewImageToolOptions {
executors.push(Arc::new(ViewImageHandler::new(ViewImageToolOptions {
can_request_original_image_detail: config.can_request_original_image_detail,
include_environment_id,
})));
@@ -358,7 +368,7 @@ fn collect_handler_tools(
};
let agent_type_description =
agent_type_description(config, params.default_agent_type_description);
handlers.push(multi_agent_v2_handler(
executors.push(multi_agent_v2_handler(
SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
available_models: config.available_models.clone(),
agent_type_description,
@@ -369,18 +379,18 @@ fn collect_handler_tools(
}),
exposure,
));
handlers.push(multi_agent_v2_handler(SendMessageHandlerV2, exposure));
handlers.push(multi_agent_v2_handler(FollowupTaskHandlerV2, exposure));
handlers.push(multi_agent_v2_handler(
executors.push(multi_agent_v2_handler(SendMessageHandlerV2, exposure));
executors.push(multi_agent_v2_handler(FollowupTaskHandlerV2, exposure));
executors.push(multi_agent_v2_handler(
WaitAgentHandlerV2::new(params.wait_agent_timeouts),
exposure,
));
handlers.push(multi_agent_v2_handler(CloseAgentHandlerV2, exposure));
handlers.push(multi_agent_v2_handler(ListAgentsHandlerV2, exposure));
executors.push(multi_agent_v2_handler(CloseAgentHandlerV2, exposure));
executors.push(multi_agent_v2_handler(ListAgentsHandlerV2, exposure));
} else {
let agent_type_description =
agent_type_description(config, params.default_agent_type_description);
handlers.push(Arc::new(SpawnAgentHandler::new(SpawnAgentToolOptions {
executors.push(Arc::new(SpawnAgentHandler::new(SpawnAgentToolOptions {
available_models: config.available_models.clone(),
agent_type_description,
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
@@ -388,29 +398,29 @@ fn collect_handler_tools(
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
})));
handlers.push(Arc::new(SendInputHandler));
handlers.push(Arc::new(ResumeAgentHandler));
handlers.push(Arc::new(WaitAgentHandler::new(params.wait_agent_timeouts)));
handlers.push(Arc::new(CloseAgentHandler));
executors.push(Arc::new(SendInputHandler));
executors.push(Arc::new(ResumeAgentHandler));
executors.push(Arc::new(WaitAgentHandler::new(params.wait_agent_timeouts)));
executors.push(Arc::new(CloseAgentHandler));
}
}
if config.agent_jobs_tools {
handlers.push(Arc::new(SpawnAgentsOnCsvHandler));
executors.push(Arc::new(SpawnAgentsOnCsvHandler));
if config.agent_jobs_worker_tools {
handlers.push(Arc::new(ReportAgentJobResultHandler));
executors.push(Arc::new(ReportAgentJobResultHandler));
}
}
if let Some(mcp_tools) = params.mcp_tools {
for tool in mcp_tools {
handlers.push(Arc::new(McpHandler::new(tool.clone())));
executors.push(Arc::new(McpHandler::new(tool.clone())));
}
}
if let Some(deferred_mcp_tools) = params.deferred_mcp_tools {
for tool in deferred_mcp_tools {
handlers.push(Arc::new(McpHandler::with_exposure(
executors.push(Arc::new(McpHandler::with_exposure(
tool.clone(),
ToolExposure::Deferred,
)));
@@ -426,26 +436,26 @@ fn collect_handler_tools(
continue;
};
handlers.push(handler);
executors.push(handler);
}
append_extension_tool_handlers(config, params.extension_tool_executors, &mut handlers);
append_extension_tool_executors(config, params.extension_tool_executors, &mut executors);
handlers
executors
}
fn append_extension_tool_handlers(
fn append_extension_tool_executors(
config: &ToolsConfig,
executors: &[Arc<dyn ExtensionToolExecutor>],
handlers: &mut Vec<Arc<dyn RegisteredTool>>,
registered_executors: &mut Vec<Arc<dyn RegisteredTool>>,
) {
if executors.is_empty() {
return;
}
let mut reserved_tool_names = handlers
let mut reserved_tool_names = registered_executors
.iter()
.map(|handler| handler.tool_name())
.map(|executor| executor.tool_name())
.collect::<HashSet<_>>();
if config.code_mode_enabled {
reserved_tool_names.insert(ToolName::plain(codex_code_mode::PUBLIC_TOOL_NAME));
@@ -453,9 +463,9 @@ fn append_extension_tool_handlers(
}
if config.search_tool
&& config.namespace_tools
&& handlers
&& registered_executors
.iter()
.any(|handler| handler.exposure() == ToolExposure::Deferred)
.any(|executor| executor.exposure() == ToolExposure::Deferred)
{
reserved_tool_names.insert(ToolName::plain(TOOL_SEARCH_TOOL_NAME));
}
@@ -466,7 +476,7 @@ fn append_extension_tool_handlers(
warn!("Skipping extension tool `{tool_name}`: handler already registered");
continue;
}
handlers.push(Arc::new(ExtensionToolHandler::new(executor)));
registered_executors.push(Arc::new(ExtensionToolHandler::new(executor)));
}
}
+30 -20
View File
@@ -28,6 +28,7 @@ use crate::tools::registry::ToolRegistry;
use codex_app_server_protocol::AppInfo;
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ToolCall as ExtensionToolCall;
use codex_extension_api::ToolExecutor;
use codex_features::Feature;
use codex_features::Features;
use codex_mcp::ToolInfo;
@@ -79,7 +80,10 @@ fn extension_tool_executor(name: &str, description: &str) -> Arc<dyn ExtensionTo
description: String,
}
impl ExtensionToolExecutor for SpecOnlyExtensionExecutor {
#[async_trait::async_trait]
impl ToolExecutor<ExtensionToolCall> for SpecOnlyExtensionExecutor {
type Output = codex_tools::JsonToolOutput;
fn tool_name(&self) -> ToolName {
ToolName::plain(self.name.as_str())
}
@@ -102,8 +106,11 @@ fn extension_tool_executor(name: &str, description: &str) -> Arc<dyn ExtensionTo
}))
}
fn handle(&self, _call: ExtensionToolCall) -> codex_extension_api::ExtensionToolFuture<'_> {
Box::pin(async { panic!("spec planning should not execute extension tools") })
async fn handle(
&self,
_call: ExtensionToolCall,
) -> Result<Self::Output, codex_tools::FunctionCallError> {
panic!("spec planning should not execute extension tools")
}
}
@@ -131,7 +138,7 @@ fn extension_tools_do_not_replace_builtin_tools() {
"update_plan",
"Extension attempt to replace a built-in tool.",
)];
let (tools, _) = build_specs_with_discoverable_tools(
let (tools, _) = build_specs_with_inputs_for_test(
&tools_config,
/*mcp_tools*/ None,
/*deferred_mcp_tools*/ None,
@@ -1882,7 +1889,7 @@ fn request_plugin_install_is_not_registered_without_feature_flag() {
permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
});
let (tools, _) = build_specs_with_discoverable_tools(
let (tools, _) = build_specs_with_inputs_for_test(
&tools_config,
/*mcp_tools*/ None,
/*deferred_mcp_tools*/ None,
@@ -1923,7 +1930,7 @@ fn request_plugin_install_can_be_registered_without_search_tool() {
permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
});
let (tools, _) = build_specs_with_discoverable_tools(
let (tools, _) = build_specs_with_inputs_for_test(
&tools_config,
/*mcp_tools*/ None,
/*deferred_mcp_tools*/ None,
@@ -1992,7 +1999,7 @@ fn request_plugin_install_description_lists_discoverable_tools() {
})),
];
let (tools, registry) = build_specs_with_discoverable_tools(
let (tools, registry) = build_specs_with_inputs_for_test(
&tools_config,
/*mcp_tools*/ None,
/*deferred_mcp_tools*/ None,
@@ -2293,7 +2300,7 @@ fn code_mode_only_exec_description_includes_extension_tool_details() {
"extension_echo",
"Echoes arguments through an extension tool.",
)];
let (tools, _) = build_specs_with_discoverable_tools(
let (tools, _) = build_specs_with_inputs_for_test(
&tools_config,
/*mcp_tools*/ None,
/*deferred_mcp_tools*/ None,
@@ -2391,7 +2398,7 @@ fn build_specs(
deferred_mcp_tools: Option<Vec<ToolInfo>>,
dynamic_tools: &[DynamicToolSpec],
) -> (Vec<ToolSpec>, ToolRegistry) {
build_specs_with_discoverable_tools(
build_specs_with_inputs_for_test(
config,
mcp_tools,
deferred_mcp_tools,
@@ -2401,7 +2408,7 @@ fn build_specs(
)
}
fn build_specs_with_discoverable_tools(
fn build_specs_with_inputs_for_test(
config: &ToolsConfig,
mcp_tools: Option<HashMap<ToolName, rmcp::model::Tool>>,
deferred_mcp_tools: Option<Vec<ToolInfo>>,
@@ -2415,17 +2422,20 @@ fn build_specs_with_discoverable_tools(
.map(|(name, tool)| tool_info_from_parts(name, tool.clone()))
.collect::<Vec<_>>()
});
let builder = build_tool_registry_builder(
let params = ToolRegistryBuildParams {
mcp_tools: mcp_tool_inputs.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: wait_agent_timeout_options(),
};
let executors = collect_tool_executors(config, params);
let builder = build_tool_registry_builder_from_executors(
config,
ToolRegistryBuildParams {
mcp_tools: mcp_tool_inputs.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: wait_agent_timeout_options(),
},
executors,
hosted_model_tool_specs(config),
);
builder.build()
}
+24 -2
View File
@@ -3,7 +3,9 @@ use crate::shell::Shell;
use crate::shell::ShellType;
use crate::test_support::construct_model_info_offline;
use crate::tools::ToolRouter;
use crate::tools::registry::ToolRegistryBuilder;
use crate::tools::router::ToolRouterParams;
use crate::tools::spec_plan::build_tool_registry_builder_from_executors;
use codex_app_server_protocol::AppInfo;
use codex_features::Feature;
use codex_features::Features;
@@ -36,6 +38,7 @@ use core_test_support::assert_regex_match;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use super::*;
@@ -270,7 +273,7 @@ fn build_specs(
deferred_mcp_tools: Option<Vec<ToolInfo>>,
dynamic_tools: &[DynamicToolSpec],
) -> ToolRegistryBuilder {
build_specs_with_discoverable_tools(
build_specs_with_inputs_for_test(
config,
mcp_tools,
deferred_mcp_tools,
@@ -280,6 +283,25 @@ fn build_specs(
)
}
fn build_specs_with_inputs_for_test(
config: &ToolsConfig,
mcp_tools: Option<Vec<ToolInfo>>,
deferred_mcp_tools: Option<Vec<ToolInfo>>,
discoverable_tools: Option<Vec<DiscoverableTool>>,
extension_tool_executors: &[Arc<dyn codex_extension_api::ExtensionToolExecutor>],
dynamic_tools: &[DynamicToolSpec],
) -> ToolRegistryBuilder {
let parts = collect_tool_router_parts(
config,
mcp_tools,
deferred_mcp_tools,
discoverable_tools,
extension_tool_executors,
dynamic_tools,
);
build_tool_registry_builder_from_executors(config, parts.executors, parts.hosted_specs)
}
#[tokio::test]
async fn get_memory_requires_feature_flag() {
let config = test_config().await;
@@ -803,7 +825,7 @@ async fn request_plugin_install_requires_apps_and_plugins_features() {
permission_profile: &PermissionProfile::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
});
let (tools, _) = build_specs_with_discoverable_tools(
let (tools, _) = build_specs_with_inputs_for_test(
&tools_config,
/*mcp_tools*/ None,
/*deferred_mcp_tools*/ None,
@@ -30,6 +30,7 @@ struct TestHandler {
tool_name: codex_tools::ToolName,
}
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for TestHandler {
type Output = FunctionToolOutput;
@@ -18,7 +18,6 @@ pub use thread_lifecycle::ThreadResumeInput;
pub use thread_lifecycle::ThreadStartInput;
pub use thread_lifecycle::ThreadStopInput;
pub use tools::ExtensionToolExecutor;
pub use tools::ExtensionToolFuture;
pub use tools::ExtensionToolOutput;
pub use turn_lifecycle::TurnAbortInput;
pub use turn_lifecycle::TurnStartInput;
@@ -1,32 +1,15 @@
use std::future::Future;
use std::pin::Pin;
use codex_tools::FunctionCallError;
use codex_tools::JsonToolOutput;
use codex_tools::ToolCall;
use codex_tools::ToolName;
use codex_tools::ToolSpec;
use codex_tools::ToolExecutor;
/// Model-facing output returned by extension-owned tools.
pub type ExtensionToolOutput = JsonToolOutput;
/// Future returned by extension-owned tool execution.
pub type ExtensionToolFuture<'a> =
Pin<Box<dyn Future<Output = Result<ExtensionToolOutput, FunctionCallError>> + Send + 'a>>;
/// Object-safe runtime contract for extension-owned model-visible tools.
/// Thin alias for extension-owned executable tools.
///
/// Implementations keep an extension tool's model-visible spec attached to the
/// executable runtime that handles calls for that tool.
pub trait ExtensionToolExecutor: Send + Sync {
/// The concrete tool name handled by this extension runtime.
fn tool_name(&self) -> ToolName;
/// 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> {}
/// The model-visible spec for this extension tool.
fn spec(&self) -> Option<ToolSpec> {
None
}
/// Execute one extension tool invocation.
fn handle(&self, call: ToolCall) -> ExtensionToolFuture<'_>;
}
impl<T> ExtensionToolExecutor for T where T: ToolExecutor<ToolCall, Output = ExtensionToolOutput> {}
+1 -1
View File
@@ -9,6 +9,7 @@ pub use codex_tools::FunctionCallError;
pub use codex_tools::JsonToolOutput;
pub use codex_tools::ResponsesApiTool;
pub use codex_tools::ToolCall;
pub use codex_tools::ToolExecutor;
pub use codex_tools::ToolName;
pub use codex_tools::ToolPayload;
pub use codex_tools::ToolSpec;
@@ -18,7 +19,6 @@ pub use contributors::ApprovalReviewFuture;
pub use contributors::ConfigContributor;
pub use contributors::ContextContributor;
pub use contributors::ExtensionToolExecutor;
pub use contributors::ExtensionToolFuture;
pub use contributors::ExtensionToolOutput;
pub use contributors::PromptFragment;
pub use contributors::PromptSlot;
+1
View File
@@ -13,6 +13,7 @@ doctest = false
workspace = true
[dependencies]
async-trait = { workspace = true }
codex-core = { workspace = true }
codex-extension-api = { workspace = true }
codex-features = { workspace = true }
+23 -20
View File
@@ -1,7 +1,6 @@
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ExtensionToolFuture;
use codex_extension_api::JsonToolOutput;
use codex_extension_api::ToolCall;
use codex_extension_api::ToolExecutor;
use codex_extension_api::ToolName;
use codex_extension_api::ToolSpec;
use schemars::JsonSchema;
@@ -35,10 +34,13 @@ pub(super) struct ListTool<B> {
pub(super) backend: B,
}
impl<B> ExtensionToolExecutor for ListTool<B>
#[async_trait::async_trait]
impl<B> ToolExecutor<ToolCall> for ListTool<B>
where
B: MemoriesBackend,
{
type Output = JsonToolOutput;
fn tool_name(&self) -> ToolName {
memory_tool_name(LIST_TOOL_NAME)
}
@@ -50,23 +52,24 @@ where
))
}
fn handle(&self, call: ToolCall) -> ExtensionToolFuture<'_> {
async fn handle(
&self,
call: ToolCall,
) -> Result<Self::Output, codex_extension_api::FunctionCallError> {
let backend = self.backend.clone();
Box::pin(async move {
let args: ListArgs = parse_args(&call)?;
let response = backend
.list(ListMemoriesRequest {
path: args.path,
cursor: args.cursor,
max_results: clamp_max_results(
args.max_results,
DEFAULT_LIST_MAX_RESULTS,
MAX_LIST_RESULTS,
),
})
.await
.map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response)))
})
let args: ListArgs = parse_args(&call)?;
let response = backend
.list(ListMemoriesRequest {
path: args.path,
cursor: args.cursor,
max_results: clamp_max_results(
args.max_results,
DEFAULT_LIST_MAX_RESULTS,
MAX_LIST_RESULTS,
),
})
.await
.map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response)))
}
}
+20 -17
View File
@@ -1,7 +1,6 @@
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ExtensionToolFuture;
use codex_extension_api::JsonToolOutput;
use codex_extension_api::ToolCall;
use codex_extension_api::ToolExecutor;
use codex_extension_api::ToolName;
use codex_extension_api::ToolSpec;
use schemars::JsonSchema;
@@ -34,10 +33,13 @@ pub(super) struct ReadTool<B> {
pub(super) backend: B,
}
impl<B> ExtensionToolExecutor for ReadTool<B>
#[async_trait::async_trait]
impl<B> ToolExecutor<ToolCall> for ReadTool<B>
where
B: MemoriesBackend,
{
type Output = JsonToolOutput;
fn tool_name(&self) -> ToolName {
memory_tool_name(READ_TOOL_NAME)
}
@@ -49,20 +51,21 @@ where
))
}
fn handle(&self, call: ToolCall) -> ExtensionToolFuture<'_> {
async fn handle(
&self,
call: ToolCall,
) -> Result<Self::Output, codex_extension_api::FunctionCallError> {
let backend = self.backend.clone();
Box::pin(async move {
let args: ReadArgs = parse_args(&call)?;
let response = backend
.read(ReadMemoryRequest {
path: args.path,
line_offset: args.line_offset.unwrap_or(1),
max_lines: args.max_lines,
max_tokens: DEFAULT_READ_MAX_TOKENS,
})
.await
.map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response)))
})
let args: ReadArgs = parse_args(&call)?;
let response = backend
.read(ReadMemoryRequest {
path: args.path,
line_offset: args.line_offset.unwrap_or(1),
max_lines: args.max_lines,
max_tokens: DEFAULT_READ_MAX_TOKENS,
})
.await
.map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response)))
}
}
+15 -12
View File
@@ -1,7 +1,6 @@
use codex_extension_api::ExtensionToolExecutor;
use codex_extension_api::ExtensionToolFuture;
use codex_extension_api::JsonToolOutput;
use codex_extension_api::ToolCall;
use codex_extension_api::ToolExecutor;
use codex_extension_api::ToolName;
use codex_extension_api::ToolSpec;
use schemars::JsonSchema;
@@ -43,10 +42,13 @@ pub(super) struct SearchTool<B> {
pub(super) backend: B,
}
impl<B> ExtensionToolExecutor for SearchTool<B>
#[async_trait::async_trait]
impl<B> ToolExecutor<ToolCall> for SearchTool<B>
where
B: MemoriesBackend,
{
type Output = JsonToolOutput;
fn tool_name(&self) -> ToolName {
memory_tool_name(SEARCH_TOOL_NAME)
}
@@ -58,16 +60,17 @@ where
))
}
fn handle(&self, call: ToolCall) -> ExtensionToolFuture<'_> {
async fn handle(
&self,
call: ToolCall,
) -> Result<Self::Output, codex_extension_api::FunctionCallError> {
let backend = self.backend.clone();
Box::pin(async move {
let args: SearchArgs = parse_args(&call)?;
let response = backend
.search(args.into_request())
.await
.map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response)))
})
let args: SearchArgs = parse_args(&call)?;
let response = backend
.search(args.into_request())
.await
.map_err(backend_error_to_function_call)?;
Ok(JsonToolOutput::new(json!(response)))
}
}
+1
View File
@@ -8,6 +8,7 @@ version.workspace = true
workspace = true
[dependencies]
async-trait = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-code-mode = { workspace = true }
codex-features = { workspace = true }
+2 -6
View File
@@ -1,5 +1,3 @@
use std::future::Future;
use crate::FunctionCallError;
use crate::ToolName;
use crate::ToolOutput;
@@ -36,6 +34,7 @@ impl ToolExposure {
/// Implementations keep the model-visible spec tied to the executable runtime.
/// Host crates can layer routing, hooks, telemetry, or other orchestration on
/// top without reopening the spec/runtime split.
#[async_trait::async_trait]
pub trait ToolExecutor<Invocation>: Send + Sync {
type Output: ToolOutput + 'static;
@@ -54,8 +53,5 @@ pub trait ToolExecutor<Invocation>: Send + Sync {
false
}
fn handle(
&self,
invocation: Invocation,
) -> impl Future<Output = Result<Self::Output, FunctionCallError>> + Send;
async fn handle(&self, invocation: Invocation) -> Result<Self::Output, FunctionCallError>;
}