From ee1b490c0e9ddd2e06d440cb3636e584ec87c15d Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 7 May 2026 15:21:24 +0100 Subject: [PATCH] v3 --- codex-rs/ext/extension-api/notes.md | 14 ++++ .../ext/extension-api/src/contributors.rs | 57 ++++++++------ .../src/contributors/session_lifecycle.rs | 6 ++ codex-rs/ext/extension-api/src/lib.rs | 2 +- codex-rs/ext/extension-api/src/registry.rs | 18 ++--- codex-rs/ext/git-attribution/src/lib.rs | 4 +- codex-rs/ext/guardian/src/lib.rs | 4 +- codex-rs/ext/memories/src/ctx.rs | 8 ++ codex-rs/ext/memories/src/lib.rs | 75 +++++++++---------- codex-rs/ext/multi-agent-v2/src/lib.rs | 4 +- 10 files changed, 111 insertions(+), 81 deletions(-) create mode 100644 codex-rs/ext/extension-api/notes.md create mode 100644 codex-rs/ext/extension-api/src/contributors/session_lifecycle.rs create mode 100644 codex-rs/ext/memories/src/ctx.rs diff --git a/codex-rs/ext/extension-api/notes.md b/codex-rs/ext/extension-api/notes.md new file mode 100644 index 0000000000..e73b106f6a --- /dev/null +++ b/codex-rs/ext/extension-api/notes.md @@ -0,0 +1,14 @@ +Everything becomes a good contributor design, which contributors do we need? + +git attribution Context +memories Context + Tool + Output +guardian Context + Request +goal Tool + Runtime +image generation Tool + Output +skills Context + Turn +personality Context +plugins / apps / connectors Context + Turn +shell snapshot Runtime +web search Tool +AGENTS.md Context (Runtime too only if you want eager refresh/cache behavior) +future sandboxing probably Request + Runtime diff --git a/codex-rs/ext/extension-api/src/contributors.rs b/codex-rs/ext/extension-api/src/contributors.rs index 33e1dd1977..3e57cea152 100644 --- a/codex-rs/ext/extension-api/src/contributors.rs +++ b/codex-rs/ext/extension-api/src/contributors.rs @@ -7,6 +7,7 @@ mod prompt; mod tool; +mod session_lifecycle; pub use prompt::PromptFragment; pub use prompt::PromptSlot; @@ -14,33 +15,41 @@ pub use tool::ToolCallError; pub use tool::ToolContribution; pub use tool::ToolHandler; +/// Extension contribution that adds prompt fragments during prompt assembly. +/// Arguably, can become async +pub trait ContextContributor: Send + Sync { + fn contribute(&self, context: &C) -> Vec; // TODO use existing fragments ofc +} + +/// Extension contribution that exposes native tools owned by a feature. +pub trait ToolContributor: Send + Sync { + /// Returns the native tools visible for the supplied runtime context. + fn tools(&self, context: &C) -> Vec>; +} + +/// Analyze or perform computation on the output. Works only if the post-processing pipeline is ordered +pub type OutputContributionFuture<'a> = std::pin::Pin> + Send + 'a>>; +pub trait OutputContributor: Send + Sync { + fn contribute<'a>( + &'a self, + context: &'a C, + output: &'a mut O, + ) -> OutputContributionFuture<'a>; +} + + + +// TODO: WIP /// Extension contribution that can claim approval requests for a runtime context. -/// -/// Implementations should make only the routing decision here. The host keeps -/// ownership of executing the chosen review flow and translating its result -/// back into the surrounding runtime. +/// (ideally we can replace it by a session lifecycle thing or a request contributor?) pub trait ApprovalInterceptorContributor: Send + Sync { /// Returns whether this contributor should intercept approvals in `context`. fn intercepts_approvals(&self, context: &C) -> bool; } -/// Extension contribution that adds prompt fragments during prompt assembly. -/// -/// Implementations should inspect only their feature-owned slice of the -/// current runtime context and describe the prompt content exposed for that -/// invocation. The host remains responsible for ordering fragments and -/// assembling prompt items. -pub trait PromptContributor: Send + Sync { - fn contribute(&self, context: &C) -> Vec; -} - -/// Extension contribution that exposes native tools owned by a feature. -/// -/// Implementations should inspect only their feature-owned slice of the -/// current runtime context and return the tools exposed for that invocation. -/// The host remains responsible for mounting those tools and adapting calls -/// into its runtime. -pub trait ToolContributor: Send + Sync { - /// Returns the native tools visible for the supplied runtime context. - fn tools(&self, context: &C) -> Vec>; -} +pub trait SessionLifecycleContributor: Send + Sync { + fn on_lifecycle_event( + &self, + event: session_lifecycle::Event<'_, S, T>, + ) -> impl Future + Send; +} \ No newline at end of file diff --git a/codex-rs/ext/extension-api/src/contributors/session_lifecycle.rs b/codex-rs/ext/extension-api/src/contributors/session_lifecycle.rs new file mode 100644 index 0000000000..6e851bab33 --- /dev/null +++ b/codex-rs/ext/extension-api/src/contributors/session_lifecycle.rs @@ -0,0 +1,6 @@ +pub enum Event<'a, S, T> { + SessionStarted(&'a S), + TurnStarted(&'a T), + TurnFinished { turn: &'a T, outcome: String }, // Make outcome better?! + SessionStopping(&'a S), +} diff --git a/codex-rs/ext/extension-api/src/lib.rs b/codex-rs/ext/extension-api/src/lib.rs index a1a0e4adc3..571d706f76 100644 --- a/codex-rs/ext/extension-api/src/lib.rs +++ b/codex-rs/ext/extension-api/src/lib.rs @@ -12,7 +12,7 @@ mod extension; mod registry; pub use contributors::ApprovalInterceptorContributor; -pub use contributors::PromptContributor; +pub use contributors::ContextContributor; pub use contributors::PromptFragment; pub use contributors::PromptSlot; pub use contributors::ToolCallError; diff --git a/codex-rs/ext/extension-api/src/registry.rs b/codex-rs/ext/extension-api/src/registry.rs index 0d96333fad..ca70e5e110 100644 --- a/codex-rs/ext/extension-api/src/registry.rs +++ b/codex-rs/ext/extension-api/src/registry.rs @@ -2,21 +2,21 @@ use std::sync::Arc; use crate::ApprovalInterceptorContributor; use crate::CodexExtension; -use crate::PromptContributor; +use crate::ContextContributor; use crate::ToolContributor; /// Mutable registry used while extensions install their typed contributions. pub struct ExtensionRegistryBuilder { - approval_interceptor_contributors: Vec>>, - prompt_contributors: Vec>>, + context_contributors: Vec>>, tool_contributors: Vec>>, + approval_interceptor_contributors: Vec>>, } impl Default for ExtensionRegistryBuilder { fn default() -> Self { Self { approval_interceptor_contributors: Vec::new(), - prompt_contributors: Vec::new(), + context_contributors: Vec::new(), tool_contributors: Vec::new(), } } @@ -55,8 +55,8 @@ impl ExtensionRegistryBuilder { } /// Registers one prompt contributor. - pub fn prompt_contributor(&mut self, contributor: Arc>) { - self.prompt_contributors.push(contributor); + pub fn prompt_contributor(&mut self, contributor: Arc>) { + self.context_contributors.push(contributor); } /// Registers one native tool contributor. @@ -68,7 +68,7 @@ impl ExtensionRegistryBuilder { pub fn build(self) -> ExtensionRegistry { ExtensionRegistry { approval_interceptor_contributors: self.approval_interceptor_contributors, - prompt_contributors: self.prompt_contributors, + prompt_contributors: self.context_contributors, tool_contributors: self.tool_contributors, } } @@ -77,7 +77,7 @@ impl ExtensionRegistryBuilder { /// Immutable typed registry produced after extensions are installed. pub struct ExtensionRegistry { approval_interceptor_contributors: Vec>>, - prompt_contributors: Vec>>, + prompt_contributors: Vec>>, tool_contributors: Vec>>, } @@ -90,7 +90,7 @@ impl ExtensionRegistry { } /// Returns the registered prompt contributors. - pub fn prompt_contributors(&self) -> &[Arc>] { + pub fn prompt_contributors(&self) -> &[Arc>] { &self.prompt_contributors } diff --git a/codex-rs/ext/git-attribution/src/lib.rs b/codex-rs/ext/git-attribution/src/lib.rs index a8b2ddbc10..8830898ddd 100644 --- a/codex-rs/ext/git-attribution/src/lib.rs +++ b/codex-rs/ext/git-attribution/src/lib.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use codex_extension_api::CodexExtension; +use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionRegistryBuilder; -use codex_extension_api::PromptContributor; use codex_extension_api::PromptFragment; const DEFAULT_ATTRIBUTION_VALUE: &str = "Codex "; @@ -39,7 +39,7 @@ impl GitAttributionExtension { } } -impl PromptContributor for GitAttributionExtension { +impl ContextContributor for GitAttributionExtension { fn contribute(&self, context: &C) -> Vec { self.instruction(context) .map(PromptFragment::developer_capability) diff --git a/codex-rs/ext/guardian/src/lib.rs b/codex-rs/ext/guardian/src/lib.rs index 10ed94a6bd..792d85c3aa 100644 --- a/codex-rs/ext/guardian/src/lib.rs +++ b/codex-rs/ext/guardian/src/lib.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use codex_extension_api::ApprovalInterceptorContributor; use codex_extension_api::CodexExtension; +use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionRegistryBuilder; -use codex_extension_api::PromptContributor; use codex_extension_api::PromptFragment; /// Runtime facts needed to expose Guardian surfaces. @@ -53,7 +53,7 @@ impl ApprovalInterceptorContributor for GuardianExtension } } -impl PromptContributor for GuardianExtension { +impl ContextContributor for GuardianExtension { fn contribute(&self, context: &C) -> Vec { self.policy_prompt(context) .map(PromptFragment::separate_developer) diff --git a/codex-rs/ext/memories/src/ctx.rs b/codex-rs/ext/memories/src/ctx.rs new file mode 100644 index 0000000000..8c3a2fc191 --- /dev/null +++ b/codex-rs/ext/memories/src/ctx.rs @@ -0,0 +1,8 @@ +/// Runtime facts needed to decide whether read-memory surfaces are visible. +/// +/// Hosts should expose the current effective values for the thread being +/// assembled. The extension owns the policy that combines those values. +pub trait MemoriesContext { + fn memory_tool_enabled(&self) -> bool; + fn use_memories(&self) -> bool; +} diff --git a/codex-rs/ext/memories/src/lib.rs b/codex-rs/ext/memories/src/lib.rs index bae504880a..fcc33bed5d 100644 --- a/codex-rs/ext/memories/src/lib.rs +++ b/codex-rs/ext/memories/src/lib.rs @@ -2,14 +2,16 @@ #![forbid(unsafe_code)] +pub mod ctx; mod list_tool; use std::path::PathBuf; use std::sync::Arc; +use crate::ctx::MemoriesContext; use codex_extension_api::CodexExtension; +use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionRegistryBuilder; -use codex_extension_api::PromptContributor; use codex_extension_api::PromptFragment; use codex_extension_api::ToolContribution; use codex_extension_api::ToolContributor; @@ -18,15 +20,6 @@ use codex_memories_read::memory_root; use codex_utils_absolute_path::AbsolutePathBuf; use list_tool::ListMemoriesTool; -/// Runtime facts needed to decide whether read-memory surfaces are visible. -/// -/// Hosts should expose the current effective values for the thread being -/// assembled. The extension owns the policy that combines those values. -pub trait MemoriesContext { - fn memory_tool_enabled(&self) -> bool; - fn use_memories(&self) -> bool; -} - /// Extension that contributes memories read surfaces. #[derive(Clone, Debug)] pub struct MemoriesExtension { @@ -34,6 +27,36 @@ pub struct MemoriesExtension { list_tool: Arc, } +impl ToolContributor for MemoriesExtension { + fn tools(&self, context: &C) -> Vec> { + if !self.is_read_surface_enabled(context) { + return Vec::new(); + } + + vec![self.list_tool.contribution()] + } +} + +impl ContextContributor for MemoriesExtension { + fn contribute(&self, context: &C) -> Vec { + if !self.is_read_surface_enabled(context) { + return Vec::new(); + } + + self.read_prompt() + .map(PromptFragment::developer_policy) + .into_iter() + .collect() + } +} + +impl CodexExtension for MemoriesExtension { + fn install(self: Arc, registry: &mut ExtensionRegistryBuilder) { + registry.tool_contributor(self.clone()); + registry.prompt_contributor(self); + } +} + impl MemoriesExtension { fn new(read_prompt: Option, memories_root: impl Into) -> Self { Self { @@ -70,37 +93,7 @@ impl MemoriesExtension { } } -impl ToolContributor for MemoriesExtension { - fn tools(&self, context: &C) -> Vec> { - if !self.is_read_surface_enabled(context) { - return Vec::new(); - } - - vec![self.list_tool.contribution()] - } -} - -impl PromptContributor for MemoriesExtension { - fn contribute(&self, context: &C) -> Vec { - if !self.is_read_surface_enabled(context) { - return Vec::new(); - } - - self.read_prompt() - .map(PromptFragment::developer_policy) - .into_iter() - .collect() - } -} - -impl CodexExtension for MemoriesExtension { - fn install(self: Arc, registry: &mut ExtensionRegistryBuilder) { - registry.tool_contributor(self.clone()); - registry.prompt_contributor(self); - } -} - -/// Creates a shared memories extension using the live memories read prompt. +/// todo Other builder pattern... pub async fn extension(codex_home: &AbsolutePathBuf) -> Arc { Arc::new(MemoriesExtension::from_codex_home(codex_home).await) } diff --git a/codex-rs/ext/multi-agent-v2/src/lib.rs b/codex-rs/ext/multi-agent-v2/src/lib.rs index c1622b6552..b57b5affe1 100644 --- a/codex-rs/ext/multi-agent-v2/src/lib.rs +++ b/codex-rs/ext/multi-agent-v2/src/lib.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use codex_extension_api::CodexExtension; +use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionRegistryBuilder; -use codex_extension_api::PromptContributor; use codex_extension_api::PromptFragment; /// Which kind of agent should receive a MultiAgentV2 usage hint. @@ -53,7 +53,7 @@ impl MultiAgentV2Extension { } } -impl PromptContributor for MultiAgentV2Extension { +impl ContextContributor for MultiAgentV2Extension { fn contribute(&self, context: &C) -> Vec { self.usage_hint_text(context) .map(PromptFragment::separate_developer)