mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: extract shared tool executor interface (#22359)
## Why Codex still models model-visible tools and executable behavior largely inside `codex-core`, which makes it harder to evolve the tool system toward a single reusable abstraction for built-ins, MCP-backed tools, dynamic tools, and later tools injected from outside core. This PR takes the next incremental step in that direction by moving the common execution-facing pieces out of core and separating them from core-only orchestration. The intent is to let shared tool abstractions improve in one place, while `codex-core` keeps the parts that are still inherently host-specific today, such as `ToolInvocation`, dispatch wiring, and hook integration. This PR is mostly moving things around. The only interesting piece is this abstraction: https://github.com/openai/codex/pull/22359/changes#diff-81af519002548ba51ed102bdaaf77e081d40a1e73a6e5f9b104bbbc96a6f1b3dR13 ## What changed - Added `codex_tools::ToolExecutor<Invocation>` as the shared execution trait for model-visible tools. - Moved the reusable execution support types from `codex-core` into `codex-tools`: - `FunctionCallError` - `ToolPayload` - `ToolOutput` - Refactored core tool implementations so that execution behavior lives on `ToolExecutor<ToolInvocation>`, while `ToolHandler` remains the core-local extension point for hook payloads, telemetry tags, diff consumers, and other orchestration concerns. - Kept the registry and dispatch flow behaviorally unchanged while making the shared/extracted boundary explicit across built-in, MCP, dynamic, extension-backed, shell, and multi-agent tool handlers. ## Verification - `cargo test -p codex-tools` - `just fix -p codex-tools` - `just fix -p codex-core` - `cargo test -p codex-core` progressed through the updated tool surfaces and then hit the existing unrelated multi-agent stack overflow in `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`.
This commit is contained in:
Generated
+2
@@ -3729,10 +3729,12 @@ dependencies = [
|
||||
"codex-protocol",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-pty",
|
||||
"codex-utils-string",
|
||||
"pretty_assertions",
|
||||
"rmcp",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,11 +1 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum FunctionCallError {
|
||||
#[error("{0}")]
|
||||
RespondToModel(String),
|
||||
#[error("LocalShellCall without call_id or id")]
|
||||
MissingLocalShellCallId,
|
||||
#[error("Fatal error: {0}")]
|
||||
Fatal(String),
|
||||
}
|
||||
pub use codex_tools::FunctionCallError;
|
||||
|
||||
@@ -71,7 +71,7 @@ use crate::tools::handlers::CreateGoalHandler;
|
||||
use crate::tools::handlers::ExecCommandHandler;
|
||||
use crate::tools::handlers::ShellHandler;
|
||||
use crate::tools::handlers::UpdateGoalHandler;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::router::ToolCallSource;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -86,7 +87,7 @@ impl CodeModeExecuteHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for CodeModeExecuteHandler {
|
||||
impl ToolExecutor<ToolInvocation> for CodeModeExecuteHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -97,10 +98,6 @@ impl ToolHandler for CodeModeExecuteHandler {
|
||||
Some(self.spec.clone())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Custom { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -121,3 +118,9 @@ impl ToolHandler for CodeModeExecuteHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for CodeModeExecuteHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Custom { .. })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -40,7 +41,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
impl ToolHandler for CodeModeWaitHandler {
|
||||
impl ToolExecutor<ToolInvocation> for CodeModeWaitHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -105,3 +106,5 @@ impl ToolHandler for CodeModeWaitHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for CodeModeWaitHandler {}
|
||||
|
||||
@@ -8,13 +8,10 @@ use crate::tools::TELEMETRY_PREVIEW_TRUNCATION_NOTICE;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use crate::unified_exec::resolve_max_tokens;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::SearchToolCallParams;
|
||||
use codex_protocol::models::ShellToolCallParams;
|
||||
use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_tools::LoadableToolSpec;
|
||||
use codex_tools::ToolName;
|
||||
@@ -23,12 +20,14 @@ use codex_utils_output_truncation::formatted_truncate_text;
|
||||
use codex_utils_string::take_bytes_at_char_boundary;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub use codex_tools::ToolOutput;
|
||||
pub use codex_tools::ToolPayload;
|
||||
|
||||
pub type SharedTurnDiffTracker = Arc<Mutex<TurnDiffTracker>>;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@@ -56,73 +55,6 @@ pub struct ToolInvocation {
|
||||
pub payload: ToolPayload,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ToolPayload {
|
||||
Function { arguments: String },
|
||||
ToolSearch { arguments: SearchToolCallParams },
|
||||
Custom { input: String },
|
||||
LocalShell { params: ShellToolCallParams },
|
||||
}
|
||||
|
||||
impl ToolPayload {
|
||||
pub fn log_payload(&self) -> Cow<'_, str> {
|
||||
match self {
|
||||
ToolPayload::Function { arguments } => Cow::Borrowed(arguments),
|
||||
ToolPayload::ToolSearch { arguments } => Cow::Owned(arguments.query.clone()),
|
||||
ToolPayload::Custom { input } => Cow::Borrowed(input),
|
||||
ToolPayload::LocalShell { params } => Cow::Owned(params.command.join(" ")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToolOutput: Send {
|
||||
fn log_preview(&self) -> String;
|
||||
|
||||
fn success_for_logging(&self) -> bool;
|
||||
|
||||
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem;
|
||||
|
||||
/// Returns the stable value exposed to `PostToolUse` hooks for this tool output.
|
||||
///
|
||||
/// Tool handlers decide whether a tool participates in `PostToolUse`, but
|
||||
/// this method lets the output type own any conversion from model-facing
|
||||
/// response content to hook-facing data. Returning `None` means the output
|
||||
/// should not produce a post-use hook payload, not merely that the tool had
|
||||
/// empty output.
|
||||
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue> {
|
||||
None
|
||||
}
|
||||
|
||||
fn code_mode_result(&self, payload: &ToolPayload) -> JsonValue {
|
||||
response_input_to_code_mode_result(self.to_response_item("", payload))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolOutput for CallToolResult {
|
||||
fn log_preview(&self) -> String {
|
||||
let output = self.as_function_call_output_payload();
|
||||
let preview = output.body.to_text().unwrap_or_else(|| output.to_string());
|
||||
telemetry_preview(&preview)
|
||||
}
|
||||
|
||||
fn success_for_logging(&self) -> bool {
|
||||
self.success()
|
||||
}
|
||||
|
||||
fn to_response_item(&self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem {
|
||||
ResponseInputItem::McpToolCallOutput {
|
||||
call_id: call_id.to_string(),
|
||||
output: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue {
|
||||
serde_json::to_value(self).unwrap_or_else(|err| {
|
||||
JsonValue::String(format!("failed to serialize mcp result: {err}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct McpToolOutput {
|
||||
pub result: CallToolResult,
|
||||
@@ -469,62 +401,6 @@ impl ExecCommandToolOutput {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn response_input_to_code_mode_result(response: ResponseInputItem) -> JsonValue {
|
||||
match response {
|
||||
ResponseInputItem::Message { content, .. } => content_items_to_code_mode_result(
|
||||
&content
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
codex_protocol::models::ContentItem::InputText { text }
|
||||
| codex_protocol::models::ContentItem::OutputText { text } => {
|
||||
FunctionCallOutputContentItem::InputText { text }
|
||||
}
|
||||
codex_protocol::models::ContentItem::InputImage { image_url, detail } => {
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
image_url,
|
||||
detail: detail.or(Some(DEFAULT_IMAGE_DETAIL)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
ResponseInputItem::FunctionCallOutput { output, .. }
|
||||
| ResponseInputItem::CustomToolCallOutput { output, .. } => match output.body {
|
||||
FunctionCallOutputBody::Text(text) => JsonValue::String(text),
|
||||
FunctionCallOutputBody::ContentItems(items) => {
|
||||
content_items_to_code_mode_result(&items)
|
||||
}
|
||||
},
|
||||
ResponseInputItem::ToolSearchOutput { tools, .. } => JsonValue::Array(tools),
|
||||
ResponseInputItem::McpToolCallOutput { output, .. } => {
|
||||
output.code_mode_result(&ToolPayload::Function {
|
||||
arguments: String::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn content_items_to_code_mode_result(items: &[FunctionCallOutputContentItem]) -> JsonValue {
|
||||
JsonValue::String(
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
FunctionCallOutputContentItem::InputText { text } if !text.trim().is_empty() => {
|
||||
Some(text.clone())
|
||||
}
|
||||
FunctionCallOutputContentItem::InputImage { image_url, .. }
|
||||
if !image_url.trim().is_empty() =>
|
||||
{
|
||||
Some(image_url.clone())
|
||||
}
|
||||
FunctionCallOutputContentItem::InputText { .. }
|
||||
| FunctionCallOutputContentItem::InputImage { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
fn function_tool_response(
|
||||
call_id: &str,
|
||||
payload: &ToolPayload,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
|
||||
use codex_protocol::models::SearchToolCallParams;
|
||||
use core_test_support::assert_regex_match;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::agent_jobs_spec::create_report_agent_job_result_tool;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -11,7 +12,7 @@ use super::*;
|
||||
|
||||
pub struct ReportAgentJobResultHandler;
|
||||
|
||||
impl ToolHandler for ReportAgentJobResultHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ReportAgentJobResultHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -22,10 +23,6 @@ impl ToolHandler for ReportAgentJobResultHandler {
|
||||
Some(create_report_agent_job_result_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session, payload, ..
|
||||
@@ -44,6 +41,12 @@ impl ToolHandler for ReportAgentJobResultHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ReportAgentJobResultHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle(
|
||||
session: Arc<Session>,
|
||||
arguments: String,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::agent_jobs_spec::create_spawn_agents_on_csv_tool;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -11,7 +12,7 @@ use super::*;
|
||||
|
||||
pub struct SpawnAgentsOnCsvHandler;
|
||||
|
||||
impl ToolHandler for SpawnAgentsOnCsvHandler {
|
||||
impl ToolExecutor<ToolInvocation> for SpawnAgentsOnCsvHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -22,10 +23,6 @@ impl ToolHandler for SpawnAgentsOnCsvHandler {
|
||||
Some(create_spawn_agents_on_csv_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -47,6 +44,12 @@ impl ToolHandler for SpawnAgentsOnCsvHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for SpawnAgentsOnCsvHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new agent job from a CSV and run it to completion.
|
||||
///
|
||||
/// Each CSV row becomes a job item. The instruction string is a template where `{column}`
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::tools::orchestrator::ToolOrchestrator;
|
||||
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;
|
||||
@@ -296,7 +297,7 @@ async fn effective_patch_permissions(
|
||||
)
|
||||
}
|
||||
|
||||
impl ToolHandler for ApplyPatchHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ApplyPatchHandler {
|
||||
type Output = ApplyPatchToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -307,53 +308,6 @@ impl ToolHandler for ApplyPatchHandler {
|
||||
Some(create_apply_patch_freeform_tool(self.multi_environment))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Custom { .. })
|
||||
}
|
||||
|
||||
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
|
||||
Some(Box::<ApplyPatchArgumentDiffConsumer>::default())
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
apply_patch_payload_command(&invocation.payload).map(|command| PreToolUsePayload {
|
||||
tool_name: HookToolName::apply_patch(),
|
||||
tool_input: serde_json::json!({ "command": command }),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let patch = updated_hook_command(&updated_input)?;
|
||||
invocation.payload = match invocation.payload {
|
||||
ToolPayload::Custom { .. } => ToolPayload::Custom {
|
||||
input: patch.to_string(),
|
||||
},
|
||||
payload => payload,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
let tool_response =
|
||||
result.post_tool_use_response(&invocation.call_id, &invocation.payload)?;
|
||||
Some(PostToolUsePayload {
|
||||
tool_name: HookToolName::apply_patch(),
|
||||
tool_use_id: invocation.call_id.clone(),
|
||||
tool_input: serde_json::json!({
|
||||
"command": apply_patch_payload_command(&invocation.payload)?,
|
||||
}),
|
||||
tool_response,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -484,6 +438,55 @@ impl ToolHandler for ApplyPatchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ApplyPatchHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Custom { .. })
|
||||
}
|
||||
|
||||
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
|
||||
Some(Box::<ApplyPatchArgumentDiffConsumer>::default())
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
apply_patch_payload_command(&invocation.payload).map(|command| PreToolUsePayload {
|
||||
tool_name: HookToolName::apply_patch(),
|
||||
tool_input: serde_json::json!({ "command": command }),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let patch = updated_hook_command(&updated_input)?;
|
||||
invocation.payload = match invocation.payload {
|
||||
ToolPayload::Custom { .. } => ToolPayload::Custom {
|
||||
input: patch.to_string(),
|
||||
},
|
||||
payload => payload,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
let tool_response =
|
||||
result.post_tool_use_response(&invocation.call_id, &invocation.payload)?;
|
||||
Some(PostToolUsePayload {
|
||||
tool_name: HookToolName::apply_patch(),
|
||||
tool_use_id: invocation.call_id.clone(),
|
||||
tool_input: serde_json::json!({
|
||||
"command": apply_patch_payload_command(&invocation.payload)?,
|
||||
}),
|
||||
tool_response,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn intercept_apply_patch(
|
||||
command: &[String],
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::tool_search_entry::ToolSearchInfo;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
@@ -52,7 +53,7 @@ impl DynamicToolHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for DynamicToolHandler {
|
||||
impl ToolExecutor<ToolInvocation> for DynamicToolHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -63,17 +64,6 @@ impl ToolHandler for DynamicToolHandler {
|
||||
self.spec.clone()
|
||||
}
|
||||
|
||||
fn search_info(&self) -> Option<ToolSearchInfo> {
|
||||
ToolSearchInfo::from_spec(
|
||||
self.search_text.clone(),
|
||||
self.spec()?,
|
||||
Some(ToolSearchSourceInfo {
|
||||
name: "Dynamic tools".to_string(),
|
||||
description: Some("Tools provided by the current Codex thread.".to_string()),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -119,6 +109,19 @@ impl ToolHandler for DynamicToolHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for DynamicToolHandler {
|
||||
fn search_info(&self) -> Option<ToolSearchInfo> {
|
||||
ToolSearchInfo::from_spec(
|
||||
self.search_text.clone(),
|
||||
self.spec()?,
|
||||
Some(ToolSearchSourceInfo {
|
||||
name: "Dynamic tools".to_string(),
|
||||
description: Some("Tools provided by the current Codex thread.".to_string()),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::await_holding_invalid_type,
|
||||
reason = "active turn checks and dynamic tool response registration must remain atomic"
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::tools::flat_tool_name;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
||||
pub(crate) struct BundledToolOutput {
|
||||
@@ -68,7 +69,7 @@ impl BundledToolHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for BundledToolHandler {
|
||||
impl ToolExecutor<ToolInvocation> for BundledToolHandler {
|
||||
type Output = BundledToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -79,6 +80,30 @@ impl ToolHandler for BundledToolHandler {
|
||||
Some(self.spec.clone())
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let arguments = self
|
||||
.arguments_from_payload(&invocation.payload)
|
||||
.ok_or_else(|| {
|
||||
FunctionCallError::Fatal(format!(
|
||||
"tool {} invoked with incompatible payload",
|
||||
self.bundle.tool_name()
|
||||
))
|
||||
})?
|
||||
.to_string();
|
||||
let value = self
|
||||
.bundle
|
||||
.executor()
|
||||
.execute(codex_tool_api::ToolCall {
|
||||
call_id: invocation.call_id,
|
||||
arguments,
|
||||
})
|
||||
.await
|
||||
.map_err(map_extension_tool_error)?;
|
||||
Ok(BundledToolOutput { value })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for BundledToolHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
self.arguments_from_payload(payload).is_some()
|
||||
}
|
||||
@@ -105,28 +130,6 @@ impl ToolHandler for BundledToolHandler {
|
||||
.post_tool_use_response(&invocation.call_id, &invocation.payload)?,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let arguments = self
|
||||
.arguments_from_payload(&invocation.payload)
|
||||
.ok_or_else(|| {
|
||||
FunctionCallError::Fatal(format!(
|
||||
"tool {} invoked with incompatible payload",
|
||||
self.bundle.tool_name()
|
||||
))
|
||||
})?
|
||||
.to_string();
|
||||
let value = self
|
||||
.bundle
|
||||
.executor()
|
||||
.execute(codex_tool_api::ToolCall {
|
||||
call_id: invocation.call_id,
|
||||
arguments,
|
||||
})
|
||||
.await
|
||||
.map_err(map_extension_tool_error)?;
|
||||
Ok(BundledToolOutput { value })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extension_tool_spec(
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::tools::context::ToolPayload;
|
||||
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::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -17,7 +18,7 @@ use super::goal_response;
|
||||
|
||||
pub struct CreateGoalHandler;
|
||||
|
||||
impl ToolHandler for CreateGoalHandler {
|
||||
impl ToolExecutor<ToolInvocation> for CreateGoalHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -71,3 +72,5 @@ impl ToolHandler for CreateGoalHandler {
|
||||
goal_response(Some(goal), CompletionBudgetReport::Omit)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for CreateGoalHandler {}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::goal_spec::GET_GOAL_TOOL_NAME;
|
||||
use crate::tools::handlers::goal_spec::create_get_goal_tool;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -14,7 +15,7 @@ use super::goal_response;
|
||||
|
||||
pub struct GetGoalHandler;
|
||||
|
||||
impl ToolHandler for GetGoalHandler {
|
||||
impl ToolExecutor<ToolInvocation> for GetGoalHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -44,3 +45,5 @@ impl ToolHandler for GetGoalHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for GetGoalHandler {}
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::tools::context::ToolPayload;
|
||||
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::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_protocol::protocol::ThreadGoalStatus;
|
||||
use codex_tools::ToolName;
|
||||
@@ -19,7 +20,7 @@ use super::goal_response;
|
||||
|
||||
pub struct UpdateGoalHandler;
|
||||
|
||||
impl ToolHandler for UpdateGoalHandler {
|
||||
impl ToolExecutor<ToolInvocation> for UpdateGoalHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -74,3 +75,5 @@ impl ToolHandler for UpdateGoalHandler {
|
||||
goal_response(Some(goal), CompletionBudgetReport::Include)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for UpdateGoalHandler {}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::tools::flat_tool_name;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::registry::PostToolUsePayload;
|
||||
use crate::tools::registry::PreToolUsePayload;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolTelemetryTags;
|
||||
use crate::tools::tool_search_entry::ToolSearchInfo;
|
||||
@@ -35,7 +36,7 @@ impl McpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for McpHandler {
|
||||
impl ToolExecutor<ToolInvocation> for McpHandler {
|
||||
type Output = McpToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -70,6 +71,51 @@ impl ToolHandler for McpHandler {
|
||||
}))
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.tool_info.supports_parallel_tool_calls
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
call_id,
|
||||
payload,
|
||||
..
|
||||
} = invocation;
|
||||
|
||||
let payload = match payload {
|
||||
ToolPayload::Function { arguments } => arguments,
|
||||
_ => {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"mcp handler received unsupported payload".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let started = Instant::now();
|
||||
let result = handle_mcp_tool_call(
|
||||
Arc::clone(&session),
|
||||
&turn,
|
||||
call_id.clone(),
|
||||
self.tool_info.server_name.clone(),
|
||||
self.tool_info.tool.name.to_string(),
|
||||
self.tool_name().to_string(),
|
||||
payload,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(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 {
|
||||
fn search_info(&self) -> Option<ToolSearchInfo> {
|
||||
let source_name = self
|
||||
.tool_info
|
||||
@@ -96,10 +142,6 @@ impl ToolHandler for McpHandler {
|
||||
)
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.tool_info.supports_parallel_tool_calls
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -159,45 +201,6 @@ impl ToolHandler for McpHandler {
|
||||
tool_response,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
call_id,
|
||||
payload,
|
||||
..
|
||||
} = invocation;
|
||||
|
||||
let payload = match payload {
|
||||
ToolPayload::Function { arguments } => arguments,
|
||||
_ => {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"mcp handler received unsupported payload".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let started = Instant::now();
|
||||
let result = handle_mcp_tool_call(
|
||||
Arc::clone(&session),
|
||||
&turn,
|
||||
call_id.clone(),
|
||||
self.tool_info.server_name.clone(),
|
||||
self.tool_info.tool.name.to_string(),
|
||||
self.tool_name().to_string(),
|
||||
payload,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_hook_tool_input(raw_arguments: &str) -> Value {
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resource_templates_tool;
|
||||
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;
|
||||
@@ -25,7 +26,7 @@ use super::serialize_function_output;
|
||||
|
||||
pub struct ListMcpResourceTemplatesHandler;
|
||||
|
||||
impl ToolHandler for ListMcpResourceTemplatesHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ListMcpResourceTemplatesHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -163,3 +164,5 @@ impl ToolHandler for ListMcpResourceTemplatesHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ListMcpResourceTemplatesHandler {}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::mcp_resource_spec::create_list_mcp_resources_tool;
|
||||
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;
|
||||
@@ -25,7 +26,7 @@ use super::serialize_function_output;
|
||||
|
||||
pub struct ListMcpResourcesHandler;
|
||||
|
||||
impl ToolHandler for ListMcpResourcesHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ListMcpResourcesHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -161,3 +162,5 @@ impl ToolHandler for ListMcpResourcesHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ListMcpResourcesHandler {}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::mcp_resource_spec::create_read_mcp_resource_tool;
|
||||
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;
|
||||
@@ -25,7 +26,7 @@ use super::serialize_function_output;
|
||||
|
||||
pub struct ReadMcpResourceHandler;
|
||||
|
||||
impl ToolHandler for ReadMcpResourceHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -144,3 +145,5 @@ impl ToolHandler for ReadMcpResourceHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ReadMcpResourceHandler {}
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
pub(crate) use crate::tools::handlers::multi_agents_common::*;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
|
||||
@@ -5,7 +5,7 @@ use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = CloseAgentResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -16,10 +16,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_close_agent_tool_v1())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
@@ -111,6 +107,12 @@ async fn handle_close_agent(
|
||||
})
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub(crate) struct CloseAgentResult {
|
||||
pub(crate) previous_status: AgentStatus,
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::sync::Arc;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = ResumeAgentResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -18,10 +18,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_resume_agent_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
@@ -139,6 +135,12 @@ async fn handle_resume_agent(
|
||||
Ok(ResumeAgentResult { status })
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ResumeAgentArgs {
|
||||
id: String,
|
||||
|
||||
@@ -6,7 +6,7 @@ use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = SendInputResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -17,10 +17,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_send_input_tool_v1())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -92,6 +88,12 @@ impl ToolHandler for Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SendInputArgs {
|
||||
target: String,
|
||||
|
||||
@@ -22,7 +22,7 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = SpawnAgentResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -33,10 +33,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_spawn_agent_tool_v1(self.options.clone()))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
@@ -197,6 +193,12 @@ async fn handle_spawn_agent(
|
||||
})
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SpawnAgentArgs {
|
||||
message: Option<String>,
|
||||
|
||||
@@ -27,7 +27,7 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = WaitAgentResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -38,10 +38,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_wait_agent_tool_v1(self.options))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -204,6 +200,12 @@ impl ToolHandler for Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct WaitArgs {
|
||||
#[serde(default)]
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::multi_agents_common::*;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
|
||||
@@ -5,7 +5,7 @@ use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = CloseAgentResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -16,10 +16,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_close_agent_tool_v2())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
@@ -123,6 +119,12 @@ async fn handle_close_agent(
|
||||
})
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct CloseAgentArgs {
|
||||
|
||||
@@ -8,7 +8,7 @@ use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -19,10 +19,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_followup_task_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let arguments = function_arguments(invocation.payload.clone())?;
|
||||
let args: FollowupTaskArgs = parse_arguments(&arguments)?;
|
||||
@@ -35,3 +31,9 @@ impl ToolHandler for Handler {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = ListAgentsResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -16,10 +16,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_list_agents_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -44,6 +40,12 @@ impl ToolHandler for Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct ListAgentsArgs {
|
||||
|
||||
@@ -8,7 +8,7 @@ use codex_tools::ToolSpec;
|
||||
|
||||
pub(crate) struct Handler;
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -19,10 +19,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_send_message_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let arguments = function_arguments(invocation.payload.clone())?;
|
||||
let args: SendMessageArgs = parse_arguments(&arguments)?;
|
||||
@@ -35,3 +31,9 @@ impl ToolHandler for Handler {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = SpawnAgentResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -35,10 +35,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_spawn_agent_tool_v2(self.options.clone()))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
@@ -228,6 +224,12 @@ async fn handle_spawn_agent(
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct SpawnAgentArgs {
|
||||
|
||||
@@ -19,7 +19,7 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
impl ToolExecutor<ToolInvocation> for Handler {
|
||||
type Output = WaitAgentResult;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -30,10 +30,6 @@ impl ToolHandler for Handler {
|
||||
Some(create_wait_agent_tool_v2(self.options))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -101,6 +97,12 @@ impl ToolHandler for Handler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for Handler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct WaitArgs {
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::plan_spec::create_update_plan_tool;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
@@ -43,7 +44,7 @@ impl ToolOutput for PlanToolOutput {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for PlanHandler {
|
||||
impl ToolExecutor<ToolInvocation> for PlanHandler {
|
||||
type Output = PlanToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -87,6 +88,8 @@ impl ToolHandler for PlanHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for PlanHandler {}
|
||||
|
||||
fn parse_update_plan_arguments(arguments: &str) -> Result<UpdatePlanArgs, FunctionCallError> {
|
||||
serde_json::from_str::<UpdatePlanArgs>(arguments).map_err(|e| {
|
||||
FunctionCallError::RespondToModel(format!("failed to parse function arguments: {e}"))
|
||||
|
||||
@@ -8,13 +8,14 @@ use crate::tools::context::ToolPayload;
|
||||
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::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
|
||||
pub struct RequestPermissionsHandler;
|
||||
|
||||
impl ToolHandler for RequestPermissionsHandler {
|
||||
impl ToolExecutor<ToolInvocation> for RequestPermissionsHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -75,3 +76,5 @@ impl ToolHandler for RequestPermissionsHandler {
|
||||
Ok(FunctionToolOutput::from_text(content, Some(true)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestPermissionsHandler {}
|
||||
|
||||
@@ -34,6 +34,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::request_plugin_install_spec::create_request_plugin_install_tool;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -49,7 +50,7 @@ impl RequestPluginInstallHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestPluginInstallHandler {
|
||||
impl ToolExecutor<ToolInvocation> for RequestPluginInstallHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -192,6 +193,8 @@ impl ToolHandler for RequestPluginInstallHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestPluginInstallHandler {}
|
||||
|
||||
async fn maybe_persist_disabled_install_request(
|
||||
session: &crate::session::session::Session,
|
||||
turn: &crate::session::turn_context::TurnContext,
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::tools::handlers::request_user_input_spec::create_request_user_input_t
|
||||
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::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::request_user_input::RequestUserInputArgs;
|
||||
@@ -18,7 +19,7 @@ pub struct RequestUserInputHandler {
|
||||
pub available_modes: Vec<ModeKind>,
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestUserInputHandler {
|
||||
impl ToolExecutor<ToolInvocation> for RequestUserInputHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -82,6 +83,8 @@ impl ToolHandler for RequestUserInputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for RequestUserInputHandler {}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "request_user_input_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::resolve_workdir_base_path;
|
||||
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;
|
||||
|
||||
@@ -21,37 +22,13 @@ use super::shell_handler::ShellHandler;
|
||||
|
||||
pub struct ContainerExecHandler;
|
||||
|
||||
impl ToolHandler for ContainerExecHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ContainerExecHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolName::plain("container.exec")
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
shell_function_pre_tool_use_payload(invocation)
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
rewrite_shell_function_updated_hook_input(invocation, updated_input, "container.exec")
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
shell_function_post_tool_use_payload(invocation, result)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -92,3 +69,29 @@ impl ToolHandler for ContainerExecHandler {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ContainerExecHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
shell_function_pre_tool_use_payload(invocation)
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
rewrite_shell_function_updated_hook_input(invocation, updated_input, "container.exec")
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
shell_function_post_tool_use_payload(invocation, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::tools::handlers::updated_hook_command;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
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;
|
||||
@@ -30,7 +31,7 @@ impl LocalShellHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for LocalShellHandler {
|
||||
impl ToolExecutor<ToolInvocation> for LocalShellHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -41,14 +42,50 @@ impl ToolHandler for LocalShellHandler {
|
||||
self.include_spec.then(create_local_shell_tool)
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::LocalShell { .. })
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.include_spec
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
tracker,
|
||||
call_id,
|
||||
payload,
|
||||
..
|
||||
} = invocation;
|
||||
|
||||
let ToolPayload::LocalShell { params } = payload else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"unsupported payload for local_shell handler".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let exec_params =
|
||||
ShellHandler::to_exec_params(¶ms, turn.as_ref(), session.conversation_id);
|
||||
run_exec_like(RunExecLikeArgs {
|
||||
tool_name: ToolName::plain("local_shell"),
|
||||
exec_params,
|
||||
hook_command: codex_shell_command::parse_command::shlex_join(¶ms.command),
|
||||
additional_permissions: None,
|
||||
prefix_rule: None,
|
||||
session,
|
||||
turn,
|
||||
tracker,
|
||||
call_id,
|
||||
freeform: false,
|
||||
shell_runtime_backend: ShellRuntimeBackend::Generic,
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for LocalShellHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::LocalShell { .. })
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
local_shell_payload_command(&invocation.payload).map(|command| PreToolUsePayload {
|
||||
tool_name: HookToolName::bash(),
|
||||
@@ -91,38 +128,4 @@ impl ToolHandler for LocalShellHandler {
|
||||
tool_response,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
tracker,
|
||||
call_id,
|
||||
payload,
|
||||
..
|
||||
} = invocation;
|
||||
|
||||
let ToolPayload::LocalShell { params } = payload else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"unsupported payload for local_shell handler".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let exec_params =
|
||||
ShellHandler::to_exec_params(¶ms, turn.as_ref(), session.conversation_id);
|
||||
run_exec_like(RunExecLikeArgs {
|
||||
tool_name: ToolName::plain("local_shell"),
|
||||
exec_params,
|
||||
hook_command: codex_shell_command::parse_command::shlex_join(¶ms.command),
|
||||
additional_permissions: None,
|
||||
prefix_rule: None,
|
||||
session,
|
||||
turn,
|
||||
tracker,
|
||||
call_id,
|
||||
freeform: false,
|
||||
shell_runtime_backend: ShellRuntimeBackend::Generic,
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::tools::handlers::updated_hook_command;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
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;
|
||||
@@ -124,7 +125,7 @@ impl From<ShellCommandBackendConfig> for ShellCommandHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ShellCommandHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ShellCommandHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -140,58 +141,10 @@ impl ToolHandler for ShellCommandHandler {
|
||||
})
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.options.is_some()
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
shell_command_payload_command(&invocation.payload).map(|command| PreToolUsePayload {
|
||||
tool_name: HookToolName::bash(),
|
||||
tool_input: serde_json::json!({ "command": command }),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let ToolPayload::Function { arguments } = invocation.payload else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"hook input rewrite received unsupported shell_command payload".to_string(),
|
||||
));
|
||||
};
|
||||
invocation.payload = ToolPayload::Function {
|
||||
arguments: rewrite_function_string_argument(
|
||||
&arguments,
|
||||
"shell_command",
|
||||
"command",
|
||||
updated_hook_command(&updated_input)?,
|
||||
)?,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
let tool_response =
|
||||
result.post_tool_use_response(&invocation.call_id, &invocation.payload)?;
|
||||
let command = shell_command_payload_command(&invocation.payload)?;
|
||||
Some(PostToolUsePayload {
|
||||
tool_name: HookToolName::bash(),
|
||||
tool_use_id: invocation.call_id.clone(),
|
||||
tool_input: serde_json::json!({ "command": command }),
|
||||
tool_response,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -243,3 +196,53 @@ impl ToolHandler for ShellCommandHandler {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ShellCommandHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
shell_command_payload_command(&invocation.payload).map(|command| PreToolUsePayload {
|
||||
tool_name: HookToolName::bash(),
|
||||
tool_input: serde_json::json!({ "command": command }),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let ToolPayload::Function { arguments } = invocation.payload else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"hook input rewrite received unsupported shell_command payload".to_string(),
|
||||
));
|
||||
};
|
||||
invocation.payload = ToolPayload::Function {
|
||||
arguments: rewrite_function_string_argument(
|
||||
&arguments,
|
||||
"shell_command",
|
||||
"command",
|
||||
updated_hook_command(&updated_input)?,
|
||||
)?,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
let tool_response =
|
||||
result.post_tool_use_response(&invocation.call_id, &invocation.payload)?;
|
||||
let command = shell_command_payload_command(&invocation.payload)?;
|
||||
Some(PostToolUsePayload {
|
||||
tool_name: HookToolName::bash(),
|
||||
tool_use_id: invocation.call_id.clone(),
|
||||
tool_input: serde_json::json!({ "command": command }),
|
||||
tool_response,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::resolve_workdir_base_path;
|
||||
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;
|
||||
@@ -62,7 +63,7 @@ impl ShellHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ShellHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ShellHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -73,34 +74,10 @@ impl ToolHandler for ShellHandler {
|
||||
self.options.map(create_shell_tool)
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.options.is_some()
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
shell_function_pre_tool_use_payload(invocation)
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
rewrite_shell_function_updated_hook_input(invocation, updated_input, "shell")
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
shell_function_post_tool_use_payload(invocation, result)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -141,3 +118,29 @@ impl ToolHandler for ShellHandler {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ShellHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
shell_function_pre_tool_use_payload(invocation)
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
rewrite_shell_function_updated_hook_input(invocation, updated_input, "shell")
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
shell_function_post_tool_use_payload(invocation, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::test_sync_spec::create_test_sync_tool;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -55,7 +56,7 @@ fn barrier_map() -> &'static tokio::sync::Mutex<HashMap<String, BarrierState>> {
|
||||
BARRIERS.get_or_init(|| tokio::sync::Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
impl ToolHandler for TestSyncHandler {
|
||||
impl ToolExecutor<ToolInvocation> for TestSyncHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -104,6 +105,8 @@ impl ToolHandler for TestSyncHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for TestSyncHandler {}
|
||||
|
||||
async fn wait_on_barrier(args: BarrierArgs) -> Result<(), FunctionCallError> {
|
||||
if args.participants == 0 {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::context::ToolSearchOutput;
|
||||
use crate::tools::handlers::tool_search_spec::create_tool_search_tool;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::tool_search_entry::ToolSearchEntry;
|
||||
use crate::tools::tool_search_entry::ToolSearchInfo;
|
||||
@@ -51,7 +52,7 @@ impl ToolSearchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ToolSearchHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ToolSearchHandler {
|
||||
type Output = ToolSearchOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -108,6 +109,8 @@ impl ToolHandler for ToolSearchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ToolSearchHandler {}
|
||||
|
||||
impl ToolSearchHandler {
|
||||
fn search(
|
||||
&self,
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::tools::handlers::updated_hook_command;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
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;
|
||||
@@ -67,7 +68,7 @@ impl ExecCommandHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ExecCommandHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
|
||||
type Output = ExecCommandToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -84,56 +85,10 @@ impl ToolHandler for ExecCommandHandler {
|
||||
))
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
let ToolPayload::Function { arguments } = &invocation.payload else {
|
||||
return None;
|
||||
};
|
||||
|
||||
parse_arguments::<ExecCommandArgs>(arguments)
|
||||
.ok()
|
||||
.map(|args| PreToolUsePayload {
|
||||
tool_name: HookToolName::bash(),
|
||||
tool_input: serde_json::json!({ "command": args.cmd }),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let ToolPayload::Function { arguments } = invocation.payload else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"hook input rewrite received unsupported exec_command payload".to_string(),
|
||||
));
|
||||
};
|
||||
invocation.payload = ToolPayload::Function {
|
||||
arguments: rewrite_function_string_argument(
|
||||
&arguments,
|
||||
"exec_command",
|
||||
"cmd",
|
||||
updated_hook_command(&updated_input)?,
|
||||
)?,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
post_unified_exec_tool_use_payload(invocation, result)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -339,6 +294,54 @@ impl ToolHandler for ExecCommandHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ExecCommandHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
let ToolPayload::Function { arguments } = &invocation.payload else {
|
||||
return None;
|
||||
};
|
||||
|
||||
parse_arguments::<ExecCommandArgs>(arguments)
|
||||
.ok()
|
||||
.map(|args| PreToolUsePayload {
|
||||
tool_name: HookToolName::bash(),
|
||||
tool_input: serde_json::json!({ "command": args.cmd }),
|
||||
})
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
mut invocation: ToolInvocation,
|
||||
updated_input: serde_json::Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
let ToolPayload::Function { arguments } = invocation.payload else {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
"hook input rewrite received unsupported exec_command payload".to_string(),
|
||||
));
|
||||
};
|
||||
invocation.payload = ToolPayload::Function {
|
||||
arguments: rewrite_function_string_argument(
|
||||
&arguments,
|
||||
"exec_command",
|
||||
"cmd",
|
||||
updated_hook_command(&updated_input)?,
|
||||
)?,
|
||||
};
|
||||
Ok(invocation)
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
post_unified_exec_tool_use_payload(invocation, result)
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_unified_exec_tty_metric(session_telemetry: &SessionTelemetry, tty: bool) {
|
||||
session_telemetry.counter(
|
||||
TOOL_CALL_UNIFIED_EXEC_METRIC,
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
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;
|
||||
@@ -30,7 +31,7 @@ struct WriteStdinArgs {
|
||||
|
||||
pub struct WriteStdinHandler;
|
||||
|
||||
impl ToolHandler for WriteStdinHandler {
|
||||
impl ToolExecutor<ToolInvocation> for WriteStdinHandler {
|
||||
type Output = ExecCommandToolOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -41,18 +42,6 @@ impl ToolHandler for WriteStdinHandler {
|
||||
Some(create_write_stdin_tool())
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
post_unified_exec_tool_use_payload(invocation, result)
|
||||
}
|
||||
|
||||
async fn handle(&self, invocation: ToolInvocation) -> Result<Self::Output, FunctionCallError> {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
@@ -99,3 +88,17 @@ impl ToolHandler for WriteStdinHandler {
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for WriteStdinHandler {
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(payload, ToolPayload::Function { .. })
|
||||
}
|
||||
|
||||
fn post_tool_use_payload(
|
||||
&self,
|
||||
invocation: &ToolInvocation,
|
||||
result: &Self::Output,
|
||||
) -> Option<PostToolUsePayload> {
|
||||
post_unified_exec_tool_use_payload(invocation, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ 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::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -61,7 +62,7 @@ enum ViewImageDetail {
|
||||
Original,
|
||||
}
|
||||
|
||||
impl ToolHandler for ViewImageHandler {
|
||||
impl ToolExecutor<ToolInvocation> for ViewImageHandler {
|
||||
type Output = ViewImageOutput;
|
||||
|
||||
fn tool_name(&self) -> ToolName {
|
||||
@@ -201,6 +202,8 @@ impl ToolHandler for ViewImageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for ViewImageHandler {}
|
||||
|
||||
pub struct ViewImageOutput {
|
||||
image_url: String,
|
||||
image_detail: Option<ImageDetail>,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -34,24 +35,13 @@ use tracing::warn;
|
||||
|
||||
pub(crate) type ToolTelemetryTags = Vec<(&'static str, String)>;
|
||||
|
||||
pub trait ToolHandler: Send + Sync {
|
||||
type Output: ToolOutput + 'static;
|
||||
|
||||
/// The concrete tool name handled by this handler instance.
|
||||
fn tool_name(&self) -> ToolName;
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
None
|
||||
}
|
||||
pub use codex_tools::ToolExecutor;
|
||||
|
||||
pub trait ToolHandler: ToolExecutor<ToolInvocation> {
|
||||
fn search_info(&self) -> Option<ToolSearchInfo> {
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
matches!(
|
||||
payload,
|
||||
@@ -62,7 +52,7 @@ pub trait ToolHandler: Send + Sync {
|
||||
fn telemetry_tags(
|
||||
&self,
|
||||
_invocation: &ToolInvocation,
|
||||
) -> impl std::future::Future<Output = ToolTelemetryTags> + Send {
|
||||
) -> impl Future<Output = ToolTelemetryTags> + Send {
|
||||
async { Vec::new() }
|
||||
}
|
||||
|
||||
@@ -96,13 +86,6 @@ pub trait ToolHandler: Send + Sync {
|
||||
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Perform the actual [ToolInvocation] and returns a [ToolOutput] containing
|
||||
/// the final output to return to the model.
|
||||
fn handle(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
) -> impl std::future::Future<Output = Result<Self::Output, FunctionCallError>> + Send;
|
||||
}
|
||||
|
||||
/// Consumes streamed argument diffs for a tool call and emits protocol events
|
||||
@@ -209,11 +192,11 @@ where
|
||||
T: ToolHandler,
|
||||
{
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolHandler::tool_name(self)
|
||||
ToolExecutor::tool_name(self)
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
ToolHandler::spec(self)
|
||||
ToolExecutor::spec(self)
|
||||
}
|
||||
|
||||
fn search_info(&self) -> Option<ToolSearchInfo> {
|
||||
@@ -221,7 +204,7 @@ where
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
ToolHandler::supports_parallel_tool_calls(self)
|
||||
ToolExecutor::supports_parallel_tool_calls(self)
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
@@ -257,7 +240,7 @@ where
|
||||
Box::pin(async move {
|
||||
let call_id = invocation.call_id.clone();
|
||||
let payload = invocation.payload.clone();
|
||||
let output = self.handle(invocation.clone()).await?;
|
||||
let output = ToolExecutor::handle(self, invocation.clone()).await?;
|
||||
let post_tool_use_payload =
|
||||
ToolHandler::post_tool_use_payload(self, &invocation, &output);
|
||||
Ok(AnyToolResult {
|
||||
|
||||
@@ -8,7 +8,7 @@ struct TestHandler {
|
||||
tool_name: codex_tools::ToolName,
|
||||
}
|
||||
|
||||
impl ToolHandler for TestHandler {
|
||||
impl ToolExecutor<ToolInvocation> for TestHandler {
|
||||
type Output = crate::tools::context::FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> codex_tools::ToolName {
|
||||
@@ -23,6 +23,8 @@ impl ToolHandler for TestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for TestHandler {}
|
||||
|
||||
#[test]
|
||||
fn handler_looks_up_namespaced_aliases_explicitly() {
|
||||
let namespace = "mcp__codex_apps__gmail";
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolCallSource;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolRegistry;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
@@ -29,7 +30,7 @@ struct TestHandler {
|
||||
tool_name: codex_tools::ToolName,
|
||||
}
|
||||
|
||||
impl ToolHandler for TestHandler {
|
||||
impl ToolExecutor<ToolInvocation> for TestHandler {
|
||||
type Output = FunctionToolOutput;
|
||||
|
||||
fn tool_name(&self) -> codex_tools::ToolName {
|
||||
@@ -41,6 +42,8 @@ impl ToolHandler for TestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolHandler for TestHandler {}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dispatch_lifecycle_trace_records_direct_and_code_mode_requesters() -> anyhow::Result<()> {
|
||||
let temp = TempDir::new()?;
|
||||
|
||||
@@ -14,6 +14,7 @@ codex-features = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-pty = { workspace = true }
|
||||
codex-utils-string = { workspace = true }
|
||||
rmcp = { workspace = true, default-features = false, features = [
|
||||
"base64",
|
||||
"macros",
|
||||
@@ -22,6 +23,7 @@ rmcp = { workspace = true, default-features = false, features = [
|
||||
] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error returned while executing a model-visible tool invocation.
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum FunctionCallError {
|
||||
#[error("{0}")]
|
||||
RespondToModel(String),
|
||||
#[error("LocalShellCall without call_id or id")]
|
||||
MissingLocalShellCallId,
|
||||
#[error("Fatal error: {0}")]
|
||||
Fatal(String),
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
mod code_mode;
|
||||
mod dynamic_tool;
|
||||
mod function_call_error;
|
||||
mod image_detail;
|
||||
mod json_schema;
|
||||
mod mcp_tool;
|
||||
@@ -11,6 +12,9 @@ mod responses_api;
|
||||
mod tool_config;
|
||||
mod tool_definition;
|
||||
mod tool_discovery;
|
||||
mod tool_executor;
|
||||
mod tool_output;
|
||||
mod tool_payload;
|
||||
mod tool_spec;
|
||||
|
||||
pub use code_mode::augment_tool_spec_for_code_mode;
|
||||
@@ -20,6 +24,7 @@ pub use code_mode::collect_code_mode_tool_definitions;
|
||||
pub use code_mode::tool_spec_to_code_mode_tool_definition;
|
||||
pub use codex_protocol::ToolName;
|
||||
pub use dynamic_tool::parse_dynamic_tool;
|
||||
pub use function_call_error::FunctionCallError;
|
||||
pub use image_detail::can_request_original_image_detail;
|
||||
pub use image_detail::normalize_output_image_detail;
|
||||
pub use image_detail::sanitize_original_image_detail;
|
||||
@@ -71,6 +76,9 @@ pub use tool_discovery::TOOL_SEARCH_TOOL_NAME;
|
||||
pub use tool_discovery::ToolSearchSourceInfo;
|
||||
pub use tool_discovery::collect_request_plugin_install_entries;
|
||||
pub use tool_discovery::filter_request_plugin_install_discoverable_tools_for_client;
|
||||
pub use tool_executor::ToolExecutor;
|
||||
pub use tool_output::ToolOutput;
|
||||
pub use tool_payload::ToolPayload;
|
||||
pub use tool_spec::ResponsesApiWebSearchFilters;
|
||||
pub use tool_spec::ResponsesApiWebSearchUserLocation;
|
||||
pub use tool_spec::ToolSpec;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
use std::future::Future;
|
||||
|
||||
use crate::FunctionCallError;
|
||||
use crate::ToolName;
|
||||
use crate::ToolOutput;
|
||||
use crate::ToolSpec;
|
||||
|
||||
/// Shared runtime contract for model-visible tools.
|
||||
///
|
||||
/// Implementations keep the model-visible spec tied to the executable runtime.
|
||||
/// Host crates can layer routing, hooks, telemetry, or other orchestration on
|
||||
/// top without reopening the spec/runtime split.
|
||||
pub trait ToolExecutor<Invocation>: Send + Sync {
|
||||
type Output: ToolOutput + 'static;
|
||||
|
||||
/// The concrete tool name handled by this runtime instance.
|
||||
fn tool_name(&self) -> ToolName;
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
invocation: Invocation,
|
||||
) -> impl Future<Output = Result<Self::Output, FunctionCallError>> + Send;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
use codex_protocol::models::DEFAULT_IMAGE_DETAIL;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_utils_string::take_bytes_at_char_boundary;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use crate::ToolPayload;
|
||||
|
||||
const TELEMETRY_PREVIEW_MAX_BYTES: usize = 2 * 1024;
|
||||
const TELEMETRY_PREVIEW_MAX_LINES: usize = 64;
|
||||
const TELEMETRY_PREVIEW_TRUNCATION_NOTICE: &str = "[... telemetry preview truncated ...]";
|
||||
|
||||
/// Model-facing output contract returned by executable tool runtimes.
|
||||
pub trait ToolOutput: Send {
|
||||
fn log_preview(&self) -> String;
|
||||
|
||||
fn success_for_logging(&self) -> bool;
|
||||
|
||||
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem;
|
||||
|
||||
/// Returns the stable value exposed to `PostToolUse` hooks for this tool output.
|
||||
///
|
||||
/// Tool handlers decide whether a tool participates in `PostToolUse`, but
|
||||
/// this method lets the output type own any conversion from model-facing
|
||||
/// response content to hook-facing data. Returning `None` means the output
|
||||
/// should not produce a post-use hook payload, not merely that the tool had
|
||||
/// empty output.
|
||||
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue> {
|
||||
None
|
||||
}
|
||||
|
||||
fn code_mode_result(&self, payload: &ToolPayload) -> JsonValue {
|
||||
response_input_to_code_mode_result(self.to_response_item("", payload))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolOutput for codex_protocol::mcp::CallToolResult {
|
||||
fn log_preview(&self) -> String {
|
||||
let output = self.as_function_call_output_payload();
|
||||
let preview = output.body.to_text().unwrap_or_else(|| output.to_string());
|
||||
telemetry_preview(&preview)
|
||||
}
|
||||
|
||||
fn success_for_logging(&self) -> bool {
|
||||
self.success()
|
||||
}
|
||||
|
||||
fn to_response_item(&self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem {
|
||||
ResponseInputItem::McpToolCallOutput {
|
||||
call_id: call_id.to_string(),
|
||||
output: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue {
|
||||
serde_json::to_value(self).unwrap_or_else(|err| {
|
||||
JsonValue::String(format!("failed to serialize mcp result: {err}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn response_input_to_code_mode_result(response: ResponseInputItem) -> JsonValue {
|
||||
match response {
|
||||
ResponseInputItem::Message { content, .. } => content_items_to_code_mode_result(
|
||||
&content
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
codex_protocol::models::ContentItem::InputText { text }
|
||||
| codex_protocol::models::ContentItem::OutputText { text } => {
|
||||
FunctionCallOutputContentItem::InputText { text }
|
||||
}
|
||||
codex_protocol::models::ContentItem::InputImage { image_url, detail } => {
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
image_url,
|
||||
detail: detail.or(Some(DEFAULT_IMAGE_DETAIL)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
ResponseInputItem::FunctionCallOutput { output, .. }
|
||||
| ResponseInputItem::CustomToolCallOutput { output, .. } => match output.body {
|
||||
FunctionCallOutputBody::Text(text) => JsonValue::String(text),
|
||||
FunctionCallOutputBody::ContentItems(items) => {
|
||||
content_items_to_code_mode_result(&items)
|
||||
}
|
||||
},
|
||||
ResponseInputItem::ToolSearchOutput { tools, .. } => JsonValue::Array(tools),
|
||||
ResponseInputItem::McpToolCallOutput { output, .. } => serde_json::to_value(output)
|
||||
.unwrap_or_else(|err| {
|
||||
JsonValue::String(format!("failed to serialize mcp result: {err}"))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn content_items_to_code_mode_result(items: &[FunctionCallOutputContentItem]) -> JsonValue {
|
||||
JsonValue::String(
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| match item {
|
||||
FunctionCallOutputContentItem::InputText { text } if !text.trim().is_empty() => {
|
||||
Some(text.clone())
|
||||
}
|
||||
FunctionCallOutputContentItem::InputImage { image_url, .. }
|
||||
if !image_url.trim().is_empty() =>
|
||||
{
|
||||
Some(image_url.clone())
|
||||
}
|
||||
FunctionCallOutputContentItem::InputText { .. }
|
||||
| FunctionCallOutputContentItem::InputImage { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
fn telemetry_preview(content: &str) -> String {
|
||||
let truncated_slice = take_bytes_at_char_boundary(content, TELEMETRY_PREVIEW_MAX_BYTES);
|
||||
let truncated_by_bytes = truncated_slice.len() < content.len();
|
||||
|
||||
let mut preview = String::new();
|
||||
let mut lines_iter = truncated_slice.lines();
|
||||
for idx in 0..TELEMETRY_PREVIEW_MAX_LINES {
|
||||
match lines_iter.next() {
|
||||
Some(line) => {
|
||||
if idx > 0 {
|
||||
preview.push('\n');
|
||||
}
|
||||
preview.push_str(line);
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
let truncated_by_lines = lines_iter.next().is_some();
|
||||
|
||||
if !truncated_by_bytes && !truncated_by_lines {
|
||||
return content.to_string();
|
||||
}
|
||||
|
||||
if preview.len() < truncated_slice.len()
|
||||
&& truncated_slice
|
||||
.as_bytes()
|
||||
.get(preview.len())
|
||||
.is_some_and(|byte| *byte == b'\n')
|
||||
{
|
||||
preview.push('\n');
|
||||
}
|
||||
|
||||
if !preview.is_empty() && !preview.ends_with('\n') {
|
||||
preview.push('\n');
|
||||
}
|
||||
preview.push_str(TELEMETRY_PREVIEW_TRUNCATION_NOTICE);
|
||||
|
||||
preview
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use codex_protocol::models::SearchToolCallParams;
|
||||
use codex_protocol::models::ShellToolCallParams;
|
||||
|
||||
/// Canonical payload shapes accepted by model-visible tool runtimes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ToolPayload {
|
||||
Function { arguments: String },
|
||||
ToolSearch { arguments: SearchToolCallParams },
|
||||
Custom { input: String },
|
||||
LocalShell { params: ShellToolCallParams },
|
||||
}
|
||||
|
||||
impl ToolPayload {
|
||||
pub fn log_payload(&self) -> Cow<'_, str> {
|
||||
match self {
|
||||
ToolPayload::Function { arguments } => Cow::Borrowed(arguments),
|
||||
ToolPayload::ToolSearch { arguments } => Cow::Owned(arguments.query.clone()),
|
||||
ToolPayload::Custom { input } => Cow::Borrowed(input),
|
||||
ToolPayload::LocalShell { params } => Cow::Owned(params.command.join(" ")),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user