feat: add config-change extension contributor (#22488)

## Why

Extensions can observe thread and turn lifecycle events today, but there
was no single host-owned hook for changes to the effective thread
configuration. That makes features that need to react to model,
permission, or tool-suggest updates either depend on individual mutation
paths or risk going stale after runtime config refreshes.

This adds a typed config-change contributor so extension-owned state can
stay synchronized with the effective thread config while the host
remains responsible for deciding when config changed.

## What Changed

- Added `ConfigContributor<C>` to `codex_extension_api`, with
before/after immutable snapshots of the effective config plus
session/thread extension stores.
- Added registry builder/accessor support through `config_contributor`
and `config_contributors`.
- Emits config-change callbacks after committed updates from session
settings, per-turn setting updates, and `refresh_runtime_config`.
- Builds effective config snapshots only when config contributors are
registered, and suppresses no-op callbacks when the before/after
snapshots are equal.
- Added a core session regression test that verifies contributors
observe both model changes and user-layer runtime config changes,
including access to session and thread extension stores.

## Validation

Added `config_change_contributor_observes_effective_config_changes` in
`codex-rs/core/src/session/tests.rs` to cover the new contributor path.
This commit is contained in:
jif-oai
2026-05-13 17:13:34 +02:00
committed by GitHub
Unverified
parent 87de4e3290
commit 34bb85519f
6 changed files with 235 additions and 3 deletions
@@ -67,6 +67,23 @@ pub trait TurnLifecycleContributor: Send + Sync {
fn on_turn_abort(&self, _input: TurnAbortInput<'_>) {}
}
/// Contributor for host-owned configuration changes.
///
/// Implementations should treat the supplied values as immutable before/after
/// snapshots of the effective thread configuration.
pub trait ConfigContributor<C>: Send + Sync {
/// Called after the host commits a changed thread configuration.
fn on_config_changed(
&self,
_session_store: &ExtensionData,
_thread_store: &ExtensionData,
_thread_id: ThreadId,
_previous_config: &C,
_new_config: &C,
) {
}
}
/// Contributor for token usage checkpoints reported by the model provider.
///
/// Implementations should keep this callback cheap. The host calls it after
+1
View File
@@ -15,6 +15,7 @@ pub use codex_tools::ToolSpec;
pub use codex_tools::parse_tool_input_schema;
pub use contributors::ApprovalReviewContributor;
pub use contributors::ApprovalReviewFuture;
pub use contributors::ConfigContributor;
pub use contributors::ContextContributor;
pub use contributors::ExtensionToolExecutor;
pub use contributors::ExtensionToolFuture;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::ApprovalReviewContributor;
use crate::ApprovalReviewFuture;
use crate::ConfigContributor;
use crate::ContextContributor;
use crate::ExtensionData;
use crate::ThreadLifecycleContributor;
@@ -14,6 +15,7 @@ use crate::TurnLifecycleContributor;
pub struct ExtensionRegistryBuilder<C> {
thread_lifecycle_contributors: Vec<Arc<dyn ThreadLifecycleContributor<C>>>,
turn_lifecycle_contributors: Vec<Arc<dyn TurnLifecycleContributor>>,
config_contributors: Vec<Arc<dyn ConfigContributor<C>>>,
token_usage_contributors: Vec<Arc<dyn TokenUsageContributor>>,
context_contributors: Vec<Arc<dyn ContextContributor>>,
tool_contributors: Vec<Arc<dyn ToolContributor>>,
@@ -26,6 +28,7 @@ impl<C> Default for ExtensionRegistryBuilder<C> {
Self {
thread_lifecycle_contributors: Vec::new(),
turn_lifecycle_contributors: Vec::new(),
config_contributors: Vec::new(),
token_usage_contributors: Vec::new(),
approval_review_contributors: Vec::new(),
context_contributors: Vec::new(),
@@ -59,6 +62,11 @@ impl<C> ExtensionRegistryBuilder<C> {
self.turn_lifecycle_contributors.push(contributor);
}
/// Registers one config contributor.
pub fn config_contributor(&mut self, contributor: Arc<dyn ConfigContributor<C>>) {
self.config_contributors.push(contributor);
}
/// Registers one token-usage contributor.
pub fn token_usage_contributor(&mut self, contributor: Arc<dyn TokenUsageContributor>) {
self.token_usage_contributors.push(contributor);
@@ -84,6 +92,7 @@ impl<C> ExtensionRegistryBuilder<C> {
ExtensionRegistry {
thread_lifecycle_contributors: self.thread_lifecycle_contributors,
turn_lifecycle_contributors: self.turn_lifecycle_contributors,
config_contributors: self.config_contributors,
token_usage_contributors: self.token_usage_contributors,
approval_review_contributors: self.approval_review_contributors,
context_contributors: self.context_contributors,
@@ -97,6 +106,7 @@ impl<C> ExtensionRegistryBuilder<C> {
pub struct ExtensionRegistry<C> {
thread_lifecycle_contributors: Vec<Arc<dyn ThreadLifecycleContributor<C>>>,
turn_lifecycle_contributors: Vec<Arc<dyn TurnLifecycleContributor>>,
config_contributors: Vec<Arc<dyn ConfigContributor<C>>>,
token_usage_contributors: Vec<Arc<dyn TokenUsageContributor>>,
context_contributors: Vec<Arc<dyn ContextContributor>>,
tool_contributors: Vec<Arc<dyn ToolContributor>>,
@@ -115,6 +125,11 @@ impl<C> ExtensionRegistry<C> {
&self.turn_lifecycle_contributors
}
/// Returns the registered config contributors.
pub fn config_contributors(&self) -> &[Arc<dyn ConfigContributor<C>>] {
&self.config_contributors
}
/// Returns the registered token-usage contributors.
pub fn token_usage_contributors(&self) -> &[Arc<dyn TokenUsageContributor>] {
&self.token_usage_contributors