Compare commits

...

1 Commits

Author SHA1 Message Date
Channing Conger
eb1b0a18f4 Introduce code-mode backend seam
Route core code-mode execution through a host-owned backend abstraction so a future subprocess client can replace the current in-process runtime without changing turn orchestration.
Keep the runtime crate reusable for direct-link consumers and move local stored_values ownership into core ahead of the process split.
2026-05-06 17:55:57 -07:00
2 changed files with 117 additions and 14 deletions

View File

@@ -0,0 +1,98 @@
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use codex_code_mode::CodeModeTurnHost;
use codex_code_mode::ExecuteRequest;
use codex_code_mode::RuntimeResponse;
use codex_code_mode::WaitOutcome;
use codex_code_mode::WaitRequest;
use serde_json::Value as JsonValue;
/// Host-facing execution boundary for code-mode runtimes.
///
/// Implementations own how JavaScript execution is reached, while callers keep
/// the model-facing host behavior in `codex-core`. The initial implementation is
/// in-process; later implementations can forward the same requests to a child
/// process without changing the call sites that manage turns.
pub(super) trait CodeModeBackend: Send + Sync {
fn allocate_cell_id(&self) -> String;
fn execute(
&self,
request: ExecuteRequest,
) -> Pin<Box<dyn Future<Output = Result<RuntimeResponse, String>> + Send + '_>>;
fn wait(
&self,
request: WaitRequest,
) -> Pin<Box<dyn Future<Output = Result<WaitOutcome, String>> + Send + '_>>;
fn start_turn_worker(&self, host: Arc<dyn CodeModeTurnHost>) -> CodeModeTurnWorker;
}
/// Opaque turn-scoped worker guard returned by code-mode backends.
///
/// The host only needs to keep this guard alive until the turn ends. Hiding the
/// concrete type keeps the public host seam independent of whether the backend
/// is local or process-backed.
pub(crate) struct CodeModeTurnWorker {
_inner: Box<dyn TurnWorkerHandle>,
}
impl CodeModeTurnWorker {
fn new(inner: impl TurnWorkerHandle + 'static) -> Self {
Self {
_inner: Box::new(inner),
}
}
}
trait TurnWorkerHandle: Send {}
impl<T> TurnWorkerHandle for T where T: Send {}
pub(super) struct InProcessCodeModeBackend {
inner: codex_code_mode::CodeModeService,
}
impl InProcessCodeModeBackend {
pub(super) fn new() -> Self {
Self {
inner: codex_code_mode::CodeModeService::new(),
}
}
}
impl Default for InProcessCodeModeBackend {
fn default() -> Self {
Self::new()
}
}
impl CodeModeBackend for InProcessCodeModeBackend {
fn allocate_cell_id(&self) -> String {
self.inner.allocate_cell_id()
}
fn execute(
&self,
request: ExecuteRequest,
) -> Pin<Box<dyn Future<Output = Result<RuntimeResponse, String>> + Send + '_>> {
Box::pin(self.inner.execute(request))
}
fn wait(
&self,
request: WaitRequest,
) -> Pin<Box<dyn Future<Output = Result<WaitOutcome, String>> + Send + '_>> {
Box::pin(self.inner.wait(request))
}
fn start_turn_worker(&self, host: Arc<dyn CodeModeTurnHost>) -> CodeModeTurnWorker {
CodeModeTurnWorker::new(self.inner.start_turn_worker(host))
}
}
pub(super) type StoredValues = HashMap<String, JsonValue>;

View File

@@ -1,3 +1,4 @@
mod backend;
mod execute_handler;
pub(crate) mod execute_spec;
mod response_adapter;
@@ -8,6 +9,10 @@ use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration;
use backend::CodeModeBackend;
use backend::CodeModeTurnWorker;
use backend::InProcessCodeModeBackend;
use backend::StoredValues;
use codex_code_mode::CodeModeNestedToolCall;
use codex_code_mode::CodeModeTurnHost;
use codex_code_mode::RuntimeResponse;
@@ -15,6 +20,7 @@ use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseInputItem;
use serde_json::Value as JsonValue;
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use crate::function_tool::FunctionCallError;
@@ -59,43 +65,42 @@ pub(crate) struct ExecContext {
}
pub(crate) struct CodeModeService {
inner: codex_code_mode::CodeModeService,
backend: Arc<dyn CodeModeBackend>,
stored_values: Mutex<StoredValues>,
}
impl CodeModeService {
pub(crate) fn new() -> Self {
Self {
inner: codex_code_mode::CodeModeService::new(),
backend: Arc::new(InProcessCodeModeBackend::new()),
stored_values: Mutex::new(StoredValues::new()),
}
}
pub(crate) async fn stored_values(&self) -> std::collections::HashMap<String, JsonValue> {
self.inner.stored_values().await
pub(crate) async fn stored_values(&self) -> StoredValues {
self.stored_values.lock().await.clone()
}
pub(crate) async fn replace_stored_values(
&self,
values: std::collections::HashMap<String, JsonValue>,
) {
self.inner.replace_stored_values(values).await;
pub(crate) async fn replace_stored_values(&self, values: StoredValues) {
*self.stored_values.lock().await = values;
}
pub(crate) fn allocate_cell_id(&self) -> String {
self.inner.allocate_cell_id()
self.backend.allocate_cell_id()
}
pub(crate) async fn execute(
&self,
request: codex_code_mode::ExecuteRequest,
) -> Result<RuntimeResponse, String> {
self.inner.execute(request).await
self.backend.execute(request).await
}
pub(crate) async fn wait(
&self,
request: codex_code_mode::WaitRequest,
) -> Result<codex_code_mode::WaitOutcome, String> {
self.inner.wait(request).await
self.backend.wait(request).await
}
pub(crate) async fn start_turn_worker(
@@ -104,7 +109,7 @@ impl CodeModeService {
turn: &Arc<TurnContext>,
router: Arc<ToolRouter>,
tracker: SharedTurnDiffTracker,
) -> Option<codex_code_mode::CodeModeTurnWorker> {
) -> Option<CodeModeTurnWorker> {
if !turn.features.enabled(Feature::CodeMode) {
return None;
}
@@ -116,7 +121,7 @@ impl CodeModeService {
let tool_runtime =
ToolCallRuntime::new(router, Arc::clone(session), Arc::clone(turn), tracker);
let host = Arc::new(CoreTurnHost { exec, tool_runtime });
Some(self.inner.start_turn_worker(host))
Some(self.backend.start_turn_worker(host))
}
}