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:
jif-oai authored and GitHub committed 2026-05-11 11:06:24 +02:00
1 parent 2abdeb34d5
commit d2c3ebac1f
14 files changed
+648

No files matched your search

@@ -0,0 +1,65 @@
use std::future::Future;
use codex_protocol::items::TurnItem;
use crate::ExtensionData;
mod prompt;
mod tool;
pub use prompt::PromptFragment;
pub use prompt::PromptSlot;
pub use tool::ToolCallError;
pub use tool::ToolContribution;
pub use tool::ToolHandler;
/// Contributor that receives host-owned thread-start input before later
/// contributors read from extension stores.
pub trait ThreadStartContributor<C>: Send + Sync {
fn contribute(&self, input: &C, session_store: &ExtensionData, thread_store: &ExtensionData);
}
/// Extension contribution that adds prompt fragments during prompt assembly.
pub trait ContextContributor: Send + Sync {
fn contribute(
&self,
session_store: &ExtensionData,
thread_store: &ExtensionData,
) -> Vec<PromptFragment>;
}
/// 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, thread_store: &ExtensionData) -> Vec<ToolContribution>;
}
/// Future returned by one ordered turn-item contribution.
pub type TurnItemContributionFuture<'a> =
std::pin::Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'a>>;
/// Ordered post-processing contribution for one parsed turn item.
///
/// Implementations may mutate the item before it is emitted and may use the
/// explicitly exposed thread- and turn-lifetime stores when they need durable
/// extension-private state.
pub trait TurnItemContributor: Send + Sync {
fn contribute<'a>(
&'a self,
thread_store: &'a ExtensionData,
turn_store: &'a ExtensionData,
item: &'a mut TurnItem,
) -> TurnItemContributionFuture<'a>;
}
// TODO: WIP (do not consider)
/// Extension contribution that can claim approval requests for a runtime context.
/// (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,
thread_store: &ExtensionData,
turn_store: &ExtensionData,
) -> bool;
}