mirror of
https://github.com/openai/codex.git
synced 2026-11-10 16:44:38 +00:00
Attribute executor skill invocations to plugins (#39309)
## What changed - Carry selected plugin identities from MCP discovery into per-turn extension data, limited to capability roots that are ready for the turn. - Annotate executor skill catalog entries with the matching plugin ID and `user` scope. - Include that attribution in `skill_invocation` analytics for both explicit and implicit executor skill invocations. ## Testing - Extend the executor skills app-server test to verify plugin ID, skill scope, and invocation type for explicit and implicit analytics events. GitOrigin-RevId: 9345600ae29bcb8eb894fffd2d6f7b05f22ebf74
This commit is contained in:
1 parent
c97bd2dcb5
commit
280d56b1d8
17 files changed
+129
-18
No files matched your search
@@ -179,6 +179,7 @@ pub struct SkillCatalogEntry {
|
||||
pub main_prompt: SkillResourceId,
|
||||
pub display_path: Option<String>,
|
||||
pub(crate) canonical_skill_id: Option<String>,
|
||||
pub(crate) plugin_id: Option<String>,
|
||||
pub(crate) analytics_scope: Option<SkillScope>,
|
||||
alias_root: Option<String>,
|
||||
alias_root_order: Option<usize>,
|
||||
@@ -205,6 +206,7 @@ impl SkillCatalogEntry {
|
||||
main_prompt,
|
||||
display_path: None,
|
||||
canonical_skill_id: None,
|
||||
plugin_id: None,
|
||||
analytics_scope: None,
|
||||
alias_root: None,
|
||||
alias_root_order: None,
|
||||
|
||||
@@ -18,6 +18,7 @@ use codex_extension_api::ExtensionMetrics;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::ExtensionWarning;
|
||||
use codex_extension_api::PromptFragment;
|
||||
use codex_extension_api::SelectedPluginSnapshot;
|
||||
use codex_extension_api::SkillInvocationContributor;
|
||||
use codex_extension_api::SkillInvocationInput;
|
||||
use codex_extension_api::SkillInvocationKind;
|
||||
@@ -276,6 +277,7 @@ where
|
||||
session_store,
|
||||
thread_store,
|
||||
/*executor_query*/ None,
|
||||
/*selected_plugins*/ None,
|
||||
/*sandbox_contexts*/ None,
|
||||
)
|
||||
}
|
||||
@@ -310,6 +312,7 @@ where
|
||||
session_store,
|
||||
thread_store,
|
||||
executor_query,
|
||||
step_store.get::<SelectedPluginSnapshot>(),
|
||||
step_store.get::<HashMap<String, FileSystemSandboxContext>>(),
|
||||
)
|
||||
}
|
||||
@@ -550,23 +553,15 @@ impl<C> SkillsExtension<C> {
|
||||
session_store: &ExtensionData,
|
||||
thread_store: &ExtensionData,
|
||||
executor_query: Option<SkillListQuery>,
|
||||
selected_plugins: Option<Arc<SelectedPluginSnapshot>>,
|
||||
sandbox_contexts: Option<Arc<HashMap<String, FileSystemSandboxContext>>>,
|
||||
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
|
||||
let Some(thread_state) = thread_store.get::<SkillsThreadState>() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let orchestrator_available = self.providers.has_orchestrator_provider()
|
||||
&& thread_state.orchestrator_skills_enabled();
|
||||
if !orchestrator_available && executor_query.is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
skill_tools(
|
||||
self.providers.clone(),
|
||||
session_store,
|
||||
thread_store,
|
||||
orchestrator_available,
|
||||
executor_query,
|
||||
selected_plugins,
|
||||
sandbox_contexts,
|
||||
Arc::clone(&self.shadow_selection),
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@ pub fn detect_implicit_skill_invocation(
|
||||
skill_id: entry.canonical_skill_id.clone(),
|
||||
scope: entry.analytics_scope,
|
||||
},
|
||||
plugin_id: None,
|
||||
plugin_id: entry.plugin_id.clone(),
|
||||
remote_plugin_id: None,
|
||||
invocation_type: InvocationType::Implicit,
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::catalog::SkillResourceId;
|
||||
use crate::catalog::SkillSearchResult;
|
||||
|
||||
pub use executor::ExecutorSkillProvider;
|
||||
pub(crate) use executor::attribute_executor_plugins;
|
||||
pub use host::HostSkillProvider;
|
||||
pub use orchestrator::OrchestratorSkillProvider;
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_extension_api::SelectedPluginSnapshot;
|
||||
use codex_protocol::capabilities::CapabilityRootLocation;
|
||||
use codex_protocol::protocol::Product;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use codex_skills::EnvironmentSkillMetadata;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
@@ -46,6 +48,22 @@ impl ExecutorSkillProvider {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn attribute_executor_plugins(
|
||||
catalog: &mut SkillCatalog,
|
||||
snapshot: &SelectedPluginSnapshot,
|
||||
) {
|
||||
for skill in &mut catalog.entries {
|
||||
if let Some(plugin) = snapshot
|
||||
.plugins
|
||||
.iter()
|
||||
.find(|plugin| plugin.selected_root_id == skill.authority.id)
|
||||
{
|
||||
skill.plugin_id = Some(plugin.plugin_id.clone());
|
||||
skill.analytics_scope = Some(SkillScope::User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SkillProvider for ExecutorSkillProvider {
|
||||
fn list(&self, query: SkillListQuery) -> SkillProviderFuture<'_, SkillCatalog> {
|
||||
Box::pin(async move {
|
||||
|
||||
@@ -14,6 +14,7 @@ use codex_extension_api::ExtensionData;
|
||||
use codex_extension_api::FunctionCallError;
|
||||
use codex_extension_api::JsonToolOutput;
|
||||
use codex_extension_api::ResponsesApiTool;
|
||||
use codex_extension_api::SelectedPluginSnapshot;
|
||||
use codex_extension_api::ThreadOriginator;
|
||||
use codex_extension_api::ToolCall;
|
||||
use codex_extension_api::ToolExecutor;
|
||||
@@ -37,6 +38,7 @@ use crate::catalog::SkillCatalog;
|
||||
use crate::catalog::SkillCatalogEntry;
|
||||
use crate::catalog::SkillSourceKind;
|
||||
use crate::provider::SkillListQuery;
|
||||
use crate::provider::attribute_executor_plugins;
|
||||
use crate::shadow_selection_experiment::ShadowSelectionExperiment;
|
||||
use crate::sources::SkillProviders;
|
||||
use crate::state::SkillsSessionState;
|
||||
@@ -53,14 +55,19 @@ pub(crate) fn skill_tools(
|
||||
providers: SkillProviders,
|
||||
session_store: &ExtensionData,
|
||||
thread_store: &ExtensionData,
|
||||
orchestrator_available: bool,
|
||||
executor_query: Option<SkillListQuery>,
|
||||
selected_plugins: Option<Arc<SelectedPluginSnapshot>>,
|
||||
sandbox_contexts: Option<Arc<HashMap<String, FileSystemSandboxContext>>>,
|
||||
shadow_selection: Arc<ShadowSelectionExperiment>,
|
||||
) -> Vec<Arc<dyn ToolExecutor<ToolCall>>> {
|
||||
let Some(thread_state) = thread_store.get::<SkillsThreadState>() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let orchestrator_available =
|
||||
providers.has_orchestrator_provider() && thread_state.orchestrator_skills_enabled();
|
||||
if !orchestrator_available && executor_query.is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
let mcp_resources = session_store
|
||||
.get::<SkillsSessionState>()
|
||||
.and_then(|state| state.mcp_resources.clone());
|
||||
@@ -72,6 +79,7 @@ pub(crate) fn skill_tools(
|
||||
analytics,
|
||||
orchestrator_available,
|
||||
executor_query,
|
||||
selected_plugins,
|
||||
sandbox_contexts,
|
||||
executor_catalog: Arc::new(OnceCell::new()),
|
||||
shadow_selection,
|
||||
@@ -127,8 +135,7 @@ impl SkillAnalytics {
|
||||
skill_id: skill.canonical_skill_id.clone(),
|
||||
scope: skill.analytics_scope,
|
||||
},
|
||||
// TODO: Include plugin identifiers once skills can be attributed to their plugin.
|
||||
plugin_id: None,
|
||||
plugin_id: skill.plugin_id.clone(),
|
||||
remote_plugin_id: None,
|
||||
invocation_type,
|
||||
}],
|
||||
@@ -144,6 +151,7 @@ struct SkillToolContext {
|
||||
analytics: Option<SkillAnalytics>,
|
||||
orchestrator_available: bool,
|
||||
executor_query: Option<SkillListQuery>,
|
||||
selected_plugins: Option<Arc<SelectedPluginSnapshot>>,
|
||||
sandbox_contexts: Option<Arc<HashMap<String, FileSystemSandboxContext>>>,
|
||||
executor_catalog: Arc<OnceCell<SkillCatalog>>,
|
||||
shadow_selection: Arc<ShadowSelectionExperiment>,
|
||||
@@ -178,10 +186,15 @@ impl SkillToolContext {
|
||||
return SkillCatalog::default();
|
||||
};
|
||||
query.turn_id = turn_id.to_string();
|
||||
self.executor_catalog
|
||||
let mut catalog = self
|
||||
.executor_catalog
|
||||
.get_or_init(|| self.providers.list_executor_for_turn(query))
|
||||
.await
|
||||
.clone()
|
||||
.clone();
|
||||
if let Some(selected_plugins) = &self.selected_plugins {
|
||||
attribute_executor_plugins(&mut catalog, selected_plugins);
|
||||
}
|
||||
catalog
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use codex_extension_api::ContextualUserFragment;
|
||||
use codex_extension_api::ExtensionEventSink;
|
||||
use codex_extension_api::ExtensionWarning;
|
||||
use codex_extension_api::SelectedPluginSnapshot;
|
||||
use codex_extension_api::WorldStateContributionInput;
|
||||
use codex_extension_api::WorldStateSectionContribution;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
@@ -11,6 +12,7 @@ use crate::HostSkillsSnapshot;
|
||||
use crate::SkillsExtensionConfig;
|
||||
use crate::catalog::SkillCatalog;
|
||||
use crate::provider::SkillListQuery;
|
||||
use crate::provider::attribute_executor_plugins;
|
||||
use crate::render::AvailableSkillsRender;
|
||||
use crate::render::RenderedSkillCatalogs;
|
||||
use crate::render::SkillMetadataBudget;
|
||||
@@ -167,10 +169,13 @@ impl<'a> CatalogContext<'a> {
|
||||
}
|
||||
|
||||
async fn discover_executor_catalog(&self, query: SkillListQuery) -> CatalogContribution {
|
||||
let catalog = self
|
||||
let mut catalog = self
|
||||
.thread_state
|
||||
.executor_catalog_snapshot(self.providers, query)
|
||||
.await;
|
||||
if let Some(selected_plugins) = self.input.turn_store.get::<SelectedPluginSnapshot>() {
|
||||
attribute_executor_plugins(&mut catalog, &selected_plugins);
|
||||
}
|
||||
self.input
|
||||
.turn_store
|
||||
.insert(ExecutorSkillsStepState(catalog.clone()));
|
||||
|
||||
Reference in new issue
Block a user