mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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:
committed by
GitHub
Unverified
parent
6a225e4005
commit
6d65686313
Generated
+2
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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> {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user