diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 52c034eb..2050d8e2 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -6,6 +6,7 @@ use std::time::Duration; use crate::AuthManager; use bytes::Bytes; use codex_protocol::mcp_protocol::AuthMode; +use codex_protocol::mcp_protocol::ConversationId; use eventsource_stream::Eventsource; use futures::prelude::*; use regex_lite::Regex; @@ -19,7 +20,6 @@ use tokio_util::io::ReaderStream; use tracing::debug; use tracing::trace; use tracing::warn; -use uuid::Uuid; use crate::chat_completions::AggregateStreamExt; use crate::chat_completions::stream_chat_completions; @@ -70,7 +70,7 @@ pub struct ModelClient { auth_manager: Option>, client: reqwest::Client, provider: ModelProviderInfo, - session_id: Uuid, + conversation_id: ConversationId, effort: ReasoningEffortConfig, summary: ReasoningSummaryConfig, } @@ -82,7 +82,7 @@ impl ModelClient { provider: ModelProviderInfo, effort: ReasoningEffortConfig, summary: ReasoningSummaryConfig, - session_id: Uuid, + conversation_id: ConversationId, ) -> Self { let client = create_client(&config.responses_originator_header); @@ -91,7 +91,7 @@ impl ModelClient { auth_manager, client, provider, - session_id, + conversation_id, effort, summary, } @@ -197,7 +197,7 @@ impl ModelClient { store: false, stream: true, include, - prompt_cache_key: Some(self.session_id.to_string()), + prompt_cache_key: Some(self.conversation_id.to_string()), text, }; @@ -223,7 +223,9 @@ impl ModelClient { req_builder = req_builder .header("OpenAI-Beta", "responses=experimental") - .header("session_id", self.session_id.to_string()) + // Send session_id for compatibility. + .header("conversation_id", self.conversation_id.to_string()) + .header("session_id", self.conversation_id.to_string()) .header(reqwest::header::ACCEPT, "text/event-stream") .json(&payload); diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 95e9a969..639493b4 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -15,6 +15,7 @@ use async_channel::Sender; use codex_apply_patch::ApplyPatchAction; use codex_apply_patch::MaybeApplyPatchVerified; use codex_apply_patch::maybe_parse_apply_patch_verified; +use codex_protocol::mcp_protocol::ConversationId; use codex_protocol::protocol::ConversationHistoryResponseEvent; use codex_protocol::protocol::TaskStartedEvent; use codex_protocol::protocol::TurnAbortReason; @@ -149,7 +150,7 @@ pub struct Codex { /// unique session id. pub struct CodexSpawnOk { pub codex: Codex, - pub session_id: Uuid, + pub conversation_id: ConversationId, } pub(crate) const INITIAL_SUBMIT_ID: &str = ""; @@ -205,7 +206,7 @@ impl Codex { session .record_initial_history(&turn_context, conversation_history) .await; - let session_id = session.session_id; + let conversation_id = session.conversation_id; // This task will run until Op::Shutdown is received. tokio::spawn(submission_loop( @@ -220,7 +221,10 @@ impl Codex { rx_event, }; - Ok(CodexSpawnOk { codex, session_id }) + Ok(CodexSpawnOk { + codex, + conversation_id, + }) } /// Submit the `op` wrapped in a `Submission` with a unique ID. @@ -269,7 +273,7 @@ struct State { /// /// A session has at most 1 running task at a time, and can be interrupted by user input. pub(crate) struct Session { - session_id: Uuid, + conversation_id: ConversationId, tx_event: Sender, /// Manager for external MCP servers/tools. @@ -358,7 +362,7 @@ impl Session { tx_event: Sender, initial_history: InitialHistory, ) -> anyhow::Result<(Arc, TurnContext)> { - let session_id = Uuid::new_v4(); + let conversation_id = ConversationId::from(Uuid::new_v4()); let ConfigureSession { provider, model, @@ -385,7 +389,7 @@ impl Session { // - spin up MCP connection manager // - perform default shell discovery // - load history metadata - let rollout_fut = RolloutRecorder::new(&config, session_id, user_instructions.clone()); + let rollout_fut = RolloutRecorder::new(&config, conversation_id, user_instructions.clone()); let mcp_fut = McpConnectionManager::new(config.mcp_servers.clone()); let default_shell_fut = shell::default_user_shell(); @@ -431,7 +435,7 @@ impl Session { } } - // Now that `session_id` is final (may have been updated by resume), + // Now that the conversation id is final (may have been updated by resume), // construct the model client. let client = ModelClient::new( config.clone(), @@ -439,7 +443,7 @@ impl Session { provider.clone(), model_reasoning_effort, model_reasoning_summary, - session_id, + conversation_id, ); let turn_context = TurnContext { client, @@ -461,7 +465,7 @@ impl Session { cwd, }; let sess = Arc::new(Session { - session_id, + conversation_id, tx_event: tx_event.clone(), mcp_connection_manager, session_manager: ExecSessionManager::default(), @@ -483,7 +487,7 @@ impl Session { let events = std::iter::once(Event { id: INITIAL_SUBMIT_ID.to_owned(), msg: EventMsg::SessionConfigured(SessionConfiguredEvent { - session_id, + session_id: conversation_id, model, history_log_id, history_entry_count, @@ -1084,7 +1088,7 @@ async fn submission_loop( provider, effective_effort, effective_summary, - sess.session_id, + sess.conversation_id, ); let new_approval_policy = approval_policy.unwrap_or(prev.approval_policy); @@ -1172,7 +1176,7 @@ async fn submission_loop( provider, effort, summary, - sess.session_id, + sess.conversation_id, ); let fresh_turn_context = TurnContext { @@ -1215,7 +1219,7 @@ async fn submission_loop( other => sess.notify_approval(&id, other), }, Op::AddToHistory { text } => { - let id = sess.session_id; + let id = sess.conversation_id; let config = config.clone(); tokio::spawn(async move { if let Err(e) = crate::message_history::append_entry(&text, &id, &config).await @@ -1246,7 +1250,7 @@ async fn submission_loop( log_id, entry: entry_opt.map(|e| { codex_protocol::message_history::HistoryEntry { - session_id: e.session_id, + conversation_id: e.session_id, ts: e.ts, text: e.text, } @@ -1352,7 +1356,7 @@ async fn submission_loop( let event = Event { id: sub_id.clone(), msg: EventMsg::ConversationHistory(ConversationHistoryResponseEvent { - conversation_id: sess.session_id, + conversation_id: sess.conversation_id, entries: sess.state.lock_unchecked().history.contents(), }), }; diff --git a/codex-rs/core/src/conversation_manager.rs b/codex-rs/core/src/conversation_manager.rs index 78674cd7..6cab2760 100644 --- a/codex-rs/core/src/conversation_manager.rs +++ b/codex-rs/core/src/conversation_manager.rs @@ -4,8 +4,8 @@ use std::sync::Arc; use crate::AuthManager; use crate::CodexAuth; +use codex_protocol::mcp_protocol::ConversationId; use tokio::sync::RwLock; -use uuid::Uuid; use crate::codex::Codex; use crate::codex::CodexSpawnOk; @@ -29,7 +29,7 @@ pub enum InitialHistory { /// Represents a newly created Codex conversation, including the first event /// (which is [`EventMsg::SessionConfigured`]). pub struct NewConversation { - pub conversation_id: Uuid, + pub conversation_id: ConversationId, pub conversation: Arc, pub session_configured: SessionConfiguredEvent, } @@ -37,7 +37,7 @@ pub struct NewConversation { /// [`ConversationManager`] is responsible for creating conversations and /// maintaining them in memory. pub struct ConversationManager { - conversations: Arc>>>, + conversations: Arc>>>, auth_manager: Arc, } @@ -70,13 +70,13 @@ impl ConversationManager { let initial_history = RolloutRecorder::get_rollout_history(resume_path).await?; let CodexSpawnOk { codex, - session_id: conversation_id, + conversation_id, } = Codex::spawn(config, auth_manager, initial_history).await?; self.finalize_spawn(codex, conversation_id).await } else { let CodexSpawnOk { codex, - session_id: conversation_id, + conversation_id, } = { Codex::spawn(config, auth_manager, InitialHistory::New).await? }; self.finalize_spawn(codex, conversation_id).await } @@ -85,7 +85,7 @@ impl ConversationManager { async fn finalize_spawn( &self, codex: Codex, - conversation_id: Uuid, + conversation_id: ConversationId, ) -> CodexResult { // The first event must be `SessionInitialized`. Validate and forward it // to the caller so that they can display it in the conversation @@ -116,7 +116,7 @@ impl ConversationManager { pub async fn get_conversation( &self, - conversation_id: Uuid, + conversation_id: ConversationId, ) -> CodexResult> { let conversations = self.conversations.read().await; conversations @@ -134,12 +134,12 @@ impl ConversationManager { let initial_history = RolloutRecorder::get_rollout_history(&rollout_path).await?; let CodexSpawnOk { codex, - session_id: conversation_id, + conversation_id, } = Codex::spawn(config, auth_manager, initial_history).await?; self.finalize_spawn(codex, conversation_id).await } - pub async fn remove_conversation(&self, conversation_id: Uuid) { + pub async fn remove_conversation(&self, conversation_id: ConversationId) { self.conversations.write().await.remove(&conversation_id); } @@ -161,7 +161,7 @@ impl ConversationManager { let auth_manager = self.auth_manager.clone(); let CodexSpawnOk { codex, - session_id: conversation_id, + conversation_id, } = Codex::spawn(config, auth_manager, history).await?; self.finalize_spawn(codex, conversation_id).await diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index 00ac145c..36b7abe6 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -1,10 +1,10 @@ +use codex_protocol::mcp_protocol::ConversationId; use reqwest::StatusCode; use serde_json; use std::io; use std::time::Duration; use thiserror::Error; use tokio::task::JoinError; -use uuid::Uuid; pub type Result = std::result::Result; @@ -49,7 +49,7 @@ pub enum CodexErr { Stream(String, Option), #[error("no conversation with id: {0}")] - ConversationNotFound(Uuid), + ConversationNotFound(ConversationId), #[error("session configured event was not the first event in the stream")] SessionConfiguredNotFirstEvent, diff --git a/codex-rs/core/src/message_history.rs b/codex-rs/core/src/message_history.rs index 75bc5aa7..bfdbde67 100644 --- a/codex-rs/core/src/message_history.rs +++ b/codex-rs/core/src/message_history.rs @@ -5,7 +5,7 @@ //! JSON-Lines tooling. Each record has the following schema: //! //! ````text -//! {"session_id":"","ts":,"text":""} +//! {"conversation_id":"","ts":,"text":""} //! ```` //! //! To minimise the chance of interleaved writes when multiple processes are @@ -22,10 +22,11 @@ use std::path::PathBuf; use serde::Deserialize; use serde::Serialize; + +use codex_protocol::mcp_protocol::ConversationId; use std::time::Duration; use tokio::fs; use tokio::io::AsyncReadExt; -use uuid::Uuid; use crate::config::Config; use crate::config_types::HistoryPersistence; @@ -54,10 +55,14 @@ fn history_filepath(config: &Config) -> PathBuf { path } -/// Append a `text` entry associated with `session_id` to the history file. Uses +/// Append a `text` entry associated with `conversation_id` to the history file. Uses /// advisory file locking to ensure that concurrent writes do not interleave, /// which entails a small amount of blocking I/O internally. -pub(crate) async fn append_entry(text: &str, session_id: &Uuid, config: &Config) -> Result<()> { +pub(crate) async fn append_entry( + text: &str, + conversation_id: &ConversationId, + config: &Config, +) -> Result<()> { match config.history.persistence { HistoryPersistence::SaveAll => { // Save everything: proceed. @@ -84,7 +89,7 @@ pub(crate) async fn append_entry(text: &str, session_id: &Uuid, config: &Config) // Construct the JSON line first so we can write it in a single syscall. let entry = HistoryEntry { - session_id: session_id.to_string(), + session_id: conversation_id.to_string(), ts, text: text.to_string(), }; diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 10fb64be..66a65a95 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -5,6 +5,7 @@ use std::fs::{self}; use std::io::Error as IoError; use std::path::Path; +use codex_protocol::mcp_protocol::ConversationId; use serde::Deserialize; use serde::Serialize; use serde_json::Value; @@ -17,7 +18,6 @@ use tokio::sync::mpsc::{self}; use tokio::sync::oneshot; use tracing::info; use tracing::warn; -use uuid::Uuid; use super::SESSIONS_SUBDIR; use super::list::ConversationsPage; @@ -32,7 +32,7 @@ use codex_protocol::models::ResponseItem; #[derive(Serialize, Deserialize, Clone, Default)] pub struct SessionMeta { - pub id: Uuid, + pub id: ConversationId, pub timestamp: String, pub instructions: Option, } @@ -55,7 +55,7 @@ pub struct SavedSession { pub items: Vec, #[serde(default)] pub state: SessionStateSnapshot, - pub session_id: Uuid, + pub session_id: ConversationId, } /// Records all [`ResponseItem`]s for a session and flushes them to disk after @@ -94,14 +94,14 @@ impl RolloutRecorder { /// error so the caller can decide whether to disable persistence. pub async fn new( config: &Config, - uuid: Uuid, + conversation_id: ConversationId, instructions: Option, ) -> std::io::Result { let LogFileInfo { file, - session_id, + conversation_id: session_id, timestamp, - } = create_log_file(config, uuid)?; + } = create_log_file(config, conversation_id)?; let timestamp_format: &[FormatItem] = format_description!( "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z" @@ -227,13 +227,16 @@ struct LogFileInfo { file: File, /// Session ID (also embedded in filename). - session_id: Uuid, + conversation_id: ConversationId, /// Timestamp for the start of the session. timestamp: OffsetDateTime, } -fn create_log_file(config: &Config, session_id: Uuid) -> std::io::Result { +fn create_log_file( + config: &Config, + conversation_id: ConversationId, +) -> std::io::Result { // Resolve ~/.codex/sessions/YYYY/MM/DD and create it if missing. let timestamp = OffsetDateTime::now_local() .map_err(|e| IoError::other(format!("failed to get local time: {e}")))?; @@ -252,7 +255,7 @@ fn create_log_file(config: &Config, session_id: Uuid) -> std::io::Result std::io::Result) -> Value { provider, effort, summary, - Uuid::new_v4(), + ConversationId::new(), ); let mut prompt = Prompt::default(); diff --git a/codex-rs/core/tests/chat_completions_sse.rs b/codex-rs/core/tests/chat_completions_sse.rs index 1df658da..6155d15e 100644 --- a/codex-rs/core/tests/chat_completions_sse.rs +++ b/codex-rs/core/tests/chat_completions_sse.rs @@ -8,10 +8,10 @@ use codex_core::ResponseEvent; use codex_core::ResponseItem; use codex_core::WireApi; use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; +use codex_protocol::mcp_protocol::ConversationId; use core_test_support::load_default_config_for_test; use futures::StreamExt; use tempfile::TempDir; -use uuid::Uuid; use wiremock::Mock; use wiremock::MockServer; use wiremock::ResponseTemplate; @@ -69,7 +69,7 @@ async fn run_stream(sse_body: &str) -> Vec { provider, effort, summary, - Uuid::new_v4(), + ConversationId::new(), ); let mut prompt = Prompt::default(); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index cc1369e7..70e6e2b6 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -242,7 +242,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn includes_session_id_and_model_headers_in_request() { +async fn includes_conversation_id_and_model_headers_in_request() { if std::env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { println!( "Skipping test because it cannot execute when network is disabled in a Codex sandbox." @@ -299,12 +299,12 @@ async fn includes_session_id_and_model_headers_in_request() { // get request from the server let request = &server.received_requests().await.unwrap()[0]; - let request_session_id = request.headers.get("session_id").unwrap(); + let request_conversation_id = request.headers.get("conversation_id").unwrap(); let request_authorization = request.headers.get("authorization").unwrap(); let request_originator = request.headers.get("originator").unwrap(); assert_eq!( - request_session_id.to_str().unwrap(), + request_conversation_id.to_str().unwrap(), conversation_id.to_string() ); assert_eq!(request_originator.to_str().unwrap(), "codex_cli_rs"); @@ -477,14 +477,14 @@ async fn chatgpt_auth_sends_correct_request() { // get request from the server let request = &server.received_requests().await.unwrap()[0]; - let request_session_id = request.headers.get("session_id").unwrap(); + let request_conversation_id = request.headers.get("conversation_id").unwrap(); let request_authorization = request.headers.get("authorization").unwrap(); let request_originator = request.headers.get("originator").unwrap(); let request_chatgpt_account_id = request.headers.get("chatgpt-account-id").unwrap(); let request_body = request.body_json::().unwrap(); assert_eq!( - request_session_id.to_str().unwrap(), + request_conversation_id.to_str().unwrap(), conversation_id.to_string() ); assert_eq!(request_originator.to_str().unwrap(), "codex_cli_rs"); diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 3c639ad2..71e718c5 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -517,7 +517,7 @@ impl EventProcessor for EventProcessorWithHumanOutput { } EventMsg::SessionConfigured(session_configured_event) => { let SessionConfiguredEvent { - session_id, + session_id: conversation_id, model, history_log_id: _, history_entry_count: _, @@ -528,7 +528,7 @@ impl EventProcessor for EventProcessorWithHumanOutput { self, "{} {}", "codex session".style(self.magenta).style(self.bold), - session_id.to_string().style(self.dimmed) + conversation_id.to_string().style(self.dimmed) ); ts_println!(self, "model: {}", model); diff --git a/codex-rs/mcp-server/src/codex_message_processor.rs b/codex-rs/mcp-server/src/codex_message_processor.rs index 828cf1f1..d7502b23 100644 --- a/codex-rs/mcp-server/src/codex_message_processor.rs +++ b/codex-rs/mcp-server/src/codex_message_processor.rs @@ -99,7 +99,7 @@ pub(crate) struct CodexMessageProcessor { conversation_listeners: HashMap>, active_login: Arc>>, // Queue of pending interrupt requests per conversation. We reply when TurnAborted arrives. - pending_interrupts: Arc>>>, + pending_interrupts: Arc>>>, } impl CodexMessageProcessor { @@ -511,7 +511,7 @@ impl CodexMessageProcessor { .. } = conversation_id; let response = NewConversationResponse { - conversation_id: ConversationId(conversation_id), + conversation_id, model: session_configured.model, }; self.outgoing.send_response(request_id, response).await; @@ -632,7 +632,7 @@ impl CodexMessageProcessor { // Reply with conversation id + model and initial messages (when present) let response = codex_protocol::mcp_protocol::ResumeConversationResponse { - conversation_id: ConversationId(conversation_id), + conversation_id, model: session_configured.model.clone(), initial_messages: session_configured.initial_messages.clone(), }; @@ -656,7 +656,7 @@ impl CodexMessageProcessor { } = params; let Ok(conversation) = self .conversation_manager - .get_conversation(conversation_id.0) + .get_conversation(conversation_id) .await else { let error = JSONRPCErrorError { @@ -704,7 +704,7 @@ impl CodexMessageProcessor { let Ok(conversation) = self .conversation_manager - .get_conversation(conversation_id.0) + .get_conversation(conversation_id) .await else { let error = JSONRPCErrorError { @@ -750,7 +750,7 @@ impl CodexMessageProcessor { let InterruptConversationParams { conversation_id } = params; let Ok(conversation) = self .conversation_manager - .get_conversation(conversation_id.0) + .get_conversation(conversation_id) .await else { let error = JSONRPCErrorError { @@ -765,7 +765,7 @@ impl CodexMessageProcessor { // Record the pending interrupt so we can reply when TurnAborted arrives. { let mut map = self.pending_interrupts.lock().await; - map.entry(conversation_id.0).or_default().push(request_id); + map.entry(conversation_id).or_default().push(request_id); } // Submit the interrupt; we'll respond upon TurnAborted. @@ -780,12 +780,12 @@ impl CodexMessageProcessor { let AddConversationListenerParams { conversation_id } = params; let Ok(conversation) = self .conversation_manager - .get_conversation(conversation_id.0) + .get_conversation(conversation_id) .await else { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, - message: format!("conversation not found: {}", conversation_id.0), + message: format!("conversation not found: {conversation_id}"), data: None, }; self.outgoing.send_error(request_id, error).await; @@ -898,7 +898,7 @@ async fn apply_bespoke_event_handling( conversation_id: ConversationId, conversation: Arc, outgoing: Arc, - pending_interrupts: Arc>>>, + pending_interrupts: Arc>>>, ) { let Event { id: event_id, msg } = event; match msg { @@ -951,7 +951,7 @@ async fn apply_bespoke_event_handling( EventMsg::TurnAborted(turn_aborted_event) => { let pending = { let mut map = pending_interrupts.lock().await; - map.remove(&conversation_id.0).unwrap_or_default() + map.remove(&conversation_id).unwrap_or_default() }; if !pending.is_empty() { let response = InterruptConversationResponse { diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index 0ba5593f..7b635d2d 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -181,8 +181,8 @@ impl CodexToolCallParam { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct CodexToolCallReplyParam { - /// The *session id* for this conversation. - pub session_id: String, + /// The conversation id for this Codex session. + pub conversation_id: String, /// The *next user prompt* to continue the Codex conversation. pub prompt: String, @@ -213,7 +213,8 @@ pub(crate) fn create_tool_for_codex_tool_call_reply_param() -> Tool { input_schema: tool_input_schema, output_schema: None, description: Some( - "Continue a Codex session by providing the session id and prompt.".to_string(), + "Continue a Codex conversation by providing the conversation id and prompt." + .to_string(), ), annotations: None, } @@ -308,21 +309,21 @@ mod tests { let tool = create_tool_for_codex_tool_call_reply_param(); let tool_json = serde_json::to_value(&tool).expect("tool serializes"); let expected_tool_json = serde_json::json!({ - "description": "Continue a Codex session by providing the session id and prompt.", + "description": "Continue a Codex conversation by providing the conversation id and prompt.", "inputSchema": { "properties": { + "conversationId": { + "description": "The conversation id for this Codex session.", + "type": "string" + }, "prompt": { "description": "The *next user prompt* to continue the Codex conversation.", "type": "string" }, - "sessionId": { - "description": "The *session id* for this conversation.", - "type": "string" - }, }, "required": [ + "conversationId", "prompt", - "sessionId", ], "type": "object", }, diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 48d520b5..4be14485 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -18,13 +18,13 @@ use codex_core::protocol::InputItem; use codex_core::protocol::Op; use codex_core::protocol::Submission; use codex_core::protocol::TaskCompleteEvent; +use codex_protocol::mcp_protocol::ConversationId; use mcp_types::CallToolResult; use mcp_types::ContentBlock; use mcp_types::RequestId; use mcp_types::TextContent; use serde_json::json; use tokio::sync::Mutex; -use uuid::Uuid; use crate::exec_approval::handle_exec_approval_request; use crate::outgoing_message::OutgoingMessageSender; @@ -43,7 +43,7 @@ pub async fn run_codex_tool_session( config: CodexConfig, outgoing: Arc, conversation_manager: Arc, - running_requests_id_to_codex_uuid: Arc>>, + running_requests_id_to_codex_uuid: Arc>>, ) { let NewConversation { conversation_id, @@ -119,13 +119,13 @@ pub async fn run_codex_tool_session_reply( outgoing: Arc, request_id: RequestId, prompt: String, - running_requests_id_to_codex_uuid: Arc>>, - session_id: Uuid, + running_requests_id_to_codex_uuid: Arc>>, + conversation_id: ConversationId, ) { running_requests_id_to_codex_uuid .lock() .await - .insert(request_id.clone(), session_id); + .insert(request_id.clone(), conversation_id); if let Err(e) = conversation .submit(Op::UserInput { items: vec![InputItem::Text { text: prompt }], @@ -154,7 +154,7 @@ async fn run_codex_tool_session_inner( codex: Arc, outgoing: Arc, request_id: RequestId, - running_requests_id_to_codex_uuid: Arc>>, + running_requests_id_to_codex_uuid: Arc>>, ) { let request_id_str = match &request_id { RequestId::String(s) => s.clone(), diff --git a/codex-rs/mcp-server/src/message_processor.rs b/codex-rs/mcp-server/src/message_processor.rs index bf5dbf98..8179cdd6 100644 --- a/codex-rs/mcp-server/src/message_processor.rs +++ b/codex-rs/mcp-server/src/message_processor.rs @@ -9,6 +9,7 @@ use crate::codex_tool_config::create_tool_for_codex_tool_call_reply_param; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::outgoing_message::OutgoingMessageSender; use codex_protocol::mcp_protocol::ClientRequest; +use codex_protocol::mcp_protocol::ConversationId; use codex_core::AuthManager; use codex_core::ConversationManager; @@ -41,7 +42,7 @@ pub(crate) struct MessageProcessor { initialized: bool, codex_linux_sandbox_exe: Option, conversation_manager: Arc, - running_requests_id_to_codex_uuid: Arc>>, + running_requests_id_to_codex_uuid: Arc>>, } impl MessageProcessor { @@ -436,7 +437,10 @@ impl MessageProcessor { tracing::info!("tools/call -> params: {:?}", arguments); // parse arguments - let CodexToolCallReplyParam { session_id, prompt } = match arguments { + let CodexToolCallReplyParam { + conversation_id, + prompt, + } = match arguments { Some(json_val) => match serde_json::from_value::(json_val) { Ok(params) => params, Err(e) => { @@ -457,12 +461,12 @@ impl MessageProcessor { }, None => { tracing::error!( - "Missing arguments for codex-reply tool-call; the `session_id` and `prompt` fields are required." + "Missing arguments for codex-reply tool-call; the `conversation_id` and `prompt` fields are required." ); let result = CallToolResult { content: vec![ContentBlock::TextContent(TextContent { r#type: "text".to_owned(), - text: "Missing arguments for codex-reply tool-call; the `session_id` and `prompt` fields are required.".to_owned(), + text: "Missing arguments for codex-reply tool-call; the `conversation_id` and `prompt` fields are required.".to_owned(), annotations: None, })], is_error: Some(true), @@ -473,14 +477,14 @@ impl MessageProcessor { return; } }; - let session_id = match Uuid::parse_str(&session_id) { - Ok(id) => id, + let conversation_id = match Uuid::parse_str(&conversation_id) { + Ok(id) => ConversationId::from(id), Err(e) => { - tracing::error!("Failed to parse session_id: {e}"); + tracing::error!("Failed to parse conversation_id: {e}"); let result = CallToolResult { content: vec![ContentBlock::TextContent(TextContent { r#type: "text".to_owned(), - text: format!("Failed to parse session_id: {e}"), + text: format!("Failed to parse conversation_id: {e}"), annotations: None, })], is_error: Some(true), @@ -496,14 +500,18 @@ impl MessageProcessor { let outgoing = self.outgoing.clone(); let running_requests_id_to_codex_uuid = self.running_requests_id_to_codex_uuid.clone(); - let codex = match self.conversation_manager.get_conversation(session_id).await { + let codex = match self + .conversation_manager + .get_conversation(conversation_id) + .await + { Ok(c) => c, Err(_) => { - tracing::warn!("Session not found for session_id: {session_id}"); + tracing::warn!("Session not found for conversation_id: {conversation_id}"); let result = CallToolResult { content: vec![ContentBlock::TextContent(TextContent { r#type: "text".to_owned(), - text: format!("Session not found for session_id: {session_id}"), + text: format!("Session not found for conversation_id: {conversation_id}"), annotations: None, })], is_error: Some(true), @@ -528,7 +536,7 @@ impl MessageProcessor { request_id, prompt, running_requests_id_to_codex_uuid, - session_id, + conversation_id, ) .await; } @@ -564,24 +572,28 @@ impl MessageProcessor { RequestId::Integer(i) => i.to_string(), }; - // Obtain the session_id while holding the first lock, then release. - let session_id = { + // Obtain the conversation id while holding the first lock, then release. + let conversation_id = { let map_guard = self.running_requests_id_to_codex_uuid.lock().await; match map_guard.get(&request_id) { - Some(id) => *id, // Uuid is Copy + Some(id) => *id, None => { tracing::warn!("Session not found for request_id: {}", request_id_string); return; } } }; - tracing::info!("session_id: {session_id}"); + tracing::info!("conversation_id: {conversation_id}"); // Obtain the Codex conversation from the server. - let codex_arc = match self.conversation_manager.get_conversation(session_id).await { + let codex_arc = match self + .conversation_manager + .get_conversation(conversation_id) + .await + { Ok(c) => c, Err(_) => { - tracing::warn!("Session not found for session_id: {session_id}"); + tracing::warn!("Session not found for conversation_id: {conversation_id}"); return; } }; diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index b735f2bf..537d29db 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -258,6 +258,7 @@ pub(crate) struct OutgoingError { mod tests { use codex_core::protocol::EventMsg; use codex_core::protocol::SessionConfiguredEvent; + use codex_protocol::mcp_protocol::ConversationId; use codex_protocol::mcp_protocol::LoginChatGptCompleteNotification; use pretty_assertions::assert_eq; use serde_json::json; @@ -270,10 +271,11 @@ mod tests { let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded_channel::(); let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx); + let conversation_id = ConversationId::new(); let event = Event { id: "1".to_string(), msg: EventMsg::SessionConfigured(SessionConfiguredEvent { - session_id: Uuid::new_v4(), + session_id: conversation_id, model: "gpt-4o".to_string(), history_log_id: 1, history_entry_count: 1000, @@ -302,8 +304,9 @@ mod tests { let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded_channel::(); let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx); + let conversation_id = ConversationId::new(); let session_configured_event = SessionConfiguredEvent { - session_id: Uuid::new_v4(), + session_id: conversation_id, model: "gpt-4o".to_string(), history_log_id: 1, history_entry_count: 1000, diff --git a/codex-rs/mcp-server/tests/suite/list_resume.rs b/codex-rs/mcp-server/tests/suite/list_resume.rs index d835e5ca..98734922 100644 --- a/codex-rs/mcp-server/tests/suite/list_resume.rs +++ b/codex-rs/mcp-server/tests/suite/list_resume.rs @@ -142,7 +142,7 @@ async fn test_list_and_resume_conversations() { } = to_response::(resume_resp) .expect("deserialize resumeConversation response"); // conversation id should be a valid UUID - let _ = uuid::Uuid::from_bytes(conversation_id.0.into_bytes()); + let _: uuid::Uuid = conversation_id.into(); } fn create_fake_rollout(codex_home: &Path, filename_ts: &str, meta_rfc3339: &str, preview: &str) { diff --git a/codex-rs/protocol/src/mcp_protocol.rs b/codex-rs/protocol/src/mcp_protocol.rs index 26d39eb6..a8d4b180 100644 --- a/codex-rs/protocol/src/mcp_protocol.rs +++ b/codex-rs/protocol/src/mcp_protocol.rs @@ -19,16 +19,34 @@ use strum_macros::Display; use ts_rs::TS; use uuid::Uuid; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Hash, Default)] #[ts(type = "string")] pub struct ConversationId(pub Uuid); +impl ConversationId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + impl Display for ConversationId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } +impl From for ConversationId { + fn from(value: Uuid) -> Self { + Self(value) + } +} + +impl From for Uuid { + fn from(value: ConversationId) -> Self { + value.0 + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, TS)] #[ts(type = "string")] pub struct GitSha(pub String); diff --git a/codex-rs/protocol/src/message_history.rs b/codex-rs/protocol/src/message_history.rs index 3a561df7..5d3799a5 100644 --- a/codex-rs/protocol/src/message_history.rs +++ b/codex-rs/protocol/src/message_history.rs @@ -3,7 +3,7 @@ use serde::Serialize; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct HistoryEntry { - pub session_id: String, + pub conversation_id: String, pub ts: u64, pub text: String, } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index a422327d..6aa9d136 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -10,7 +10,14 @@ use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; +use crate::config_types::ReasoningEffort as ReasoningEffortConfig; +use crate::config_types::ReasoningSummary as ReasoningSummaryConfig; use crate::custom_prompts::CustomPrompt; +use crate::mcp_protocol::ConversationId; +use crate::message_history::HistoryEntry; +use crate::models::ResponseItem; +use crate::parse_command::ParsedCommand; +use crate::plan_tool::UpdatePlanArgs; use mcp_types::CallToolResult; use mcp_types::Tool as McpTool; use serde::Deserialize; @@ -18,14 +25,6 @@ use serde::Serialize; use serde_with::serde_as; use strum_macros::Display; use ts_rs::TS; -use uuid::Uuid; - -use crate::config_types::ReasoningEffort as ReasoningEffortConfig; -use crate::config_types::ReasoningSummary as ReasoningSummaryConfig; -use crate::message_history::HistoryEntry; -use crate::models::ResponseItem; -use crate::parse_command::ParsedCommand; -use crate::plan_tool::UpdatePlanArgs; /// Open/close tags for special user-input blocks. Used across crates to avoid /// duplicated hardcoded strings. @@ -791,7 +790,7 @@ pub struct WebSearchEndEvent { /// in-memory transcript. #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ConversationHistoryResponseEvent { - pub conversation_id: Uuid, + pub conversation_id: ConversationId, pub entries: Vec, } @@ -931,8 +930,8 @@ pub struct ListCustomPromptsResponseEvent { #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct SessionConfiguredEvent { - /// Unique id for this session. - pub session_id: Uuid, + /// Name left as session_id instead of conversation_id for backwards compatibility. + pub session_id: ConversationId, /// Tell the client what model is being queried. pub model: String, @@ -1014,11 +1013,11 @@ mod tests { /// amount of nesting. #[test] fn serialize_event() { - let session_id: Uuid = uuid::uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); + let conversation_id = ConversationId(uuid::uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8")); let event = Event { id: "1234".to_string(), msg: EventMsg::SessionConfigured(SessionConfiguredEvent { - session_id, + session_id: conversation_id, model: "codex-mini-latest".to_string(), history_log_id: 0, history_entry_count: 0, diff --git a/codex-rs/tui/src/app_backtrack.rs b/codex-rs/tui/src/app_backtrack.rs index c716d6c4..b893fd79 100644 --- a/codex-rs/tui/src/app_backtrack.rs +++ b/codex-rs/tui/src/app_backtrack.rs @@ -4,6 +4,7 @@ use crate::pager_overlay::Overlay; use crate::tui; use crate::tui::TuiEvent; use codex_core::protocol::ConversationHistoryResponseEvent; +use codex_protocol::mcp_protocol::ConversationId; use color_eyre::eyre::Result; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; @@ -14,13 +15,13 @@ pub(crate) struct BacktrackState { /// True when Esc has primed backtrack mode in the main view. pub(crate) primed: bool, /// Session id of the base conversation to fork from. - pub(crate) base_id: Option, + pub(crate) base_id: Option, /// Current step count (Nth last user message). pub(crate) count: usize, /// True when the transcript overlay is showing a backtrack preview. pub(crate) overlay_preview_active: bool, /// Pending fork request: (base_id, drop_count, prefill). - pub(crate) pending: Option<(uuid::Uuid, usize, String)>, + pub(crate) pending: Option<(ConversationId, usize, String)>, } impl App { @@ -91,7 +92,7 @@ impl App { pub(crate) fn request_backtrack( &mut self, prefill: String, - base_id: uuid::Uuid, + base_id: ConversationId, drop_last_messages: usize, ) { self.backtrack.pending = Some((base_id, drop_last_messages, prefill)); @@ -135,7 +136,7 @@ impl App { fn prime_backtrack(&mut self) { self.backtrack.primed = true; self.backtrack.count = 0; - self.backtrack.base_id = self.chat_widget.session_id(); + self.backtrack.base_id = self.chat_widget.conversation_id(); self.chat_widget.show_esc_backtrack_hint(); } @@ -151,7 +152,7 @@ impl App { /// When overlay is already open, begin preview mode and select latest user message. fn begin_overlay_backtrack_preview(&mut self, tui: &mut tui::Tui) { self.backtrack.primed = true; - self.backtrack.base_id = self.chat_widget.session_id(); + self.backtrack.base_id = self.chat_widget.conversation_id(); self.backtrack.overlay_preview_active = true; let sel = self.compute_backtrack_selection(tui, 1); self.apply_backtrack_selection(sel); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 678eb183..dca821f2 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -85,7 +85,7 @@ use codex_core::protocol::AskForApproval; use codex_core::protocol::SandboxPolicy; use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig; use codex_file_search::FileMatch; -use uuid::Uuid; +use codex_protocol::mcp_protocol::ConversationId; // Track information about an in-flight exec command. struct RunningCommand { @@ -121,7 +121,7 @@ pub(crate) struct ChatWidget { reasoning_buffer: String, // Accumulates full reasoning content for transcript-only recording full_reasoning_buffer: String, - session_id: Option, + conversation_id: Option, frame_requester: FrameRequester, // Whether to include the initial welcome banner on session configured show_welcome_banner: bool, @@ -163,7 +163,7 @@ impl ChatWidget { fn on_session_configured(&mut self, event: codex_core::protocol::SessionConfiguredEvent) { self.bottom_pane .set_history_metadata(event.history_log_id, event.history_entry_count); - self.session_id = Some(event.session_id); + self.conversation_id = Some(event.session_id); let initial_messages = event.initial_messages.clone(); if let Some(messages) = initial_messages { self.replay_initial_messages(messages); @@ -660,7 +660,7 @@ impl ChatWidget { interrupts: InterruptManager::new(), reasoning_buffer: String::new(), full_reasoning_buffer: String::new(), - session_id: None, + conversation_id: None, queued_user_messages: VecDeque::new(), show_welcome_banner: true, suppress_session_configured_redraw: false, @@ -712,7 +712,7 @@ impl ChatWidget { interrupts: InterruptManager::new(), reasoning_buffer: String::new(), full_reasoning_buffer: String::new(), - session_id: None, + conversation_id: None, queued_user_messages: VecDeque::new(), show_welcome_banner: false, suppress_session_configured_redraw: true, @@ -1159,7 +1159,7 @@ impl ChatWidget { self.add_to_history(history_cell::new_status_output( &self.config, usage_ref, - &self.session_id, + &self.conversation_id, )); } @@ -1360,8 +1360,8 @@ impl ChatWidget { .unwrap_or_default() } - pub(crate) fn session_id(&self) -> Option { - self.session_id + pub(crate) fn conversation_id(&self) -> Option { + self.conversation_id } /// Return a reference to the widget's current config (includes any diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index dfcbbec7..e423e804 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -25,6 +25,7 @@ use codex_core::protocol::PatchApplyEndEvent; use codex_core::protocol::StreamErrorEvent; use codex_core::protocol::TaskCompleteEvent; use codex_core::protocol::TaskStartedEvent; +use codex_protocol::mcp_protocol::ConversationId; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyModifiers; @@ -35,11 +36,10 @@ use std::io::BufRead; use std::io::BufReader; use std::path::PathBuf; use tokio::sync::mpsc::unbounded_channel; -use uuid::Uuid; fn test_config() -> Config { // Use base defaults to avoid depending on host state. - codex_core::config::Config::load_from_base_config_with_overrides( + Config::load_from_base_config_with_overrides( ConfigToml::default(), ConfigOverrides::default(), std::env::temp_dir(), @@ -79,7 +79,7 @@ fn final_answer_without_newline_is_flushed_immediately() { // Set up a VT100 test terminal to capture ANSI visual output let width: u16 = 80; let height: u16 = 2000; - let viewport = ratatui::layout::Rect::new(0, height - 1, width, 1); + let viewport = Rect::new(0, height - 1, width, 1); let backend = ratatui::backend::TestBackend::new(width, height); let mut terminal = crate::custom_terminal::Terminal::with_options(backend) .expect("failed to construct terminal"); @@ -132,13 +132,15 @@ fn final_answer_without_newline_is_flushed_immediately() { fn resumed_initial_messages_render_history() { let (mut chat, mut rx, _ops) = make_chatwidget_manual(); + let conversation_id = ConversationId::new(); + let configured = codex_core::protocol::SessionConfiguredEvent { - session_id: Uuid::nil(), + session_id: conversation_id, model: "test-model".to_string(), history_log_id: 0, history_entry_count: 0, initial_messages: Some(vec![ - EventMsg::UserMessage(codex_core::protocol::UserMessageEvent { + EventMsg::UserMessage(UserMessageEvent { message: "hello from user".to_string(), kind: Some(InputMessageKind::Plain), }), @@ -185,7 +187,7 @@ async fn helpers_are_available_and_do_not_panic() { ))); let init = ChatWidgetInit { config: cfg, - frame_requester: crate::tui::FrameRequester::test_dummy(), + frame_requester: FrameRequester::test_dummy(), app_event_tx: tx, initial_prompt: None, initial_images: Vec::new(), @@ -208,7 +210,7 @@ fn make_chatwidget_manual() -> ( let cfg = test_config(); let bottom = BottomPane::new(BottomPaneParams { app_event_tx: app_event_tx.clone(), - frame_requester: crate::tui::FrameRequester::test_dummy(), + frame_requester: FrameRequester::test_dummy(), has_input_focus: true, enhanced_keys_supported: false, placeholder_text: "Ask Codex to do anything".to_string(), @@ -228,10 +230,10 @@ fn make_chatwidget_manual() -> ( interrupts: InterruptManager::new(), reasoning_buffer: String::new(), full_reasoning_buffer: String::new(), - session_id: None, - frame_requester: crate::tui::FrameRequester::test_dummy(), + conversation_id: None, + frame_requester: FrameRequester::test_dummy(), show_welcome_banner: true, - queued_user_messages: std::collections::VecDeque::new(), + queued_user_messages: VecDeque::new(), suppress_session_configured_redraw: false, }; (widget, rx, op_rx) @@ -367,11 +369,10 @@ fn begin_exec(chat: &mut ChatWidget, call_id: &str, raw_cmd: &str) { // Build the full command vec and parse it using core's parser, // then convert to protocol variants for the event payload. let command = vec!["bash".to_string(), "-lc".to_string(), raw_cmd.to_string()]; - let parsed_cmd: Vec = - codex_core::parse_command::parse_command(&command) - .into_iter() - .map(Into::into) - .collect(); + let parsed_cmd: Vec = codex_core::parse_command::parse_command(&command) + .into_iter() + .map(Into::into) + .collect(); chat.handle_codex_event(Event { id: call_id.to_string(), msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent { @@ -412,7 +413,7 @@ fn active_blob(chat: &ChatWidget) -> String { lines_to_single_string(&lines) } -fn open_fixture(name: &str) -> std::fs::File { +fn open_fixture(name: &str) -> File { // 1) Prefer fixtures within this crate { let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -620,7 +621,7 @@ async fn binary_size_transcript_snapshot() { // Set up a VT100 test terminal to capture ANSI visual output let width: u16 = 80; let height: u16 = 2000; - let viewport = ratatui::layout::Rect::new(0, height - 1, width, 1); + let viewport = Rect::new(0, height - 1, width, 1); let backend = ratatui::backend::TestBackend::new(width, height); let mut terminal = crate::custom_terminal::Terminal::with_options(backend) .expect("failed to construct terminal"); @@ -805,7 +806,7 @@ fn approval_modal_exec_snapshot() { // Build a chat widget with manual channels to avoid spawning the agent. let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); // Ensure policy allows surfacing approvals explicitly (not strictly required for direct event). - chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest; + chat.config.approval_policy = AskForApproval::OnRequest; // Inject an exec approval request to display the approval modal. let ev = ExecApprovalRequestEvent { call_id: "call-approve-cmd".into(), @@ -835,7 +836,7 @@ fn approval_modal_exec_snapshot() { #[test] fn approval_modal_exec_without_reason_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); - chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest; + chat.config.approval_policy = AskForApproval::OnRequest; let ev = ExecApprovalRequestEvent { call_id: "call-approve-cmd-noreason".into(), @@ -861,10 +862,10 @@ fn approval_modal_exec_without_reason_snapshot() { #[test] fn approval_modal_patch_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); - chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest; + chat.config.approval_policy = AskForApproval::OnRequest; // Build a small changeset and a reason/grant_root to exercise the prompt text. - let mut changes = std::collections::HashMap::new(); + let mut changes = HashMap::new(); changes.insert( PathBuf::from("README.md"), FileChange::Add { @@ -910,7 +911,7 @@ fn interrupt_restores_queued_messages_into_composer() { chat.handle_codex_event(Event { id: "turn-1".into(), msg: EventMsg::TurnAborted(codex_core::protocol::TurnAbortedEvent { - reason: codex_core::protocol::TurnAbortReason::Interrupted, + reason: TurnAbortReason::Interrupted, }), }); @@ -1344,7 +1345,7 @@ fn apply_patch_full_flow_integration_like() { fn apply_patch_untrusted_shows_approval_modal() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); // Ensure approval policy is untrusted (OnRequest) - chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest; + chat.config.approval_policy = AskForApproval::OnRequest; // Simulate a patch approval request from backend let mut changes = HashMap::new(); @@ -1363,8 +1364,8 @@ fn apply_patch_untrusted_shows_approval_modal() { }); // Render and ensure the approval modal title is present - let area = ratatui::layout::Rect::new(0, 0, 80, 12); - let mut buf = ratatui::buffer::Buffer::empty(area); + let area = Rect::new(0, 0, 80, 12); + let mut buf = Buffer::empty(area); (&chat).render_ref(area, &mut buf); let mut contains_title = false; @@ -1389,7 +1390,7 @@ fn apply_patch_request_shows_diff_summary() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(); // Ensure we are in OnRequest so an approval is surfaced - chat.config.approval_policy = codex_core::protocol::AskForApproval::OnRequest; + chat.config.approval_policy = AskForApproval::OnRequest; // Simulate backend asking to apply a patch adding two lines to README.md let mut changes = HashMap::new(); @@ -1691,7 +1692,7 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() { let width: u16 = 80; let ui_height: u16 = chat.desired_height(width); let vt_height: u16 = 40; - let viewport = ratatui::layout::Rect::new(0, vt_height - ui_height, width, ui_height); + let viewport = Rect::new(0, vt_height - ui_height, width, ui_height); // Use TestBackend for the terminal (no real ANSI emitted by drawing), // but capture VT100 escape stream for history insertion with a separate writer. @@ -1706,7 +1707,7 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() { } // 2) Render the ChatWidget UI into an off-screen buffer using WidgetRef directly - let mut ui_buf = ratatui::buffer::Buffer::empty(viewport); + let mut ui_buf = Buffer::empty(viewport); (&chat).render_ref(viewport, &mut ui_buf); // 3) Build VT100 visual from the captured ANSI diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index c8fb5923..8dd9154c 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -27,6 +27,7 @@ use codex_core::protocol::McpInvocation; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::SessionConfiguredEvent; use codex_core::protocol::TokenUsage; +use codex_protocol::mcp_protocol::ConversationId; use codex_protocol::parse_command::ParsedCommand; use image::DynamicImage; use image::ImageReader; @@ -49,7 +50,6 @@ use std::time::Duration; use std::time::Instant; use tracing::error; use unicode_width::UnicodeWidthStr; -use uuid::Uuid; #[derive(Clone, Debug)] pub(crate) struct CommandOutput { @@ -821,7 +821,7 @@ pub(crate) fn new_completed_mcp_tool_call( pub(crate) fn new_status_output( config: &Config, usage: &TokenUsage, - session_id: &Option, + session_id: &Option, ) -> PlainHistoryCell { let mut lines: Vec> = Vec::new(); lines.push("/status".magenta().into());