diff --git a/codex-rs/code-mode/src/lib.rs b/codex-rs/code-mode/src/lib.rs index 841e568be3..27488d4707 100644 --- a/codex-rs/code-mode/src/lib.rs +++ b/codex-rs/code-mode/src/lib.rs @@ -22,9 +22,11 @@ pub use runtime::DEFAULT_WAIT_YIELD_TIME_MS; pub use runtime::ExecuteRequest; pub use runtime::RuntimeResponse; pub use runtime::WaitRequest; +pub use service::CodeModeRuntime; pub use service::CodeModeService; pub use service::CodeModeTurnHost; pub use service::CodeModeTurnWorker; +pub use service::CodeModeTurnWorkerHandle; pub const PUBLIC_TOOL_NAME: &str = "exec"; pub const WAIT_TOOL_NAME: &str = "wait"; diff --git a/codex-rs/code-mode/src/service.rs b/codex-rs/code-mode/src/service.rs index 5b67dd17b8..3d7d8b63a0 100644 --- a/codex-rs/code-mode/src/service.rs +++ b/codex-rs/code-mode/src/service.rs @@ -34,6 +34,22 @@ pub trait CodeModeTurnHost: Send + Sync { async fn notify(&self, call_id: String, cell_id: String, text: String) -> Result<(), String>; } +#[async_trait] +pub trait CodeModeRuntime: Send + Sync { + async fn stored_values(&self) -> HashMap; + + async fn replace_stored_values(&self, values: HashMap); + + async fn execute(&self, request: ExecuteRequest) -> Result; + + async fn wait(&self, request: WaitRequest) -> Result; + + fn start_turn_worker( + &self, + host: Arc, + ) -> Box; +} + #[derive(Clone)] struct SessionHandle { control_tx: mpsc::UnboundedSender, @@ -219,6 +235,10 @@ pub struct CodeModeTurnWorker { shutdown_tx: Option>, } +pub trait CodeModeTurnWorkerHandle: Send {} + +impl CodeModeTurnWorkerHandle for CodeModeTurnWorker {} + impl Drop for CodeModeTurnWorker { fn drop(&mut self) { if let Some(shutdown_tx) = self.shutdown_tx.take() { @@ -227,6 +247,32 @@ impl Drop for CodeModeTurnWorker { } } +#[async_trait] +impl CodeModeRuntime for CodeModeService { + async fn stored_values(&self) -> HashMap { + CodeModeService::stored_values(self).await + } + + async fn replace_stored_values(&self, values: HashMap) { + CodeModeService::replace_stored_values(self, values).await; + } + + async fn execute(&self, request: ExecuteRequest) -> Result { + CodeModeService::execute(self, request).await + } + + async fn wait(&self, request: WaitRequest) -> Result { + CodeModeService::wait(self, request).await + } + + fn start_turn_worker( + &self, + host: Arc, + ) -> Box { + Box::new(CodeModeService::start_turn_worker(self, host)) + } +} + enum SessionControlCommand { Poll { yield_time_ms: u64, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 7137bf4d3a..a26e2e50e7 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -422,6 +422,7 @@ pub(crate) struct CodexSpawnArgs { pub(crate) inherited_shell_snapshot: Option>, pub(crate) inherited_exec_policy: Option>, pub(crate) user_shell_override: Option, + pub(crate) code_mode_runtime: Option>, pub(crate) parent_trace: Option, } @@ -476,6 +477,7 @@ impl Codex { inherited_shell_snapshot, user_shell_override, inherited_exec_policy, + code_mode_runtime, parent_trace: _, } = args; let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); @@ -660,6 +662,7 @@ impl Codex { mcp_manager.clone(), skills_watcher, agent_control, + code_mode_runtime, ) .await .map_err(|e| { @@ -1478,6 +1481,7 @@ impl Session { mcp_manager: Arc, skills_watcher: Arc, agent_control: AgentControl, + code_mode_runtime: Option>, ) -> anyhow::Result> { debug!( "Configuring session: model={}; provider={:?}", @@ -1903,8 +1907,9 @@ impl Session { config.features.enabled(Feature::RuntimeMetrics), Self::build_model_client_beta_features_header(config.as_ref()), ), - code_mode_service: crate::tools::code_mode::CodeModeService::new( - config.js_repl_node_path.clone(), + code_mode_service: code_mode_runtime.map_or_else( + || crate::tools::code_mode::CodeModeService::new(config.js_repl_node_path.clone()), + crate::tools::code_mode::CodeModeService::from_runtime, ), environment: environment_manager.current().await?, }; diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 3f1508cca1..453f59c322 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -93,6 +93,7 @@ pub(crate) async fn run_codex_thread_interactive( inherited_shell_snapshot: None, user_shell_override: None, inherited_exec_policy: Some(Arc::clone(&parent_session.services.exec_policy)), + code_mode_runtime: None, parent_trace: None, }) .await?; diff --git a/codex-rs/core/src/codex_tests_guardian.rs b/codex-rs/core/src/codex_tests_guardian.rs index f5698d58ff..d90a9443d1 100644 --- a/codex-rs/core/src/codex_tests_guardian.rs +++ b/codex-rs/core/src/codex_tests_guardian.rs @@ -456,6 +456,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() { inherited_shell_snapshot: None, inherited_exec_policy: Some(Arc::new(parent_exec_policy)), user_shell_override: None, + code_mode_runtime: None, parent_trace: None, }) .await diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index cc2861ccdd..40af3a032a 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -201,6 +201,7 @@ pub use client_common::Prompt; pub use client_common::REVIEW_PROMPT; pub use client_common::ResponseEvent; pub use client_common::ResponseStream; +pub use codex_code_mode::CodeModeRuntime; pub use codex_sandboxing::get_platform_sandbox; pub use codex_tools::parse_tool_input_schema; pub use compact::content_items_to_text; diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 7f272f9595..9e2131deda 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -447,6 +447,27 @@ impl ThreadManager { persist_extended_history, metrics_service_name, parent_trace, + /*code_mode_runtime*/ None, + /*user_shell_override*/ None, + )) + .await + } + + pub async fn start_thread_with_code_mode_runtime( + &self, + config: Config, + code_mode_runtime: Arc, + ) -> CodexResult { + Box::pin(self.state.spawn_thread( + config, + InitialHistory::New, + Arc::clone(&self.state.auth_manager), + self.agent_control(), + Vec::new(), + /*persist_extended_history*/ false, + /*metrics_service_name*/ None, + /*parent_trace*/ None, + Some(code_mode_runtime), /*user_shell_override*/ None, )) .await @@ -487,6 +508,7 @@ impl ThreadManager { persist_extended_history, /*metrics_service_name*/ None, parent_trace, + /*code_mode_runtime*/ None, /*user_shell_override*/ None, )) .await @@ -506,6 +528,7 @@ impl ThreadManager { /*persist_extended_history*/ false, /*metrics_service_name*/ None, /*parent_trace*/ None, + /*code_mode_runtime*/ None, /*user_shell_override*/ Some(user_shell_override), )) .await @@ -528,6 +551,7 @@ impl ThreadManager { /*persist_extended_history*/ false, /*metrics_service_name*/ None, /*parent_trace*/ None, + /*code_mode_runtime*/ None, /*user_shell_override*/ Some(user_shell_override), )) .await @@ -635,6 +659,7 @@ impl ThreadManager { persist_extended_history, /*metrics_service_name*/ None, parent_trace, + /*code_mode_runtime*/ None, /*user_shell_override*/ None, )) .await @@ -736,6 +761,7 @@ impl ThreadManagerState { inherited_shell_snapshot, inherited_exec_policy, /*parent_trace*/ None, + /*code_mode_runtime*/ None, /*user_shell_override*/ None, )) .await @@ -763,6 +789,7 @@ impl ThreadManagerState { inherited_shell_snapshot, inherited_exec_policy, /*parent_trace*/ None, + /*code_mode_runtime*/ None, /*user_shell_override*/ None, )) .await @@ -791,6 +818,7 @@ impl ThreadManagerState { inherited_shell_snapshot, inherited_exec_policy, /*parent_trace*/ None, + /*code_mode_runtime*/ None, /*user_shell_override*/ None, )) .await @@ -808,6 +836,7 @@ impl ThreadManagerState { persist_extended_history: bool, metrics_service_name: Option, parent_trace: Option, + code_mode_runtime: Option>, user_shell_override: Option, ) -> CodexResult { Box::pin(self.spawn_thread_with_source( @@ -822,6 +851,7 @@ impl ThreadManagerState { /*inherited_shell_snapshot*/ None, /*inherited_exec_policy*/ None, parent_trace, + code_mode_runtime, user_shell_override, )) .await @@ -841,6 +871,7 @@ impl ThreadManagerState { inherited_shell_snapshot: Option>, inherited_exec_policy: Option>, parent_trace: Option, + code_mode_runtime: Option>, user_shell_override: Option, ) -> CodexResult { let watch_registration = self.skills_watcher.register_config( @@ -868,6 +899,7 @@ impl ThreadManagerState { inherited_shell_snapshot, inherited_exec_policy, user_shell_override, + code_mode_runtime, parent_trace, }) .await?; diff --git a/codex-rs/core/src/tools/code_mode/mod.rs b/codex-rs/core/src/tools/code_mode/mod.rs index c8f16ca602..ec896d2828 100644 --- a/codex-rs/core/src/tools/code_mode/mod.rs +++ b/codex-rs/core/src/tools/code_mode/mod.rs @@ -6,7 +6,9 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; +use codex_code_mode::CodeModeRuntime; use codex_code_mode::CodeModeTurnHost; +use codex_code_mode::CodeModeTurnWorkerHandle; use codex_code_mode::RuntimeResponse; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::FunctionCallOutputPayload; @@ -48,14 +50,16 @@ pub(crate) struct ExecContext { } pub(crate) struct CodeModeService { - inner: codex_code_mode::CodeModeService, + inner: Arc, } impl CodeModeService { pub(crate) fn new(_js_repl_node_path: Option) -> Self { - Self { - inner: codex_code_mode::CodeModeService::new(), - } + Self::from_runtime(Arc::new(codex_code_mode::CodeModeService::new())) + } + + pub(crate) fn from_runtime(inner: Arc) -> Self { + Self { inner } } pub(crate) async fn stored_values(&self) -> std::collections::HashMap { @@ -89,7 +93,7 @@ impl CodeModeService { turn: &Arc, router: Arc, tracker: SharedTurnDiffTracker, - ) -> Option { + ) -> Option> { if !turn.features.enabled(Feature::CodeMode) { return None; }