mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Add a skill invocation extension contributor (#32200)
## What changed - Add `SkillInvocationContributor` and registration support to the extension API. - Provide invocation callbacks with session-, thread-, and turn-scoped extension data, the turn ID, skill resource, and explicit or implicit invocation kind. - Notify registered contributors when Codex observes a deduplicated implicit skill invocation. ## Testing - Extend the extension registry round-trip test to cover skill invocation contributors. GitOrigin-RevId: f428336728889d9bf4fba41a7dd8d3a6f9b9728c
This commit is contained in:
@@ -4,6 +4,8 @@ use crate::session::turn_context::TurnContext;
|
||||
use codex_analytics::InvocationType;
|
||||
use codex_analytics::SkillInvocation;
|
||||
use codex_analytics::build_track_events_context;
|
||||
use codex_extension_api::SkillInvocationInput;
|
||||
use codex_extension_api::SkillInvocationKind;
|
||||
use codex_protocol::protocol::SkillScope;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_plugins::PluginSkillRoot;
|
||||
@@ -86,6 +88,19 @@ pub(crate) async fn maybe_emit_implicit_skill_invocation(
|
||||
return;
|
||||
}
|
||||
|
||||
for contributor in sess.services.extensions.skill_invocation_contributors() {
|
||||
contributor
|
||||
.on_skill_invocation(SkillInvocationInput {
|
||||
session_store: &sess.services.session_extension_data,
|
||||
thread_store: &sess.services.thread_extension_data,
|
||||
turn_store: turn_context.extension_data.as_ref(),
|
||||
turn_id: turn_context.sub_id.as_str(),
|
||||
skill_resource: skill_path.as_ref(),
|
||||
kind: SkillInvocationKind::Implicit,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
turn_context.session_telemetry.counter(
|
||||
"codex.skill.injected",
|
||||
/*inc*/ 1,
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::ExtensionData;
|
||||
mod context;
|
||||
mod mcp;
|
||||
mod prompt;
|
||||
mod skill_invocation;
|
||||
mod thread_lifecycle;
|
||||
mod tool_lifecycle;
|
||||
mod turn_input;
|
||||
@@ -25,6 +26,8 @@ pub use mcp::McpServerContribution;
|
||||
pub use mcp::McpServerContributionContext;
|
||||
pub use prompt::PromptFragment;
|
||||
pub use prompt::PromptSlot;
|
||||
pub use skill_invocation::SkillInvocationInput;
|
||||
pub use skill_invocation::SkillInvocationKind;
|
||||
pub use thread_lifecycle::ThreadIdleInput;
|
||||
pub use thread_lifecycle::ThreadResumeInput;
|
||||
pub use thread_lifecycle::ThreadStartInput;
|
||||
@@ -247,6 +250,23 @@ pub trait TokenUsageContributor: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// Contributor for skill invocations observed by the host or an owning extension.
|
||||
///
|
||||
/// Implementations should treat the skill resource as an opaque identity and keep this callback
|
||||
/// cheap because it runs inline with skill loading or command dispatch.
|
||||
pub trait SkillInvocationContributor: Send + Sync {
|
||||
/// Called after one explicit skill load or deduplicated implicit skill invocation is observed.
|
||||
fn on_skill_invocation<'a>(
|
||||
&'a self,
|
||||
_input: SkillInvocationInput<'a>,
|
||||
) -> ExtensionFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let _self = self;
|
||||
let _input = _input;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension contribution that exposes native tools owned by a feature.
|
||||
pub trait ToolContributor: Send + Sync {
|
||||
/// Returns the native tools visible for the supplied extension stores.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use crate::ExtensionData;
|
||||
|
||||
/// Input supplied when the host or an extension observes one skill invocation.
|
||||
pub struct SkillInvocationInput<'a> {
|
||||
/// Store scoped to the host session runtime.
|
||||
pub session_store: &'a ExtensionData,
|
||||
/// Store scoped to this thread runtime.
|
||||
pub thread_store: &'a ExtensionData,
|
||||
/// Store scoped to this turn runtime.
|
||||
pub turn_store: &'a ExtensionData,
|
||||
/// Current turn submission id.
|
||||
pub turn_id: &'a str,
|
||||
/// Main prompt path or opaque resource id for the invoked skill.
|
||||
pub skill_resource: &'a str,
|
||||
/// How the skill invocation was initiated.
|
||||
pub kind: SkillInvocationKind,
|
||||
}
|
||||
|
||||
/// How an observed skill invocation was initiated.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum SkillInvocationKind {
|
||||
/// The user explicitly mentioned the skill.
|
||||
Explicit,
|
||||
/// The model read the skill instructions or ran one of its scripts.
|
||||
Implicit,
|
||||
}
|
||||
@@ -42,6 +42,9 @@ pub use contributors::PreviousWorldStateSection;
|
||||
pub use contributors::PromptFragment;
|
||||
pub use contributors::PromptSlot;
|
||||
pub use contributors::RenderedWorldStateFragment;
|
||||
pub use contributors::SkillInvocationContributor;
|
||||
pub use contributors::SkillInvocationInput;
|
||||
pub use contributors::SkillInvocationKind;
|
||||
pub use contributors::ThreadIdleInput;
|
||||
pub use contributors::ThreadLifecycleContributor;
|
||||
pub use contributors::ThreadResumeInput;
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::ExtensionData;
|
||||
use crate::ExtensionEventSink;
|
||||
use crate::McpServerContributor;
|
||||
use crate::NoopExtensionEventSink;
|
||||
use crate::SkillInvocationContributor;
|
||||
use crate::ThreadLifecycleContributor;
|
||||
use crate::TokenUsageContributor;
|
||||
use crate::ToolContributor;
|
||||
@@ -24,6 +25,7 @@ pub struct ExtensionRegistryBuilder<C: Sync> {
|
||||
turn_lifecycle_contributors: Vec<Arc<dyn TurnLifecycleContributor>>,
|
||||
config_contributors: Vec<Arc<dyn ConfigContributor<C>>>,
|
||||
token_usage_contributors: Vec<Arc<dyn TokenUsageContributor>>,
|
||||
skill_invocation_contributors: Vec<Arc<dyn SkillInvocationContributor>>,
|
||||
context_contributors: Vec<Arc<dyn ContextContributor>>,
|
||||
mcp_server_contributors: Vec<Arc<dyn McpServerContributor<C>>>,
|
||||
turn_input_contributors: Vec<Arc<dyn TurnInputContributor>>,
|
||||
@@ -41,6 +43,7 @@ impl<C: Sync> Default for ExtensionRegistryBuilder<C> {
|
||||
turn_lifecycle_contributors: Vec::new(),
|
||||
config_contributors: Vec::new(),
|
||||
token_usage_contributors: Vec::new(),
|
||||
skill_invocation_contributors: Vec::new(),
|
||||
approval_review_contributors: Vec::new(),
|
||||
context_contributors: Vec::new(),
|
||||
mcp_server_contributors: Vec::new(),
|
||||
@@ -99,6 +102,14 @@ impl<C: Sync> ExtensionRegistryBuilder<C> {
|
||||
self.token_usage_contributors.push(contributor);
|
||||
}
|
||||
|
||||
/// Registers one skill-invocation contributor.
|
||||
pub fn skill_invocation_contributor(
|
||||
&mut self,
|
||||
contributor: Arc<dyn SkillInvocationContributor>,
|
||||
) {
|
||||
self.skill_invocation_contributors.push(contributor);
|
||||
}
|
||||
|
||||
/// Registers one prompt contributor.
|
||||
pub fn prompt_contributor(&mut self, contributor: Arc<dyn ContextContributor>) {
|
||||
self.context_contributors.push(contributor);
|
||||
@@ -137,6 +148,7 @@ impl<C: Sync> ExtensionRegistryBuilder<C> {
|
||||
turn_lifecycle_contributors: self.turn_lifecycle_contributors,
|
||||
config_contributors: self.config_contributors,
|
||||
token_usage_contributors: self.token_usage_contributors,
|
||||
skill_invocation_contributors: self.skill_invocation_contributors,
|
||||
approval_review_contributors: self.approval_review_contributors,
|
||||
context_contributors: self.context_contributors,
|
||||
mcp_server_contributors: self.mcp_server_contributors,
|
||||
@@ -155,6 +167,7 @@ pub struct ExtensionRegistry<C: Sync> {
|
||||
turn_lifecycle_contributors: Vec<Arc<dyn TurnLifecycleContributor>>,
|
||||
config_contributors: Vec<Arc<dyn ConfigContributor<C>>>,
|
||||
token_usage_contributors: Vec<Arc<dyn TokenUsageContributor>>,
|
||||
skill_invocation_contributors: Vec<Arc<dyn SkillInvocationContributor>>,
|
||||
context_contributors: Vec<Arc<dyn ContextContributor>>,
|
||||
mcp_server_contributors: Vec<Arc<dyn McpServerContributor<C>>>,
|
||||
turn_input_contributors: Vec<Arc<dyn TurnInputContributor>>,
|
||||
@@ -190,6 +203,11 @@ impl<C: Sync> ExtensionRegistry<C> {
|
||||
&self.token_usage_contributors
|
||||
}
|
||||
|
||||
/// Returns the registered skill-invocation contributors.
|
||||
pub fn skill_invocation_contributors(&self) -> &[Arc<dyn SkillInvocationContributor>] {
|
||||
&self.skill_invocation_contributors
|
||||
}
|
||||
|
||||
/// Claims the first rendered approval-review prompt accepted by an
|
||||
/// installed contributor.
|
||||
pub async fn approval_review(
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_extension_api::ExtensionFuture;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::PromptFragment;
|
||||
use codex_extension_api::PromptSlot;
|
||||
use codex_extension_api::SkillInvocationContributor;
|
||||
use codex_extension_api::ThreadLifecycleContributor;
|
||||
use codex_extension_api::TokenUsageContributor;
|
||||
use codex_extension_api::ToolCall;
|
||||
@@ -53,6 +54,8 @@ impl ConfigContributor<()> for AllContributors {}
|
||||
|
||||
impl TokenUsageContributor for AllContributors {}
|
||||
|
||||
impl SkillInvocationContributor for AllContributors {}
|
||||
|
||||
impl TurnInputContributor for AllContributors {
|
||||
fn contribute<'a>(
|
||||
&'a self,
|
||||
@@ -117,6 +120,7 @@ async fn build_round_trips_every_contributor_category() {
|
||||
builder.turn_lifecycle_contributor(contributor.clone());
|
||||
builder.config_contributor(contributor.clone());
|
||||
builder.token_usage_contributor(contributor.clone());
|
||||
builder.skill_invocation_contributor(contributor.clone());
|
||||
builder.prompt_contributor(contributor.clone());
|
||||
builder.turn_input_contributor(contributor.clone());
|
||||
builder.tool_contributor(contributor.clone());
|
||||
@@ -129,6 +133,7 @@ async fn build_round_trips_every_contributor_category() {
|
||||
assert_eq!(registry.turn_lifecycle_contributors().len(), 1);
|
||||
assert_eq!(registry.config_contributors().len(), 1);
|
||||
assert_eq!(registry.token_usage_contributors().len(), 1);
|
||||
assert_eq!(registry.skill_invocation_contributors().len(), 1);
|
||||
assert_eq!(registry.context_contributors().len(), 1);
|
||||
assert_eq!(registry.turn_input_contributors().len(), 1);
|
||||
assert_eq!(registry.tool_contributors().len(), 1);
|
||||
|
||||
Reference in New Issue
Block a user