Run code mode exclusively through the standalone host (#36217)

## What changed

- Move the V8 implementation into a dedicated `codex-code-mode-runtime` crate used by `codex-code-mode-host`, removing the embedded runtime fallback from the Codex process.
- Resolve the host executable from the active installation layout and check its availability before selecting tools.
- Fall back to direct tools with a one-time warning when optional code mode is unavailable. Keep `code_mode_only` and `disable_in_process_fallback` configurations fail-closed.

## Testing

- Cover host discovery for standalone and package layouts, including missing hosts and symlinks.
- Verify direct-tool fallback, one-time warnings, and fail-closed code-mode-only behavior.

GitOrigin-RevId: 5aa3c6f1db148b2231fc24089a2ee0e2b00dbddb
This commit is contained in:
Channing Conger
2026-07-30 20:17:55 +00:00
committed by copyberry
parent acd540f158
commit 97576b1794
51 changed files with 627 additions and 424 deletions

View File

@@ -0,0 +1,313 @@
mod types;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use serde_json::Value as JsonValue;
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
pub(crate) use self::types::CellEvent;
pub(crate) use self::types::CellId;
pub(crate) use self::types::CreateCellRequest;
pub(crate) use self::types::Error;
pub(crate) use self::types::ImageDetail;
pub(crate) use self::types::NestedToolCall;
pub(crate) use self::types::ObserveMode;
pub(crate) use self::types::OutputItem;
pub(crate) use self::types::SessionRuntimeDelegate;
pub(crate) use self::types::ToolDefinition;
pub(crate) use self::types::ToolKind;
pub(crate) use self::types::ToolName;
use crate::TaskFailureHandler;
use crate::cell_actor::CellActor;
use crate::cell_actor::CellError;
use crate::cell_actor::CellEventFuture;
use crate::cell_actor::CellHandle;
use crate::cell_actor::CellHost;
use crate::cell_actor::CellState;
use crate::cell_actor::CellToolCall;
use crate::cell_actor::CompletionCommit;
type RuntimeEventFuture = Pin<Box<dyn Future<Output = Result<CellEvent, Error>> + Send + 'static>>;
/// Owns all cells and shared state for one transport-neutral code-mode session.
pub(crate) struct SessionRuntime<D: SessionRuntimeDelegate> {
inner: Arc<Inner<D>>,
}
struct Inner<D: SessionRuntimeDelegate> {
stored_values: Mutex<HashMap<String, JsonValue>>,
cells: Mutex<HashMap<CellId, CellHandle>>,
cell_tasks: TaskTracker,
shutdown_token: CancellationToken,
delegate: Arc<D>,
task_failure_handler: Option<TaskFailureHandler>,
next_cell_id: AtomicU64,
}
impl<D: SessionRuntimeDelegate> SessionRuntime<D> {
pub(crate) fn new(delegate: Arc<D>) -> Self {
Self::new_with_task_failure_handler(delegate, /*task_failure_handler*/ None)
}
pub(crate) fn new_with_task_failure_handler(
delegate: Arc<D>,
task_failure_handler: Option<TaskFailureHandler>,
) -> Self {
Self {
inner: Arc::new(Inner {
stored_values: Mutex::new(HashMap::new()),
cells: Mutex::new(HashMap::new()),
cell_tasks: TaskTracker::new(),
shutdown_token: CancellationToken::new(),
delegate,
task_failure_handler,
next_cell_id: AtomicU64::new(1),
}),
}
}
pub(crate) async fn execute(
&self,
request: CreateCellRequest,
initial_observe_mode: ObserveMode,
) -> Result<StartedCell, Error> {
if self.inner.shutdown_token.is_cancelled() {
return Err(Error::ShuttingDown);
}
let cell_id = self.allocate_cell_id()?;
let initial_event = self
.start_cell(cell_id.clone(), request, initial_observe_mode)
.await?;
Ok(StartedCell {
cell_id,
initial_event,
})
}
pub(crate) async fn observe(
&self,
cell_id: &CellId,
mode: ObserveMode,
) -> Result<CellEvent, Error> {
self.begin_observe(cell_id, mode).await?.event().await
}
pub(crate) async fn begin_observe(
&self,
cell_id: &CellId,
mode: ObserveMode,
) -> Result<PendingEvent, Error> {
let handle = self
.inner
.cells
.lock()
.await
.get(cell_id)
.cloned()
.ok_or_else(|| Error::MissingCell(cell_id.clone()))?;
Ok(PendingEvent {
event: map_actor_event(cell_id.clone(), handle.observe(mode)),
})
}
pub(crate) async fn terminate(&self, cell_id: &CellId) -> Result<CellEvent, Error> {
let handle = self
.inner
.cells
.lock()
.await
.get(cell_id)
.cloned()
.ok_or_else(|| Error::MissingCell(cell_id.clone()))?;
handle
.terminate()
.await
.map_err(|error| actor_error(cell_id, error))
}
pub(crate) async fn shutdown(&self) -> Result<(), Error> {
self.begin_shutdown();
// Taking the registry lock ensures every cell that passed the shutdown
// check has registered its actor with the tracker before we wait.
let cells = self.inner.cells.lock().await;
self.inner.cell_tasks.close();
drop(cells);
self.inner.cell_tasks.wait().await;
Ok(())
}
fn allocate_cell_id(&self) -> Result<CellId, Error> {
self.inner
.next_cell_id
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |next_cell_id| {
next_cell_id.checked_add(1)
})
.map(|cell_id| CellId::new(cell_id.to_string()))
.map_err(|_| Error::CellIdSpaceExhausted)
}
async fn start_cell(
&self,
cell_id: CellId,
request: CreateCellRequest,
initial_observe_mode: ObserveMode,
) -> Result<RuntimeEventFuture, Error> {
let stored_values = self.inner.stored_values.lock().await.clone();
let host = Arc::new(RuntimeCellHost {
cell_id: cell_id.clone(),
inner: Arc::clone(&self.inner),
});
let mut cells = self.inner.cells.lock().await;
if self.inner.shutdown_token.is_cancelled() {
return Err(Error::ShuttingDown);
}
if cells.contains_key(&cell_id) {
return Err(Error::DuplicateCell(cell_id));
}
let cell_state = Arc::new(CellState::new(self.inner.shutdown_token.child_token()));
let (handle, initial_event, task) = CellActor::prepare(
request,
stored_values,
host,
initial_observe_mode,
cell_state,
self.inner.task_failure_handler.clone(),
)
.map_err(Error::Runtime)?;
cells.insert(cell_id.clone(), handle);
let task = self.inner.cell_tasks.spawn(task);
if let Some(task_failure_handler) = self.inner.task_failure_handler.clone() {
let failed_cell_id = cell_id.clone();
let _failure_watcher = self.inner.cell_tasks.spawn(async move {
if let Err(err) = task.await {
task_failure_handler(format!(
"code-mode cell {failed_cell_id} task failed: {err}"
));
}
});
}
drop(cells);
Ok(map_actor_event(cell_id, initial_event))
}
fn begin_shutdown(&self) {
self.inner.shutdown_token.cancel();
self.inner.cell_tasks.close();
}
}
impl<D: SessionRuntimeDelegate> Drop for SessionRuntime<D> {
fn drop(&mut self) {
self.begin_shutdown();
}
}
/// A cell admitted by [`SessionRuntime::execute`].
pub(crate) struct StartedCell {
pub(crate) cell_id: CellId,
initial_event: RuntimeEventFuture,
}
impl StartedCell {
pub(crate) async fn initial_event(self) -> Result<CellEvent, Error> {
self.initial_event.await
}
}
/// An admitted observation that has not reached its requested frontier yet.
pub(crate) struct PendingEvent {
event: RuntimeEventFuture,
}
impl PendingEvent {
pub(crate) async fn event(self) -> Result<CellEvent, Error> {
self.event.await
}
}
struct RuntimeCellHost<D: SessionRuntimeDelegate> {
cell_id: CellId,
inner: Arc<Inner<D>>,
}
impl<D: SessionRuntimeDelegate> CellHost for RuntimeCellHost<D> {
async fn invoke_tool(
&self,
invocation: CellToolCall,
cancellation_token: CancellationToken,
) -> Result<JsonValue, String> {
self.inner
.delegate
.invoke_tool(
NestedToolCall {
cell_id: self.cell_id.clone(),
runtime_tool_call_id: invocation.id,
tool_name: invocation.name,
tool_kind: invocation.kind,
input: invocation.input,
},
cancellation_token,
)
.await
}
async fn notify(
&self,
call_id: String,
text: String,
cancellation_token: CancellationToken,
) -> Result<(), String> {
self.inner
.delegate
.notify(call_id, self.cell_id.clone(), text, cancellation_token)
.await
}
async fn commit_completion(
&self,
stored_value_writes: HashMap<String, JsonValue>,
event: CellEvent,
pending_initial_yield_items: Option<Vec<OutputItem>>,
cell_state: Arc<CellState>,
) -> CompletionCommit {
let cancellation_token = cell_state.cancellation_token();
let mut stored_values = tokio::select! {
biased;
_ = cancellation_token.cancelled() => {
return CompletionCommit::Rejected(event);
}
stored_values = self.inner.stored_values.lock() => stored_values,
};
cell_state.commit_completion(event, pending_initial_yield_items, || {
stored_values.extend(stored_value_writes);
})
}
async fn closed(&self) {
self.inner.cells.lock().await.remove(&self.cell_id);
self.inner.delegate.cell_closed(&self.cell_id);
}
}
fn map_actor_event(cell_id: CellId, event: CellEventFuture) -> RuntimeEventFuture {
Box::pin(async move { event.await.map_err(|error| actor_error(&cell_id, error)) })
}
fn actor_error(cell_id: &CellId, error: CellError) -> Error {
match error {
CellError::Busy => Error::BusyObserver(cell_id.clone()),
CellError::AlreadyTerminating => Error::AlreadyTerminating(cell_id.clone()),
CellError::Closed => Error::ClosedCell(cell_id.clone()),
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;

View File

@@ -0,0 +1,265 @@
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use std::task::Waker;
use std::time::Duration;
use pretty_assertions::assert_eq;
use serde_json::Value as JsonValue;
use tokio_util::sync::CancellationToken;
use super::*;
use crate::cell_actor::CompletionCommit;
struct RecordingDelegate;
struct PanickingClosedDelegate;
impl SessionRuntimeDelegate for RecordingDelegate {
async fn invoke_tool(
&self,
_invocation: NestedToolCall,
_cancellation_token: CancellationToken,
) -> Result<JsonValue, String> {
Ok(JsonValue::Null)
}
async fn notify(
&self,
_call_id: String,
_cell_id: CellId,
_text: String,
_cancellation_token: CancellationToken,
) -> Result<(), String> {
Ok(())
}
fn cell_closed(&self, _cell_id: &CellId) {}
}
impl SessionRuntimeDelegate for PanickingClosedDelegate {
async fn invoke_tool(
&self,
_invocation: NestedToolCall,
_cancellation_token: CancellationToken,
) -> Result<JsonValue, String> {
Ok(JsonValue::Null)
}
async fn notify(
&self,
_call_id: String,
_cell_id: CellId,
_text: String,
_cancellation_token: CancellationToken,
) -> Result<(), String> {
Ok(())
}
fn cell_closed(&self, _cell_id: &CellId) {
panic!("cell close panic probe");
}
}
#[tokio::test]
async fn reports_cell_actor_panics_to_the_owner() {
let (failure_tx, mut failure_rx) = tokio::sync::mpsc::unbounded_channel();
let runtime = SessionRuntime::new_with_task_failure_handler(
Arc::new(PanickingClosedDelegate),
Some(Arc::new(move |reason| {
let _ = failure_tx.send(reason);
})),
);
let started = runtime
.execute(
execute_request(r#"text("done");"#),
ObserveMode::YieldAfter(Duration::from_secs(1)),
)
.await
.expect("start cell");
assert_eq!(
started.initial_event().await,
Ok(CellEvent::Completed {
content_items: vec![OutputItem::Text {
text: "done".to_string(),
}],
error_text: None,
})
);
runtime.shutdown().await.expect("shutdown runtime");
let failure = failure_rx
.try_recv()
.expect("shutdown should wait for the cell failure watcher");
assert!(failure.contains("code-mode cell 1 task failed"));
}
#[tokio::test]
async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_load_it() {
let runtime = SessionRuntime::new(Arc::new(RecordingDelegate));
let cell_state = Arc::new(CellState::new(CancellationToken::new()));
let host = RuntimeCellHost {
cell_id: CellId::new("terminating-writer"),
inner: Arc::clone(&runtime.inner),
};
let completion = CellEvent::Completed {
content_items: vec![OutputItem::Text {
text: "uncommitted output".to_string(),
}],
error_text: None,
};
let stored_values = runtime.inner.stored_values.lock().await;
let commit = host.commit_completion(
HashMap::from([(
"candidate".to_string(),
JsonValue::String("lost".to_string()),
)]),
completion.clone(),
/*pending_initial_yield_items*/ None,
Arc::clone(&cell_state),
);
tokio::pin!(commit);
let waker = Waker::noop();
let mut context = Context::from_waker(waker);
assert!(matches!(commit.as_mut().poll(&mut context), Poll::Pending));
let termination = cell_state.request_termination();
drop(stored_values);
assert_eq!(commit.await, CompletionCommit::Rejected(completion));
let terminated = CellEvent::Terminated {
content_items: Vec::new(),
};
assert_eq!(
cell_state.finish_termination(terminated.clone()),
Some(terminated.clone())
);
assert_eq!(termination.await, Ok(terminated));
assert!(
!runtime
.inner
.stored_values
.lock()
.await
.contains_key("candidate")
);
let reader = runtime
.execute(
CreateCellRequest {
tool_call_id: "reader".to_string(),
enabled_tools: Vec::new(),
source: r#"text(String(load("candidate")));"#.to_string(),
},
ObserveMode::YieldAfter(Duration::from_secs(1)),
)
.await
.unwrap();
assert_eq!(
reader.initial_event().await,
Ok(CellEvent::Completed {
content_items: vec![OutputItem::Text {
text: "undefined".to_string(),
}],
error_text: None,
})
);
runtime.shutdown().await.unwrap();
}
fn execute_request(source: &str) -> CreateCellRequest {
CreateCellRequest {
tool_call_id: "call-1".to_string(),
enabled_tools: Vec::new(),
source: source.to_string(),
}
}
#[tokio::test]
async fn cell_id_allocation_fails_before_wrapping() {
let runtime = SessionRuntime::new(Arc::new(RecordingDelegate));
runtime
.inner
.next_cell_id
.store(u64::MAX, Ordering::Relaxed);
assert_eq!(
runtime
.execute(
execute_request(r#"text("unreachable");"#),
ObserveMode::YieldAfter(Duration::from_secs(1)),
)
.await
.err(),
Some(Error::CellIdSpaceExhausted)
);
}
#[tokio::test]
#[expect(
clippy::await_holding_invalid_type,
reason = "test holds the registry lock to force admission ahead of shutdown"
)]
async fn shutdown_rejects_cell_admission_queued_before_the_registry_lock() {
let runtime = Arc::new(SessionRuntime::new(Arc::new(RecordingDelegate)));
let cells = runtime.inner.cells.lock().await;
let execution = runtime.execute(
execute_request("while (true) {}"),
ObserveMode::YieldAfter(Duration::from_millis(/*millis*/ 1)),
);
tokio::pin!(execution);
std::future::poll_fn(|context| match execution.as_mut().poll(context) {
Poll::Pending => Poll::Ready(()),
Poll::Ready(Ok(_)) => panic!("execution completed before the registry lock was released"),
Poll::Ready(Err(error)) => {
panic!("execution failed before the registry lock was released: {error}")
}
})
.await;
let shutdown = runtime.shutdown();
tokio::pin!(shutdown);
std::future::poll_fn(|context| match shutdown.as_mut().poll(context) {
Poll::Pending => Poll::Ready(()),
Poll::Ready(Ok(())) => panic!("shutdown completed before acquiring the registry lock"),
Poll::Ready(Err(error)) => {
panic!("shutdown failed before acquiring the registry lock: {error}")
}
})
.await;
drop(cells);
assert!(matches!(execution.await, Err(Error::ShuttingDown)));
assert_eq!(shutdown.await, Ok(()));
}
#[tokio::test]
async fn drop_terminates_cells_when_the_registry_is_locked() {
let runtime = SessionRuntime::new(Arc::new(RecordingDelegate));
let started = runtime
.execute(
execute_request("while (true) {}"),
ObserveMode::YieldAfter(Duration::from_millis(/*millis*/ 1)),
)
.await
.unwrap();
assert_eq!(started.cell_id, CellId::new("1"));
assert_eq!(
started.initial_event().await,
Ok(CellEvent::Yielded {
content_items: Vec::new(),
})
);
let inner = Arc::clone(&runtime.inner);
let cells = inner.cells.lock().await;
drop(runtime);
drop(cells);
tokio::time::timeout(Duration::from_secs(/*secs*/ 1), inner.cell_tasks.wait())
.await
.unwrap();
assert!(inner.cell_tasks.is_empty());
}

View File

@@ -0,0 +1,179 @@
use std::fmt;
use std::future::Future;
use std::time::Duration;
use serde_json::Value as JsonValue;
use tokio_util::sync::CancellationToken;
/// Identifies one execution cell within a session runtime.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct CellId(String);
impl CellId {
pub(crate) fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for CellId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
/// Selects the next observable frontier for a running cell.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ObserveMode {
YieldAfter(Duration),
PendingFrontier,
}
/// An observable cell lifecycle event.
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum CellEvent {
Yielded {
content_items: Vec<OutputItem>,
},
Pending {
content_items: Vec<OutputItem>,
pending_tool_call_ids: Vec<String>,
},
Completed {
content_items: Vec<OutputItem>,
error_text: Option<String>,
},
Terminated {
content_items: Vec<OutputItem>,
},
}
/// Output emitted by a cell since its preceding observation.
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum OutputItem {
Text {
text: String,
},
Image {
image_url: String,
detail: Option<ImageDetail>,
},
Audio {
audio_url: String,
},
}
/// Requested image fidelity for an output image.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ImageDetail {
Auto,
Low,
High,
Original,
}
/// Transport-neutral input for creating a cell.
///
/// The owning session assigns the cell ID when it admits the request.
pub(crate) struct CreateCellRequest {
pub(crate) tool_call_id: String,
pub(crate) enabled_tools: Vec<ToolDefinition>,
pub(crate) source: String,
}
/// Tool metadata exposed to code running inside a cell.
pub(crate) struct ToolDefinition {
pub(crate) name: String,
pub(crate) tool_name: ToolName,
pub(crate) description: String,
pub(crate) kind: ToolKind,
}
/// A tool name with an optional namespace.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ToolName {
pub(crate) name: String,
pub(crate) namespace: Option<String>,
}
/// The JavaScript calling convention for a tool.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ToolKind {
Function,
Freeform,
}
/// A nested tool request emitted by a running cell.
pub(crate) struct NestedToolCall {
pub(crate) cell_id: CellId,
pub(crate) runtime_tool_call_id: String,
pub(crate) tool_name: ToolName,
pub(crate) tool_kind: ToolKind,
pub(crate) input: Option<JsonValue>,
}
/// Host callbacks used by cells owned by a [`super::SessionRuntime`].
///
/// Implementations must honor cancellation tokens. `cell_closed` is called
/// after the runtime has stopped routing requests to the cell.
pub(crate) trait SessionRuntimeDelegate: Send + Sync + 'static {
fn invoke_tool(
&self,
invocation: NestedToolCall,
cancellation_token: CancellationToken,
) -> impl Future<Output = Result<JsonValue, String>> + Send;
fn notify(
&self,
call_id: String,
cell_id: CellId,
text: String,
cancellation_token: CancellationToken,
) -> impl Future<Output = Result<(), String>> + Send;
fn cell_closed(&self, cell_id: &CellId);
}
/// A failure reported by a session runtime operation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Error {
ShuttingDown,
CellIdSpaceExhausted,
DuplicateCell(CellId),
MissingCell(CellId),
BusyObserver(CellId),
AlreadyTerminating(CellId),
ClosedCell(CellId),
Runtime(String),
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ShuttingDown => formatter.write_str("code mode session is shutting down"),
Self::CellIdSpaceExhausted => {
formatter.write_str("code mode session exhausted its cell ID space")
}
Self::DuplicateCell(cell_id) => write!(formatter, "exec cell {cell_id} already exists"),
Self::MissingCell(cell_id) => write!(formatter, "exec cell {cell_id} not found"),
Self::BusyObserver(cell_id) => {
write!(
formatter,
"exec cell {cell_id} already has an active observer"
)
}
Self::AlreadyTerminating(cell_id) => {
write!(formatter, "exec cell {cell_id} is already terminating")
}
Self::ClosedCell(cell_id) => {
write!(formatter, "exec cell {cell_id} closed unexpectedly")
}
Self::Runtime(error_text) => formatter.write_str(error_text),
}
}
}
impl std::error::Error for Error {}