mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
extension: wire extension registries into sessions (#21737)
## Why [#21736](https://github.com/openai/codex/pull/21736) introduces the typed extension API, but the runtime does not yet carry a registry through thread/session startup or give contributors host-owned stores to read from. This PR wires that host-side path so later feature migrations can move product-specific behavior behind typed contributions without adding another bespoke seam directly to `codex-core`. ## What changed - Thread `ExtensionRegistry<Config>` through `ThreadManager`, `CodexSpawnArgs`, `Session`, and sub-agent spawn paths. - Wire `ThreadStartContributor` and `ContextContributor` - Expose the small supporting surface needed by non-core callers that construct threads directly, including `empty_extension_registry()` through `codex-core-api`. This PR lands the host plumbing only: the app-server registry is still empty, and concrete feature migrations are intended to follow separately.
This commit is contained in:
@@ -53,6 +53,7 @@ use codex_config::types::OAuthCredentialsStoreMode;
|
||||
use codex_exec_server::Environment;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_extension_api::PromptSlot;
|
||||
use codex_features::FEATURES;
|
||||
use codex_features::Feature;
|
||||
use codex_features::unstable_features_warning_event;
|
||||
@@ -392,6 +393,7 @@ pub(crate) struct CodexSpawnArgs {
|
||||
pub(crate) skills_manager: Arc<SkillsManager>,
|
||||
pub(crate) plugins_manager: Arc<PluginsManager>,
|
||||
pub(crate) mcp_manager: Arc<McpManager>,
|
||||
pub(crate) extensions: Arc<codex_extension_api::ExtensionRegistry<crate::config::Config>>,
|
||||
pub(crate) conversation_history: InitialHistory,
|
||||
pub(crate) session_source: SessionSource,
|
||||
pub(crate) thread_source: Option<ThreadSource>,
|
||||
@@ -455,6 +457,7 @@ impl Codex {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
extensions,
|
||||
conversation_history,
|
||||
session_source,
|
||||
thread_source,
|
||||
@@ -650,6 +653,7 @@ impl Codex {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager.clone(),
|
||||
extensions,
|
||||
agent_control,
|
||||
environment_manager,
|
||||
analytics_events_client,
|
||||
@@ -2570,6 +2574,7 @@ impl Session {
|
||||
) -> Vec<ResponseItem> {
|
||||
let mut developer_sections = Vec::<String>::with_capacity(8);
|
||||
let mut contextual_user_sections = Vec::<String>::with_capacity(2);
|
||||
let mut separate_developer_sections = Vec::<String>::new();
|
||||
let (
|
||||
reference_context_item,
|
||||
previous_turn_settings,
|
||||
@@ -2714,6 +2719,24 @@ impl Session {
|
||||
{
|
||||
developer_sections.push(commit_message_instruction);
|
||||
}
|
||||
for contributor in self.services.extensions.context_contributors() {
|
||||
for fragment in contributor.contribute(
|
||||
&self.services.session_extension_data,
|
||||
&self.services.thread_extension_data,
|
||||
) {
|
||||
match fragment.slot() {
|
||||
PromptSlot::DeveloperPolicy | PromptSlot::DeveloperCapabilities => {
|
||||
developer_sections.push(fragment.text().to_string());
|
||||
}
|
||||
PromptSlot::ContextualUser => {
|
||||
contextual_user_sections.push(fragment.text().to_string());
|
||||
}
|
||||
PromptSlot::SeparateDeveloper => {
|
||||
separate_developer_sections.push(fragment.text().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(user_instructions) = turn_context.user_instructions.as_deref() {
|
||||
contextual_user_sections.push(
|
||||
UserInstructions {
|
||||
@@ -2746,6 +2769,13 @@ impl Session {
|
||||
{
|
||||
items.push(developer_message);
|
||||
}
|
||||
for section in separate_developer_sections {
|
||||
if let Some(developer_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(vec![section])
|
||||
{
|
||||
items.push(developer_message);
|
||||
}
|
||||
}
|
||||
if let Some(usage_hint_text) = multi_agent_v2_usage_hint_text
|
||||
&& let Some(usage_hint_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(vec![
|
||||
|
||||
@@ -364,6 +364,7 @@ impl Session {
|
||||
skills_manager: Arc<SkillsManager>,
|
||||
plugins_manager: Arc<PluginsManager>,
|
||||
mcp_manager: Arc<McpManager>,
|
||||
extensions: Arc<codex_extension_api::ExtensionRegistry<crate::config::Config>>,
|
||||
agent_control: AgentControl,
|
||||
environment_manager: Arc<EnvironmentManager>,
|
||||
analytics_events_client: Option<AnalyticsEventsClient>,
|
||||
@@ -810,6 +811,16 @@ impl Session {
|
||||
SessionId::from(thread_id)
|
||||
};
|
||||
let agent_control = agent_control.with_session_id(session_id);
|
||||
let session_extension_data = codex_extension_api::ExtensionData::new();
|
||||
let thread_extension_data = codex_extension_api::ExtensionData::new();
|
||||
for contributor in extensions.thread_start_contributors() {
|
||||
contributor.contribute(
|
||||
config.as_ref(),
|
||||
&session_extension_data,
|
||||
&thread_extension_data,
|
||||
);
|
||||
}
|
||||
|
||||
let services = SessionServices {
|
||||
// Initialize the MCP connection manager with an uninitialized
|
||||
// instance. It will be replaced with one created via
|
||||
@@ -845,6 +856,10 @@ impl Session {
|
||||
skills_manager,
|
||||
plugins_manager: Arc::clone(&plugins_manager),
|
||||
mcp_manager: Arc::clone(&mcp_manager),
|
||||
extensions,
|
||||
// TODO(jif): extract session to share between sub-agents
|
||||
session_extension_data,
|
||||
thread_extension_data,
|
||||
agent_control,
|
||||
network_proxy,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
|
||||
@@ -3725,6 +3725,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
Arc::new(codex_extension_api::ExtensionRegistryBuilder::new().build()),
|
||||
AgentControl::default(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
@@ -3871,6 +3872,9 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
extensions: Arc::new(codex_extension_api::ExtensionRegistryBuilder::new().build()),
|
||||
session_extension_data: codex_extension_api::ExtensionData::new(),
|
||||
thread_extension_data: codex_extension_api::ExtensionData::new(),
|
||||
agent_control,
|
||||
network_proxy: None,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
@@ -4061,6 +4065,7 @@ async fn make_session_with_config_and_rx(
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
Arc::new(codex_extension_api::ExtensionRegistryBuilder::new().build()),
|
||||
AgentControl::default(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
@@ -4163,6 +4168,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
Arc::new(codex_extension_api::ExtensionRegistryBuilder::new().build()),
|
||||
agent_control,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
@@ -5586,6 +5592,9 @@ where
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
extensions: Arc::new(codex_extension_api::ExtensionRegistryBuilder::new().build()),
|
||||
session_extension_data: codex_extension_api::ExtensionData::new(),
|
||||
thread_extension_data: codex_extension_api::ExtensionData::new(),
|
||||
agent_control,
|
||||
network_proxy: None,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
@@ -6133,6 +6142,73 @@ async fn make_multi_agent_v2_usage_hint_test_session(
|
||||
(session, turn_context)
|
||||
}
|
||||
|
||||
struct GitAttributionTestContributor;
|
||||
struct GitAttributionTestState;
|
||||
|
||||
impl codex_extension_api::ContextContributor for GitAttributionTestContributor {
|
||||
fn contribute(
|
||||
&self,
|
||||
_session_store: &codex_extension_api::ExtensionData,
|
||||
thread_store: &codex_extension_api::ExtensionData,
|
||||
) -> Vec<codex_extension_api::PromptFragment> {
|
||||
thread_store
|
||||
.get::<GitAttributionTestState>()
|
||||
.is_some()
|
||||
.then(|| {
|
||||
codex_extension_api::PromptFragment::developer_policy(
|
||||
"git attribution extension enabled",
|
||||
)
|
||||
})
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn git_attribution_test_registry()
|
||||
-> Arc<codex_extension_api::ExtensionRegistry<crate::config::Config>> {
|
||||
let mut builder = codex_extension_api::ExtensionRegistryBuilder::new();
|
||||
builder.prompt_contributor(Arc::new(GitAttributionTestContributor));
|
||||
Arc::new(builder.build())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_initial_context_includes_git_attribution_from_extensions() {
|
||||
let (mut session, turn_context) = make_session_and_context().await;
|
||||
session.services.extensions = git_attribution_test_registry();
|
||||
session
|
||||
.services
|
||||
.thread_extension_data
|
||||
.insert(GitAttributionTestState);
|
||||
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let developer_messages = developer_message_texts(&initial_context);
|
||||
|
||||
assert!(
|
||||
developer_messages
|
||||
.iter()
|
||||
.flatten()
|
||||
.any(|text| *text == "git attribution extension enabled"),
|
||||
"expected git attribution developer text, got {developer_messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_initial_context_omits_git_attribution_when_feature_is_disabled() {
|
||||
let (mut session, turn_context) = make_session_and_context().await;
|
||||
session.services.extensions = git_attribution_test_registry();
|
||||
|
||||
let initial_context = session.build_initial_context(&turn_context).await;
|
||||
let developer_messages = developer_message_texts(&initial_context);
|
||||
|
||||
assert!(
|
||||
!developer_messages
|
||||
.iter()
|
||||
.flatten()
|
||||
.any(|text| *text == "git attribution extension enabled"),
|
||||
"did not expect git attribution developer text, got {developer_messages:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_initial_context_adds_multi_agent_v2_root_usage_hint_as_developer_message() {
|
||||
let (session, turn_context) =
|
||||
|
||||
@@ -742,6 +742,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
mcp_manager,
|
||||
extensions: codex_extension_api::empty_extension_registry(),
|
||||
conversation_history: InitialHistory::New,
|
||||
session_source: SessionSource::SubAgent(SubAgentSource::Other(
|
||||
GUARDIAN_REVIEWER_NAME.to_string(),
|
||||
|
||||
@@ -52,11 +52,11 @@ impl TurnEnvironment {
|
||||
|
||||
/// The context needed for a single turn of the thread.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TurnContext {
|
||||
pub struct TurnContext {
|
||||
pub(crate) sub_id: String,
|
||||
pub(crate) trace_id: Option<String>,
|
||||
pub(crate) realtime_active: bool,
|
||||
pub(crate) config: Arc<Config>,
|
||||
pub config: Arc<Config>,
|
||||
pub(crate) auth_manager: Option<Arc<AuthManager>>,
|
||||
pub(crate) model_info: ModelInfo,
|
||||
pub(crate) session_telemetry: SessionTelemetry,
|
||||
@@ -84,7 +84,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
|
||||
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
|
||||
pub(crate) tools_config: ToolsConfig,
|
||||
pub(crate) features: ManagedFeatures,
|
||||
pub features: ManagedFeatures,
|
||||
pub(crate) ghost_snapshot: GhostSnapshotConfig,
|
||||
pub(crate) final_output_json_schema: Option<Value>,
|
||||
pub(crate) codex_self_exe: Option<PathBuf>,
|
||||
|
||||
Reference in New Issue
Block a user