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