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
This commit is contained in:
Adam Perry @ OpenAI
2026-08-27 02:00:31 +00:00
committed by copyberry
parent eed1dee69e
commit 81e180044d
85 changed files with 477 additions and 202 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ use codex_protocol::turn_input::TurnStartOptions;
use crate::session::completed_session_loop_termination;
pub(crate) struct GuardianReadOnlyHistoryTools(
pub(crate) Vec<Arc<dyn codex_tools::ToolExecutor<codex_tools::ToolCall>>>,
pub(crate) Vec<Arc<dyn for<'call> codex_tools::ToolExecutor<codex_tools::ToolCall<'call>>>>,
);
/// Start an interactive sub-Codex thread and return its runtime and IO channels.
+5 -5
View File
@@ -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(
+2
View File
@@ -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,
@@ -146,7 +146,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -54,7 +54,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -357,7 +357,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -80,7 +80,10 @@ impl ToolExecutor<ToolInvocation> 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!(
+4 -1
View File
@@ -105,7 +105,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -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<dyn codex_tools::ToolExecutor<ExtensionToolCall>>);
pub(crate) struct ExtensionToolAdapter(
Arc<dyn for<'call> codex_tools::ToolExecutor<ExtensionToolCall<'call>>>,
);
impl ExtensionToolAdapter {
pub(crate) fn new(executor: Arc<dyn codex_tools::ToolExecutor<ExtensionToolCall>>) -> Self {
pub(crate) fn new(
executor: Arc<dyn for<'call> codex_tools::ToolExecutor<ExtensionToolCall<'call>>>,
) -> Self {
Self(executor)
}
}
@@ -56,7 +61,10 @@ impl ToolExecutor<ToolInvocation> 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<codex_tools::ToolCall> for StubExtensionExecutor {
impl<'call> codex_extension_api::ToolExecutor<codex_tools::ToolCall<'call>>
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<Mutex<Option<codex_tools::ToolCall>>>,
captured_call: Arc<Mutex<Option<codex_tools::ToolCall<'static>>>>,
captured_sandbox_cwds: Arc<Mutex<Vec<Option<PathUri>>>>,
}
impl codex_extension_api::ToolExecutor<codex_tools::ToolCall> for CapturingExtensionExecutor {
impl<'call> codex_extension_api::ToolExecutor<codex_tools::ToolCall<'call>>
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<Box<dyn codex_tools::ToolOutput>, 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::<Vec<_>>(),
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]
@@ -67,7 +67,10 @@ impl ToolExecutor<ToolInvocation> 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(
@@ -67,7 +67,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
+4 -1
View File
@@ -163,7 +163,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -30,7 +30,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -30,7 +30,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -33,7 +33,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -21,7 +21,10 @@ impl ToolExecutor<ToolInvocation> 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) })
}
}
@@ -24,7 +24,10 @@ impl ToolExecutor<ToolInvocation> 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) })
}
}
@@ -21,7 +21,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -36,7 +36,10 @@ impl ToolExecutor<ToolInvocation> 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) })
}
}
@@ -43,7 +43,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -17,7 +17,10 @@ impl ToolExecutor<ToolInvocation> 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;
@@ -15,7 +15,10 @@ impl ToolExecutor<ToolInvocation> 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);
@@ -15,7 +15,10 @@ impl ToolExecutor<ToolInvocation> 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;
@@ -17,7 +17,10 @@ impl ToolExecutor<ToolInvocation> 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;
@@ -37,7 +37,10 @@ impl ToolExecutor<ToolInvocation> 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;
@@ -28,7 +28,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -24,7 +24,10 @@ impl ToolExecutor<ToolInvocation> 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(
+4 -1
View File
@@ -54,7 +54,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -34,7 +34,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -82,7 +82,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -37,7 +37,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -56,7 +56,10 @@ impl ToolExecutor<ToolInvocation> 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,
+4 -1
View File
@@ -73,7 +73,10 @@ impl ToolExecutor<ToolInvocation> 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,
@@ -73,7 +73,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -179,7 +179,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -99,7 +99,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -46,7 +46,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -81,7 +81,10 @@ impl ToolExecutor<ToolInvocation> 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))
}
}
@@ -103,7 +103,10 @@ impl ToolExecutor<ToolInvocation> 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,
+4 -1
View File
@@ -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)))
+12 -3
View File
@@ -20,7 +20,10 @@ impl ToolExecutor<ToolInvocation> 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<ToolInvocation> 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<ToolInvocation> 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()
+10 -5
View File
@@ -65,14 +65,14 @@ impl codex_extension_api::ToolContributor for ExtensionEchoContributor {
&self,
_session_store: &ExtensionData,
_thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ExtensionToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>> {
vec![Arc::new(ExtensionEchoExecutor)]
}
}
struct ExtensionEchoExecutor;
impl ToolExecutor<ExtensionToolCall> for ExtensionEchoExecutor {
impl<'call> ToolExecutor<ExtensionToolCall<'call>> for ExtensionEchoExecutor {
fn tool_name(&self) -> ToolName {
ToolName::namespaced("extension/", "echo")
}
@@ -100,7 +100,10 @@ impl ToolExecutor<ExtensionToolCall> 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<ExtensionToolCall> for ExtensionEchoExecutor {
impl ExtensionEchoExecutor {
async fn handle_call(
&self,
call: ExtensionToolCall,
call: ExtensionToolCall<'_>,
) -> Result<Box<dyn codex_tools::ToolOutput>, codex_tools::FunctionCallError> {
let arguments: serde_json::Value =
serde_json::from_str(call.function_arguments()?).expect("test arguments should parse");
@@ -130,7 +133,9 @@ fn extension_tool_test_registry() -> Arc<ExtensionRegistry<Config>> {
fn test_tool_router(
step_context: &StepContext,
mcp_tools: Vec<RegisteredTool>,
extension_tool_executors: impl IntoIterator<Item = Arc<dyn ToolExecutor<ExtensionToolCall>>>,
extension_tool_executors: impl IntoIterator<
Item = Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>,
>,
dynamic_tools: &[DynamicToolSpec],
) -> ToolRouter {
let mut registry = build_core_tool_registry(
+9 -4
View File
@@ -285,7 +285,9 @@ pub(crate) fn append_source_tools(
turn_context: &TurnContext,
registry: &mut ToolRegistry,
mcp_tools: Vec<RegisteredTool>,
extension_tool_executors: impl IntoIterator<Item = Arc<dyn ToolExecutor<ExtensionToolCall>>>,
extension_tool_executors: impl IntoIterator<
Item = Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>,
>,
dynamic_tools: &[DynamicToolSpec],
) -> Vec<ToolSpec> {
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<Item = Arc<dyn ToolExecutor<ExtensionToolCall>>> + 'a {
) -> impl Iterator<Item = Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>> + 'a {
session
.services
.extensions
@@ -1283,7 +1285,7 @@ fn append_tool_search_executor(
fn append_extension_tool_executors(
turn_context: &TurnContext,
executors: impl IntoIterator<Item = Arc<dyn ToolExecutor<ExtensionToolCall>>>,
executors: impl IntoIterator<Item = Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>>,
registry: &mut ToolRegistry,
) -> Option<ToolName> {
let standalone_web_search_enabled = standalone_web_search_enabled(turn_context);
@@ -1356,7 +1358,10 @@ impl ToolExecutor<ToolInvocation> 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)
}
}
+11 -5
View File
@@ -69,7 +69,7 @@ const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";
struct ToolPlanInputs {
tool_runtimes: Vec<RegisteredTool>,
tool_suggest_candidates: Option<ToolSuggestCandidates>,
extension_tool_executors: Vec<Arc<dyn ToolExecutor<ExtensionToolCall>>>,
extension_tool_executors: Vec<Arc<dyn for<'call> ToolExecutor<ExtensionToolCall<'call>>>>,
wait_for_environment_tool_config: Option<Arc<WaitForEnvironmentToolConfig>>,
dynamic_tools: Vec<DynamicToolSpec>,
}
@@ -310,7 +310,7 @@ struct TestNamespaceExtensionTool {
tool_name: &'static str,
}
impl ToolExecutor<ExtensionToolCall> for TestNamespaceExtensionTool {
impl<'call> ToolExecutor<ExtensionToolCall<'call>> for TestNamespaceExtensionTool {
fn tool_name(&self) -> ToolName {
ToolName::namespaced(self.namespace, self.tool_name)
}
@@ -330,7 +330,10 @@ impl ToolExecutor<ExtensionToolCall> 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<dyn ToolOutput>)
})
@@ -339,7 +342,7 @@ impl ToolExecutor<ExtensionToolCall> for TestNamespaceExtensionTool {
struct DeferredExtensionTool;
impl ToolExecutor<ExtensionToolCall> for DeferredExtensionTool {
impl<'call> ToolExecutor<ExtensionToolCall<'call>> for DeferredExtensionTool {
fn tool_name(&self) -> ToolName {
ToolName::plain("extension_echo")
}
@@ -366,7 +369,10 @@ impl ToolExecutor<ExtensionToolCall> 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") })
}
}
@@ -48,7 +48,10 @@ impl ToolExecutor<ToolInvocation> 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)))
+7 -4
View File
@@ -4748,7 +4748,7 @@ impl ToolContributor for NamespacedCustomTool {
&self,
_session_store: &ExtensionData,
_thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
Vec::new()
}
@@ -4757,7 +4757,7 @@ impl ToolContributor for NamespacedCustomTool {
_session_store: &ExtensionData,
_thread_store: &ExtensionData,
_step_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
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<ToolCall> for NamespacedCustomTool {
impl<'call> ToolExecutor<ToolCall<'call>> for NamespacedCustomTool {
fn tool_name(&self) -> ToolName {
ToolName::namespaced("editor", "apply_patch")
}
@@ -4787,7 +4787,10 @@ impl ToolExecutor<ToolCall> 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(
@@ -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<Mutex<Vec<TurnInputEnvironment>>>);
struct RecordingTurnInputContributor(Arc<Mutex<Vec<RecordedTurnInputEnvironment>>>);
impl TurnInputContributor for RecordingTurnInputContributor {
fn contribute<'a>(
&'a self,
input: TurnInputContext,
input: TurnInputContext<'a>,
_extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
_session_store: &'a ExtensionData,
_thread_store: &'a ExtensionData,
_turn_store: &'a ExtensionData,
) -> ExtensionFuture<'a, Vec<Box<dyn ContextualUserFragment + Send>>> {
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<ExtensionRegistry<Config>> {
let mut extensions = ExtensionRegistryBuilder::<Config>::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::<Vec<_>>();
assert_eq!(recorded_environments, vec![(environment_id, cwd, true)]);
.clone();
assert_eq!(
recorded_environments,
vec![RecordedTurnInputEnvironment {
environment_id,
cwd,
is_primary: true,
}]
);
Ok(())
}
+6 -3
View File
@@ -941,12 +941,12 @@ impl ToolContributor for DeferredCustomTool {
&self,
_session_store: &ExtensionData,
_thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
vec![Arc::new(Self)]
}
}
impl ToolExecutor<ToolCall> for DeferredCustomTool {
impl<'call> ToolExecutor<ToolCall<'call>> for DeferredCustomTool {
fn tool_name(&self) -> ToolName {
ToolName::plain("custom_echo")
}
@@ -968,7 +968,10 @@ impl ToolExecutor<ToolCall> 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(
@@ -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",
@@ -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<Arc<dyn ExtensionMetrics>>,
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<Arc<dyn ToolExecutor<ToolCall>>>;
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>>;
/// 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<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
self.tools(session_store, thread_store)
}
}
@@ -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<UserInput>,
/// Resolved turn environments, in host priority order.
pub environments: Vec<TurnInputEnvironment>,
pub environments: Vec<TurnInputEnvironment<'a>>,
}
+2 -2
View File
@@ -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<Arc<dyn ExtensionMetrics>>,
_session_store: &'a ExtensionData,
_thread_store: &'a ExtensionData,
@@ -144,7 +144,7 @@ impl ToolContributor for AllContributors {
&self,
_session_store: &ExtensionData,
_thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
Vec::new()
}
}
+3 -1
View File
@@ -415,7 +415,9 @@ where
&self,
_session_store: &ExtensionData,
thread_store: &ExtensionData,
) -> Vec<Arc<dyn codex_extension_api::ToolExecutor<codex_extension_api::ToolCall>>> {
) -> Vec<
Arc<dyn for<'call> codex_extension_api::ToolExecutor<codex_extension_api::ToolCall<'call>>>,
> {
let Some(runtime) = goal_runtime_handle(thread_store) else {
return Vec::new();
};
+12 -6
View File
@@ -136,7 +136,7 @@ impl GoalToolExecutor {
}
}
impl ToolExecutor<ToolCall> for GoalToolExecutor {
impl<'call> ToolExecutor<ToolCall<'call>> for GoalToolExecutor {
fn tool_name(&self) -> ToolName {
ToolName::plain(match self.kind {
GoalToolKind::Get => GET_GOAL_TOOL_NAME,
@@ -153,7 +153,13 @@ impl ToolExecutor<ToolCall> 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<ToolCall> for GoalToolExecutor {
impl GoalToolExecutor {
async fn handle_get(
&self,
invocation: ToolCall,
invocation: ToolCall<'_>,
) -> Result<Box<dyn ToolOutput>, FunctionCallError> {
let _ = invocation.function_arguments()?;
let goal = self
@@ -184,7 +190,7 @@ impl GoalToolExecutor {
async fn handle_create(
&self,
invocation: ToolCall,
invocation: ToolCall<'_>,
) -> Result<Box<dyn ToolOutput>, 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<Box<dyn ToolOutput>, 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<String>,
goal: ThreadGoal,
) {
@@ -1229,7 +1229,7 @@ async fn goal_service_enforces_maximum_token_budget_on_creation_and_updates() ->
async fn installed_tools(
runtime: Arc<codex_state::StateRuntime>,
thread_id: ThreadId,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
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<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
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<dyn ToolExecutor<ToolCall>>]) -> Vec<String> {
fn tool_names(tools: &[Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>]) -> Vec<String> {
tools.iter().map(|tool| tool.tool_name().name).collect()
}
@@ -1343,7 +1343,7 @@ impl GoalExtensionHarness {
})
}
fn tools(&self) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
fn tools(&self) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
self.registry
.tool_contributors()
.iter()
@@ -1470,16 +1470,16 @@ impl GoalExtensionHarness {
}
fn tool_by_name<'a>(
tools: &'a [Arc<dyn ToolExecutor<ToolCall>>],
tools: &'a [Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>],
name: &str,
) -> &'a Arc<dyn ToolExecutor<ToolCall>> {
) -> &'a Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>> {
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(),
+2 -2
View File
@@ -135,7 +135,7 @@ impl ToolContributor for HistoryNotesExtension {
&self,
session_store: &ExtensionData,
thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
let Some(config) = thread_store.get::<HistoryNotesExtensionConfig>() 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<dyn ToolExecutor<ToolCall>>
)) as Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>
})
.collect()
}
+9 -3
View File
@@ -260,7 +260,10 @@ impl HistoryNotesTool {
}
}
async fn handle_call(&self, call: ToolCall) -> Result<Box<dyn ToolOutput>, FunctionCallError> {
async fn handle_call(
&self,
call: ToolCall<'_>,
) -> Result<Box<dyn ToolOutput>, FunctionCallError> {
let arguments = call.function_arguments()?;
let arguments = if arguments.trim().is_empty() {
json!({})
@@ -286,7 +289,7 @@ impl HistoryNotesTool {
}
}
impl ToolExecutor<ToolCall> for HistoryNotesTool {
impl<'call> ToolExecutor<ToolCall<'call>> for HistoryNotesTool {
fn tool_name(&self) -> ToolName {
ToolName::namespaced(self.action.namespace(), self.action.name())
}
@@ -316,7 +319,10 @@ impl ToolExecutor<ToolCall> 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))
}
}
@@ -350,7 +350,7 @@ fn exposed_tools(
registry: &ExtensionRegistry<Config>,
session_store: &ExtensionData,
thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
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(),
@@ -86,7 +86,7 @@ impl ToolContributor for ImageGenerationExtension {
&self,
_session_store: &ExtensionData,
thread_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
let Some(config) = thread_store.get::<ImageGenerationExtensionConfig>() else {
return Vec::new();
};
+13 -7
View File
@@ -113,7 +113,7 @@ fn extension_turn_item(item: ImageGenerationItem, legacy_event: EventMsg) -> Ext
}
}
impl ToolExecutor<ToolCall> for ImageGenerationTool {
impl<'call> ToolExecutor<ToolCall<'call>> 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<ToolCall> 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<Box<dyn ToolOutput>, FunctionCallError> {
async fn handle_call(
&self,
call: ToolCall<'_>,
) -> Result<Box<dyn ToolOutput>, 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<ImageGenerationFailure> {
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<ImageRequest, FunctionCallError> {
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<Item =
async fn image_url(
path: &AbsolutePathBuf,
environment: &ToolEnvironment,
environment: &ToolEnvironment<'_>,
) -> Result<ImageUrl, FunctionCallError> {
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<ImagegenArgs, FunctionCallError> {
fn parse_args(call: &ToolCall<'_>) -> Result<ImagegenArgs, FunctionCallError> {
serde_json::from_str(call.function_arguments()?)
.map_err(|err| FunctionCallError::RespondToModel(err.to_string()))
}
+3 -1
View File
@@ -106,7 +106,9 @@ impl ToolContributor for MemoriesExtension {
&self,
_session_store: &ExtensionData,
thread_store: &ExtensionData,
) -> Vec<Arc<dyn codex_extension_api::ToolExecutor<codex_extension_api::ToolCall>>> {
) -> Vec<
Arc<dyn for<'call> codex_extension_api::ToolExecutor<codex_extension_api::ToolCall<'call>>>,
> {
let Some(config) = thread_store.get::<MemoriesExtensionConfig>() else {
return Vec::new();
};
+4 -1
View File
@@ -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<dyn ToolExecutor<ToolCall>> {
fn memory_tool(
memory_root: &Path,
tool_name: &str,
) -> Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>> {
let expected_tool_name = memory_tool_name(tool_name);
crate::tools::memory_tools(
LocalMemoriesBackend::from_memory_root(memory_root),
@@ -41,7 +41,7 @@ pub(super) struct AddAdHocNoteTool<B> {
pub(super) metrics_client: Option<MetricsClient>,
}
impl<B> ToolExecutor<ToolCall> for AddAdHocNoteTool<B>
impl<'call, B> ToolExecutor<ToolCall<'call>> for AddAdHocNoteTool<B>
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<Box<dyn codex_extension_api::ToolOutput>, codex_extension_api::FunctionCallError>
{
let backend = self.backend.clone();
+6 -3
View File
@@ -39,7 +39,7 @@ pub(super) struct ListTool<B> {
pub(super) metrics_client: Option<MetricsClient>,
}
impl<B> ToolExecutor<ToolCall> for ListTool<B>
impl<'call, B> ToolExecutor<ToolCall<'call>> for ListTool<B>
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<Box<dyn codex_extension_api::ToolOutput>, codex_extension_api::FunctionCallError>
{
let backend = self.backend.clone();
+2 -2
View File
@@ -28,7 +28,7 @@ mod search;
pub(crate) fn memory_tools<B>(
backend: B,
metrics_client: Option<MetricsClient>,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>>
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>>
where
B: MemoriesBackend,
{
@@ -77,7 +77,7 @@ pub(super) fn memory_function_tool<I: JsonSchema, O: JsonSchema>(
})
}
fn parse_args<T: for<'de> Deserialize<'de>>(call: &ToolCall) -> Result<T, FunctionCallError> {
fn parse_args<T: for<'de> Deserialize<'de>>(call: &ToolCall<'_>) -> Result<T, FunctionCallError> {
let arguments = call.function_arguments()?;
let value = if arguments.trim().is_empty() {
Value::Object(serde_json::Map::new())
+6 -3
View File
@@ -38,7 +38,7 @@ pub(super) struct ReadTool<B> {
pub(super) metrics_client: Option<MetricsClient>,
}
impl<B> ToolExecutor<ToolCall> for ReadTool<B>
impl<'call, B> ToolExecutor<ToolCall<'call>> for ReadTool<B>
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<Box<dyn codex_extension_api::ToolOutput>, codex_extension_api::FunctionCallError>
{
let backend = self.backend.clone();
+6 -3
View File
@@ -47,7 +47,7 @@ pub(super) struct SearchTool<B> {
pub(super) metrics_client: Option<MetricsClient>,
}
impl<B> ToolExecutor<ToolCall> for SearchTool<B>
impl<'call, B> ToolExecutor<ToolCall<'call>> for SearchTool<B>
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<Box<dyn codex_extension_api::ToolOutput>, codex_extension_api::FunctionCallError>
{
let backend = self.backend.clone();
+6 -4
View File
@@ -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<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
self.build_skill_tools(
session_store,
thread_store,
@@ -290,7 +291,7 @@ where
session_store: &ExtensionData,
thread_store: &ExtensionData,
step_store: &ExtensionData,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
let resolved_executor_roots = step_store
.get::<Vec<ResolvedSelectedCapabilityRoot>>()
.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<Arc<dyn ExtensionMetrics>>,
session_store: &'a ExtensionData,
thread_store: &'a ExtensionData,
@@ -562,7 +563,7 @@ impl<C> SkillsExtension<C> {
executor_query: Option<SkillListQuery>,
selected_plugins: Option<Arc<SelectedPluginSnapshot>>,
sandbox_contexts: Option<Arc<HashMap<String, FileSystemSandboxContext>>>,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
skill_tools(
self.providers.clone(),
session_store,
@@ -606,6 +607,7 @@ impl<C> SkillsExtension<C> {
.read_skill(
&self.providers,
SkillReadRequest {
_lifetime: PhantomData,
authority: entry.authority.clone(),
package: entry.id.clone(),
resource: entry.main_prompt.clone(),
+8 -2
View File
@@ -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>;
}
+4 -1
View File
@@ -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!(
+4 -1
View File
@@ -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(
@@ -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)
@@ -69,7 +69,7 @@ impl ShadowSelectionExperiment {
pub(crate) fn run(
&self,
input: &TurnInputContext,
input: &TurnInputContext<'_>,
catalog: &SkillCatalog,
explicitly_selected: &[SkillCatalogEntry],
host_snapshot: Option<&HostSkillsSnapshot>,
+1 -1
View File
@@ -171,7 +171,7 @@ impl SkillProviders {
pub(crate) async fn read(
&self,
request: SkillReadRequest,
request: SkillReadRequest<'_>,
) -> Result<SkillReadResult, SkillProviderError> {
let mut last_error = None;
for source in self
+3 -3
View File
@@ -222,7 +222,7 @@ impl SkillsThreadState {
pub(crate) async fn read_skill(
&self,
providers: &SkillProviders,
request: SkillReadRequest,
request: SkillReadRequest<'_>,
) -> SkillProviderResult<SkillReadResult> {
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(),
+5 -2
View File
@@ -63,7 +63,7 @@ pub(super) struct ListTool {
pub(super) context: SkillToolContext,
}
impl ToolExecutor<ToolCall> for ListTool {
impl<'call> ToolExecutor<ToolCall<'call>> for ListTool {
fn tool_name(&self) -> ToolName {
skill_tool_name(TOOL_NAME)
}
@@ -75,7 +75,10 @@ impl ToolExecutor<ToolCall> 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);
+2 -2
View File
@@ -63,7 +63,7 @@ pub(crate) fn skill_tools(
selected_plugins: Option<Arc<SelectedPluginSnapshot>>,
sandbox_contexts: Option<Arc<HashMap<String, FileSystemSandboxContext>>>,
shadow_selection: Arc<ShadowSelectionExperiment>,
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
) -> Vec<Arc<dyn for<'call> ToolExecutor<ToolCall<'call>>>> {
let Some(thread_state) = thread_store.get::<SkillsThreadState>() else {
return Vec::new();
};
@@ -314,7 +314,7 @@ fn skill_function_tool<I: JsonSchema, O: JsonSchema>(name: &str, description: &s
})
}
fn parse_args<T: for<'de> Deserialize<'de>>(call: &ToolCall) -> Result<T, FunctionCallError> {
fn parse_args<T: for<'de> Deserialize<'de>>(call: &ToolCall<'_>) -> Result<T, FunctionCallError> {
let arguments = call.function_arguments()?;
let value = if arguments.trim().is_empty() {
Value::Object(serde_json::Map::new())
+7 -2
View File
@@ -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<ToolCall> for ReadTool {
impl<'call> ToolExecutor<ToolCall<'call>> for ReadTool {
fn tool_name(&self) -> ToolName {
skill_tool_name(TOOL_NAME)
}
@@ -66,7 +67,10 @@ impl ToolExecutor<ToolCall> 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<ToolCall> for ReadTool {
.read_skill(
&self.context.providers,
SkillReadRequest {
_lifetime: PhantomData,
authority: authority.clone(),
package: package.clone(),
resource: requested_resource.clone(),
@@ -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(),
+12 -13
View File
@@ -2449,7 +2449,7 @@ async fn prompt_hidden_skill_can_still_be_invoked() -> TestResult {
#[derive(Clone)]
struct StaticSkillProvider {
catalog: SkillCatalog,
read_requests: Arc<Mutex<Vec<SkillReadRequest>>>,
read_requests: Arc<Mutex<Vec<(SkillAuthority, SkillPackageId, SkillResourceId)>>>,
list_calls: Option<Arc<AtomicUsize>>,
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<Mutex<Vec<SkillReadRequest>>>,
requests: &Mutex<Vec<(SkillAuthority, SkillPackageId, SkillResourceId)>>,
) -> 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()
}
@@ -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")) })
}
+3 -1
View File
@@ -121,7 +121,9 @@ impl ToolContributor for WebSearchExtension {
&self,
session_store: &ExtensionData,
thread_store: &ExtensionData,
) -> Vec<Arc<dyn codex_extension_api::ToolExecutor<codex_extension_api::ToolCall>>> {
) -> Vec<
Arc<dyn for<'call> codex_extension_api::ToolExecutor<codex_extension_api::ToolCall<'call>>>,
> {
let Some(config) = thread_store.get::<WebSearchExtensionConfig>() else {
return Vec::new();
};
+10 -4
View File
@@ -49,7 +49,7 @@ pub(crate) struct WebSearchTool {
pub(crate) originator: Option<String>,
}
impl ToolExecutor<ToolCall> for WebSearchTool {
impl<'call> ToolExecutor<ToolCall<'call>> for WebSearchTool {
fn tool_name(&self) -> ToolName {
ToolName::namespaced(WEB_NAMESPACE, RUN_TOOL_NAME)
}
@@ -83,13 +83,19 @@ impl ToolExecutor<ToolCall> 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<Box<dyn ToolOutput>, FunctionCallError> {
async fn handle_call(
&self,
call: ToolCall<'_>,
) -> Result<Box<dyn ToolOutput>, 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<SearchCommands, FunctionCallError> {
fn parse_commands(call: &ToolCall<'_>) -> Result<SearchCommands, FunctionCallError> {
let arguments = call.function_arguments()?;
if arguments.trim().is_empty() {
return Ok(SearchCommands::default());
+8 -5
View File
@@ -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<dyn ExecutorFileSystem>,
/// 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<dyn TurnItemEmitter>,
pub environments: Vec<ToolEnvironment>,
pub environments: Vec<ToolEnvironment<'call>>,
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
+4 -1
View File
@@ -123,5 +123,8 @@ pub trait ToolExecutor<Invocation>: 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;
}