From 2405c4002643efed9db402fd2ebe6ddf28fc7ecd Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Sun, 27 Jul 2025 20:01:35 -0700 Subject: [PATCH] chore: update Codex::spawn() to return a struct instead of a tuple (#1677) Also update `init_codex()` to return a `struct` instead of a tuple, as well. --- codex-rs/cli/src/proto.rs | 3 ++- codex-rs/core/src/codex.rs | 21 ++++++++++++---- codex-rs/core/src/codex_wrapper.rs | 25 +++++++++++++++++--- codex-rs/core/src/lib.rs | 1 + codex-rs/core/tests/client.rs | 5 ++-- codex-rs/core/tests/live_agent.rs | 3 ++- codex-rs/core/tests/stream_no_completed.rs | 3 ++- codex-rs/exec/src/lib.rs | 12 +++++++--- codex-rs/mcp-server/src/codex_tool_runner.rs | 12 ++++++++-- codex-rs/tui/src/chatwidget.rs | 24 +++++++++++-------- 10 files changed, 81 insertions(+), 28 deletions(-) diff --git a/codex-rs/cli/src/proto.rs b/codex-rs/cli/src/proto.rs index ec395dd1..64b292d5 100644 --- a/codex-rs/cli/src/proto.rs +++ b/codex-rs/cli/src/proto.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use clap::Parser; use codex_common::CliConfigOverrides; use codex_core::Codex; +use codex_core::CodexSpawnOk; use codex_core::config::Config; use codex_core::config::ConfigOverrides; use codex_core::protocol::Submission; @@ -35,7 +36,7 @@ pub async fn run_main(opts: ProtoCli) -> anyhow::Result<()> { let config = Config::load_with_cli_overrides(overrides_vec, ConfigOverrides::default())?; let ctrl_c = notify_on_sigint(); - let (codex, _init_id, _session_id) = Codex::spawn(config, ctrl_c.clone()).await?; + let CodexSpawnOk { codex, .. } = Codex::spawn(config, ctrl_c.clone()).await?; let codex = Arc::new(codex); // Task that reads JSON lines from stdin and forwards to Submission Queue diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index d9d40a8a..5764440e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -97,11 +97,18 @@ pub struct Codex { rx_event: Receiver, } +/// Wrapper returned by [`Codex::spawn`] containing the spawned [`Codex`], +/// the submission id for the initial `ConfigureSession` request and the +/// unique session id. +pub struct CodexSpawnOk { + pub codex: Codex, + pub init_id: String, + pub session_id: Uuid, +} + impl Codex { - /// Spawn a new [`Codex`] and initialize the session. Returns the instance - /// of `Codex` and the ID of the `SessionInitialized` event that was - /// submitted to start the session. - pub async fn spawn(config: Config, ctrl_c: Arc) -> CodexResult<(Codex, String, Uuid)> { + /// Spawn a new [`Codex`] and initialize the session. + pub async fn spawn(config: Config, ctrl_c: Arc) -> CodexResult { // experimental resume path (undocumented) let resume_path = config.experimental_resume.clone(); info!("resume_path: {resume_path:?}"); @@ -139,7 +146,11 @@ impl Codex { }; let init_id = codex.submit(configure_session).await?; - Ok((codex, init_id, session_id)) + Ok(CodexSpawnOk { + codex, + init_id, + session_id, + }) } /// Submit the `op` wrapped in a `Submission` with a unique ID. diff --git a/codex-rs/core/src/codex_wrapper.rs b/codex-rs/core/src/codex_wrapper.rs index 31f8295e..b8057929 100644 --- a/codex-rs/core/src/codex_wrapper.rs +++ b/codex-rs/core/src/codex_wrapper.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use crate::Codex; +use crate::CodexSpawnOk; use crate::config::Config; use crate::protocol::Event; use crate::protocol::EventMsg; @@ -8,14 +9,27 @@ use crate::util::notify_on_sigint; use tokio::sync::Notify; use uuid::Uuid; +/// Represents an active Codex conversation, including the first event +/// (which is [`EventMsg::SessionConfigured`]). +pub struct CodexConversation { + pub codex: Codex, + pub session_id: Uuid, + pub session_configured: Event, + pub ctrl_c: Arc, +} + /// Spawn a new [`Codex`] and initialize the session. /// /// Returns the wrapped [`Codex`] **and** the `SessionInitialized` event that /// is received as a response to the initial `ConfigureSession` submission so /// that callers can surface the information to the UI. -pub async fn init_codex(config: Config) -> anyhow::Result<(Codex, Event, Arc, Uuid)> { +pub async fn init_codex(config: Config) -> anyhow::Result { let ctrl_c = notify_on_sigint(); - let (codex, init_id, session_id) = Codex::spawn(config, ctrl_c.clone()).await?; + let CodexSpawnOk { + codex, + init_id, + session_id, + } = Codex::spawn(config, ctrl_c.clone()).await?; // The first event must be `SessionInitialized`. Validate and forward it to // the caller so that they can display it in the conversation history. @@ -34,5 +48,10 @@ pub async fn init_codex(config: Config) -> anyhow::Result<(Codex, Event, Arc Result { let mut config = load_default_config_for_test(&codex_home); config.model_provider.request_max_retries = Some(2); config.model_provider.stream_max_retries = Some(2); - let (agent, _init_id, _session_id) = + let CodexSpawnOk { codex: agent, .. } = Codex::spawn(config, std::sync::Arc::new(Notify::new())).await?; Ok(agent) diff --git a/codex-rs/core/tests/stream_no_completed.rs b/codex-rs/core/tests/stream_no_completed.rs index 153330bf..8e5d83a0 100644 --- a/codex-rs/core/tests/stream_no_completed.rs +++ b/codex-rs/core/tests/stream_no_completed.rs @@ -4,6 +4,7 @@ use std::time::Duration; use codex_core::Codex; +use codex_core::CodexSpawnOk; use codex_core::ModelProviderInfo; use codex_core::exec::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; use codex_core::protocol::EventMsg; @@ -94,7 +95,7 @@ async fn retries_on_early_close() { let codex_home = TempDir::new().unwrap(); let mut config = load_default_config_for_test(&codex_home); config.model_provider = model_provider; - let (codex, _init_id, _session_id) = Codex::spawn(config, ctrl_c).await.unwrap(); + let CodexSpawnOk { codex, .. } = Codex::spawn(config, ctrl_c).await.unwrap(); codex .submit(Op::UserInput { diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 126e92f5..f966d200 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -9,7 +9,8 @@ use std::path::PathBuf; use std::sync::Arc; pub use cli::Cli; -use codex_core::codex_wrapper; +use codex_core::codex_wrapper::CodexConversation; +use codex_core::codex_wrapper::{self}; use codex_core::config::Config; use codex_core::config::ConfigOverrides; use codex_core::config_types::SandboxMode; @@ -155,9 +156,14 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any .with_writer(std::io::stderr) .try_init(); - let (codex_wrapper, event, ctrl_c, _session_id) = codex_wrapper::init_codex(config).await?; + let CodexConversation { + codex: codex_wrapper, + session_configured, + ctrl_c, + .. + } = codex_wrapper::init_codex(config).await?; let codex = Arc::new(codex_wrapper); - info!("Codex initialized with event: {event:?}"); + info!("Codex initialized with event: {session_configured:?}"); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); { diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index f2cacf6c..22a36b83 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use std::sync::Arc; use codex_core::Codex; +use codex_core::codex_wrapper::CodexConversation; use codex_core::codex_wrapper::init_codex; use codex_core::config::Config as CodexConfig; use codex_core::protocol::AgentMessageEvent; @@ -42,7 +43,12 @@ pub async fn run_codex_tool_session( session_map: Arc>>>, running_requests_id_to_codex_uuid: Arc>>, ) { - let (codex, first_event, _ctrl_c, session_id) = match init_codex(config).await { + let CodexConversation { + codex, + session_configured, + session_id, + .. + } = match init_codex(config).await { Ok(res) => res, Err(e) => { let result = CallToolResult { @@ -66,7 +72,9 @@ pub async fn run_codex_tool_session( drop(session_map); // Send initial SessionConfigured event. - outgoing.send_event_as_notification(&first_event).await; + outgoing + .send_event_as_notification(&session_configured) + .await; // Use the original MCP request ID as the `sub_id` for the Codex submission so that // any events emitted for this tool-call can be correlated with the diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 01285c02..5e839d14 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use std::sync::Arc; +use codex_core::codex_wrapper::CodexConversation; use codex_core::codex_wrapper::init_codex; use codex_core::config::Config; use codex_core::protocol::AgentMessageDeltaEvent; @@ -89,19 +90,22 @@ impl ChatWidget<'_> { // Create the Codex asynchronously so the UI loads as quickly as possible. let config_for_agent_loop = config.clone(); tokio::spawn(async move { - let (codex, session_event, _ctrl_c, _session_id) = - match init_codex(config_for_agent_loop).await { - Ok(vals) => vals, - Err(e) => { - // TODO: surface this error to the user. - tracing::error!("failed to initialize codex: {e}"); - return; - } - }; + let CodexConversation { + codex, + session_configured, + .. + } = match init_codex(config_for_agent_loop).await { + Ok(vals) => vals, + Err(e) => { + // TODO: surface this error to the user. + tracing::error!("failed to initialize codex: {e}"); + return; + } + }; // Forward the captured `SessionInitialized` event that was consumed // inside `init_codex()` so it can be rendered in the UI. - app_event_tx_clone.send(AppEvent::CodexEvent(session_event.clone())); + app_event_tx_clone.send(AppEvent::CodexEvent(session_configured.clone())); let codex = Arc::new(codex); let codex_clone = codex.clone(); tokio::spawn(async move {