diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index 498e65c7b..c6a3155b7 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -69,6 +69,8 @@ use codex_analytics::InvocationType; use codex_analytics::TurnResolvedConfigFact; use codex_analytics::build_track_events_context; use codex_async_utils::OrCancelExt; +use codex_extension_api::TurnInputContext; +use codex_extension_api::TurnInputEnvironment; use codex_features::Feature; use codex_git_utils::get_git_repo_root; use codex_git_utils::get_git_repo_root_with_fs; @@ -513,6 +515,9 @@ async fn build_skills_and_plugins( }; let skills_outcome = turn_context.turn_skills.outcome.as_ref(); let connector_slug_counts = build_connector_slug_counts(&available_connectors); + let extension_injection_items = + build_extension_turn_input_items(sess, turn_context, &user_input, cancellation_token) + .await?; let skill_name_counts_lower = build_skill_name_counts(&skills_outcome.skills, &skills_outcome.disabled_paths).1; let mentioned_skills = collect_explicit_skill_mentions( @@ -588,9 +593,57 @@ async fn build_skills_and_plugins( let mut injection_items = skill_items; injection_items.extend(plugin_items); + injection_items.extend(extension_injection_items); Some((injection_items, explicitly_enabled_connectors)) } +async fn build_extension_turn_input_items( + sess: &Arc, + turn_context: &TurnContext, + user_input: &[UserInput], + cancellation_token: &CancellationToken, +) -> Option> { + let contributors = sess.services.extensions.turn_input_contributors().to_vec(); + if contributors.is_empty() { + return Some(Vec::new()); + } + + let environments = turn_context + .environments + .turn_environments + .iter() + .enumerate() + .map(|(index, environment)| TurnInputEnvironment { + environment_id: environment.environment_id.clone(), + cwd: environment.cwd.as_path().to_path_buf(), + is_primary: index == 0, + }) + .collect::>(); + + let input = TurnInputContext { + turn_id: turn_context.sub_id.to_string(), + user_input: user_input.to_vec(), + environments, + }; + + let mut items = Vec::new(); + for contributor in contributors { + let contributed_items = contributor + .contribute( + input.clone(), + &sess.services.session_extension_data, + &sess.services.thread_extension_data, + turn_context.extension_data.as_ref(), + ) + .or_cancel(cancellation_token) + .await + .ok()?; + items.extend(contributed_items); + } + + Some(items) +} + async fn track_turn_resolved_config_analytics( sess: &Session, turn_context: &TurnContext, diff --git a/codex-rs/ext/extension-api/src/contributors.rs b/codex-rs/ext/extension-api/src/contributors.rs index 71007e381..98bc395a2 100644 --- a/codex-rs/ext/extension-api/src/contributors.rs +++ b/codex-rs/ext/extension-api/src/contributors.rs @@ -2,6 +2,7 @@ use std::future::Future; use std::sync::Arc; use codex_protocol::items::TurnItem; +use codex_protocol::models::ResponseItem; use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::TokenUsageInfo; use codex_tools::ToolCall; @@ -12,6 +13,7 @@ use crate::ExtensionData; mod prompt; mod thread_lifecycle; mod tool_lifecycle; +mod turn_input; mod turn_lifecycle; pub use prompt::PromptFragment; @@ -25,6 +27,8 @@ pub use tool_lifecycle::ToolCallSource; pub use tool_lifecycle::ToolFinishInput; pub use tool_lifecycle::ToolLifecycleFuture; pub use tool_lifecycle::ToolStartInput; +pub use turn_input::TurnInputContext; +pub use turn_input::TurnInputEnvironment; pub use turn_lifecycle::TurnAbortInput; pub use turn_lifecycle::TurnErrorInput; pub use turn_lifecycle::TurnStartInput; @@ -85,6 +89,25 @@ pub trait TurnLifecycleContributor: Send + Sync { async fn on_turn_error(&self, _input: TurnErrorInput<'_>) {} } +/// WARNING: DO NOT USE YET +/// Extension contribution that can add turn-local model input. +/// +/// Implementations should resolve only the model-visible input they own and +/// must preserve authority boundaries for external resources. Expensive or +/// host-specific dependencies belong on the extension value installed by the +/// host, not in this input. +#[async_trait::async_trait] +pub trait TurnInputContributor: Send + Sync { + /// Returns additional model input items for one submitted turn. + async fn contribute( + &self, + input: TurnInputContext, + session_store: &ExtensionData, + thread_store: &ExtensionData, + turn_store: &ExtensionData, + ) -> Vec; +} + /// Contributor for host-owned configuration changes. /// /// Implementations should treat the supplied values as immutable before/after diff --git a/codex-rs/ext/extension-api/src/contributors/turn_input.rs b/codex-rs/ext/extension-api/src/contributors/turn_input.rs new file mode 100644 index 000000000..9e4723440 --- /dev/null +++ b/codex-rs/ext/extension-api/src/contributors/turn_input.rs @@ -0,0 +1,25 @@ +use std::path::PathBuf; + +use codex_protocol::user_input::UserInput; + +/// Host-owned turn environment summary visible to turn-input contributors. +#[derive(Debug, Clone)] +pub struct TurnInputEnvironment { + /// Stable host environment id used to route executor-scoped capabilities. + pub environment_id: String, + /// Effective working directory for this turn in the environment. + pub cwd: PathBuf, + /// Whether this is the primary environment for the turn. + pub is_primary: bool, +} + +/// Turn facts supplied before the host records turn-local model input items. +#[derive(Debug, Clone)] +pub struct TurnInputContext { + /// Stable host-owned turn identifier. + pub turn_id: String, + /// User input submitted for this turn. + pub user_input: Vec, + /// Resolved turn environments, in host priority order. + pub environments: Vec, +} diff --git a/codex-rs/ext/extension-api/src/lib.rs b/codex-rs/ext/extension-api/src/lib.rs index 4705581c8..870e331fb 100644 --- a/codex-rs/ext/extension-api/src/lib.rs +++ b/codex-rs/ext/extension-api/src/lib.rs @@ -10,6 +10,7 @@ pub use capabilities::NoopExtensionEventSink; pub use capabilities::NoopResponseItemInjector; pub use capabilities::ResponseItemInjectionFuture; pub use capabilities::ResponseItemInjector; +pub use codex_protocol::models::ResponseItem; pub use codex_tools::ConversationHistory; pub use codex_tools::ExtensionTurnItem; pub use codex_tools::FunctionCallError; @@ -46,6 +47,9 @@ pub use contributors::ToolLifecycleFuture; pub use contributors::ToolStartInput; pub use contributors::TurnAbortInput; pub use contributors::TurnErrorInput; +pub use contributors::TurnInputContext; +pub use contributors::TurnInputContributor; +pub use contributors::TurnInputEnvironment; pub use contributors::TurnItemContributor; pub use contributors::TurnLifecycleContributor; pub use contributors::TurnStartInput; diff --git a/codex-rs/ext/extension-api/src/registry.rs b/codex-rs/ext/extension-api/src/registry.rs index a35972625..084938711 100644 --- a/codex-rs/ext/extension-api/src/registry.rs +++ b/codex-rs/ext/extension-api/src/registry.rs @@ -12,6 +12,7 @@ use crate::ThreadLifecycleContributor; use crate::TokenUsageContributor; use crate::ToolContributor; use crate::ToolLifecycleContributor; +use crate::TurnInputContributor; use crate::TurnItemContributor; use crate::TurnLifecycleContributor; @@ -23,6 +24,7 @@ pub struct ExtensionRegistryBuilder { config_contributors: Vec>>, token_usage_contributors: Vec>, context_contributors: Vec>, + turn_input_contributors: Vec>, tool_contributors: Vec>, tool_lifecycle_contributors: Vec>, turn_item_contributors: Vec>, @@ -39,6 +41,7 @@ impl Default for ExtensionRegistryBuilder { token_usage_contributors: Vec::new(), approval_review_contributors: Vec::new(), context_contributors: Vec::new(), + turn_input_contributors: Vec::new(), tool_contributors: Vec::new(), tool_lifecycle_contributors: Vec::new(), turn_item_contributors: Vec::new(), @@ -98,6 +101,11 @@ impl ExtensionRegistryBuilder { self.context_contributors.push(contributor); } + /// Registers one turn-input contributor. + pub fn turn_input_contributor(&mut self, contributor: Arc) { + self.turn_input_contributors.push(contributor); + } + /// Registers one native tool contributor. pub fn tool_contributor(&mut self, contributor: Arc) { self.tool_contributors.push(contributor); @@ -123,6 +131,7 @@ impl ExtensionRegistryBuilder { token_usage_contributors: self.token_usage_contributors, approval_review_contributors: self.approval_review_contributors, context_contributors: self.context_contributors, + turn_input_contributors: self.turn_input_contributors, tool_contributors: self.tool_contributors, tool_lifecycle_contributors: self.tool_lifecycle_contributors, turn_item_contributors: self.turn_item_contributors, @@ -138,6 +147,7 @@ pub struct ExtensionRegistry { config_contributors: Vec>>, token_usage_contributors: Vec>, context_contributors: Vec>, + turn_input_contributors: Vec>, tool_contributors: Vec>, tool_lifecycle_contributors: Vec>, turn_item_contributors: Vec>, @@ -195,6 +205,11 @@ impl ExtensionRegistry { &self.context_contributors } + /// Returns the registered turn-input contributors. + pub fn turn_input_contributors(&self) -> &[Arc] { + &self.turn_input_contributors + } + /// Returns the registered native tool contributors. pub fn tool_contributors(&self) -> &[Arc] { &self.tool_contributors