diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index ed119aabf..b30b93cc1 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2851,6 +2851,7 @@ impl Session { AskForApproval::Never => { return Some(RequestPermissionsResponse { permissions: PermissionProfile::default(), + scope: PermissionGrantScope::Turn, }); } AskForApproval::Reject(reject_config) @@ -2858,6 +2859,7 @@ impl Session { { return Some(RequestPermissionsResponse { permissions: PermissionProfile::default(), + scope: PermissionGrantScope::Turn, }); } AskForApproval::OnFailure diff --git a/codex-rs/core/src/codex_tests.rs b/codex-rs/core/src/codex_tests.rs index 66e6cd27b..e3410ffb9 100644 --- a/codex-rs/core/src/codex_tests.rs +++ b/codex-rs/core/src/codex_tests.rs @@ -46,8 +46,8 @@ use crate::state::TaskKind; use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; use crate::tools::ToolRouter; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; use crate::tools::context::ToolPayload; use crate::tools::handlers::ShellHandler; use crate::tools::handlers::UnifiedExecHandler; @@ -89,6 +89,13 @@ use std::time::Duration as StdDuration; #[path = "codex_tests_guardian.rs"] mod guardian_tests; +fn expect_text_tool_output(output: &dyn std::any::Any) -> String { + let Some(output) = output.downcast_ref::() else { + panic!("unexpected tool output"); + }; + output.text.clone() +} + struct InstructionsTestCase { slug: &'static str, expects_apply_patch_instructions: bool, @@ -2293,6 +2300,7 @@ async fn request_permissions_emits_event_when_reject_policy_allows_requests() { }), ..Default::default() }, + scope: codex_protocol::request_permissions::PermissionGrantScope::Turn, }; let handle = tokio::spawn({ @@ -2377,6 +2385,7 @@ async fn request_permissions_returns_empty_grant_when_reject_policy_blocks_reque Some( codex_protocol::request_permissions::RequestPermissionsResponse { permissions: codex_protocol::models::PermissionProfile::default(), + scope: codex_protocol::request_permissions::PermissionGrantScope::Turn, } ) ); @@ -4133,13 +4142,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() { }) .await; - let output = match resp2.expect("expected Ok result") { - ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - .. - } => content, - _ => panic!("unexpected tool output"), - }; + let output = expect_text_tool_output(&*resp2.expect("expected Ok result")); #[derive(Deserialize, PartialEq, Eq, Debug)] struct ResponseExecMetadata { diff --git a/codex-rs/core/src/codex_tests_guardian.rs b/codex-rs/core/src/codex_tests_guardian.rs index cc3b3d0ad..9e3bea6b6 100644 --- a/codex-rs/core/src/codex_tests_guardian.rs +++ b/codex-rs/core/src/codex_tests_guardian.rs @@ -8,12 +8,12 @@ use crate::features::Feature; use crate::guardian::GUARDIAN_SUBAGENT_NAME; use crate::protocol::AskForApproval; use crate::sandboxing::SandboxPermissions; +use crate::tools::context::TextToolOutput; use crate::turn_diff_tracker::TurnDiffTracker; use codex_app_server_protocol::ConfigLayerSource; use codex_execpolicy::Decision; use codex_execpolicy::Evaluation; use codex_execpolicy::RuleMatch; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::NetworkPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemSandboxPolicy; @@ -33,6 +33,13 @@ use std::fs; use std::sync::Arc; use tempfile::tempdir; +fn expect_text_output(output: &dyn std::any::Any) -> String { + let Some(output) = output.downcast_ref::() else { + panic!("unexpected tool output"); + }; + output.text.clone() +} + #[tokio::test] async fn guardian_allows_shell_additional_permissions_requests_past_policy_validation() { let server = start_mock_server().await; @@ -152,13 +159,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid }) .await; - let output = match resp.expect("expected Ok result") { - ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - .. - } => content, - _ => panic!("unexpected tool output"), - }; + let output = expect_text_output(&*resp.expect("expected Ok result")); #[derive(Deserialize, PartialEq, Eq, Debug)] struct ResponseExecMetadata { diff --git a/codex-rs/core/src/tools/context.rs b/codex-rs/core/src/tools/context.rs index 9f85e6acf..deab5cc0e 100644 --- a/codex-rs/core/src/tools/context.rs +++ b/codex-rs/core/src/tools/context.rs @@ -6,10 +6,12 @@ use crate::tools::TELEMETRY_PREVIEW_TRUNCATION_NOTICE; use crate::turn_diff_tracker::TurnDiffTracker; use codex_protocol::mcp::CallToolResult; use codex_protocol::models::FunctionCallOutputBody; +use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ShellToolCallParams; use codex_utils_string::take_bytes_at_char_boundary; +use std::any::Any; use std::borrow::Cow; use std::sync::Arc; use tokio::sync::Mutex; @@ -61,65 +63,111 @@ impl ToolPayload { } } -#[derive(Clone)] -pub enum ToolOutput { - Function { - // Canonical output body for function-style tools. This may be plain text - // or structured content items. - body: FunctionCallOutputBody, - success: Option, - }, - Mcp { - result: Result, - }, +pub trait ToolOutput: Any + Send { + fn log_preview(&self) -> String; + + fn success_for_logging(&self) -> bool; + + fn into_response(self: Box, call_id: &str, payload: &ToolPayload) -> ResponseInputItem; } -impl ToolOutput { - pub fn log_preview(&self) -> String { - match self { - ToolOutput::Function { body, .. } => { - telemetry_preview(&body.to_text().unwrap_or_default()) - } - ToolOutput::Mcp { result } => format!("{result:?}"), - } +pub type ToolOutputBox = Box; + +pub struct McpToolOutput { + pub result: Result, +} + +impl ToolOutput for McpToolOutput { + fn log_preview(&self) -> String { + format!("{:?}", self.result) } - pub fn success_for_logging(&self) -> bool { - match self { - ToolOutput::Function { success, .. } => success.unwrap_or(true), - ToolOutput::Mcp { result } => result.is_ok(), - } + fn success_for_logging(&self) -> bool { + self.result.is_ok() } - pub fn into_response(self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem { - match self { - ToolOutput::Function { body, success } => { - // `custom_tool_call` is the Responses API item type for freeform - // tools (`ToolSpec::Freeform`, e.g. freeform `apply_patch` or - // `js_repl`). - if matches!(payload, ToolPayload::Custom { .. }) { - return ResponseInputItem::CustomToolCallOutput { - call_id: call_id.to_string(), - output: FunctionCallOutputPayload { body, success }, - }; - } - - // Function-style outputs (JSON function tools, including dynamic - // tools and MCP adaptation) preserve the exact body shape. - ResponseInputItem::FunctionCallOutput { - call_id: call_id.to_string(), - output: FunctionCallOutputPayload { body, success }, - } - } - // Direct MCP response path for MCP tool result envelopes. - ToolOutput::Mcp { result } => ResponseInputItem::McpToolCallOutput { - call_id: call_id.to_string(), - result, - }, + fn into_response(self: Box, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem { + let Self { result } = *self; + ResponseInputItem::McpToolCallOutput { + call_id: call_id.to_string(), + result, } } } +pub struct TextToolOutput { + pub text: String, + pub success: Option, +} + +impl ToolOutput for TextToolOutput { + fn log_preview(&self) -> String { + telemetry_preview(&self.text) + } + + fn success_for_logging(&self) -> bool { + self.success.unwrap_or(true) + } + + fn into_response(self: Box, call_id: &str, payload: &ToolPayload) -> ResponseInputItem { + let Self { text, success } = *self; + function_tool_response( + call_id, + payload, + FunctionCallOutputBody::Text(text), + success, + ) + } +} + +pub struct ContentToolOutput { + pub content: Vec, + pub success: Option, +} + +impl ToolOutput for ContentToolOutput { + fn log_preview(&self) -> String { + telemetry_preview( + &FunctionCallOutputBody::ContentItems(self.content.clone()) + .to_text() + .unwrap_or_default(), + ) + } + + fn success_for_logging(&self) -> bool { + self.success.unwrap_or(true) + } + + fn into_response(self: Box, call_id: &str, payload: &ToolPayload) -> ResponseInputItem { + let Self { content, success } = *self; + function_tool_response( + call_id, + payload, + FunctionCallOutputBody::ContentItems(content), + success, + ) + } +} + +fn function_tool_response( + call_id: &str, + payload: &ToolPayload, + body: FunctionCallOutputBody, + success: Option, +) -> ResponseInputItem { + if matches!(payload, ToolPayload::Custom { .. }) { + return ResponseInputItem::CustomToolCallOutput { + call_id: call_id.to_string(), + output: FunctionCallOutputPayload { body, success }, + }; + } + + ResponseInputItem::FunctionCallOutput { + call_id: call_id.to_string(), + output: FunctionCallOutputPayload { body, success }, + } +} + 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(); @@ -163,7 +211,6 @@ fn telemetry_preview(content: &str) -> String { #[cfg(test)] mod tests { use super::*; - use codex_protocol::models::FunctionCallOutputContentItem; use pretty_assertions::assert_eq; #[test] @@ -171,10 +218,10 @@ mod tests { let payload = ToolPayload::Custom { input: "patch".to_string(), }; - let response = ToolOutput::Function { - body: FunctionCallOutputBody::Text("patched".to_string()), + let response = Box::new(TextToolOutput { + text: "patched".to_string(), success: Some(true), - } + }) .into_response("call-42", &payload); match response { @@ -193,10 +240,10 @@ mod tests { let payload = ToolPayload::Function { arguments: "{}".to_string(), }; - let response = ToolOutput::Function { - body: FunctionCallOutputBody::Text("ok".to_string()), + let response = Box::new(TextToolOutput { + text: "ok".to_string(), success: Some(true), - } + }) .into_response("fn-1", &payload); match response { @@ -215,8 +262,8 @@ mod tests { let payload = ToolPayload::Custom { input: "patch".to_string(), }; - let response = ToolOutput::Function { - body: FunctionCallOutputBody::ContentItems(vec![ + let response = Box::new(ContentToolOutput { + content: vec![ FunctionCallOutputContentItem::InputText { text: "line 1".to_string(), }, @@ -227,9 +274,9 @@ mod tests { FunctionCallOutputContentItem::InputText { text: "line 2".to_string(), }, - ]), + ], success: Some(true), - } + }) .into_response("call-99", &payload); match response { @@ -257,12 +304,10 @@ mod tests { #[test] fn log_preview_uses_content_items_when_plain_text_is_missing() { - let output = ToolOutput::Function { - body: FunctionCallOutputBody::ContentItems(vec![ - FunctionCallOutputContentItem::InputText { - text: "preview".to_string(), - }, - ]), + let output = ContentToolOutput { + content: vec![FunctionCallOutputContentItem::InputText { + text: "preview".to_string(), + }], success: Some(true), }; diff --git a/codex-rs/core/src/tools/handlers/agent_jobs.rs b/codex-rs/core/src/tools/handlers/agent_jobs.rs index 8b3d823c2..a3b747d50 100644 --- a/codex-rs/core/src/tools/handlers/agent_jobs.rs +++ b/codex-rs/core/src/tools/handlers/agent_jobs.rs @@ -6,8 +6,9 @@ use crate::codex::TurnContext; use crate::config::Config; use crate::error::CodexErr; use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::multi_agents::build_agent_spawn_config; use crate::tools::handlers::parse_arguments; @@ -15,7 +16,6 @@ use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; use async_trait::async_trait; use codex_protocol::ThreadId; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use codex_protocol::user_input::UserInput; @@ -183,7 +183,7 @@ impl ToolHandler for BatchJobHandler { matches!(payload, ToolPayload::Function { .. }) } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -223,7 +223,7 @@ mod spawn_agents_on_csv { session: Arc, turn: Arc, arguments: String, - ) -> Result { + ) -> Result { let args: SpawnAgentsOnCsvArgs = parse_arguments(arguments.as_str())?; if args.instruction.trim().is_empty() { return Err(FunctionCallError::RespondToModel( @@ -456,10 +456,10 @@ mod spawn_agents_on_csv { "failed to serialize spawn_agents_on_csv result: {err}" )) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } @@ -469,7 +469,7 @@ mod report_agent_job_result { pub async fn handle( session: Arc, arguments: String, - ) -> Result { + ) -> Result { let args: ReportAgentJobResultArgs = parse_arguments(arguments.as_str())?; if !args.result.is_object() { return Err(FunctionCallError::RespondToModel( @@ -505,10 +505,10 @@ mod report_agent_job_result { "failed to serialize report_agent_job_result result: {err}" )) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index f5b79084a..aeca890c7 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -1,4 +1,3 @@ -use codex_protocol::models::FunctionCallOutputBody; use std::collections::BTreeMap; use std::path::Path; @@ -13,8 +12,9 @@ use crate::codex::Session; use crate::codex::TurnContext; use crate::function_tool::FunctionCallError; use crate::tools::context::SharedTurnDiffTracker; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::events::ToolEmitter; use crate::tools::events::ToolEventCtx; @@ -107,7 +107,7 @@ impl ToolHandler for ApplyPatchHandler { true } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -140,10 +140,10 @@ impl ToolHandler for ApplyPatchHandler { match apply_patch::apply_patch(turn.as_ref(), changes).await { InternalApplyPatchInvocation::Output(item) => { let content = item?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } InternalApplyPatchInvocation::DelegateToExec(apply) => { let changes = convert_apply_patch_to_protocol(&apply.action); @@ -204,10 +204,10 @@ impl ToolHandler for ApplyPatchHandler { Some(&tracker), ); let content = emitter.finish(event_ctx, out).await?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } } @@ -241,7 +241,7 @@ pub(crate) async fn intercept_apply_patch( tracker: Option<&SharedTurnDiffTracker>, call_id: &str, tool_name: &str, -) -> Result, FunctionCallError> { +) -> Result, FunctionCallError> { match codex_apply_patch::maybe_parse_apply_patch_verified(command, cwd) { codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => { session @@ -255,10 +255,10 @@ pub(crate) async fn intercept_apply_patch( match apply_patch::apply_patch(turn.as_ref(), changes).await { InternalApplyPatchInvocation::Output(item) => { let content = item?; - Ok(Some(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Some(Box::new(TextToolOutput { + text: content, success: Some(true), - })) + }))) } InternalApplyPatchInvocation::DelegateToExec(apply) => { let changes = convert_apply_patch_to_protocol(&apply.action); @@ -317,10 +317,10 @@ pub(crate) async fn intercept_apply_patch( tracker.as_ref().copied(), ); let content = emitter.finish(event_ctx, out).await?; - Ok(Some(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Some(Box::new(TextToolOutput { + text: content, success: Some(true), - })) + }))) } } } diff --git a/codex-rs/core/src/tools/handlers/artifacts.rs b/codex-rs/core/src/tools/handlers/artifacts.rs index a0df7ce3b..38f228291 100644 --- a/codex-rs/core/src/tools/handlers/artifacts.rs +++ b/codex-rs/core/src/tools/handlers/artifacts.rs @@ -16,8 +16,9 @@ use crate::exec::StreamOutput; use crate::features::Feature; use crate::function_tool::FunctionCallError; use crate::protocol::ExecCommandSource; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::events::ToolEmitter; use crate::tools::events::ToolEventCtx; @@ -25,7 +26,6 @@ use crate::tools::events::ToolEventFailure; use crate::tools::events::ToolEventStage; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; -use codex_protocol::models::FunctionCallOutputBody; const ARTIFACTS_TOOL_NAME: &str = "artifacts"; const ARTIFACTS_PRAGMA_PREFIXES: [&str; 2] = ["// codex-artifacts:", "// codex-artifact-tool:"]; @@ -54,7 +54,7 @@ impl ToolHandler for ArtifactsHandler { true } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -112,10 +112,10 @@ impl ToolHandler for ArtifactsHandler { ) .await; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(format_artifact_output(&output)), + Ok(Box::new(TextToolOutput { + text: format_artifact_output(&output), success: Some(success), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/dynamic.rs b/codex-rs/core/src/tools/handlers/dynamic.rs index 23c8474b9..e06ffc7d6 100644 --- a/codex-rs/core/src/tools/handlers/dynamic.rs +++ b/codex-rs/core/src/tools/handlers/dynamic.rs @@ -1,8 +1,9 @@ use crate::codex::Session; use crate::codex::TurnContext; use crate::function_tool::FunctionCallError; +use crate::tools::context::ContentToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -10,7 +11,6 @@ use crate::tools::registry::ToolKind; use async_trait::async_trait; use codex_protocol::dynamic_tools::DynamicToolCallRequest; use codex_protocol::dynamic_tools::DynamicToolResponse; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::protocol::DynamicToolCallResponseEvent; use codex_protocol::protocol::EventMsg; @@ -31,7 +31,7 @@ impl ToolHandler for DynamicToolHandler { true } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -67,12 +67,10 @@ impl ToolHandler for DynamicToolHandler { .into_iter() .map(FunctionCallOutputContentItem::from) .collect::>(); - let body = FunctionCallOutputBody::ContentItems(body); - - Ok(ToolOutput::Function { - body, + Ok(Box::new(ContentToolOutput { + content: body, success: Some(success), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/grep_files.rs b/codex-rs/core/src/tools/handlers/grep_files.rs index 9fbc6c17a..9d9434d85 100644 --- a/codex-rs/core/src/tools/handlers/grep_files.rs +++ b/codex-rs/core/src/tools/handlers/grep_files.rs @@ -1,4 +1,3 @@ -use codex_protocol::models::FunctionCallOutputBody; use std::path::Path; use std::time::Duration; @@ -8,8 +7,9 @@ use tokio::process::Command; use tokio::time::timeout; use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -42,7 +42,7 @@ impl ToolHandler for GrepFilesHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { payload, turn, .. } = invocation; let arguments = match payload { @@ -86,15 +86,15 @@ impl ToolHandler for GrepFilesHandler { run_rg_search(pattern, include.as_deref(), &search_path, limit, &turn.cwd).await?; if search_results.is_empty() { - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text("No matches found.".to_string()), + Ok(Box::new(TextToolOutput { + text: "No matches found.".to_string(), success: Some(false), - }) + })) } else { - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(search_results.join("\n")), + Ok(Box::new(TextToolOutput { + text: search_results.join("\n"), success: Some(true), - }) + })) } } } diff --git a/codex-rs/core/src/tools/handlers/js_repl.rs b/codex-rs/core/src/tools/handlers/js_repl.rs index 362d25b81..dec739942 100644 --- a/codex-rs/core/src/tools/handlers/js_repl.rs +++ b/codex-rs/core/src/tools/handlers/js_repl.rs @@ -9,8 +9,10 @@ use crate::exec::StreamOutput; use crate::features::Feature; use crate::function_tool::FunctionCallError; use crate::protocol::ExecCommandSource; +use crate::tools::context::ContentToolOutput; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::events::ToolEmitter; use crate::tools::events::ToolEventCtx; @@ -21,7 +23,6 @@ use crate::tools::js_repl::JS_REPL_PRAGMA_PREFIX; use crate::tools::js_repl::JsReplArgs; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputContentItem; pub struct JsReplHandler; @@ -106,7 +107,7 @@ impl ToolHandler for JsReplHandler { ) } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -173,14 +174,17 @@ impl ToolHandler for JsReplHandler { ) .await; - Ok(ToolOutput::Function { - body: if items.is_empty() { - FunctionCallOutputBody::Text(content) - } else { - FunctionCallOutputBody::ContentItems(items) - }, - success: Some(true), - }) + if items.is_empty() { + Ok(Box::new(TextToolOutput { + text: content, + success: Some(true), + })) + } else { + Ok(Box::new(ContentToolOutput { + content: items, + success: Some(true), + })) + } } } @@ -190,7 +194,7 @@ impl ToolHandler for JsReplResetHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { if !invocation.session.features().enabled(Feature::JsRepl) { return Err(FunctionCallError::RespondToModel( "js_repl is disabled by feature flag".to_string(), @@ -198,10 +202,10 @@ impl ToolHandler for JsReplResetHandler { } let manager = invocation.turn.js_repl.manager().await?; manager.reset().await?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text("js_repl kernel reset".to_string()), + Ok(Box::new(TextToolOutput { + text: "js_repl kernel reset".to_string(), success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/list_dir.rs b/codex-rs/core/src/tools/handlers/list_dir.rs index 5535ce0ba..fd0062102 100644 --- a/codex-rs/core/src/tools/handlers/list_dir.rs +++ b/codex-rs/core/src/tools/handlers/list_dir.rs @@ -1,4 +1,3 @@ -use codex_protocol::models::FunctionCallOutputBody; use std::collections::VecDeque; use std::ffi::OsStr; use std::fs::FileType; @@ -11,8 +10,9 @@ use serde::Deserialize; use tokio::fs; use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -52,7 +52,7 @@ impl ToolHandler for ListDirHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { payload, .. } = invocation; let arguments = match payload { @@ -102,10 +102,10 @@ impl ToolHandler for ListDirHandler { let mut output = Vec::with_capacity(entries.len() + 1); output.push(format!("Absolute path: {}", path.display())); output.extend(entries); - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(output.join("\n")), + Ok(Box::new(TextToolOutput { + text: output.join("\n"), success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/mcp.rs b/codex-rs/core/src/tools/handlers/mcp.rs index d67c233e6..932cdce89 100644 --- a/codex-rs/core/src/tools/handlers/mcp.rs +++ b/codex-rs/core/src/tools/handlers/mcp.rs @@ -3,8 +3,11 @@ use std::sync::Arc; use crate::function_tool::FunctionCallError; use crate::mcp_tool_call::handle_mcp_tool_call; +use crate::tools::context::ContentToolOutput; +use crate::tools::context::McpToolOutput; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; @@ -18,7 +21,7 @@ impl ToolHandler for McpHandler { ToolKind::Mcp } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -54,11 +57,19 @@ impl ToolHandler for McpHandler { .await; match response { - ResponseInputItem::McpToolCallOutput { result, .. } => Ok(ToolOutput::Mcp { result }), + ResponseInputItem::McpToolCallOutput { result, .. } => { + Ok(Box::new(McpToolOutput { result })) + } ResponseInputItem::FunctionCallOutput { output, .. } => { let success = output.success; - let body = output.body; - Ok(ToolOutput::Function { body, success }) + match output.body { + codex_protocol::models::FunctionCallOutputBody::Text(text) => { + Ok(Box::new(TextToolOutput { text, success })) + } + codex_protocol::models::FunctionCallOutputBody::ContentItems(content) => { + Ok(Box::new(ContentToolOutput { content, success })) + } + } } _ => Err(FunctionCallError::RespondToModel( "mcp handler received unexpected response variant".to_string(), diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index 2a0aabcbc..c0f0691e2 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -1,4 +1,3 @@ -use codex_protocol::models::FunctionCallOutputBody; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -25,8 +24,9 @@ use crate::protocol::EventMsg; use crate::protocol::McpInvocation; use crate::protocol::McpToolCallBeginEvent; use crate::protocol::McpToolCallEndEvent; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; @@ -184,7 +184,7 @@ impl ToolHandler for McpResourceHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -245,7 +245,7 @@ async fn handle_list_resources( turn: Arc, call_id: String, arguments: Option, -) -> Result { +) -> Result { let args: ListResourcesArgs = parse_args_with_default(arguments.clone())?; let ListResourcesArgs { server, cursor } = args; let server = normalize_optional_string(server); @@ -298,10 +298,12 @@ async fn handle_list_resources( match payload_result { Ok(payload) => match serialize_function_output(payload) { Ok(output) => { - let ToolOutput::Function { body, success } = &output else { - unreachable!("MCP resource handler should return function output"); + let Some(output_text) = + (&*output as &dyn std::any::Any).downcast_ref::() + else { + unreachable!("MCP resource handler should return text output"); }; - let content = body.to_text().unwrap_or_default(); + let content = output_text.text.clone(); let duration = start.elapsed(); emit_tool_call_end( &session, @@ -309,7 +311,7 @@ async fn handle_list_resources( &call_id, invocation, duration, - Ok(call_tool_result_from_content(&content, *success)), + Ok(call_tool_result_from_content(&content, output_text.success)), ) .await; Ok(output) @@ -351,7 +353,7 @@ async fn handle_list_resource_templates( turn: Arc, call_id: String, arguments: Option, -) -> Result { +) -> Result { let args: ListResourceTemplatesArgs = parse_args_with_default(arguments.clone())?; let ListResourceTemplatesArgs { server, cursor } = args; let server = normalize_optional_string(server); @@ -406,10 +408,12 @@ async fn handle_list_resource_templates( match payload_result { Ok(payload) => match serialize_function_output(payload) { Ok(output) => { - let ToolOutput::Function { body, success } = &output else { - unreachable!("MCP resource handler should return function output"); + let Some(output_text) = + (&*output as &dyn std::any::Any).downcast_ref::() + else { + unreachable!("MCP resource handler should return text output"); }; - let content = body.to_text().unwrap_or_default(); + let content = output_text.text.clone(); let duration = start.elapsed(); emit_tool_call_end( &session, @@ -417,7 +421,7 @@ async fn handle_list_resource_templates( &call_id, invocation, duration, - Ok(call_tool_result_from_content(&content, *success)), + Ok(call_tool_result_from_content(&content, output_text.success)), ) .await; Ok(output) @@ -459,7 +463,7 @@ async fn handle_read_resource( turn: Arc, call_id: String, arguments: Option, -) -> Result { +) -> Result { let args: ReadResourceArgs = parse_args(arguments.clone())?; let ReadResourceArgs { server, uri } = args; let server = normalize_required_string("server", server)?; @@ -499,10 +503,12 @@ async fn handle_read_resource( match payload_result { Ok(payload) => match serialize_function_output(payload) { Ok(output) => { - let ToolOutput::Function { body, success } = &output else { - unreachable!("MCP resource handler should return function output"); + let Some(output_text) = + (&*output as &dyn std::any::Any).downcast_ref::() + else { + unreachable!("MCP resource handler should return text output"); }; - let content = body.to_text().unwrap_or_default(); + let content = output_text.text.clone(); let duration = start.elapsed(); emit_tool_call_end( &session, @@ -510,7 +516,7 @@ async fn handle_read_resource( &call_id, invocation, duration, - Ok(call_tool_result_from_content(&content, *success)), + Ok(call_tool_result_from_content(&content, output_text.success)), ) .await; Ok(output) @@ -614,7 +620,7 @@ fn normalize_required_string(field: &str, value: String) -> Result(payload: T) -> Result +fn serialize_function_output(payload: T) -> Result where T: Serialize, { @@ -624,10 +630,10 @@ where )) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } fn parse_arguments(raw_args: &str) -> Result, FunctionCallError> { diff --git a/codex-rs/core/src/tools/handlers/multi_agents.rs b/codex-rs/core/src/tools/handlers/multi_agents.rs index 1d3805b32..58cca0094 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents.rs @@ -13,8 +13,9 @@ use crate::config::Config; use crate::error::CodexErr; use crate::features::Feature; use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -22,7 +23,6 @@ use crate::tools::registry::ToolKind; use async_trait::async_trait; use codex_protocol::ThreadId; use codex_protocol::models::BaseInstructions; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::protocol::CollabAgentInteractionBeginEvent; use codex_protocol::protocol::CollabAgentInteractionEndEvent; use codex_protocol::protocol::CollabAgentRef; @@ -65,7 +65,7 @@ impl ToolHandler for MultiAgentHandler { matches!(payload, ToolPayload::Function { .. }) } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -127,7 +127,7 @@ mod spawn { turn: Arc, call_id: String, arguments: String, - ) -> Result { + ) -> Result { let args: SpawnAgentArgs = parse_arguments(&arguments)?; let role_name = args .agent_type @@ -225,10 +225,10 @@ mod spawn { FunctionCallError::Fatal(format!("failed to serialize spawn_agent result: {err}")) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } @@ -255,7 +255,7 @@ mod send_input { turn: Arc, call_id: String, arguments: String, - ) -> Result { + ) -> Result { let args: SendInputArgs = parse_arguments(&arguments)?; let receiver_thread_id = agent_id(&args.id)?; let input_items = parse_collab_input(args.message, args.items)?; @@ -318,10 +318,10 @@ mod send_input { FunctionCallError::Fatal(format!("failed to serialize send_input result: {err}")) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } @@ -345,7 +345,7 @@ mod resume_agent { turn: Arc, call_id: String, arguments: String, - ) -> Result { + ) -> Result { let args: ResumeAgentArgs = parse_arguments(&arguments)?; let receiver_thread_id = agent_id(&args.id)?; let (receiver_agent_nickname, receiver_agent_role) = session @@ -432,10 +432,10 @@ mod resume_agent { FunctionCallError::Fatal(format!("failed to serialize resume_agent result: {err}")) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } async fn try_resume_closed_agent( @@ -495,7 +495,7 @@ pub(crate) mod wait { turn: Arc, call_id: String, arguments: String, - ) -> Result { + ) -> Result { let args: WaitArgs = parse_arguments(&arguments)?; if args.ids.is_empty() { return Err(FunctionCallError::RespondToModel( @@ -645,10 +645,10 @@ pub(crate) mod wait { FunctionCallError::Fatal(format!("failed to serialize wait result: {err}")) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: None, - }) + })) } async fn wait_for_final_status( @@ -688,7 +688,7 @@ pub mod close_agent { turn: Arc, call_id: String, arguments: String, - ) -> Result { + ) -> Result { let args: CloseAgentArgs = parse_arguments(&arguments)?; let agent_id = agent_id(&args.id)?; let (receiver_agent_nickname, receiver_agent_role) = session @@ -765,10 +765,10 @@ pub mod close_agent { FunctionCallError::Fatal(format!("failed to serialize close_agent result: {err}")) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } @@ -993,6 +993,8 @@ mod tests { use crate::protocol::SandboxPolicy; use crate::protocol::SessionSource; use crate::protocol::SubAgentSource; + use crate::tools::context::TextToolOutput; + use crate::tools::context::ToolOutputBox; use crate::turn_diff_tracker::TurnDiffTracker; use codex_protocol::ThreadId; use codex_protocol::models::ContentItem; @@ -1038,6 +1040,13 @@ mod tests { ) } + fn expect_text_output(output: ToolOutputBox) -> (String, Option) { + let output = (&*output as &dyn std::any::Any) + .downcast_ref::() + .expect("expected text output"); + (output.text.clone(), output.success) + } + #[tokio::test] async fn handler_rejects_non_function_payloads() { let (session, turn) = make_session_and_context().await; @@ -1160,13 +1169,7 @@ mod tests { .handle(invocation) .await .expect("spawn_agent should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - .. - } = output - else { - panic!("expected function output"); - }; + let (content, _) = expect_text_output(output); let result: SpawnAgentResult = serde_json::from_str(&content).expect("spawn_agent result should be json"); let agent_id = agent_id(&result.agent_id).expect("agent_id should be valid"); @@ -1259,13 +1262,7 @@ mod tests { .handle(invocation) .await .expect("spawn_agent should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - .. - } = output - else { - panic!("expected function output"); - }; + let (content, _) = expect_text_output(output); let result: SpawnAgentResult = serde_json::from_str(&content).expect("spawn_agent result should be json"); let agent_id = agent_id(&result.agent_id).expect("agent_id should be valid"); @@ -1349,14 +1346,7 @@ mod tests { .handle(invocation) .await .expect("spawn should succeed within configured depth"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: SpawnAgentResult = serde_json::from_str(&content).expect("spawn_agent result should be json"); assert!(!result.agent_id.is_empty()); @@ -1601,14 +1591,7 @@ mod tests { .handle(invocation) .await .expect("resume_agent should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: resume_agent::ResumeAgentResult = serde_json::from_str(&content).expect("resume_agent result should be json"); assert_eq!(result.status, status_before); @@ -1670,14 +1653,7 @@ mod tests { .handle(resume_invocation) .await .expect("resume_agent should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: resume_agent::ResumeAgentResult = serde_json::from_str(&content).expect("resume_agent result should be json"); assert_ne!(result.status, AgentStatus::NotFound); @@ -1693,14 +1669,7 @@ mod tests { .handle(send_invocation) .await .expect("send_input should succeed after resume"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: serde_json::Value = serde_json::from_str(&content).expect("send_input result should be json"); let submission_id = result @@ -1825,14 +1794,7 @@ mod tests { .handle(invocation) .await .expect("wait should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: wait::WaitResult = serde_json::from_str(&content).expect("wait result should be json"); assert_eq!( @@ -1869,14 +1831,7 @@ mod tests { .handle(invocation) .await .expect("wait should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: wait::WaitResult = serde_json::from_str(&content).expect("wait result should be json"); assert_eq!( @@ -1966,14 +1921,7 @@ mod tests { .handle(invocation) .await .expect("wait should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: wait::WaitResult = serde_json::from_str(&content).expect("wait result should be json"); assert_eq!( @@ -2006,14 +1954,7 @@ mod tests { .handle(invocation) .await .expect("close_agent should succeed"); - let ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), - success, - .. - } = output - else { - panic!("expected function output"); - }; + let (content, success) = expect_text_output(output); let result: close_agent::CloseAgentResult = serde_json::from_str(&content).expect("close_agent result should be json"); assert_eq!(result.status, status_before); diff --git a/codex-rs/core/src/tools/handlers/plan.rs b/codex-rs/core/src/tools/handlers/plan.rs index 2b43429cc..638eb6379 100644 --- a/codex-rs/core/src/tools/handlers/plan.rs +++ b/codex-rs/core/src/tools/handlers/plan.rs @@ -3,15 +3,15 @@ use crate::client_common::tools::ToolSpec; use crate::codex::Session; use crate::codex::TurnContext; use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; use crate::tools::spec::JsonSchema; use async_trait::async_trait; use codex_protocol::config_types::ModeKind; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::EventMsg; use std::collections::BTreeMap; @@ -67,7 +67,7 @@ impl ToolHandler for PlanHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -88,10 +88,10 @@ impl ToolHandler for PlanHandler { let content = handle_update_plan(session.as_ref(), turn.as_ref(), arguments, call_id).await?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/read_file.rs b/codex-rs/core/src/tools/handlers/read_file.rs index 59c6ced7f..25cab314d 100644 --- a/codex-rs/core/src/tools/handlers/read_file.rs +++ b/codex-rs/core/src/tools/handlers/read_file.rs @@ -1,4 +1,3 @@ -use codex_protocol::models::FunctionCallOutputBody; use std::collections::VecDeque; use std::path::PathBuf; @@ -7,8 +6,9 @@ use codex_utils_string::take_bytes_at_char_boundary; use serde::Deserialize; use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -98,7 +98,7 @@ impl ToolHandler for ReadFileHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { payload, .. } = invocation; let arguments = match payload { @@ -146,10 +146,10 @@ impl ToolHandler for ReadFileHandler { indentation::read_block(&path, offset, limit, indentation).await? } }; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(collected.join("\n")), + Ok(Box::new(TextToolOutput { + text: collected.join("\n"), success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/request_permissions.rs b/codex-rs/core/src/tools/handlers/request_permissions.rs index 9e4235abf..3650a339e 100644 --- a/codex-rs/core/src/tools/handlers/request_permissions.rs +++ b/codex-rs/core/src/tools/handlers/request_permissions.rs @@ -1,11 +1,11 @@ use async_trait::async_trait; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::request_permissions::RequestPermissionsArgs; use crate::function_tool::FunctionCallError; use crate::sandboxing::normalize_additional_permissions; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments_with_base_path; use crate::tools::registry::ToolHandler; @@ -24,7 +24,7 @@ impl ToolHandler for RequestPermissionsHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -67,9 +67,9 @@ impl ToolHandler for RequestPermissionsHandler { )) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/request_user_input.rs b/codex-rs/core/src/tools/handlers/request_user_input.rs index f9b444186..1a5ed6559 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -1,13 +1,12 @@ -use async_trait::async_trait; -use codex_protocol::models::FunctionCallOutputBody; - use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; +use async_trait::async_trait; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES; use codex_protocol::request_user_input::RequestUserInputArgs; @@ -63,7 +62,7 @@ impl ToolHandler for RequestUserInputHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -116,10 +115,10 @@ impl ToolHandler for RequestUserInputHandler { )) })?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/search_tool_bm25.rs b/codex-rs/core/src/tools/handlers/search_tool_bm25.rs index cb54a9b56..556883cbf 100644 --- a/codex-rs/core/src/tools/handlers/search_tool_bm25.rs +++ b/codex-rs/core/src/tools/handlers/search_tool_bm25.rs @@ -3,7 +3,6 @@ use bm25::Document; use bm25::Language; use bm25::SearchEngineBuilder; use codex_app_server_protocol::AppInfo; -use codex_protocol::models::FunctionCallOutputBody; use serde::Deserialize; use serde_json::json; use std::collections::HashMap; @@ -13,8 +12,9 @@ use crate::connectors; use crate::function_tool::FunctionCallError; use crate::mcp::CODEX_APPS_MCP_SERVER_NAME; use crate::mcp_connection_manager::ToolInfo; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -78,7 +78,7 @@ impl ToolHandler for SearchToolBm25Handler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { payload, session, @@ -141,10 +141,10 @@ impl ToolHandler for SearchToolBm25Handler { "tools": [], }) .to_string(); - return Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + return Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }); + })); } let documents: Vec> = entries @@ -184,10 +184,10 @@ impl ToolHandler for SearchToolBm25Handler { }) .to_string(); - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index 0c50d0226..542f3a279 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; use codex_protocol::ThreadId; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::ShellCommandToolCallParams; use codex_protocol::models::ShellToolCallParams; use std::sync::Arc; @@ -15,8 +14,9 @@ use crate::is_safe_command::is_known_safe_command; use crate::protocol::ExecCommandSource; use crate::shell::Shell; use crate::skills::maybe_emit_implicit_skill_invocation; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::events::ToolEmitter; use crate::tools::events::ToolEventCtx; @@ -165,7 +165,7 @@ impl ToolHandler for ShellHandler { } } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -253,7 +253,7 @@ impl ToolHandler for ShellCommandHandler { .unwrap_or(true) } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -305,7 +305,7 @@ impl ToolHandler for ShellCommandHandler { } impl ShellHandler { - async fn run_exec_like(args: RunExecLikeArgs) -> Result { + async fn run_exec_like(args: RunExecLikeArgs) -> Result { let RunExecLikeArgs { tool_name, exec_params, @@ -449,10 +449,10 @@ impl ShellHandler { .map(|result| result.output); let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None); let content = emitter.finish(event_ctx, out).await?; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/test_sync.rs b/codex-rs/core/src/tools/handlers/test_sync.rs index 4d8fe1025..210feda76 100644 --- a/codex-rs/core/src/tools/handlers/test_sync.rs +++ b/codex-rs/core/src/tools/handlers/test_sync.rs @@ -1,4 +1,3 @@ -use codex_protocol::models::FunctionCallOutputBody; use std::collections::HashMap; use std::collections::hash_map::Entry; use std::sync::Arc; @@ -11,8 +10,9 @@ use tokio::sync::Barrier; use tokio::time::sleep; use crate::function_tool::FunctionCallError; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -61,7 +61,7 @@ impl ToolHandler for TestSyncHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { payload, .. } = invocation; let arguments = match payload { @@ -91,10 +91,10 @@ impl ToolHandler for TestSyncHandler { sleep(Duration::from_millis(delay)).await; } - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text("ok".to_string()), + Ok(Box::new(TextToolOutput { + text: "ok".to_string(), success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/unified_exec.rs b/codex-rs/core/src/tools/handlers/unified_exec.rs index c2fc536b3..649a3ebab 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec.rs @@ -7,8 +7,9 @@ use crate::sandboxing::SandboxPermissions; use crate::shell::Shell; use crate::shell::get_shell_by_model_provided_path; use crate::skills::maybe_emit_implicit_skill_invocation; +use crate::tools::context::TextToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::apply_granted_turn_permissions; use crate::tools::handlers::apply_patch::intercept_apply_patch; @@ -24,7 +25,6 @@ use crate::unified_exec::UnifiedExecProcessManager; use crate::unified_exec::UnifiedExecResponse; use crate::unified_exec::WriteStdinRequest; use async_trait::async_trait; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::PermissionProfile; use serde::Deserialize; use std::path::PathBuf; @@ -114,7 +114,7 @@ impl ToolHandler for UnifiedExecHandler { !is_known_safe_command(&command) } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, @@ -291,10 +291,10 @@ impl ToolHandler for UnifiedExecHandler { let content = format_response(&response); - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::Text(content), + Ok(Box::new(TextToolOutput { + text: content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/handlers/view_image.rs b/codex-rs/core/src/tools/handlers/view_image.rs index 640175112..ffcf0f5dd 100644 --- a/codex-rs/core/src/tools/handlers/view_image.rs +++ b/codex-rs/core/src/tools/handlers/view_image.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; use codex_protocol::models::ContentItem; -use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::ImageDetail; use codex_protocol::models::local_image_content_items_with_label_number; @@ -13,8 +12,9 @@ use crate::features::Feature; use crate::function_tool::FunctionCallError; use crate::protocol::EventMsg; use crate::protocol::ViewImageToolCallEvent; +use crate::tools::context::ContentToolOutput; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; @@ -36,7 +36,7 @@ impl ToolHandler for ViewImageHandler { ToolKind::Function } - async fn handle(&self, invocation: ToolInvocation) -> Result { + async fn handle(&self, invocation: ToolInvocation) -> Result { if !invocation .turn .model_info @@ -121,9 +121,9 @@ impl ToolHandler for ViewImageHandler { ) .await; - Ok(ToolOutput::Function { - body: FunctionCallOutputBody::ContentItems(content), + Ok(Box::new(ContentToolOutput { + content, success: Some(true), - }) + })) } } diff --git a/codex-rs/core/src/tools/registry.rs b/codex-rs/core/src/tools/registry.rs index 74a44f1db..6e66ba289 100644 --- a/codex-rs/core/src/tools/registry.rs +++ b/codex-rs/core/src/tools/registry.rs @@ -10,7 +10,7 @@ use crate::memories::usage::emit_metric_for_tool_read; use crate::protocol::SandboxPolicy; use crate::sandbox_tags::sandbox_tag; use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolOutput; +use crate::tools::context::ToolOutputBox; use crate::tools::context::ToolPayload; use async_trait::async_trait; use codex_hooks::HookEvent; @@ -52,7 +52,7 @@ pub trait ToolHandler: Send + Sync { /// Perform the actual [ToolInvocation] and returns a [ToolOutput] containing /// the final output to return to the model. - async fn handle(&self, invocation: ToolInvocation) -> Result; + async fn handle(&self, invocation: ToolInvocation) -> Result; } pub struct ToolRegistry {