mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
extension: add initial typed extension API (#21736)
## Why `codex-core` still owns a growing amount of product-specific behavior. This PR starts the extraction path by introducing a small, typed first-party extension seam: features can install the contribution families they actually own, while the host keeps lifecycle and state ownership instead of pushing a broad service locator into the API. See the `examples/` for illustration ## Known limitations * Tool contract definition will be shared with core * Fragments must be extracted * Missing some contributors
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
#[path = "enabled_extensions/shared_state_extension.rs"]
|
||||
mod shared_state_extension;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_extension_api::ExtensionData;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use shared_state_extension::SharedStateExtension;
|
||||
use shared_state_extension::recorded_style_contributions;
|
||||
use shared_state_extension::recorded_usage_contributions;
|
||||
|
||||
fn main() {
|
||||
// 1. Build the extension value owned by the host.
|
||||
let extension = Arc::new(SharedStateExtension);
|
||||
|
||||
// 2. Install it into the registry for the thread-start input type this host exposes.
|
||||
let registry = ExtensionRegistryBuilder::<()>::new()
|
||||
.with_extension(extension)
|
||||
.build();
|
||||
|
||||
// 3. The host decides which stores are shared.
|
||||
let session_store = ExtensionData::new();
|
||||
let first_thread_store = ExtensionData::new();
|
||||
let second_thread_store = ExtensionData::new();
|
||||
|
||||
// 4. Reusing the same session store shares session state across threads.
|
||||
let first_thread_fragments = contribute_prompt(®istry, &session_store, &first_thread_store);
|
||||
contribute_prompt(®istry, &session_store, &first_thread_store);
|
||||
contribute_prompt(®istry, &session_store, &second_thread_store);
|
||||
|
||||
println!("first prompt fragments: {}", first_thread_fragments.len());
|
||||
println!(
|
||||
"session style contributions: {}",
|
||||
recorded_style_contributions(&session_store)
|
||||
);
|
||||
println!(
|
||||
"session usage contributions: {}",
|
||||
recorded_usage_contributions(&session_store)
|
||||
);
|
||||
println!(
|
||||
"first thread style contributions: {}",
|
||||
recorded_style_contributions(&first_thread_store)
|
||||
);
|
||||
println!(
|
||||
"first thread usage contributions: {}",
|
||||
recorded_usage_contributions(&first_thread_store)
|
||||
);
|
||||
println!(
|
||||
"second thread style contributions: {}",
|
||||
recorded_style_contributions(&second_thread_store)
|
||||
);
|
||||
println!(
|
||||
"second thread usage contributions: {}",
|
||||
recorded_usage_contributions(&second_thread_store)
|
||||
);
|
||||
}
|
||||
|
||||
fn contribute_prompt(
|
||||
registry: &codex_extension_api::ExtensionRegistry<()>,
|
||||
session_store: &ExtensionData,
|
||||
thread_store: &ExtensionData,
|
||||
) -> Vec<codex_extension_api::PromptFragment> {
|
||||
registry
|
||||
.context_contributors()
|
||||
.iter()
|
||||
.flat_map(|contributor| contributor.contribute(session_store, thread_store))
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use codex_extension_api::CodexExtension;
|
||||
use codex_extension_api::ContextContributor;
|
||||
use codex_extension_api::ExtensionData;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::PromptFragment;
|
||||
|
||||
/// Small tutorial extension that installs two prompt contributors.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SharedStateExtension;
|
||||
|
||||
impl CodexExtension<()> for SharedStateExtension {
|
||||
fn install(self: Arc<Self>, registry: &mut ExtensionRegistryBuilder<()>) {
|
||||
registry.prompt_contributor(Arc::new(StyleContributor));
|
||||
registry.prompt_contributor(Arc::new(UsageContributor));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct StyleContributor;
|
||||
|
||||
impl ContextContributor for StyleContributor {
|
||||
fn contribute(
|
||||
&self,
|
||||
session_store: &ExtensionData,
|
||||
thread_store: &ExtensionData,
|
||||
) -> Vec<PromptFragment> {
|
||||
contribution_counts(session_store).record_style();
|
||||
contribution_counts(thread_store).record_style();
|
||||
|
||||
vec![PromptFragment::developer_policy(
|
||||
"Prefer short answers unless the user asks for detail.",
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UsageContributor;
|
||||
|
||||
impl ContextContributor for UsageContributor {
|
||||
fn contribute(
|
||||
&self,
|
||||
session_store: &ExtensionData,
|
||||
thread_store: &ExtensionData,
|
||||
) -> Vec<PromptFragment> {
|
||||
contribution_counts(session_store).record_usage();
|
||||
contribution_counts(thread_store).record_usage();
|
||||
|
||||
vec![PromptFragment::developer_capability(
|
||||
"This extension can contribute more than one prompt fragment.",
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how many style contributions were recorded in `store`.
|
||||
pub fn recorded_style_contributions(store: &ExtensionData) -> u64 {
|
||||
store
|
||||
.get::<ContributionCounts>()
|
||||
.map(|counts| counts.style())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns how many usage contributions were recorded in `store`.
|
||||
pub fn recorded_usage_contributions(store: &ExtensionData) -> u64 {
|
||||
store
|
||||
.get::<ContributionCounts>()
|
||||
.map(|counts| counts.usage())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ContributionCounts {
|
||||
style: AtomicU64,
|
||||
usage: AtomicU64,
|
||||
}
|
||||
|
||||
impl ContributionCounts {
|
||||
fn record_style(&self) {
|
||||
self.style.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn record_usage(&self) {
|
||||
self.usage.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn style(&self) -> u64 {
|
||||
self.style.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn usage(&self) -> u64 {
|
||||
self.usage.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
fn contribution_counts(store: &ExtensionData) -> Arc<ContributionCounts> {
|
||||
store.get_or_init::<ContributionCounts>(Default::default)
|
||||
}
|
||||
Reference in New Issue
Block a user