mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
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:
1 parent
eed1dee69e
commit
81e180044d
85 files changed
+477
-202
No files matched your search
@@ -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(),
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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!(
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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")) })
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user