From a5f3a348275a67b15f752c21a7ecc99dc1a1b670 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 13 May 2025 20:44:42 -0700 Subject: [PATCH] fix: change EventMsg enum so every variant takes a single struct (#925) https://github.com/openai/codex/pull/922 did this for the `SessionConfigured` enum variant, and I think it is generally helpful to be able to work with the values as each enum variant as their own type, so this converts the remaining variants and updates all of the callsites. Added a simple unit test to verify that the JSON-serialized version of `Event` does not have any unexpected nesting. --- codex-rs/core/src/codex.rs | 60 +++-- codex-rs/core/src/codex_wrapper.rs | 2 +- codex-rs/core/src/mcp_tool_call.rs | 18 +- codex-rs/core/src/protocol.rs | 243 +++++++++++-------- codex-rs/core/tests/live_agent.rs | 35 ++- codex-rs/core/tests/previous_response_id.rs | 8 +- codex-rs/exec/src/event_processor.rs | 47 ++-- codex-rs/mcp-server/src/codex_tool_runner.rs | 9 +- codex-rs/tui/src/chatwidget.rs | 49 ++-- 9 files changed, 288 insertions(+), 183 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 26e1f665..440451a9 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -55,12 +55,22 @@ use crate::models::ResponseInputItem; use crate::models::ResponseItem; use crate::models::ShellToolCallParams; use crate::project_doc::create_full_instructions; +use crate::protocol::AgentMessageEvent; +use crate::protocol::AgentReasoningEvent; +use crate::protocol::ApplyPatchApprovalRequestEvent; use crate::protocol::AskForApproval; +use crate::protocol::BackgroundEventEvent; +use crate::protocol::ErrorEvent; use crate::protocol::Event; use crate::protocol::EventMsg; +use crate::protocol::ExecApprovalRequestEvent; +use crate::protocol::ExecCommandBeginEvent; +use crate::protocol::ExecCommandEndEvent; use crate::protocol::FileChange; use crate::protocol::InputItem; use crate::protocol::Op; +use crate::protocol::PatchApplyBeginEvent; +use crate::protocol::PatchApplyEndEvent; use crate::protocol::ReviewDecision; use crate::protocol::SandboxPolicy; use crate::protocol::SessionConfiguredEvent; @@ -227,11 +237,11 @@ impl Session { let (tx_approve, rx_approve) = oneshot::channel(); let event = Event { id: sub_id.clone(), - msg: EventMsg::ExecApprovalRequest { + msg: EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent { command, cwd, reason, - }, + }), }; let _ = self.tx_event.send(event).await; { @@ -251,11 +261,11 @@ impl Session { let (tx_approve, rx_approve) = oneshot::channel(); let event = Event { id: sub_id.clone(), - msg: EventMsg::ApplyPatchApprovalRequest { + msg: EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent { changes: convert_apply_patch_to_protocol(action), reason, grant_root, - }, + }), }; let _ = self.tx_event.send(event).await; { @@ -297,11 +307,11 @@ impl Session { async fn notify_exec_command_begin(&self, sub_id: &str, call_id: &str, params: &ExecParams) { let event = Event { id: sub_id.to_string(), - msg: EventMsg::ExecCommandBegin { + msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent { call_id: call_id.to_string(), command: params.command.clone(), cwd: params.cwd.clone(), - }, + }), }; let _ = self.tx_event.send(event).await; } @@ -319,12 +329,12 @@ impl Session { id: sub_id.to_string(), // Because stdout and stderr could each be up to 100 KiB, we send // truncated versions. - msg: EventMsg::ExecCommandEnd { + msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent { call_id: call_id.to_string(), stdout: stdout.chars().take(MAX_STREAM_OUTPUT).collect(), stderr: stderr.chars().take(MAX_STREAM_OUTPUT).collect(), exit_code, - }, + }), }; let _ = self.tx_event.send(event).await; } @@ -335,9 +345,9 @@ impl Session { async fn notify_background_event(&self, sub_id: &str, message: impl Into) { let event = Event { id: sub_id.to_string(), - msg: EventMsg::BackgroundEvent { + msg: EventMsg::BackgroundEvent(BackgroundEventEvent { message: message.into(), - }, + }), }; let _ = self.tx_event.send(event).await; } @@ -460,9 +470,9 @@ impl AgentTask { self.handle.abort(); let event = Event { id: self.sub_id, - msg: EventMsg::Error { + msg: EventMsg::Error(ErrorEvent { message: "Turn interrupted".to_string(), - }, + }), }; let tx_event = self.sess.tx_event.clone(); tokio::spawn(async move { @@ -483,10 +493,10 @@ async fn submission_loop( let send_no_session_event = |sub_id: String| async { let event = Event { id: sub_id, - msg: EventMsg::Error { + msg: EventMsg::Error(ErrorEvent { message: "No session initialized, expected 'ConfigureSession' as first Op" .to_string(), - }, + }), }; tx_event.send(event).await.ok(); }; @@ -534,7 +544,7 @@ async fn submission_loop( error!(message); let event = Event { id: sub.id, - msg: EventMsg::Error { message }, + msg: EventMsg::Error(ErrorEvent { message }), }; if let Err(e) = tx_event.send(event).await { error!("failed to send error message: {e:?}"); @@ -577,7 +587,7 @@ async fn submission_loop( error!("{message}"); mcp_connection_errors.push(Event { id: sub.id.clone(), - msg: EventMsg::Error { message }, + msg: EventMsg::Error(ErrorEvent { message }), }); (McpConnectionManager::default(), Default::default()) } @@ -591,7 +601,7 @@ async fn submission_loop( error!("{message}"); mcp_connection_errors.push(Event { id: sub.id.clone(), - msg: EventMsg::Error { message }, + msg: EventMsg::Error(ErrorEvent { message }), }); } } @@ -792,9 +802,9 @@ async fn run_task(sess: Arc, sub_id: String, input: Vec) { info!("Turn error: {e:#}"); let event = Event { id: sub_id.clone(), - msg: EventMsg::Error { + msg: EventMsg::Error(ErrorEvent { message: e.to_string(), - }, + }), }; sess.tx_event.send(event).await.ok(); return; @@ -933,7 +943,7 @@ async fn handle_response_item( if let ContentItem::OutputText { text } = item { let event = Event { id: sub_id.to_string(), - msg: EventMsg::AgentMessage { message: text }, + msg: EventMsg::AgentMessage(AgentMessageEvent { message: text }), }; sess.tx_event.send(event).await.ok(); } @@ -946,7 +956,7 @@ async fn handle_response_item( }; let event = Event { id: sub_id.to_string(), - msg: EventMsg::AgentReasoning { text }, + msg: EventMsg::AgentReasoning(AgentReasoningEvent { text }), }; sess.tx_event.send(event).await.ok(); } @@ -1346,11 +1356,11 @@ async fn apply_patch( .tx_event .send(Event { id: sub_id.clone(), - msg: EventMsg::PatchApplyBegin { + msg: EventMsg::PatchApplyBegin(PatchApplyBeginEvent { call_id: call_id.clone(), auto_approved, changes: convert_apply_patch_to_protocol(&action), - }, + }), }) .await; @@ -1435,12 +1445,12 @@ async fn apply_patch( .tx_event .send(Event { id: sub_id.clone(), - msg: EventMsg::PatchApplyEnd { + msg: EventMsg::PatchApplyEnd(PatchApplyEndEvent { call_id: call_id.clone(), stdout: String::from_utf8_lossy(&stdout).to_string(), stderr: String::from_utf8_lossy(&stderr).to_string(), success: success_flag, - }, + }), }) .await; diff --git a/codex-rs/core/src/codex_wrapper.rs b/codex-rs/core/src/codex_wrapper.rs index 431b580c..f2ece22d 100644 --- a/codex-rs/core/src/codex_wrapper.rs +++ b/codex-rs/core/src/codex_wrapper.rs @@ -24,7 +24,7 @@ pub async fn init_codex(config: Config) -> anyhow::Result<(Codex, Event, Arc ( - EventMsg::McpToolCallEnd { + EventMsg::McpToolCallEnd(McpToolCallEndEvent { call_id, success: !result.is_error.unwrap_or(false), result: Some(result), - }, + }), None, ), Err(e) => ( - EventMsg::McpToolCallEnd { + EventMsg::McpToolCallEnd(McpToolCallEndEvent { call_id, success: false, result: None, - }, + }), Some(e), ), }; notify_mcp_tool_call_event(sess, sub_id, tool_call_end_event.clone()).await; - let EventMsg::McpToolCallEnd { + let EventMsg::McpToolCallEnd(McpToolCallEndEvent { call_id, success, result, - } = tool_call_end_event + }) = tool_call_end_event else { unimplemented!("unexpected event type"); }; diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index e4b83826..80087430 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -303,9 +303,7 @@ pub struct Event { #[serde(tag = "type", rename_all = "snake_case")] pub enum EventMsg { /// Error while executing a submission - Error { - message: String, - }, + Error(ErrorEvent), /// Agent has started a task TaskStarted, @@ -314,117 +312,145 @@ pub enum EventMsg { TaskComplete, /// Agent text output message - AgentMessage { - message: String, - }, + AgentMessage(AgentMessageEvent), /// Reasoning event from agent. - AgentReasoning { - text: String, - }, + AgentReasoning(AgentReasoningEvent), /// Ack the client's configure message. SessionConfigured(SessionConfiguredEvent), - McpToolCallBegin { - /// Identifier so this can be paired with the McpToolCallEnd event. - call_id: String, + McpToolCallBegin(McpToolCallBeginEvent), - /// Name of the MCP server as defined in the config. - server: String, - - /// Name of the tool as given by the MCP server. - tool: String, - - /// Arguments to the tool call. - arguments: Option, - }, - - McpToolCallEnd { - /// Identifier for the McpToolCallBegin that finished. - call_id: String, - - /// Whether the tool call was successful. If `false`, `result` might - /// not be present. - success: bool, - - /// Result of the tool call. Note this could be an error. - result: Option, - }, + McpToolCallEnd(McpToolCallEndEvent), /// Notification that the server is about to execute a command. - ExecCommandBegin { - /// Identifier so this can be paired with the ExecCommandEnd event. - call_id: String, - /// The command to be executed. - command: Vec, - /// The command's working directory if not the default cwd for the - /// agent. - cwd: PathBuf, - }, + ExecCommandBegin(ExecCommandBeginEvent), - ExecCommandEnd { - /// Identifier for the ExecCommandBegin that finished. - call_id: String, - /// Captured stdout - stdout: String, - /// Captured stderr - stderr: String, - /// The command's exit code. - exit_code: i32, - }, + ExecCommandEnd(ExecCommandEndEvent), - ExecApprovalRequest { - /// The command to be executed. - command: Vec, - /// The command's working directory. - cwd: PathBuf, - /// Optional human‑readable reason for the approval (e.g. retry without - /// sandbox). - #[serde(skip_serializing_if = "Option::is_none")] - reason: Option, - }, + ExecApprovalRequest(ExecApprovalRequestEvent), - ApplyPatchApprovalRequest { - changes: HashMap, - /// Optional explanatory reason (e.g. request for extra write access). - #[serde(skip_serializing_if = "Option::is_none")] - reason: Option, + ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent), - /// When set, the agent is asking the user to allow writes under this - /// root for the remainder of the session. - #[serde(skip_serializing_if = "Option::is_none")] - grant_root: Option, - }, - - BackgroundEvent { - message: String, - }, + BackgroundEvent(BackgroundEventEvent), /// Notification that the agent is about to apply a code patch. Mirrors /// `ExecCommandBegin` so front‑ends can show progress indicators. - PatchApplyBegin { - /// Identifier so this can be paired with the PatchApplyEnd event. - call_id: String, - - /// If true, there was no ApplyPatchApprovalRequest for this patch. - auto_approved: bool, - - /// The changes to be applied. - changes: HashMap, - }, + PatchApplyBegin(PatchApplyBeginEvent), /// Notification that a patch application has finished. - PatchApplyEnd { - /// Identifier for the PatchApplyBegin that finished. - call_id: String, - /// Captured stdout (summary printed by apply_patch). - stdout: String, - /// Captured stderr (parser errors, IO failures, etc.). - stderr: String, - /// Whether the patch was applied successfully. - success: bool, - }, + PatchApplyEnd(PatchApplyEndEvent), +} + +// Individual event payload types matching each `EventMsg` variant. + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ErrorEvent { + pub message: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct AgentMessageEvent { + pub message: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct AgentReasoningEvent { + pub text: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct McpToolCallBeginEvent { + /// Identifier so this can be paired with the McpToolCallEnd event. + pub call_id: String, + /// Name of the MCP server as defined in the config. + pub server: String, + /// Name of the tool as given by the MCP server. + pub tool: String, + /// Arguments to the tool call. + pub arguments: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct McpToolCallEndEvent { + /// Identifier for the corresponding McpToolCallBegin that finished. + pub call_id: String, + /// Whether the tool call was successful. If `false`, `result` might not be present. + pub success: bool, + /// Result of the tool call. Note this could be an error. + pub result: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ExecCommandBeginEvent { + /// Identifier so this can be paired with the ExecCommandEnd event. + pub call_id: String, + /// The command to be executed. + pub command: Vec, + /// The command's working directory if not the default cwd for the agent. + pub cwd: PathBuf, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ExecCommandEndEvent { + /// Identifier for the ExecCommandBegin that finished. + pub call_id: String, + /// Captured stdout + pub stdout: String, + /// Captured stderr + pub stderr: String, + /// The command's exit code. + pub exit_code: i32, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ExecApprovalRequestEvent { + /// The command to be executed. + pub command: Vec, + /// The command's working directory. + pub cwd: PathBuf, + /// Optional human-readable reason for the approval (e.g. retry without sandbox). + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ApplyPatchApprovalRequestEvent { + pub changes: HashMap, + /// Optional explanatory reason (e.g. request for extra write access). + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// When set, the agent is asking the user to allow writes under this root for the remainder of the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub grant_root: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct BackgroundEventEvent { + pub message: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PatchApplyBeginEvent { + /// Identifier so this can be paired with the PatchApplyEnd event. + pub call_id: String, + /// If true, there was no ApplyPatchApprovalRequest for this patch. + pub auto_approved: bool, + /// The changes to be applied. + pub changes: HashMap, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PatchApplyEndEvent { + /// Identifier for the PatchApplyBegin that finished. + pub call_id: String, + /// Captured stdout (summary printed by apply_patch). + pub stdout: String, + /// Captured stderr (parser errors, IO failures, etc.). + pub stderr: String, + /// Whether the patch was applied successfully. + pub success: bool, } #[derive(Debug, Default, Clone, Deserialize, Serialize)] @@ -478,3 +504,28 @@ pub struct Chunk { pub deleted_lines: Vec, pub inserted_lines: Vec, } + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + use super::*; + + /// Serialize Event to verify that its JSON representation has the expected + /// amount of nesting. + #[test] + fn serialize_event() { + let session_id: Uuid = uuid::uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); + let event = Event { + id: "1234".to_string(), + msg: EventMsg::SessionConfigured(SessionConfiguredEvent { + session_id, + model: "o4-mini".to_string(), + }), + }; + let serialized = serde_json::to_string(&event).unwrap(); + assert_eq!( + serialized, + r#"{"id":"1234","msg":{"type":"session_configured","session_id":"67e55044-10b1-426f-9247-bb680e5fe0c8","model":"o4-mini"}}"# + ); + } +} diff --git a/codex-rs/core/tests/live_agent.rs b/codex-rs/core/tests/live_agent.rs index c43c5c19..d6afb895 100644 --- a/codex-rs/core/tests/live_agent.rs +++ b/codex-rs/core/tests/live_agent.rs @@ -22,6 +22,8 @@ use std::time::Duration; use codex_core::Codex; use codex_core::config::Config; use codex_core::error::CodexErr; +use codex_core::protocol::AgentMessageEvent; +use codex_core::protocol::ErrorEvent; use codex_core::protocol::EventMsg; use codex_core::protocol::InputItem; use codex_core::protocol::Op; @@ -92,9 +94,11 @@ async fn live_streaming_and_prev_id_reset() { .expect("agent closed"); match ev.msg { - EventMsg::AgentMessage { .. } => saw_message_before_complete = true, + EventMsg::AgentMessage(_) => saw_message_before_complete = true, EventMsg::TaskComplete => break, - EventMsg::Error { message } => panic!("agent reported error in task1: {message}"), + EventMsg::Error(ErrorEvent { message }) => { + panic!("agent reported error in task1: {message}") + } _ => (), } } @@ -122,11 +126,15 @@ async fn live_streaming_and_prev_id_reset() { .expect("agent closed"); match &ev.msg { - EventMsg::AgentMessage { message } if message.contains("second turn succeeded") => { + EventMsg::AgentMessage(AgentMessageEvent { message }) + if message.contains("second turn succeeded") => + { got_expected = true; } EventMsg::TaskComplete => break, - EventMsg::Error { message } => panic!("agent reported error in task2: {message}"), + EventMsg::Error(ErrorEvent { message }) => { + panic!("agent reported error in task2: {message}") + } _ => (), } } @@ -171,19 +179,28 @@ async fn live_shell_function_call() { .expect("agent closed"); match ev.msg { - EventMsg::ExecCommandBegin { command, .. } => { + EventMsg::ExecCommandBegin(codex_core::protocol::ExecCommandBeginEvent { + command, + call_id: _, + cwd: _, + }) => { assert_eq!(command, vec!["echo", MARKER]); saw_begin = true; } - EventMsg::ExecCommandEnd { - stdout, exit_code, .. - } => { + EventMsg::ExecCommandEnd(codex_core::protocol::ExecCommandEndEvent { + stdout, + exit_code, + call_id: _, + stderr: _, + }) => { assert_eq!(exit_code, 0, "echo returned non‑zero exit code"); assert!(stdout.contains(MARKER)); saw_end_with_output = true; } EventMsg::TaskComplete => break, - EventMsg::Error { message } => panic!("agent error during shell test: {message}"), + EventMsg::Error(codex_core::protocol::ErrorEvent { message }) => { + panic!("agent error during shell test: {message}") + } _ => (), } } diff --git a/codex-rs/core/tests/previous_response_id.rs b/codex-rs/core/tests/previous_response_id.rs index 2c899df0..166e2be3 100644 --- a/codex-rs/core/tests/previous_response_id.rs +++ b/codex-rs/core/tests/previous_response_id.rs @@ -4,6 +4,8 @@ use codex_core::Codex; use codex_core::ModelProviderInfo; use codex_core::config::Config; use codex_core::exec::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR; +use codex_core::protocol::ErrorEvent; +use codex_core::protocol::EventMsg; use codex_core::protocol::InputItem; use codex_core::protocol::Op; use serde_json::Value; @@ -127,7 +129,7 @@ async fn keeps_previous_response_id_between_tasks() { .await .unwrap() .unwrap(); - if matches!(ev.msg, codex_core::protocol::EventMsg::TaskComplete) { + if matches!(ev.msg, EventMsg::TaskComplete) { break; } } @@ -149,8 +151,8 @@ async fn keeps_previous_response_id_between_tasks() { .unwrap() .unwrap(); match ev.msg { - codex_core::protocol::EventMsg::TaskComplete => break, - codex_core::protocol::EventMsg::Error { message } => { + EventMsg::TaskComplete => break, + EventMsg::Error(ErrorEvent { message }) => { panic!("unexpected error: {message}") } _ => (), diff --git a/codex-rs/exec/src/event_processor.rs b/codex-rs/exec/src/event_processor.rs index d43f9d59..191d616b 100644 --- a/codex-rs/exec/src/event_processor.rs +++ b/codex-rs/exec/src/event_processor.rs @@ -1,8 +1,17 @@ use chrono::Utc; use codex_common::elapsed::format_elapsed; +use codex_core::protocol::AgentMessageEvent; +use codex_core::protocol::BackgroundEventEvent; +use codex_core::protocol::ErrorEvent; use codex_core::protocol::Event; use codex_core::protocol::EventMsg; +use codex_core::protocol::ExecCommandBeginEvent; +use codex_core::protocol::ExecCommandEndEvent; use codex_core::protocol::FileChange; +use codex_core::protocol::McpToolCallBeginEvent; +use codex_core::protocol::McpToolCallEndEvent; +use codex_core::protocol::PatchApplyBeginEvent; +use codex_core::protocol::PatchApplyEndEvent; use owo_colors::OwoColorize; use owo_colors::Style; use shlex::try_join; @@ -95,11 +104,11 @@ impl EventProcessor { pub(crate) fn process_event(&mut self, event: Event) { let Event { id, msg } = event; match msg { - EventMsg::Error { message } => { + EventMsg::Error(ErrorEvent { message }) => { let prefix = "ERROR:".style(self.red); ts_println!("{prefix} {message}"); } - EventMsg::BackgroundEvent { message } => { + EventMsg::BackgroundEvent(BackgroundEventEvent { message }) => { ts_println!("{}", message.style(self.dimmed)); } EventMsg::TaskStarted => { @@ -110,15 +119,15 @@ impl EventProcessor { let msg = format!("Task complete: {id}"); ts_println!("{}", msg.style(self.bold)); } - EventMsg::AgentMessage { message } => { + EventMsg::AgentMessage(AgentMessageEvent { message }) => { let prefix = "Agent message:".style(self.bold); ts_println!("{prefix} {message}"); } - EventMsg::ExecCommandBegin { + EventMsg::ExecCommandBegin(ExecCommandBeginEvent { call_id, command, cwd, - } => { + }) => { self.call_id_to_command.insert( call_id.clone(), ExecCommandBegin { @@ -133,12 +142,12 @@ impl EventProcessor { cwd.to_string_lossy(), ); } - EventMsg::ExecCommandEnd { + EventMsg::ExecCommandEnd(ExecCommandEndEvent { call_id, stdout, stderr, exit_code, - } => { + }) => { let exec_command = self.call_id_to_command.remove(&call_id); let (duration, call) = if let Some(ExecCommandBegin { command, @@ -173,19 +182,21 @@ impl EventProcessor { } // Handle MCP tool calls (e.g. calling external functions via MCP). - EventMsg::McpToolCallBegin { + EventMsg::McpToolCallBegin(McpToolCallBeginEvent { call_id, server, tool, arguments, - } => { + }) => { // Build fully-qualified tool name: server.tool let fq_tool_name = format!("{server}.{tool}"); // Format arguments as compact JSON so they fit on one line. let args_str = arguments .as_ref() - .map(|v| serde_json::to_string(v).unwrap_or_else(|_| v.to_string())) + .map(|v: &serde_json::Value| { + serde_json::to_string(v).unwrap_or_else(|_| v.to_string()) + }) .unwrap_or_default(); let invocation = if args_str.is_empty() { @@ -208,11 +219,11 @@ impl EventProcessor { invocation.style(self.bold), ); } - EventMsg::McpToolCallEnd { + EventMsg::McpToolCallEnd(McpToolCallEndEvent { call_id, success, result, - } => { + }) => { // Retrieve start time and invocation for duration calculation and labeling. let info = self.call_id_to_tool_call.remove(&call_id); @@ -243,11 +254,11 @@ impl EventProcessor { } } } - EventMsg::PatchApplyBegin { + EventMsg::PatchApplyBegin(PatchApplyBeginEvent { call_id, auto_approved, changes, - } => { + }) => { // Store metadata so we can calculate duration later when we // receive the corresponding PatchApplyEnd event. self.call_id_to_patch.insert( @@ -321,12 +332,12 @@ impl EventProcessor { } } } - EventMsg::PatchApplyEnd { + EventMsg::PatchApplyEnd(PatchApplyEndEvent { call_id, stdout, stderr, success, - } => { + }) => { let patch_begin = self.call_id_to_patch.remove(&call_id); // Compute duration and summary label similar to exec commands. @@ -355,10 +366,10 @@ impl EventProcessor { println!("{}", line.style(self.dimmed)); } } - EventMsg::ExecApprovalRequest { .. } => { + EventMsg::ExecApprovalRequest(_) => { // Should we exit? } - EventMsg::ApplyPatchApprovalRequest { .. } => { + EventMsg::ApplyPatchApprovalRequest(_) => { // Should we exit? } _ => { diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 2f8a1a34..34534809 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -4,6 +4,7 @@ use codex_core::codex_wrapper::init_codex; use codex_core::config::Config as CodexConfig; +use codex_core::protocol::AgentMessageEvent; use codex_core::protocol::Event; use codex_core::protocol::EventMsg; use codex_core::protocol::InputItem; @@ -85,10 +86,10 @@ pub async fn run_codex_tool_session( let _ = outgoing.send(codex_event_to_notification(&event)).await; match &event.msg { - EventMsg::AgentMessage { message } => { + EventMsg::AgentMessage(AgentMessageEvent { message }) => { last_agent_message = Some(message.clone()); } - EventMsg::ExecApprovalRequest { .. } => { + EventMsg::ExecApprovalRequest(_) => { let result = CallToolResult { content: vec![CallToolResultContent::TextContent(TextContent { r#type: "text".to_string(), @@ -106,7 +107,7 @@ pub async fn run_codex_tool_session( .await; break; } - EventMsg::ApplyPatchApprovalRequest { .. } => { + EventMsg::ApplyPatchApprovalRequest(_) => { let result = CallToolResult { content: vec![CallToolResultContent::TextContent(TextContent { r#type: "text".to_string(), @@ -153,7 +154,7 @@ pub async fn run_codex_tool_session( .await; break; } - EventMsg::SessionConfigured { .. } => { + EventMsg::SessionConfigured(_) => { tracing::error!("unexpected SessionConfigured event"); } _ => {} diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index accb7305..a7ba51eb 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5,10 +5,20 @@ use std::sync::mpsc::Sender; use codex_core::codex_wrapper::init_codex; use codex_core::config::Config; +use codex_core::protocol::AgentMessageEvent; +use codex_core::protocol::AgentReasoningEvent; +use codex_core::protocol::ApplyPatchApprovalRequestEvent; +use codex_core::protocol::ErrorEvent; use codex_core::protocol::Event; use codex_core::protocol::EventMsg; +use codex_core::protocol::ExecApprovalRequestEvent; +use codex_core::protocol::ExecCommandBeginEvent; +use codex_core::protocol::ExecCommandEndEvent; use codex_core::protocol::InputItem; +use codex_core::protocol::McpToolCallBeginEvent; +use codex_core::protocol::McpToolCallEndEvent; use codex_core::protocol::Op; +use codex_core::protocol::PatchApplyBeginEvent; use crossterm::event::KeyEvent; use ratatui::buffer::Buffer; use ratatui::layout::Constraint; @@ -213,11 +223,11 @@ impl ChatWidget<'_> { .add_session_info(&self.config, event); self.request_redraw()?; } - EventMsg::AgentMessage { message } => { + EventMsg::AgentMessage(AgentMessageEvent { message }) => { self.conversation_history.add_agent_message(message); self.request_redraw()?; } - EventMsg::AgentReasoning { text } => { + EventMsg::AgentReasoning(AgentReasoningEvent { text }) => { self.conversation_history.add_agent_reasoning(text); self.request_redraw()?; } @@ -229,15 +239,15 @@ impl ChatWidget<'_> { self.bottom_pane.set_task_running(false)?; self.request_redraw()?; } - EventMsg::Error { message } => { + EventMsg::Error(ErrorEvent { message }) => { self.conversation_history.add_error(message); self.bottom_pane.set_task_running(false)?; } - EventMsg::ExecApprovalRequest { + EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent { command, cwd, reason, - } => { + }) => { let request = ApprovalRequest::Exec { id, command, @@ -246,11 +256,11 @@ impl ChatWidget<'_> { }; self.bottom_pane.push_approval_request(request)?; } - EventMsg::ApplyPatchApprovalRequest { + EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent { changes, reason, grant_root, - } => { + }) => { // ------------------------------------------------------------------ // Before we even prompt the user for approval we surface the patch // summary in the main conversation so that the dialog appears in a @@ -276,18 +286,20 @@ impl ChatWidget<'_> { self.bottom_pane.push_approval_request(request)?; self.request_redraw()?; } - EventMsg::ExecCommandBegin { - call_id, command, .. - } => { + EventMsg::ExecCommandBegin(ExecCommandBeginEvent { + call_id, + command, + cwd: _, + }) => { self.conversation_history .add_active_exec_command(call_id, command); self.request_redraw()?; } - EventMsg::PatchApplyBegin { + EventMsg::PatchApplyBegin(PatchApplyBeginEvent { call_id: _, auto_approved, changes, - } => { + }) => { // Even when a patch is auto‑approved we still display the // summary so the user can follow along. self.conversation_history @@ -297,32 +309,31 @@ impl ChatWidget<'_> { } self.request_redraw()?; } - EventMsg::ExecCommandEnd { + EventMsg::ExecCommandEnd(ExecCommandEndEvent { call_id, exit_code, stdout, stderr, - .. - } => { + }) => { self.conversation_history .record_completed_exec_command(call_id, stdout, stderr, exit_code); self.request_redraw()?; } - EventMsg::McpToolCallBegin { + EventMsg::McpToolCallBegin(McpToolCallBeginEvent { call_id, server, tool, arguments, - } => { + }) => { self.conversation_history .add_active_mcp_tool_call(call_id, server, tool, arguments); self.request_redraw()?; } - EventMsg::McpToolCallEnd { + EventMsg::McpToolCallEnd(McpToolCallEndEvent { call_id, success, result, - } => { + }) => { self.conversation_history .record_completed_mcp_tool_call(call_id, success, result); self.request_redraw()?;