From 81e180044d91c42f20818c43fbc2558603e4361d Mon Sep 17 00:00:00 2001 From: "Adam Perry @ OpenAI" Date: Thu, 27 Aug 2026 01:54:58 +0000 Subject: [PATCH] Scope extension capabilities to invocation lifetimes (#41020) ## What changed - Add callback lifetimes to extension `ToolCall`, `ToolEnvironment`, turn-input context, and skill-read request types. - Require extension tool executors to handle calls for any invocation lifetime and tie their returned futures to that lifetime. - Update built-in extensions, adapters, and tests to use the lifetime-scoped APIs without retaining host-owned environment capabilities. GitOrigin-RevId: aca9ce3a1b6870df550bc84ec9308cc65533f161 --- codex-rs/core/src/codex_delegate.rs | 2 +- codex-rs/core/src/guardian/review.rs | 10 +-- codex-rs/core/src/session/turn.rs | 2 + .../src/tools/code_mode/execute_handler.rs | 5 +- .../core/src/tools/code_mode/wait_handler.rs | 5 +- .../core/src/tools/handlers/apply_patch.rs | 5 +- .../core/src/tools/handlers/current_time.rs | 5 +- codex-rs/core/src/tools/handlers/dynamic.rs | 5 +- .../src/tools/handlers/extension_tools.rs | 68 ++++++++++++++----- .../tools/handlers/get_context_remaining.rs | 5 +- .../list_available_plugins_to_install.rs | 5 +- codex-rs/core/src/tools/handlers/mcp.rs | 5 +- .../list_mcp_resource_templates.rs | 5 +- .../mcp_resource/list_mcp_resources.rs | 5 +- .../mcp_resource/read_mcp_resource.rs | 5 +- .../handlers/multi_agents/close_agent.rs | 5 +- .../handlers/multi_agents/resume_agent.rs | 5 +- .../tools/handlers/multi_agents/send_input.rs | 5 +- .../src/tools/handlers/multi_agents/spawn.rs | 5 +- .../src/tools/handlers/multi_agents/wait.rs | 5 +- .../handlers/multi_agents_v2/followup_task.rs | 5 +- .../multi_agents_v2/interrupt_agent.rs | 5 +- .../handlers/multi_agents_v2/list_agents.rs | 5 +- .../handlers/multi_agents_v2/send_message.rs | 5 +- .../tools/handlers/multi_agents_v2/spawn.rs | 5 +- .../tools/handlers/multi_agents_v2/wait.rs | 5 +- .../src/tools/handlers/new_context_window.rs | 5 +- codex-rs/core/src/tools/handlers/plan.rs | 5 +- .../src/tools/handlers/request_permissions.rs | 5 +- .../tools/handlers/request_plugin_install.rs | 5 +- .../src/tools/handlers/request_user_input.rs | 5 +- .../tools/handlers/send_user_message_async.rs | 5 +- codex-rs/core/src/tools/handlers/sleep.rs | 5 +- codex-rs/core/src/tools/handlers/test_sync.rs | 5 +- .../core/src/tools/handlers/tool_search.rs | 5 +- .../handlers/unified_exec/exec_command.rs | 5 +- .../handlers/unified_exec/write_stdin.rs | 5 +- .../core/src/tools/handlers/view_image.rs | 5 +- .../tools/handlers/wait_for_environment.rs | 5 +- codex-rs/core/src/tools/parallel.rs | 5 +- codex-rs/core/src/tools/registry_tests.rs | 15 +++- codex-rs/core/src/tools/router_tests.rs | 15 ++-- codex-rs/core/src/tools/spec_plan.rs | 13 ++-- codex-rs/core/src/tools/spec_plan_tests.rs | 16 +++-- .../src/tools/tool_dispatch_trace_tests.rs | 5 +- codex-rs/core/tests/suite/code_mode.rs | 11 +-- .../core/tests/suite/model_visible_layout.rs | 43 +++++++----- codex-rs/core/tests/suite/search_tool.rs | 9 ++- codex-rs/core/tests/suite/skills_extension.rs | 10 ++- .../ext/extension-api/src/contributors.rs | 6 +- .../src/contributors/turn_input.rs | 9 ++- codex-rs/ext/extension-api/tests/registry.rs | 4 +- codex-rs/ext/goal/src/extension.rs | 4 +- codex-rs/ext/goal/src/tool.rs | 18 +++-- .../ext/goal/tests/goal_extension_backend.rs | 14 ++-- codex-rs/ext/history-notes/src/extension.rs | 4 +- codex-rs/ext/history-notes/src/tools.rs | 12 +++- .../tests/history_notes_extension.rs | 4 +- .../ext/image-generation/src/extension.rs | 2 +- codex-rs/ext/image-generation/src/tool.rs | 20 ++++-- codex-rs/ext/memories/src/extension.rs | 4 +- codex-rs/ext/memories/src/tests.rs | 5 +- .../ext/memories/src/tools/ad_hoc_note.rs | 9 ++- codex-rs/ext/memories/src/tools/list.rs | 9 ++- codex-rs/ext/memories/src/tools/mod.rs | 4 +- codex-rs/ext/memories/src/tools/read.rs | 9 ++- codex-rs/ext/memories/src/tools/search.rs | 9 ++- codex-rs/ext/skills/src/extension.rs | 10 +-- codex-rs/ext/skills/src/provider.rs | 10 ++- codex-rs/ext/skills/src/provider/executor.rs | 5 +- codex-rs/ext/skills/src/provider/host.rs | 5 +- .../ext/skills/src/provider/orchestrator.rs | 5 +- .../src/shadow_selection_experiment/mod.rs | 2 +- codex-rs/ext/skills/src/sources.rs | 2 +- codex-rs/ext/skills/src/state.rs | 6 +- codex-rs/ext/skills/src/tools/list.rs | 7 +- codex-rs/ext/skills/src/tools/mod.rs | 4 +- codex-rs/ext/skills/src/tools/read.rs | 9 ++- .../tests/executor_file_system_authority.rs | 3 + codex-rs/ext/skills/tests/skills_extension.rs | 25 ++++--- .../shadow_task_context_tests.rs | 5 +- codex-rs/ext/web-search/src/extension.rs | 4 +- codex-rs/ext/web-search/src/tool.rs | 14 ++-- codex-rs/tools/src/tool_call.rs | 13 ++-- codex-rs/tools/src/tool_executor.rs | 5 +- 85 files changed, 477 insertions(+), 202 deletions(-) diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 0a74f925e1..7733012007 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -43,7 +43,7 @@ use codex_protocol::turn_input::TurnStartOptions; use crate::session::completed_session_loop_termination; pub(crate) struct GuardianReadOnlyHistoryTools( - pub(crate) Vec>>, + pub(crate) Vec codex_tools::ToolExecutor>>>, ); /// Start an interactive sub-Codex thread and return its runtime and IO channels. diff --git a/codex-rs/core/src/guardian/review.rs b/codex-rs/core/src/guardian/review.rs index 145fa12dcb..6aaeffda7a 100644 --- a/codex-rs/core/src/guardian/review.rs +++ b/codex-rs/core/src/guardian/review.rs @@ -25,6 +25,7 @@ use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::WarningEvent; +use futures::future::BoxFuture; use std::sync::Arc; use tokio::sync::oneshot; use tokio::time::Instant; @@ -748,9 +749,8 @@ pub(crate) async fn review_approval_request( request: GuardianApprovalRequest, reasons: ApprovalRequestReasons, ) -> ReviewDecision { - // Box the delegated review future so callers do not inline the entire - // guardian session state machine into their own async stack. - Box::pin(run_guardian_review( + // Erase the delegated future to bound its async state and the tool handlers' Send checks. + let review: BoxFuture<'_, ReviewDecision> = Box::pin(run_guardian_review( Arc::clone(session), context.into(), review_id, @@ -761,8 +761,8 @@ pub(crate) async fn review_approval_request( approval_request_source: GuardianApprovalRequestSource::MainTurn, external_cancel: None, }, - )) - .await + )); + review.await } pub(crate) async fn review_approval_request_with_cancel( diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 9b88c0f5a8..d1a0dc4bf3 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::collections::HashSet; +use std::marker::PhantomData; use std::sync::Arc; use std::sync::atomic::Ordering; @@ -931,6 +932,7 @@ async fn build_extension_turn_input_items( .turn_environments() .enumerate() .map(|(index, environment)| TurnInputEnvironment { + _lifetime: PhantomData, environment_id: environment.selection.environment_id.clone(), cwd: environment.cwd().clone(), is_primary: index == 0, diff --git a/codex-rs/core/src/tools/code_mode/execute_handler.rs b/codex-rs/core/src/tools/code_mode/execute_handler.rs index 5b90528e63..c8a497fac1 100644 --- a/codex-rs/core/src/tools/code_mode/execute_handler.rs +++ b/codex-rs/core/src/tools/code_mode/execute_handler.rs @@ -146,7 +146,10 @@ impl ToolExecutor for CodeModeExecuteHandler { self.spec.clone() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/code_mode/wait_handler.rs b/codex-rs/core/src/tools/code_mode/wait_handler.rs index 63bd28dea4..bf34f2d136 100644 --- a/codex-rs/core/src/tools/code_mode/wait_handler.rs +++ b/codex-rs/core/src/tools/code_mode/wait_handler.rs @@ -54,7 +54,10 @@ impl ToolExecutor for CodeModeWaitHandler { create_wait_tool() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index 4bfa2811c4..f1559f5407 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -357,7 +357,10 @@ impl ToolExecutor for ApplyPatchHandler { create_apply_patch_freeform_tool(self.multi_environment) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/current_time.rs b/codex-rs/core/src/tools/handlers/current_time.rs index 781f5b0731..c0a9e61201 100644 --- a/codex-rs/core/src/tools/handlers/current_time.rs +++ b/codex-rs/core/src/tools/handlers/current_time.rs @@ -80,7 +80,10 @@ impl ToolExecutor for CurrentTimeHandler { }) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { if !matches!(invocation.payload, ToolPayload::Function { .. }) { return Err(FunctionCallError::RespondToModel(format!( diff --git a/codex-rs/core/src/tools/handlers/dynamic.rs b/codex-rs/core/src/tools/handlers/dynamic.rs index 4b8fa03797..f2b7860d06 100644 --- a/codex-rs/core/src/tools/handlers/dynamic.rs +++ b/codex-rs/core/src/tools/handlers/dynamic.rs @@ -105,7 +105,10 @@ impl ToolExecutor for DynamicToolHandler { ) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/extension_tools.rs b/codex-rs/core/src/tools/handlers/extension_tools.rs index aca1f6fec7..cad51e0be7 100644 --- a/codex-rs/core/src/tools/handlers/extension_tools.rs +++ b/codex-rs/core/src/tools/handlers/extension_tools.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::sync::Arc; use std::sync::Weak; @@ -27,10 +28,14 @@ use crate::tools::registry::CoreToolRuntime; use crate::tools::registry::ToolExecutor; use crate::turn_metadata::McpTurnMetadataContext; -pub(crate) struct ExtensionToolAdapter(Arc>); +pub(crate) struct ExtensionToolAdapter( + Arc codex_tools::ToolExecutor>>, +); impl ExtensionToolAdapter { - pub(crate) fn new(executor: Arc>) -> Self { + pub(crate) fn new( + executor: Arc codex_tools::ToolExecutor>>, + ) -> Self { Self(executor) } } @@ -56,7 +61,10 @@ impl ToolExecutor for ExtensionToolAdapter { self.0.search_info() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { self.0.handle(to_extension_call(&invocation).await).await }) } } @@ -155,7 +163,7 @@ impl TurnItemEmitter for CoreTurnItemEmitter { } } -async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall { +async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall<'_> { let conversation_history = ConversationHistory::new(invocation.session.clone_history().await.into_raw_items()); let codex_turn_metadata = invocation @@ -185,6 +193,7 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall { .additional_permissions; let file_system_sandbox_context = environment.sandbox_context(additional_permissions); environments.push(ToolEnvironment { + _lifetime: PhantomData, environment_id: environment.selection.environment_id.clone(), cwd: native_cwd, file_system: environment.environment.get_filesystem(), @@ -226,6 +235,7 @@ mod tests { use codex_tools::ToolCallSource as ExtensionToolCallSource; use codex_utils_absolute_path::test_support::PathExt; use codex_utils_absolute_path::test_support::test_path_buf; + use codex_utils_path_uri::PathUri; use core_test_support::responses::strip_response_item_id; use core_test_support::responses::strip_response_item_ids; use pretty_assertions::assert_eq; @@ -246,7 +256,9 @@ mod tests { struct StubExtensionExecutor; - impl codex_extension_api::ToolExecutor for StubExtensionExecutor { + impl<'call> codex_extension_api::ToolExecutor> + for StubExtensionExecutor + { fn tool_name(&self) -> codex_tools::ToolName { codex_tools::ToolName::plain("extension_echo") } @@ -270,7 +282,13 @@ mod tests { }) } - fn handle(&self, _call: codex_tools::ToolCall) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>( + &'a self, + _call: codex_tools::ToolCall<'call>, + ) -> codex_tools::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async { Ok( Box::new(codex_tools::JsonToolOutput::new(json!({ "ok": true }))) @@ -281,10 +299,13 @@ mod tests { } struct CapturingExtensionExecutor { - captured_call: Arc>>, + captured_call: Arc>>>, + captured_sandbox_cwds: Arc>>>, } - impl codex_extension_api::ToolExecutor for CapturingExtensionExecutor { + impl<'call> codex_extension_api::ToolExecutor> + for CapturingExtensionExecutor + { fn tool_name(&self) -> codex_tools::ToolName { codex_tools::ToolName::plain("extension_echo") } @@ -300,7 +321,13 @@ mod tests { }) } - fn handle(&self, call: codex_tools::ToolCall) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>( + &'a self, + call: codex_tools::ToolCall<'call>, + ) -> codex_tools::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } @@ -308,7 +335,7 @@ mod tests { impl CapturingExtensionExecutor { async fn handle_call( &self, - call: codex_tools::ToolCall, + call: codex_tools::ToolCall<'_>, ) -> Result, codex_tools::FunctionCallError> { call.turn_item_emitter .emit_started(ExtensionTurnItem { @@ -321,6 +348,16 @@ mod tests { legacy_events: Vec::new(), }) .await; + // Record owned metadata only; the invocation lifetime belongs to this callback. + *self.captured_sandbox_cwds.lock().await = call + .environments + .iter() + .map(|environment| environment.file_system_sandbox_context.cwd.clone()) + .collect(); + let call = codex_tools::ToolCall { + environments: Vec::new(), + ..call + }; *self.captured_call.lock().await = Some(call); Ok( Box::new(codex_tools::JsonToolOutput::new(json!({ "ok": true }))) @@ -382,8 +419,10 @@ mod tests { #[tokio::test] async fn passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call() { let captured_call = Arc::new(Mutex::new(None)); + let captured_sandbox_cwds = Arc::new(Mutex::new(Vec::new())); let handler = ExtensionToolAdapter::new(Arc::new(CapturingExtensionExecutor { captured_call: Arc::clone(&captured_call), + captured_sandbox_cwds: Arc::clone(&captured_sandbox_cwds), })); let (session, turn, rx) = crate::session::tests::make_session_and_context_with_rx().await; let weak_session = Arc::downgrade(&session); @@ -465,14 +504,7 @@ mod tests { runtime_tool_call_id: "nested-call-1".to_string(), } ); - assert_eq!( - captured_call - .environments - .iter() - .map(|environment| environment.file_system_sandbox_context.cwd.clone()) - .collect::>(), - expected_sandbox_cwds - ); + assert_eq!(*captured_sandbox_cwds.lock().await, expected_sandbox_cwds); assert_eq!( strip_response_item_ids(captured_call.conversation_history.items()), vec![expected_history_item] diff --git a/codex-rs/core/src/tools/handlers/get_context_remaining.rs b/codex-rs/core/src/tools/handlers/get_context_remaining.rs index 1bc4c33b34..3eecdc54f7 100644 --- a/codex-rs/core/src/tools/handlers/get_context_remaining.rs +++ b/codex-rs/core/src/tools/handlers/get_context_remaining.rs @@ -67,7 +67,10 @@ impl ToolExecutor for GetContextRemainingHandler { create_get_context_remaining_tool() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { if !matches!(invocation.payload, ToolPayload::Function { .. }) { return Err(FunctionCallError::RespondToModel( diff --git a/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs b/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs index 766b0e9b75..1b001f7c45 100644 --- a/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs +++ b/codex-rs/core/src/tools/handlers/list_available_plugins_to_install.rs @@ -67,7 +67,10 @@ impl ToolExecutor for ListAvailablePluginsToInstallHandler { false } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/mcp.rs b/codex-rs/core/src/tools/handlers/mcp.rs index 6995c4d2f6..9d84ddc36b 100644 --- a/codex-rs/core/src/tools/handlers/mcp.rs +++ b/codex-rs/core/src/tools/handlers/mcp.rs @@ -163,7 +163,10 @@ impl ToolExecutor for McpHandler { ) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs index a988038dec..d6baedcb91 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resource_templates.rs @@ -30,7 +30,10 @@ impl ToolExecutor for ListMcpResourceTemplatesHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs index d0d0d0f564..24e8228c62 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/list_mcp_resources.rs @@ -30,7 +30,10 @@ impl ToolExecutor for ListMcpResourcesHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs index 3049f391c9..ab534066e7 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource/read_mcp_resource.rs @@ -33,7 +33,10 @@ impl ToolExecutor for ReadMcpResourceHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs b/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs index cdaf4233bb..fe672460e4 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/close_agent.rs @@ -21,7 +21,10 @@ impl ToolExecutor for Handler { ) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { handle_close_agent(invocation).await.map(boxed_tool_output) }) } } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs b/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs index 7f5b2e9c17..e2fe5801d9 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/resume_agent.rs @@ -24,7 +24,10 @@ impl ToolExecutor for Handler { ) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { handle_resume_agent(invocation).await.map(boxed_tool_output) }) } } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs b/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs index e35233d131..33e228fae5 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/send_input.rs @@ -21,7 +21,10 @@ impl ToolExecutor for Handler { ) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs index 0a5325c195..9dbf2adda0 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs @@ -36,7 +36,10 @@ impl ToolExecutor for Handler { ) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { handle_spawn_agent(invocation).await.map(boxed_tool_output) }) } } diff --git a/codex-rs/core/src/tools/handlers/multi_agents/wait.rs b/codex-rs/core/src/tools/handlers/multi_agents/wait.rs index 84ebc35c1e..9f6f970d08 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/wait.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/wait.rs @@ -43,7 +43,10 @@ impl ToolExecutor for Handler { ) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs index 244fe6e08b..19de4d842f 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/followup_task.rs @@ -17,7 +17,10 @@ impl ToolExecutor for Handler { create_followup_task_tool() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let mut analytics = ToolCallAnalytics::new(&invocation, CollabAgentTool::FollowupTask); let result = self.handle_call(invocation, &mut analytics).await; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/interrupt_agent.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/interrupt_agent.rs index 811c23d1e8..b6eb267d9c 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/interrupt_agent.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/interrupt_agent.rs @@ -15,7 +15,10 @@ impl ToolExecutor for Handler { create_interrupt_agent_tool_v2() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let mut analytics = ToolCallAnalytics::new(&invocation, CollabAgentTool::InterruptAgent); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs index 48485c8063..669a994589 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/list_agents.rs @@ -15,7 +15,10 @@ impl ToolExecutor for Handler { create_list_agents_tool() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let analytics = ToolCallAnalytics::new(&invocation, CollabAgentTool::ListAgents); let result = self.handle_call(invocation).await; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs index 3461827a03..70fc7d4350 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/send_message.rs @@ -17,7 +17,10 @@ impl ToolExecutor for Handler { create_send_message_tool() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let mut analytics = ToolCallAnalytics::new(&invocation, CollabAgentTool::SendMessage); let result = self.handle_call(invocation, &mut analytics).await; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index 61a6240f9a..63020097bc 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -37,7 +37,10 @@ impl ToolExecutor for Handler { create_spawn_agent_tool_v2(self.options.clone()) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let analytics = invocation.session.services.analytics_events_client.clone(); let sender_thread_id = invocation.session.thread_id; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs index 14715ad326..d2f92feb8d 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs @@ -28,7 +28,10 @@ impl ToolExecutor for Handler { create_wait_agent_tool_v2(self.options) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/new_context_window.rs b/codex-rs/core/src/tools/handlers/new_context_window.rs index e1cd656399..45b1a191db 100644 --- a/codex-rs/core/src/tools/handlers/new_context_window.rs +++ b/codex-rs/core/src/tools/handlers/new_context_window.rs @@ -24,7 +24,10 @@ impl ToolExecutor for NewContextWindowHandler { create_new_context_window_tool() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { if !matches!(invocation.payload, ToolPayload::Function { .. }) { return Err(FunctionCallError::RespondToModel( diff --git a/codex-rs/core/src/tools/handlers/plan.rs b/codex-rs/core/src/tools/handlers/plan.rs index 7fdd1fb4a3..3495656223 100644 --- a/codex-rs/core/src/tools/handlers/plan.rs +++ b/codex-rs/core/src/tools/handlers/plan.rs @@ -54,7 +54,10 @@ impl ToolExecutor for PlanHandler { create_update_plan_tool() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/request_permissions.rs b/codex-rs/core/src/tools/handlers/request_permissions.rs index 4d66985cb7..fc28c80d81 100644 --- a/codex-rs/core/src/tools/handlers/request_permissions.rs +++ b/codex-rs/core/src/tools/handlers/request_permissions.rs @@ -34,7 +34,10 @@ impl ToolExecutor for RequestPermissionsHandler { create_request_permissions_tool(request_permissions_tool_description()) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/request_plugin_install.rs b/codex-rs/core/src/tools/handlers/request_plugin_install.rs index 25348720ac..ede1da318f 100644 --- a/codex-rs/core/src/tools/handlers/request_plugin_install.rs +++ b/codex-rs/core/src/tools/handlers/request_plugin_install.rs @@ -82,7 +82,10 @@ impl ToolExecutor for RequestPluginInstallHandler { false } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } 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 331797fa56..8608045962 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -37,7 +37,10 @@ impl ToolExecutor for RequestUserInputHandler { create_request_user_input_tool(request_user_input_tool_description(&self.available_modes)) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/send_user_message_async.rs b/codex-rs/core/src/tools/handlers/send_user_message_async.rs index fbf4603b65..cb3a958a02 100644 --- a/codex-rs/core/src/tools/handlers/send_user_message_async.rs +++ b/codex-rs/core/src/tools/handlers/send_user_message_async.rs @@ -56,7 +56,10 @@ impl ToolExecutor for SendUserMessageAsyncHandler { }) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let ToolInvocation { session, diff --git a/codex-rs/core/src/tools/handlers/sleep.rs b/codex-rs/core/src/tools/handlers/sleep.rs index c3068e6e8d..179157a6bf 100644 --- a/codex-rs/core/src/tools/handlers/sleep.rs +++ b/codex-rs/core/src/tools/handlers/sleep.rs @@ -73,7 +73,10 @@ impl ToolExecutor for SleepHandler { ToolExposure::DirectModelOnly } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let ToolInvocation { session, diff --git a/codex-rs/core/src/tools/handlers/test_sync.rs b/codex-rs/core/src/tools/handlers/test_sync.rs index e4b4a9e1ac..f9f15f01ad 100644 --- a/codex-rs/core/src/tools/handlers/test_sync.rs +++ b/codex-rs/core/src/tools/handlers/test_sync.rs @@ -73,7 +73,10 @@ impl ToolExecutor for TestSyncHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/tool_search.rs b/codex-rs/core/src/tools/handlers/tool_search.rs index 15c2292c76..04253c04bb 100644 --- a/codex-rs/core/src/tools/handlers/tool_search.rs +++ b/codex-rs/core/src/tools/handlers/tool_search.rs @@ -179,7 +179,10 @@ impl ToolExecutor for ToolSearchHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs index b3b600b549..c72a278abd 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs @@ -99,7 +99,10 @@ impl ToolExecutor for ExecCommandHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs b/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs index 2a8d3921f0..657da46674 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec/write_stdin.rs @@ -46,7 +46,10 @@ impl ToolExecutor for WriteStdinHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/view_image.rs b/codex-rs/core/src/tools/handlers/view_image.rs index 5c2d83ea4a..7f80995857 100644 --- a/codex-rs/core/src/tools/handlers/view_image.rs +++ b/codex-rs/core/src/tools/handlers/view_image.rs @@ -81,7 +81,10 @@ impl ToolExecutor for ViewImageHandler { true } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(self.handle_call(invocation)) } } diff --git a/codex-rs/core/src/tools/handlers/wait_for_environment.rs b/codex-rs/core/src/tools/handlers/wait_for_environment.rs index 0f3a1628fb..daa9b81d11 100644 --- a/codex-rs/core/src/tools/handlers/wait_for_environment.rs +++ b/codex-rs/core/src/tools/handlers/wait_for_environment.rs @@ -103,7 +103,10 @@ impl ToolExecutor for WaitForEnvironmentHandler { }) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async move { let ToolInvocation { payload, diff --git a/codex-rs/core/src/tools/parallel.rs b/codex-rs/core/src/tools/parallel.rs index 191850c4eb..7886e4ee98 100644 --- a/codex-rs/core/src/tools/parallel.rs +++ b/codex-rs/core/src/tools/parallel.rs @@ -537,7 +537,10 @@ mod tests { }) } - fn handle(&self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async { Ok( Box::new(FunctionToolOutput::from_text("ok".to_string(), Some(true))) diff --git a/codex-rs/core/src/tools/registry_tests.rs b/codex-rs/core/src/tools/registry_tests.rs index 6cc67a8053..798dba3652 100644 --- a/codex-rs/core/src/tools/registry_tests.rs +++ b/codex-rs/core/src/tools/registry_tests.rs @@ -20,7 +20,10 @@ impl ToolExecutor for TestHandler { test_spec(&self.tool_name) } - fn handle(&self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async { Ok( Box::new(crate::tools::context::FunctionToolOutput::from_text( @@ -48,7 +51,10 @@ impl ToolExecutor for ReadinessTestHandler { self.handler.spec() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { self.handler.handle(invocation) } } @@ -81,7 +87,10 @@ impl ToolExecutor for LifecycleTestHandler { test_spec(&self.tool_name) } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { assert_eq!( invocation.tool_name, self.tool_name.clone().with_default_namespace() diff --git a/codex-rs/core/src/tools/router_tests.rs b/codex-rs/core/src/tools/router_tests.rs index 9d80d39a0f..7a12768805 100644 --- a/codex-rs/core/src/tools/router_tests.rs +++ b/codex-rs/core/src/tools/router_tests.rs @@ -65,14 +65,14 @@ impl codex_extension_api::ToolContributor for ExtensionEchoContributor { &self, _session_store: &ExtensionData, _thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { vec![Arc::new(ExtensionEchoExecutor)] } } struct ExtensionEchoExecutor; -impl ToolExecutor for ExtensionEchoExecutor { +impl<'call> ToolExecutor> for ExtensionEchoExecutor { fn tool_name(&self) -> ToolName { ToolName::namespaced("extension/", "echo") } @@ -100,7 +100,10 @@ impl ToolExecutor for ExtensionEchoExecutor { }) } - fn handle(&self, call: ExtensionToolCall) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ExtensionToolCall<'call>) -> codex_tools::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } @@ -108,7 +111,7 @@ impl ToolExecutor for ExtensionEchoExecutor { impl ExtensionEchoExecutor { async fn handle_call( &self, - call: ExtensionToolCall, + call: ExtensionToolCall<'_>, ) -> Result, codex_tools::FunctionCallError> { let arguments: serde_json::Value = serde_json::from_str(call.function_arguments()?).expect("test arguments should parse"); @@ -130,7 +133,9 @@ fn extension_tool_test_registry() -> Arc> { fn test_tool_router( step_context: &StepContext, mcp_tools: Vec, - extension_tool_executors: impl IntoIterator>>, + extension_tool_executors: impl IntoIterator< + Item = Arc ToolExecutor>>, + >, dynamic_tools: &[DynamicToolSpec], ) -> ToolRouter { let mut registry = build_core_tool_registry( diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index dad0ba3487..9e50225a29 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -285,7 +285,9 @@ pub(crate) fn append_source_tools( turn_context: &TurnContext, registry: &mut ToolRegistry, mcp_tools: Vec, - extension_tool_executors: impl IntoIterator>>, + extension_tool_executors: impl IntoIterator< + Item = Arc ToolExecutor>>, + >, dynamic_tools: &[DynamicToolSpec], ) -> Vec { if crate::guardian::is_basic_session_source(&turn_context.session_source) { @@ -305,7 +307,7 @@ pub(crate) fn append_source_tools( pub(crate) fn extension_tool_executors<'a>( session: &'a Session, step_store: &'a ExtensionData, -) -> impl Iterator>> + 'a { +) -> impl Iterator ToolExecutor>>> + 'a { session .services .extensions @@ -1283,7 +1285,7 @@ fn append_tool_search_executor( fn append_extension_tool_executors( turn_context: &TurnContext, - executors: impl IntoIterator>>, + executors: impl IntoIterator ToolExecutor>>>, registry: &mut ToolRegistry, ) -> Option { let standalone_web_search_enabled = standalone_web_search_enabled(turn_context); @@ -1356,7 +1358,10 @@ impl ToolExecutor for MultiAgentV2NamespaceOverride { self.handler.search_info() } - fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { self.handler.handle(invocation) } } diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index 50ab556077..a334cd4679 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -69,7 +69,7 @@ const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration"; struct ToolPlanInputs { tool_runtimes: Vec, tool_suggest_candidates: Option, - extension_tool_executors: Vec>>, + extension_tool_executors: Vec ToolExecutor>>>, wait_for_environment_tool_config: Option>, dynamic_tools: Vec, } @@ -310,7 +310,7 @@ struct TestNamespaceExtensionTool { tool_name: &'static str, } -impl ToolExecutor for TestNamespaceExtensionTool { +impl<'call> ToolExecutor> for TestNamespaceExtensionTool { fn tool_name(&self) -> ToolName { ToolName::namespaced(self.namespace, self.tool_name) } @@ -330,7 +330,10 @@ impl ToolExecutor for TestNamespaceExtensionTool { }) } - fn handle(&self, _call: ExtensionToolCall) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, _call: ExtensionToolCall<'call>) -> codex_tools::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async { Ok(Box::new(codex_tools::JsonToolOutput::new(json!({}))) as Box) }) @@ -339,7 +342,7 @@ impl ToolExecutor for TestNamespaceExtensionTool { struct DeferredExtensionTool; -impl ToolExecutor for DeferredExtensionTool { +impl<'call> ToolExecutor> for DeferredExtensionTool { fn tool_name(&self) -> ToolName { ToolName::plain("extension_echo") } @@ -366,7 +369,10 @@ impl ToolExecutor for DeferredExtensionTool { ToolExposure::Deferred } - fn handle(&self, _call: ExtensionToolCall) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, _call: ExtensionToolCall<'call>) -> codex_tools::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async { panic!("spec planning should not execute extension tools") }) } } diff --git a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs index fd5cff4fa2..a06c87d3d3 100644 --- a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs +++ b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs @@ -48,7 +48,10 @@ impl ToolExecutor for TestHandler { }) } - fn handle(&self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, _invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'a> + where + ToolInvocation: 'a, + { Box::pin(async { Ok( Box::new(FunctionToolOutput::from_text("ok".to_string(), Some(true))) diff --git a/codex-rs/core/tests/suite/code_mode.rs b/codex-rs/core/tests/suite/code_mode.rs index 3f0b41fe81..9c9c195a33 100644 --- a/codex-rs/core/tests/suite/code_mode.rs +++ b/codex-rs/core/tests/suite/code_mode.rs @@ -4748,7 +4748,7 @@ impl ToolContributor for NamespacedCustomTool { &self, _session_store: &ExtensionData, _thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { Vec::new() } @@ -4757,7 +4757,7 @@ impl ToolContributor for NamespacedCustomTool { _session_store: &ExtensionData, _thread_store: &ExtensionData, _step_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { vec![Arc::new(Self { generation: self.generations.fetch_add(1, Ordering::Relaxed) + 1, generations: Arc::clone(&self.generations), @@ -4765,7 +4765,7 @@ impl ToolContributor for NamespacedCustomTool { } } -impl ToolExecutor for NamespacedCustomTool { +impl<'call> ToolExecutor> for NamespacedCustomTool { fn tool_name(&self) -> ToolName { ToolName::namespaced("editor", "apply_patch") } @@ -4787,7 +4787,10 @@ impl ToolExecutor for NamespacedCustomTool { }) } - fn handle(&self, call: ToolCall) -> ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async move { let ToolPayload::Custom { input } = call.payload else { return Err(FunctionCallError::Fatal( diff --git a/codex-rs/core/tests/suite/model_visible_layout.rs b/codex-rs/core/tests/suite/model_visible_layout.rs index c7fe7d1de2..fa8445eb38 100644 --- a/codex-rs/core/tests/suite/model_visible_layout.rs +++ b/codex-rs/core/tests/suite/model_visible_layout.rs @@ -16,7 +16,6 @@ use codex_extension_api::ExtensionRegistry; use codex_extension_api::ExtensionRegistryBuilder; use codex_extension_api::TurnInputContext; use codex_extension_api::TurnInputContributor; -use codex_extension_api::TurnInputEnvironment; use codex_features::Feature; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; @@ -53,27 +52,38 @@ use serde_json::json; const PRETURN_CONTEXT_DIFF_CWD: &str = "PRETURN_CONTEXT_DIFF_CWD"; -struct RecordingTurnInputContributor(Arc>>); +struct RecordingTurnInputContributor(Arc>>); impl TurnInputContributor for RecordingTurnInputContributor { fn contribute<'a>( &'a self, - input: TurnInputContext, + input: TurnInputContext<'a>, _extension_metrics: Option>, _session_store: &'a ExtensionData, _thread_store: &'a ExtensionData, _turn_store: &'a ExtensionData, ) -> ExtensionFuture<'a, Vec>> { Box::pin(async move { - self.0 - .lock() - .expect("recorded environments lock") - .extend(input.environments); + let mut recorded_environments = self.0.lock().expect("recorded environments lock"); + for environment in input.environments { + recorded_environments.push(RecordedTurnInputEnvironment { + environment_id: environment.environment_id, + cwd: environment.cwd, + is_primary: environment.is_primary, + }); + } Vec::new() }) } } +#[derive(Clone, Debug, Eq, PartialEq)] +struct RecordedTurnInputEnvironment { + environment_id: String, + cwd: PathUri, + is_primary: bool, +} + fn skills_extensions() -> Arc> { let mut extensions = ExtensionRegistryBuilder::::new(); install(&mut extensions, |config: &Config| SkillsExtensionConfig { @@ -175,16 +185,15 @@ async fn turn_input_contributors_receive_foreign_environment_cwds() -> Result<() let recorded_environments = recorded_environments .lock() .expect("recorded environments lock") - .iter() - .map(|environment| { - ( - environment.environment_id.clone(), - environment.cwd.clone(), - environment.is_primary, - ) - }) - .collect::>(); - assert_eq!(recorded_environments, vec![(environment_id, cwd, true)]); + .clone(); + assert_eq!( + recorded_environments, + vec![RecordedTurnInputEnvironment { + environment_id, + cwd, + is_primary: true, + }] + ); Ok(()) } diff --git a/codex-rs/core/tests/suite/search_tool.rs b/codex-rs/core/tests/suite/search_tool.rs index 3e1e79b4ff..45b0fcb93d 100644 --- a/codex-rs/core/tests/suite/search_tool.rs +++ b/codex-rs/core/tests/suite/search_tool.rs @@ -941,12 +941,12 @@ impl ToolContributor for DeferredCustomTool { &self, _session_store: &ExtensionData, _thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { vec![Arc::new(Self)] } } -impl ToolExecutor for DeferredCustomTool { +impl<'call> ToolExecutor> for DeferredCustomTool { fn tool_name(&self) -> ToolName { ToolName::plain("custom_echo") } @@ -968,7 +968,10 @@ impl ToolExecutor for DeferredCustomTool { ToolExposure::Deferred } - fn handle(&self, call: ToolCall) -> ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async move { let ToolPayload::Custom { input } = call.payload else { return Err(FunctionCallError::Fatal( diff --git a/codex-rs/core/tests/suite/skills_extension.rs b/codex-rs/core/tests/suite/skills_extension.rs index fa92c557ae..29d5b14a26 100644 --- a/codex-rs/core/tests/suite/skills_extension.rs +++ b/codex-rs/core/tests/suite/skills_extension.rs @@ -134,7 +134,10 @@ impl SkillProvider for StaticSkillProvider { Box::pin(async move { Ok(catalog) }) } - fn read(&self, request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult> { + fn read<'a>( + &'a self, + request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult> { let result = self .main_prompt_contents .clone() @@ -158,7 +161,10 @@ impl SkillProvider for CatalogSkillProvider { Box::pin(async { Ok(self.catalog.clone()) }) } - fn read(&self, _request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult> { + fn read<'a>( + &'a self, + _request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult> { Box::pin(async { Err(SkillProviderError::new( "production-flow catalog test does not read skills", diff --git a/codex-rs/ext/extension-api/src/contributors.rs b/codex-rs/ext/extension-api/src/contributors.rs index 26ddfbc03f..8e9fd0a1f9 100644 --- a/codex-rs/ext/extension-api/src/contributors.rs +++ b/codex-rs/ext/extension-api/src/contributors.rs @@ -227,7 +227,7 @@ pub trait TurnInputContributor: Send + Sync { /// capability is bound to the effective model for that turn. fn contribute<'a>( &'a self, - input: TurnInputContext, + input: TurnInputContext<'a>, extension_metrics: Option>, session_store: &'a ExtensionData, thread_store: &'a ExtensionData, @@ -303,7 +303,7 @@ pub trait ToolContributor: Send + Sync { &self, session_store: &ExtensionData, thread_store: &ExtensionData, - ) -> Vec>>; + ) -> Vec ToolExecutor>>>; /// Returns native tools bound to one sampling step. fn tools_for_step( @@ -311,7 +311,7 @@ pub trait ToolContributor: Send + Sync { session_store: &ExtensionData, thread_store: &ExtensionData, _step_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { self.tools(session_store, thread_store) } } diff --git a/codex-rs/ext/extension-api/src/contributors/turn_input.rs b/codex-rs/ext/extension-api/src/contributors/turn_input.rs index 8d2411f842..61437279cd 100644 --- a/codex-rs/ext/extension-api/src/contributors/turn_input.rs +++ b/codex-rs/ext/extension-api/src/contributors/turn_input.rs @@ -1,24 +1,27 @@ use codex_protocol::user_input::UserInput; use codex_utils_path_uri::PathUri; +use std::marker::PhantomData; /// Host-owned turn environment summary visible to turn-input contributors. #[derive(Debug, Clone)] -pub struct TurnInputEnvironment { +pub struct TurnInputEnvironment<'a> { /// Stable host environment id used to route executor-scoped capabilities. pub environment_id: String, /// Effective working directory for this turn in the environment. pub cwd: PathUri, /// Whether this is the primary environment for the turn. pub is_primary: bool, + // TODO(anp): Replace the marker with callback-scoped environment access. + pub _lifetime: PhantomData<&'a ()>, } /// Turn facts supplied before the host records turn-local model input items. #[derive(Debug, Clone)] -pub struct TurnInputContext { +pub struct TurnInputContext<'a> { /// Stable host-owned turn identifier. pub turn_id: String, /// User input submitted for this turn. pub user_input: Vec, /// Resolved turn environments, in host priority order. - pub environments: Vec, + pub environments: Vec>, } diff --git a/codex-rs/ext/extension-api/tests/registry.rs b/codex-rs/ext/extension-api/tests/registry.rs index 7a8fb416fe..e07d375ab5 100644 --- a/codex-rs/ext/extension-api/tests/registry.rs +++ b/codex-rs/ext/extension-api/tests/registry.rs @@ -125,7 +125,7 @@ fn host_skill_discovery_preserves_legacy_and_host_contributor_behavior() { impl TurnInputContributor for AllContributors { fn contribute<'a>( &'a self, - input: TurnInputContext, + input: TurnInputContext<'a>, _extension_metrics: Option>, _session_store: &'a ExtensionData, _thread_store: &'a ExtensionData, @@ -144,7 +144,7 @@ impl ToolContributor for AllContributors { &self, _session_store: &ExtensionData, _thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { Vec::new() } } diff --git a/codex-rs/ext/goal/src/extension.rs b/codex-rs/ext/goal/src/extension.rs index 923f436581..24d515c613 100644 --- a/codex-rs/ext/goal/src/extension.rs +++ b/codex-rs/ext/goal/src/extension.rs @@ -415,7 +415,9 @@ where &self, _session_store: &ExtensionData, thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec< + Arc codex_extension_api::ToolExecutor>>, + > { let Some(runtime) = goal_runtime_handle(thread_store) else { return Vec::new(); }; diff --git a/codex-rs/ext/goal/src/tool.rs b/codex-rs/ext/goal/src/tool.rs index f15a5e90fb..582edf39e9 100644 --- a/codex-rs/ext/goal/src/tool.rs +++ b/codex-rs/ext/goal/src/tool.rs @@ -136,7 +136,7 @@ impl GoalToolExecutor { } } -impl ToolExecutor for GoalToolExecutor { +impl<'call> ToolExecutor> for GoalToolExecutor { fn tool_name(&self) -> ToolName { ToolName::plain(match self.kind { GoalToolKind::Get => GET_GOAL_TOOL_NAME, @@ -153,7 +153,13 @@ impl ToolExecutor for GoalToolExecutor { } } - fn handle(&self, invocation: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + fn handle<'a>( + &'a self, + invocation: ToolCall<'call>, + ) -> codex_extension_api::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async move { match self.kind { GoalToolKind::Get => self.handle_get(invocation).await, @@ -167,7 +173,7 @@ impl ToolExecutor for GoalToolExecutor { impl GoalToolExecutor { async fn handle_get( &self, - invocation: ToolCall, + invocation: ToolCall<'_>, ) -> Result, FunctionCallError> { let _ = invocation.function_arguments()?; let goal = self @@ -184,7 +190,7 @@ impl GoalToolExecutor { async fn handle_create( &self, - invocation: ToolCall, + invocation: ToolCall<'_>, ) -> Result, FunctionCallError> { let mut request: CreateGoalRequest = parse_arguments(invocation.function_arguments()?)?; request.objective = request.objective.trim().to_string(); @@ -227,7 +233,7 @@ impl GoalToolExecutor { async fn handle_update( &self, - invocation: ToolCall, + invocation: ToolCall<'_>, ) -> Result, FunctionCallError> { let args: UpdateGoalArgs = parse_arguments(invocation.function_arguments()?)?; if !matches!( @@ -299,7 +305,7 @@ impl GoalToolExecutor { fn emit_goal_updated_from_tool_call( &self, - invocation: &ToolCall, + invocation: &ToolCall<'_>, turn_id: Option, goal: ThreadGoal, ) { diff --git a/codex-rs/ext/goal/tests/goal_extension_backend.rs b/codex-rs/ext/goal/tests/goal_extension_backend.rs index 2e856ffedd..72832764e8 100644 --- a/codex-rs/ext/goal/tests/goal_extension_backend.rs +++ b/codex-rs/ext/goal/tests/goal_extension_backend.rs @@ -1229,7 +1229,7 @@ async fn goal_service_enforces_maximum_token_budget_on_creation_and_updates() -> async fn installed_tools( runtime: Arc, thread_id: ThreadId, -) -> Vec>> { +) -> Vec ToolExecutor>>> { installed_tools_with_start( runtime, thread_id, @@ -1244,7 +1244,7 @@ async fn installed_tools_with_start( thread_id: ThreadId, session_source: SessionSource, persistent_thread_state_available: bool, -) -> Vec>> { +) -> Vec ToolExecutor>>> { let mut builder = ExtensionRegistryBuilder::<()>::new(); let goal_service = Arc::new(GoalService::new()); install_with_backend( @@ -1284,7 +1284,7 @@ async fn installed_tools_with_start( .collect() } -fn tool_names(tools: &[Arc>]) -> Vec { +fn tool_names(tools: &[Arc ToolExecutor>>]) -> Vec { tools.iter().map(|tool| tool.tool_name().name).collect() } @@ -1343,7 +1343,7 @@ impl GoalExtensionHarness { }) } - fn tools(&self) -> Vec>> { + fn tools(&self) -> Vec ToolExecutor>>> { self.registry .tool_contributors() .iter() @@ -1470,16 +1470,16 @@ impl GoalExtensionHarness { } fn tool_by_name<'a>( - tools: &'a [Arc>], + tools: &'a [Arc ToolExecutor>>], name: &str, -) -> &'a Arc> { +) -> &'a Arc ToolExecutor>> { tools .iter() .find(|tool| tool.tool_name().namespace.is_none() && tool.tool_name().name == name) .expect("requested goal tool should exist") } -fn tool_call(tool_name: &str, call_id: &str, arguments: serde_json::Value) -> ToolCall { +fn tool_call(tool_name: &str, call_id: &str, arguments: serde_json::Value) -> ToolCall<'static> { ToolCall { turn_id: "turn-1".to_string(), call_id: call_id.to_string(), diff --git a/codex-rs/ext/history-notes/src/extension.rs b/codex-rs/ext/history-notes/src/extension.rs index d9cd3f4929..069e104d9b 100644 --- a/codex-rs/ext/history-notes/src/extension.rs +++ b/codex-rs/ext/history-notes/src/extension.rs @@ -135,7 +135,7 @@ impl ToolContributor for HistoryNotesExtension { &self, session_store: &ExtensionData, thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { let Some(config) = thread_store.get::() else { return Vec::new(); }; @@ -151,7 +151,7 @@ impl ToolContributor for HistoryNotesExtension { config.backend.clone(), session_store.level_id().to_string(), identity.agent_name.clone(), - )) as Arc> + )) as Arc ToolExecutor>> }) .collect() } diff --git a/codex-rs/ext/history-notes/src/tools.rs b/codex-rs/ext/history-notes/src/tools.rs index c7908db106..0d1454115f 100644 --- a/codex-rs/ext/history-notes/src/tools.rs +++ b/codex-rs/ext/history-notes/src/tools.rs @@ -260,7 +260,10 @@ impl HistoryNotesTool { } } - async fn handle_call(&self, call: ToolCall) -> Result, FunctionCallError> { + async fn handle_call( + &self, + call: ToolCall<'_>, + ) -> Result, FunctionCallError> { let arguments = call.function_arguments()?; let arguments = if arguments.trim().is_empty() { json!({}) @@ -286,7 +289,7 @@ impl HistoryNotesTool { } } -impl ToolExecutor for HistoryNotesTool { +impl<'call> ToolExecutor> for HistoryNotesTool { fn tool_name(&self) -> ToolName { ToolName::namespaced(self.action.namespace(), self.action.name()) } @@ -316,7 +319,10 @@ impl ToolExecutor for HistoryNotesTool { self.action.supports_parallel_tool_calls() } - fn handle(&self, call: ToolCall) -> ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } diff --git a/codex-rs/ext/history-notes/tests/history_notes_extension.rs b/codex-rs/ext/history-notes/tests/history_notes_extension.rs index a87296f615..be5b31c3b5 100644 --- a/codex-rs/ext/history-notes/tests/history_notes_extension.rs +++ b/codex-rs/ext/history-notes/tests/history_notes_extension.rs @@ -350,7 +350,7 @@ fn exposed_tools( registry: &ExtensionRegistry, session_store: &ExtensionData, thread_store: &ExtensionData, -) -> Vec>> { +) -> Vec ToolExecutor>>> { registry .tool_contributors() .iter() @@ -358,7 +358,7 @@ fn exposed_tools( .collect() } -fn tool_call(tool_name: ToolName, arguments: serde_json::Value) -> ToolCall { +fn tool_call(tool_name: ToolName, arguments: serde_json::Value) -> ToolCall<'static> { ToolCall { turn_id: "turn-1".to_string(), call_id: "call-read-file".to_string(), diff --git a/codex-rs/ext/image-generation/src/extension.rs b/codex-rs/ext/image-generation/src/extension.rs index 38a24eed7c..551e14fd89 100644 --- a/codex-rs/ext/image-generation/src/extension.rs +++ b/codex-rs/ext/image-generation/src/extension.rs @@ -86,7 +86,7 @@ impl ToolContributor for ImageGenerationExtension { &self, _session_store: &ExtensionData, thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { let Some(config) = thread_store.get::() else { return Vec::new(); }; diff --git a/codex-rs/ext/image-generation/src/tool.rs b/codex-rs/ext/image-generation/src/tool.rs index ecbd5dd3dd..550548bb98 100644 --- a/codex-rs/ext/image-generation/src/tool.rs +++ b/codex-rs/ext/image-generation/src/tool.rs @@ -113,7 +113,7 @@ fn extension_turn_item(item: ImageGenerationItem, legacy_event: EventMsg) -> Ext } } -impl ToolExecutor for ImageGenerationTool { +impl<'call> ToolExecutor> for ImageGenerationTool { /// Keeps the tool in the existing image-generation Responses namespace. fn tool_name(&self) -> ToolName { ToolName::namespaced(IMAGE_GEN_NAMESPACE, IMAGEGEN_TOOL_NAME) @@ -130,13 +130,19 @@ impl ToolExecutor for ImageGenerationTool { } /// Executes the selected image operation and returns the completed image result. - fn handle(&self, call: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> codex_extension_api::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } impl ImageGenerationTool { - async fn handle_call(&self, call: ToolCall) -> Result, FunctionCallError> { + async fn handle_call( + &self, + call: ToolCall<'_>, + ) -> Result, FunctionCallError> { let args = parse_args(&call)?; let request = request_for_call_args(&args, call.conversation_history.items(), &call.environments) @@ -263,7 +269,7 @@ fn usage_limit_failure(error: &CodexErr) -> Option { async fn save_image_generation_result( save_root: Option<&AbsolutePathBuf>, - environment: Option<&ToolEnvironment>, + environment: Option<&ToolEnvironment<'_>>, session_id: &str, call_id: &str, result: &str, @@ -403,7 +409,7 @@ enum ImageRequest { async fn request_for_call_args( args: &ImagegenArgs, history: &[ResponseItem], - environments: &[ToolEnvironment], + environments: &[ToolEnvironment<'_>], ) -> Result { let paths = args.referenced_image_paths.as_deref().unwrap_or_default(); if paths.len() > MAX_EDIT_IMAGES { @@ -534,7 +540,7 @@ fn output_image_urls(output: &FunctionCallOutputPayload) -> impl Iterator, ) -> Result { let path_uri = PathUri::from_abs_path(path); let sandbox = environment.file_system_sandbox_context.clone(); @@ -562,7 +568,7 @@ async fn image_url( } /// Parses the strict model-facing arguments for an image-generation call. -fn parse_args(call: &ToolCall) -> Result { +fn parse_args(call: &ToolCall<'_>) -> Result { serde_json::from_str(call.function_arguments()?) .map_err(|err| FunctionCallError::RespondToModel(err.to_string())) } diff --git a/codex-rs/ext/memories/src/extension.rs b/codex-rs/ext/memories/src/extension.rs index 0904604079..c383a7e655 100644 --- a/codex-rs/ext/memories/src/extension.rs +++ b/codex-rs/ext/memories/src/extension.rs @@ -106,7 +106,9 @@ impl ToolContributor for MemoriesExtension { &self, _session_store: &ExtensionData, thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec< + Arc codex_extension_api::ToolExecutor>>, + > { let Some(config) = thread_store.get::() else { return Vec::new(); }; diff --git a/codex-rs/ext/memories/src/tests.rs b/codex-rs/ext/memories/src/tests.rs index de581de6e4..0dc714b696 100644 --- a/codex-rs/ext/memories/src/tests.rs +++ b/codex-rs/ext/memories/src/tests.rs @@ -580,7 +580,10 @@ async fn search_tool_rejects_legacy_single_query() { assert!(err.to_string().contains("query")); } -fn memory_tool(memory_root: &Path, tool_name: &str) -> Arc> { +fn memory_tool( + memory_root: &Path, + tool_name: &str, +) -> Arc ToolExecutor>> { let expected_tool_name = memory_tool_name(tool_name); crate::tools::memory_tools( LocalMemoriesBackend::from_memory_root(memory_root), diff --git a/codex-rs/ext/memories/src/tools/ad_hoc_note.rs b/codex-rs/ext/memories/src/tools/ad_hoc_note.rs index 0ce9cade67..3adb1ddaf1 100644 --- a/codex-rs/ext/memories/src/tools/ad_hoc_note.rs +++ b/codex-rs/ext/memories/src/tools/ad_hoc_note.rs @@ -41,7 +41,7 @@ pub(super) struct AddAdHocNoteTool { pub(super) metrics_client: Option, } -impl ToolExecutor for AddAdHocNoteTool +impl<'call, B> ToolExecutor> for AddAdHocNoteTool where B: MemoriesBackend, { @@ -56,7 +56,10 @@ where ) } - fn handle(&self, call: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> codex_extension_api::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } @@ -67,7 +70,7 @@ where { async fn handle_call( &self, - call: ToolCall, + call: ToolCall<'_>, ) -> Result, codex_extension_api::FunctionCallError> { let backend = self.backend.clone(); diff --git a/codex-rs/ext/memories/src/tools/list.rs b/codex-rs/ext/memories/src/tools/list.rs index b7b9ad0586..a14be8e8be 100644 --- a/codex-rs/ext/memories/src/tools/list.rs +++ b/codex-rs/ext/memories/src/tools/list.rs @@ -39,7 +39,7 @@ pub(super) struct ListTool { pub(super) metrics_client: Option, } -impl ToolExecutor for ListTool +impl<'call, B> ToolExecutor> for ListTool where B: MemoriesBackend, { @@ -54,7 +54,10 @@ where ) } - fn handle(&self, call: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> codex_extension_api::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } @@ -65,7 +68,7 @@ where { async fn handle_call( &self, - call: ToolCall, + call: ToolCall<'_>, ) -> Result, codex_extension_api::FunctionCallError> { let backend = self.backend.clone(); diff --git a/codex-rs/ext/memories/src/tools/mod.rs b/codex-rs/ext/memories/src/tools/mod.rs index 768f278866..22cf0e295d 100644 --- a/codex-rs/ext/memories/src/tools/mod.rs +++ b/codex-rs/ext/memories/src/tools/mod.rs @@ -28,7 +28,7 @@ mod search; pub(crate) fn memory_tools( backend: B, metrics_client: Option, -) -> Vec>> +) -> Vec ToolExecutor>>> where B: MemoriesBackend, { @@ -77,7 +77,7 @@ pub(super) fn memory_function_tool( }) } -fn parse_args Deserialize<'de>>(call: &ToolCall) -> Result { +fn parse_args Deserialize<'de>>(call: &ToolCall<'_>) -> Result { let arguments = call.function_arguments()?; let value = if arguments.trim().is_empty() { Value::Object(serde_json::Map::new()) diff --git a/codex-rs/ext/memories/src/tools/read.rs b/codex-rs/ext/memories/src/tools/read.rs index 6625cdaf8b..78a9eac836 100644 --- a/codex-rs/ext/memories/src/tools/read.rs +++ b/codex-rs/ext/memories/src/tools/read.rs @@ -38,7 +38,7 @@ pub(super) struct ReadTool { pub(super) metrics_client: Option, } -impl ToolExecutor for ReadTool +impl<'call, B> ToolExecutor> for ReadTool where B: MemoriesBackend, { @@ -53,7 +53,10 @@ where ) } - fn handle(&self, call: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> codex_extension_api::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } @@ -64,7 +67,7 @@ where { async fn handle_call( &self, - call: ToolCall, + call: ToolCall<'_>, ) -> Result, codex_extension_api::FunctionCallError> { let backend = self.backend.clone(); diff --git a/codex-rs/ext/memories/src/tools/search.rs b/codex-rs/ext/memories/src/tools/search.rs index 928c98b475..ca79bfa2c5 100644 --- a/codex-rs/ext/memories/src/tools/search.rs +++ b/codex-rs/ext/memories/src/tools/search.rs @@ -47,7 +47,7 @@ pub(super) struct SearchTool { pub(super) metrics_client: Option, } -impl ToolExecutor for SearchTool +impl<'call, B> ToolExecutor> for SearchTool where B: MemoriesBackend, { @@ -62,7 +62,10 @@ where ) } - fn handle(&self, call: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> codex_extension_api::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } @@ -73,7 +76,7 @@ where { async fn handle_call( &self, - call: ToolCall, + call: ToolCall<'_>, ) -> Result, codex_extension_api::FunctionCallError> { let backend = self.backend.clone(); diff --git a/codex-rs/ext/skills/src/extension.rs b/codex-rs/ext/skills/src/extension.rs index e87d2bd187..e2400b9db4 100644 --- a/codex-rs/ext/skills/src/extension.rs +++ b/codex-rs/ext/skills/src/extension.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::marker::PhantomData; use std::sync::Arc; use crate::HostSkillsSnapshot; @@ -275,7 +276,7 @@ where &self, session_store: &ExtensionData, thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { self.build_skill_tools( session_store, thread_store, @@ -290,7 +291,7 @@ where session_store: &ExtensionData, thread_store: &ExtensionData, step_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { let resolved_executor_roots = step_store .get::>() .map(|roots| roots.as_slice().to_vec()) @@ -357,7 +358,7 @@ where { fn contribute<'a>( &'a self, - input: TurnInputContext, + input: TurnInputContext<'a>, extension_metrics: Option>, session_store: &'a ExtensionData, thread_store: &'a ExtensionData, @@ -562,7 +563,7 @@ impl SkillsExtension { executor_query: Option, selected_plugins: Option>, sandbox_contexts: Option>>, - ) -> Vec>> { + ) -> Vec ToolExecutor>>> { skill_tools( self.providers.clone(), session_store, @@ -606,6 +607,7 @@ impl SkillsExtension { .read_skill( &self.providers, SkillReadRequest { + _lifetime: PhantomData, authority: entry.authority.clone(), package: entry.id.clone(), resource: entry.main_prompt.clone(), diff --git a/codex-rs/ext/skills/src/provider.rs b/codex-rs/ext/skills/src/provider.rs index afd7d7a5a5..8922506b3d 100644 --- a/codex-rs/ext/skills/src/provider.rs +++ b/codex-rs/ext/skills/src/provider.rs @@ -1,4 +1,5 @@ use std::future::Future; +use std::marker::PhantomData; use std::pin::Pin; use std::sync::Arc; @@ -43,7 +44,9 @@ pub struct SkillListQuery { } #[derive(Clone, Debug)] -pub struct SkillReadRequest { +pub struct SkillReadRequest<'a> { + // TODO(anp): Replace the marker with callback-scoped environment access. + pub _lifetime: PhantomData<&'a ()>, pub authority: SkillAuthority, pub package: SkillPackageId, pub resource: SkillResourceId, @@ -71,7 +74,10 @@ pub type SkillProviderFuture<'a, T> = pub trait SkillProvider: Send + Sync { fn list(&self, query: SkillListQuery) -> SkillProviderFuture<'_, SkillCatalog>; - fn read(&self, request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult>; + fn read<'a>( + &'a self, + request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult>; fn search(&self, request: SkillSearchRequest) -> SkillProviderFuture<'_, SkillSearchResult>; } diff --git a/codex-rs/ext/skills/src/provider/executor.rs b/codex-rs/ext/skills/src/provider/executor.rs index 616690b2a6..ddf2c82d91 100644 --- a/codex-rs/ext/skills/src/provider/executor.rs +++ b/codex-rs/ext/skills/src/provider/executor.rs @@ -121,7 +121,10 @@ impl SkillProvider for ExecutorSkillProvider { }) } - fn read(&self, request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult> { + fn read<'a>( + &'a self, + request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult> { Box::pin(async move { if request.authority.kind != SkillSourceKind::Executor { return Err(SkillProviderError::new(format!( diff --git a/codex-rs/ext/skills/src/provider/host.rs b/codex-rs/ext/skills/src/provider/host.rs index 5469ae8ddb..9aed89618c 100644 --- a/codex-rs/ext/skills/src/provider/host.rs +++ b/codex-rs/ext/skills/src/provider/host.rs @@ -46,7 +46,10 @@ impl SkillProvider for HostSkillProvider { }) } - fn read(&self, request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult> { + fn read<'a>( + &'a self, + request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult> { Box::pin(async move { let Some(host_snapshot) = request.host_snapshot else { return Err(SkillProviderError::new( diff --git a/codex-rs/ext/skills/src/provider/orchestrator.rs b/codex-rs/ext/skills/src/provider/orchestrator.rs index e984693620..d5b7015e23 100644 --- a/codex-rs/ext/skills/src/provider/orchestrator.rs +++ b/codex-rs/ext/skills/src/provider/orchestrator.rs @@ -174,7 +174,10 @@ impl SkillProvider for OrchestratorSkillProvider { }) } - fn read(&self, request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult> { + fn read<'a>( + &'a self, + request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult> { Box::pin(async move { if request.authority != SkillAuthority::new(SkillSourceKind::Orchestrator, CODEX_APPS_MCP_SERVER_NAME) diff --git a/codex-rs/ext/skills/src/shadow_selection_experiment/mod.rs b/codex-rs/ext/skills/src/shadow_selection_experiment/mod.rs index dbff29cf7b..953fec778b 100644 --- a/codex-rs/ext/skills/src/shadow_selection_experiment/mod.rs +++ b/codex-rs/ext/skills/src/shadow_selection_experiment/mod.rs @@ -69,7 +69,7 @@ impl ShadowSelectionExperiment { pub(crate) fn run( &self, - input: &TurnInputContext, + input: &TurnInputContext<'_>, catalog: &SkillCatalog, explicitly_selected: &[SkillCatalogEntry], host_snapshot: Option<&HostSkillsSnapshot>, diff --git a/codex-rs/ext/skills/src/sources.rs b/codex-rs/ext/skills/src/sources.rs index dc7ec24ac3..691eb3fa86 100644 --- a/codex-rs/ext/skills/src/sources.rs +++ b/codex-rs/ext/skills/src/sources.rs @@ -171,7 +171,7 @@ impl SkillProviders { pub(crate) async fn read( &self, - request: SkillReadRequest, + request: SkillReadRequest<'_>, ) -> Result { let mut last_error = None; for source in self diff --git a/codex-rs/ext/skills/src/state.rs b/codex-rs/ext/skills/src/state.rs index dde1aa4c68..bb13611320 100644 --- a/codex-rs/ext/skills/src/state.rs +++ b/codex-rs/ext/skills/src/state.rs @@ -222,7 +222,7 @@ impl SkillsThreadState { pub(crate) async fn read_skill( &self, providers: &SkillProviders, - request: SkillReadRequest, + request: SkillReadRequest<'_>, ) -> SkillProviderResult { if request.authority.kind != SkillSourceKind::Orchestrator { return providers.read(request).await; @@ -349,8 +349,8 @@ struct SkillReadCacheKey { resource: SkillResourceId, } -impl From<&SkillReadRequest> for SkillReadCacheKey { - fn from(request: &SkillReadRequest) -> Self { +impl From<&SkillReadRequest<'_>> for SkillReadCacheKey { + fn from(request: &SkillReadRequest<'_>) -> Self { Self { authority: request.authority.clone(), package: request.package.clone(), diff --git a/codex-rs/ext/skills/src/tools/list.rs b/codex-rs/ext/skills/src/tools/list.rs index bb79b9e17d..a71f46e053 100644 --- a/codex-rs/ext/skills/src/tools/list.rs +++ b/codex-rs/ext/skills/src/tools/list.rs @@ -63,7 +63,7 @@ pub(super) struct ListTool { pub(super) context: SkillToolContext, } -impl ToolExecutor for ListTool { +impl<'call> ToolExecutor> for ListTool { fn tool_name(&self) -> ToolName { skill_tool_name(TOOL_NAME) } @@ -75,7 +75,10 @@ impl ToolExecutor for ListTool { ) } - fn handle(&self, call: ToolCall) -> ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async move { let args: ListArgs = parse_args(&call)?; let response_byte_budget = call.response_byte_budget(MAX_SKILL_RESPONSE_BYTES); diff --git a/codex-rs/ext/skills/src/tools/mod.rs b/codex-rs/ext/skills/src/tools/mod.rs index 54d663e2a5..22bac2bce9 100644 --- a/codex-rs/ext/skills/src/tools/mod.rs +++ b/codex-rs/ext/skills/src/tools/mod.rs @@ -63,7 +63,7 @@ pub(crate) fn skill_tools( selected_plugins: Option>, sandbox_contexts: Option>>, shadow_selection: Arc, -) -> Vec>> { +) -> Vec ToolExecutor>>> { let Some(thread_state) = thread_store.get::() else { return Vec::new(); }; @@ -314,7 +314,7 @@ fn skill_function_tool(name: &str, description: &s }) } -fn parse_args Deserialize<'de>>(call: &ToolCall) -> Result { +fn parse_args Deserialize<'de>>(call: &ToolCall<'_>) -> Result { let arguments = call.function_arguments()?; let value = if arguments.trim().is_empty() { Value::Object(serde_json::Map::new()) diff --git a/codex-rs/ext/skills/src/tools/read.rs b/codex-rs/ext/skills/src/tools/read.rs index 4c901224ff..74f6c8c52e 100644 --- a/codex-rs/ext/skills/src/tools/read.rs +++ b/codex-rs/ext/skills/src/tools/read.rs @@ -1,3 +1,4 @@ +use std::marker::PhantomData; use std::sync::Arc; use codex_analytics::InvocationType; @@ -54,7 +55,7 @@ pub(super) struct ReadTool { pub(super) context: SkillToolContext, } -impl ToolExecutor for ReadTool { +impl<'call> ToolExecutor> for ReadTool { fn tool_name(&self) -> ToolName { skill_tool_name(TOOL_NAME) } @@ -66,7 +67,10 @@ impl ToolExecutor for ReadTool { ) } - fn handle(&self, call: ToolCall) -> ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(async move { let args: ReadArgs = parse_args(&call)?; let response_byte_budget = call.response_byte_budget(MAX_SKILL_RESPONSE_BYTES); @@ -183,6 +187,7 @@ impl ToolExecutor for ReadTool { .read_skill( &self.context.providers, SkillReadRequest { + _lifetime: PhantomData, authority: authority.clone(), package: package.clone(), resource: requested_resource.clone(), diff --git a/codex-rs/ext/skills/tests/executor_file_system_authority.rs b/codex-rs/ext/skills/tests/executor_file_system_authority.rs index 215670139e..1e248dc4e0 100644 --- a/codex-rs/ext/skills/tests/executor_file_system_authority.rs +++ b/codex-rs/ext/skills/tests/executor_file_system_authority.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::io; +use std::marker::PhantomData; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; @@ -318,6 +319,7 @@ async fn windows_executor_skill_read_rejects_disabled_sandbox_on_any_orchestrato ); let error = provider .read(SkillReadRequest { + _lifetime: PhantomData, authority: SkillAuthority::new(SkillSourceKind::Executor, "windows-root"), package: SkillPackageId("skill://windows-root/C:/skill".into()), resource, @@ -876,6 +878,7 @@ async fn high_level_discovery_reuses_materialized_skill_contents_for_reads() { panic!("expected exactly one skill"); }; let request = SkillReadRequest { + _lifetime: PhantomData, authority: entry.authority.clone(), package: entry.id.clone(), resource: entry.main_prompt.clone(), diff --git a/codex-rs/ext/skills/tests/skills_extension.rs b/codex-rs/ext/skills/tests/skills_extension.rs index dc371257ca..cf6857bb7d 100644 --- a/codex-rs/ext/skills/tests/skills_extension.rs +++ b/codex-rs/ext/skills/tests/skills_extension.rs @@ -2449,7 +2449,7 @@ async fn prompt_hidden_skill_can_still_be_invoked() -> TestResult { #[derive(Clone)] struct StaticSkillProvider { catalog: SkillCatalog, - read_requests: Arc>>, + read_requests: Arc>>, list_calls: Option>, fail_first_list: bool, } @@ -2565,13 +2565,20 @@ impl SkillProvider for StaticSkillProvider { }) } - fn read(&self, request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult> { + fn read<'a>( + &'a self, + request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult> { let read_requests = Arc::clone(&self.read_requests); Box::pin(async move { read_requests .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) - .push(request.clone()); + .push(( + request.authority.clone(), + request.package.clone(), + request.resource.clone(), + )); Ok(SkillReadResult { resource: request.resource, contents: "# Lint Fix\n\nRun the formatter.".to_string(), @@ -2637,18 +2644,10 @@ fn test_codex_home() -> PathBuf { } fn read_request_keys( - requests: &Arc>>, + requests: &Mutex>, ) -> Vec<(SkillAuthority, SkillPackageId, SkillResourceId)> { requests .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) - .iter() - .map(|request| { - ( - request.authority.clone(), - request.package.clone(), - request.resource.clone(), - ) - }) - .collect() + .clone() } diff --git a/codex-rs/ext/skills/tests/skills_extension/shadow_task_context_tests.rs b/codex-rs/ext/skills/tests/skills_extension/shadow_task_context_tests.rs index 791330a75a..1a2e9f4456 100644 --- a/codex-rs/ext/skills/tests/skills_extension/shadow_task_context_tests.rs +++ b/codex-rs/ext/skills/tests/skills_extension/shadow_task_context_tests.rs @@ -11,7 +11,10 @@ impl SkillProvider for FailedReads { self.0.list(query) } - fn read(&self, _request: SkillReadRequest) -> SkillProviderFuture<'_, SkillReadResult> { + fn read<'a>( + &'a self, + _request: SkillReadRequest<'a>, + ) -> SkillProviderFuture<'a, SkillReadResult> { Box::pin(async { Err(SkillProviderError::new("read unavailable")) }) } diff --git a/codex-rs/ext/web-search/src/extension.rs b/codex-rs/ext/web-search/src/extension.rs index e269a18a3f..a1a66471cf 100644 --- a/codex-rs/ext/web-search/src/extension.rs +++ b/codex-rs/ext/web-search/src/extension.rs @@ -121,7 +121,9 @@ impl ToolContributor for WebSearchExtension { &self, session_store: &ExtensionData, thread_store: &ExtensionData, - ) -> Vec>> { + ) -> Vec< + Arc codex_extension_api::ToolExecutor>>, + > { let Some(config) = thread_store.get::() else { return Vec::new(); }; diff --git a/codex-rs/ext/web-search/src/tool.rs b/codex-rs/ext/web-search/src/tool.rs index 9747b75edd..aaea949801 100644 --- a/codex-rs/ext/web-search/src/tool.rs +++ b/codex-rs/ext/web-search/src/tool.rs @@ -49,7 +49,7 @@ pub(crate) struct WebSearchTool { pub(crate) originator: Option, } -impl ToolExecutor for WebSearchTool { +impl<'call> ToolExecutor> for WebSearchTool { fn tool_name(&self) -> ToolName { ToolName::namespaced(WEB_NAMESPACE, RUN_TOOL_NAME) } @@ -83,13 +83,19 @@ impl ToolExecutor for WebSearchTool { true } - fn handle(&self, call: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + fn handle<'a>(&'a self, call: ToolCall<'call>) -> codex_extension_api::ToolExecutorFuture<'a> + where + 'call: 'a, + { Box::pin(self.handle_call(call)) } } impl WebSearchTool { - async fn handle_call(&self, call: ToolCall) -> Result, FunctionCallError> { + async fn handle_call( + &self, + call: ToolCall<'_>, + ) -> Result, FunctionCallError> { let commands = parse_commands(&call)?; let command_action = command_action(&commands); let provider = self @@ -196,7 +202,7 @@ fn search_request_headers(originator: Option<&str>, turn_metadata: Option<&str>) headers } -fn parse_commands(call: &ToolCall) -> Result { +fn parse_commands(call: &ToolCall<'_>) -> Result { let arguments = call.function_arguments()?; if arguments.trim().is_empty() { return Ok(SearchCommands::default()); diff --git a/codex-rs/tools/src/tool_call.rs b/codex-rs/tools/src/tool_call.rs index 4de11fad95..3ba56a9a4e 100644 --- a/codex-rs/tools/src/tool_call.rs +++ b/codex-rs/tools/src/tool_call.rs @@ -9,6 +9,7 @@ use codex_protocol::protocol::EventMsg; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_output_truncation::TruncationPolicy; use std::future::Future; +use std::marker::PhantomData; use std::pin::Pin; use std::sync::Arc; @@ -61,7 +62,7 @@ pub trait TurnItemEmitter: Send + Sync { /// Host-owned turn environment summary visible to extension tools. #[derive(Clone)] -pub struct ToolEnvironment { +pub struct ToolEnvironment<'call> { /// Stable host environment id used to route executor-scoped capabilities. pub environment_id: String, /// Effective working directory for this turn in the environment. @@ -70,6 +71,8 @@ pub struct ToolEnvironment { pub file_system: Arc, /// Sandbox context to use for filesystem operations. pub file_system_sandbox_context: FileSystemSandboxContext, + // TODO(anp): Replace the marker with callback-scoped environment access. + pub _lifetime: PhantomData<&'call ()>, } /// Turn-item emitter used when a caller does not expose visible item emission. @@ -101,7 +104,7 @@ pub enum ToolCallSource { } #[derive(Clone)] -pub struct ToolCall { +pub struct ToolCall<'call> { pub turn_id: String, pub call_id: String, pub tool_name: ToolName, @@ -111,11 +114,11 @@ pub struct ToolCall { pub source: ToolCallSource, pub conversation_history: ConversationHistory, pub turn_item_emitter: Arc, - pub environments: Vec, + pub environments: Vec>, pub payload: ToolPayload, } -impl std::fmt::Debug for ToolCall { +impl std::fmt::Debug for ToolCall<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ToolCall") .field("turn_id", &self.turn_id) @@ -136,7 +139,7 @@ impl std::fmt::Debug for ToolCall { } } -impl ToolCall { +impl ToolCall<'_> { /// Returns the response-content budget, bounded by the tool's own size limit. /// /// Direct calls use the host's effective text-output allowance. Code Mode receives diff --git a/codex-rs/tools/src/tool_executor.rs b/codex-rs/tools/src/tool_executor.rs index edba56d850..5af16c3b7d 100644 --- a/codex-rs/tools/src/tool_executor.rs +++ b/codex-rs/tools/src/tool_executor.rs @@ -123,5 +123,8 @@ pub trait ToolExecutor: Send + Sync { false } - fn handle(&self, invocation: Invocation) -> ToolExecutorFuture<'_>; + /// Handles one invocation without retaining capabilities borrowed by the host. + fn handle<'a>(&'a self, invocation: Invocation) -> ToolExecutorFuture<'a> + where + Invocation: 'a; }