mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add turn-scoped context contributions (#28911)
## Summary - keep context injection on a single ContextContributor trait - split context injection into thread-scoped and turn-scoped contribution methods - wire turn-scoped fragments into initial context assembly so extensions can contribute context from turn-local state
This commit is contained in:
@@ -74,7 +74,11 @@ async fn contribute_prompt(
|
||||
) -> Vec<codex_extension_api::PromptFragment> {
|
||||
let mut fragments = Vec::new();
|
||||
for contributor in registry.context_contributors() {
|
||||
fragments.extend(contributor.contribute(session_store, thread_store).await);
|
||||
fragments.extend(
|
||||
contributor
|
||||
.contribute_thread_context(session_store, thread_store)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
fragments
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn install(registry: &mut ExtensionRegistryBuilder<()>) {
|
||||
struct StyleContributor;
|
||||
|
||||
impl ContextContributor for StyleContributor {
|
||||
fn contribute<'a>(
|
||||
fn contribute_thread_context<'a>(
|
||||
&'a self,
|
||||
session_store: &'a ExtensionData,
|
||||
thread_store: &'a ExtensionData,
|
||||
@@ -37,7 +37,7 @@ impl ContextContributor for StyleContributor {
|
||||
struct UsageContributor;
|
||||
|
||||
impl ContextContributor for UsageContributor {
|
||||
fn contribute<'a>(
|
||||
fn contribute_thread_context<'a>(
|
||||
&'a self,
|
||||
session_store: &'a ExtensionData,
|
||||
thread_store: &'a ExtensionData,
|
||||
|
||||
@@ -11,6 +11,7 @@ use codex_tools::ToolExecutor;
|
||||
|
||||
use crate::ExtensionData;
|
||||
|
||||
mod context;
|
||||
mod mcp;
|
||||
mod prompt;
|
||||
mod thread_lifecycle;
|
||||
@@ -18,6 +19,7 @@ mod tool_lifecycle;
|
||||
mod turn_input;
|
||||
mod turn_lifecycle;
|
||||
|
||||
pub use context::TurnContextContributionInput;
|
||||
pub use mcp::McpServerContribution;
|
||||
pub use mcp::McpServerContributionContext;
|
||||
pub use prompt::PromptFragment;
|
||||
@@ -62,12 +64,34 @@ pub trait McpServerContributor<C: Sync>: Send + Sync {
|
||||
}
|
||||
|
||||
/// Extension contribution that adds prompt fragments during prompt assembly.
|
||||
///
|
||||
/// Implementations should use the method matching the scope needed by the
|
||||
/// fragment: thread/session context for stable inputs, and turn context for
|
||||
/// fragments that depend on turn-local host state.
|
||||
pub trait ContextContributor: Send + Sync {
|
||||
fn contribute<'a>(
|
||||
fn contribute_thread_context<'a>(
|
||||
&'a self,
|
||||
session_store: &'a ExtensionData,
|
||||
thread_store: &'a ExtensionData,
|
||||
) -> ExtensionFuture<'a, Vec<PromptFragment>>;
|
||||
) -> ExtensionFuture<'a, Vec<PromptFragment>> {
|
||||
Box::pin(async move {
|
||||
let _self = self;
|
||||
let _session_store = session_store;
|
||||
let _thread_store = thread_store;
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
|
||||
fn contribute_turn_context<'a>(
|
||||
&'a self,
|
||||
input: TurnContextContributionInput<'a>,
|
||||
) -> ExtensionFuture<'a, Vec<PromptFragment>> {
|
||||
Box::pin(async move {
|
||||
let _self = self;
|
||||
let _input = input;
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Contributor for host-owned thread lifecycle gates.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use codex_protocol::ThreadId;
|
||||
|
||||
use crate::ExtensionData;
|
||||
|
||||
/// Host context available while extensions contribute turn-scoped context fragments.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TurnContextContributionInput<'a> {
|
||||
/// Stable host-owned thread identifier.
|
||||
pub thread_id: ThreadId,
|
||||
/// Stable host-owned turn identifier.
|
||||
pub turn_id: &'a str,
|
||||
/// 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.
|
||||
pub turn_store: &'a ExtensionData,
|
||||
/// Effective model context window for this turn, when known.
|
||||
pub model_context_window: Option<i64>,
|
||||
}
|
||||
@@ -54,6 +54,7 @@ pub use contributors::ToolLifecycleContributor;
|
||||
pub use contributors::ToolLifecycleFuture;
|
||||
pub use contributors::ToolStartInput;
|
||||
pub use contributors::TurnAbortInput;
|
||||
pub use contributors::TurnContextContributionInput;
|
||||
pub use contributors::TurnErrorInput;
|
||||
pub use contributors::TurnInputContext;
|
||||
pub use contributors::TurnInputContributor;
|
||||
|
||||
@@ -12,12 +12,14 @@ use codex_extension_api::ExtensionEventSink;
|
||||
use codex_extension_api::ExtensionFuture;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::PromptFragment;
|
||||
use codex_extension_api::PromptSlot;
|
||||
use codex_extension_api::ThreadLifecycleContributor;
|
||||
use codex_extension_api::TokenUsageContributor;
|
||||
use codex_extension_api::ToolCall;
|
||||
use codex_extension_api::ToolContributor;
|
||||
use codex_extension_api::ToolExecutor;
|
||||
use codex_extension_api::ToolLifecycleContributor;
|
||||
use codex_extension_api::TurnContextContributionInput;
|
||||
use codex_extension_api::TurnInputContext;
|
||||
use codex_extension_api::TurnInputContributor;
|
||||
use codex_extension_api::TurnItemContributor;
|
||||
@@ -34,7 +36,7 @@ use pretty_assertions::assert_eq;
|
||||
struct AllContributors;
|
||||
|
||||
impl ContextContributor for AllContributors {
|
||||
fn contribute<'a>(
|
||||
fn contribute_thread_context<'a>(
|
||||
&'a self,
|
||||
_session_store: &'a ExtensionData,
|
||||
_thread_store: &'a ExtensionData,
|
||||
@@ -147,7 +149,7 @@ async fn build_round_trips_every_contributor_category() {
|
||||
struct NamedContextContributor(&'static str);
|
||||
|
||||
impl ContextContributor for NamedContextContributor {
|
||||
fn contribute<'a>(
|
||||
fn contribute_thread_context<'a>(
|
||||
&'a self,
|
||||
_session_store: &'a ExtensionData,
|
||||
_thread_store: &'a ExtensionData,
|
||||
@@ -158,6 +160,20 @@ impl ContextContributor for NamedContextContributor {
|
||||
}
|
||||
}
|
||||
|
||||
struct NamedTurnContextContributor(&'static str);
|
||||
|
||||
impl ContextContributor for NamedTurnContextContributor {
|
||||
fn contribute_turn_context<'a>(
|
||||
&'a self,
|
||||
_input: TurnContextContributionInput<'a>,
|
||||
) -> ExtensionFuture<'a, Vec<PromptFragment>> {
|
||||
Box::pin(std::future::ready(vec![PromptFragment::new(
|
||||
PromptSlot::ContextualUser,
|
||||
self.0,
|
||||
)]))
|
||||
}
|
||||
}
|
||||
|
||||
struct RecordingTurnItemContributor {
|
||||
name: &'static str,
|
||||
calls: Arc<Mutex<Vec<&'static str>>>,
|
||||
@@ -186,6 +202,8 @@ async fn contributors_preserve_registration_order() {
|
||||
let mut builder = ExtensionRegistryBuilder::<()>::new();
|
||||
builder.prompt_contributor(Arc::new(NamedContextContributor("first")));
|
||||
builder.prompt_contributor(Arc::new(NamedContextContributor("second")));
|
||||
builder.prompt_contributor(Arc::new(NamedTurnContextContributor("turn-first")));
|
||||
builder.prompt_contributor(Arc::new(NamedTurnContextContributor("turn-second")));
|
||||
for name in ["first", "second"] {
|
||||
builder.turn_item_contributor(Arc::new(RecordingTurnItemContributor {
|
||||
name,
|
||||
@@ -199,7 +217,25 @@ async fn contributors_preserve_registration_order() {
|
||||
|
||||
let mut fragments = Vec::new();
|
||||
for contributor in registry.context_contributors() {
|
||||
fragments.extend(contributor.contribute(&session_store, &thread_store).await);
|
||||
fragments.extend(
|
||||
contributor
|
||||
.contribute_thread_context(&session_store, &thread_store)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
for contributor in registry.context_contributors() {
|
||||
fragments.extend(
|
||||
contributor
|
||||
.contribute_turn_context(TurnContextContributionInput {
|
||||
thread_id: codex_protocol::ThreadId::default(),
|
||||
turn_id: turn_store.level_id(),
|
||||
session_store: &session_store,
|
||||
thread_store: &thread_store,
|
||||
turn_store: &turn_store,
|
||||
model_context_window: Some(123),
|
||||
})
|
||||
.await,
|
||||
);
|
||||
}
|
||||
let mut item = TurnItem::HookPrompt(HookPromptItem {
|
||||
id: "item".to_string(),
|
||||
@@ -217,6 +253,8 @@ async fn contributors_preserve_registration_order() {
|
||||
vec![
|
||||
PromptFragment::developer_policy("first"),
|
||||
PromptFragment::developer_policy("second"),
|
||||
PromptFragment::new(PromptSlot::ContextualUser, "turn-first"),
|
||||
PromptFragment::new(PromptSlot::ContextualUser, "turn-second"),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -48,7 +48,7 @@ impl MemoriesExtensionConfig {
|
||||
}
|
||||
|
||||
impl ContextContributor for MemoriesExtension {
|
||||
fn contribute<'a>(
|
||||
fn contribute_thread_context<'a>(
|
||||
&'a self,
|
||||
_session_store: &'a ExtensionData,
|
||||
thread_store: &'a ExtensionData,
|
||||
|
||||
@@ -181,7 +181,7 @@ async fn prompt_contribution_uses_memory_summary_when_enabled() {
|
||||
});
|
||||
|
||||
let fragments = extension
|
||||
.contribute(&ExtensionData::new("session"), &thread_store)
|
||||
.contribute_thread_context(&ExtensionData::new("session"), &thread_store)
|
||||
.await;
|
||||
|
||||
assert_eq!(fragments.len(), 1);
|
||||
|
||||
@@ -103,7 +103,7 @@ impl<C> ContextContributor for SkillsExtension<C>
|
||||
where
|
||||
C: Send + Sync + 'static,
|
||||
{
|
||||
fn contribute<'a>(
|
||||
fn contribute_thread_context<'a>(
|
||||
&'a self,
|
||||
session_store: &'a ExtensionData,
|
||||
thread_store: &'a ExtensionData,
|
||||
|
||||
@@ -183,7 +183,7 @@ async fn selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_in
|
||||
.await;
|
||||
|
||||
let prompt_fragments = registry.context_contributors()[0]
|
||||
.contribute(&session_store, &thread_store)
|
||||
.contribute_thread_context(&session_store, &thread_store)
|
||||
.await;
|
||||
assert_eq!(1, prompt_fragments.len());
|
||||
assert!(
|
||||
@@ -228,7 +228,7 @@ async fn selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_in
|
||||
read_request_keys(&read_requests)
|
||||
);
|
||||
let rebuilt_prompt_fragments = registry.context_contributors()[0]
|
||||
.contribute(&session_store, &thread_store)
|
||||
.contribute_thread_context(&session_store, &thread_store)
|
||||
.await;
|
||||
assert_eq!(1, rebuilt_prompt_fragments.len());
|
||||
assert!(rebuilt_prompt_fragments[0].text().contains("lint-fix"));
|
||||
@@ -294,7 +294,7 @@ async fn orchestrator_catalog_snapshot_caches_failure() -> TestResult {
|
||||
.await;
|
||||
|
||||
let initial_fragments = registry.context_contributors()[0]
|
||||
.contribute(&session_store, &thread_store)
|
||||
.contribute_thread_context(&session_store, &thread_store)
|
||||
.await;
|
||||
assert!(initial_fragments.is_empty());
|
||||
let EventMsg::Warning(warning) = event_rx.try_recv()?.msg else {
|
||||
|
||||
Reference in New Issue
Block a user