diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index a5a88ee9..5301f022 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -22,8 +22,8 @@ use crate::error::Result; use crate::error::SandboxErr; use crate::protocol::Event; use crate::protocol::EventMsg; -use crate::protocol::ExecCommandStderrDeltaEvent; -use crate::protocol::ExecCommandStdoutDeltaEvent; +use crate::protocol::ExecCommandOutputDeltaEvent; +use crate::protocol::ExecOutputStream; use crate::protocol::SandboxPolicy; use crate::seatbelt::spawn_command_under_seatbelt; use crate::spawn::StdioPolicy; @@ -359,17 +359,15 @@ async fn read_capped( if let Some(stream) = &stream { let chunk = tmp[..n].to_vec(); - let msg = if is_stderr { - EventMsg::ExecCommandStderrDelta(ExecCommandStderrDeltaEvent { - call_id: stream.call_id.clone(), - chunk: ByteBuf::from(chunk), - }) - } else { - EventMsg::ExecCommandStdoutDelta(ExecCommandStdoutDeltaEvent { - call_id: stream.call_id.clone(), - chunk: ByteBuf::from(chunk), - }) - }; + let msg = EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent { + call_id: stream.call_id.clone(), + stream: if is_stderr { + ExecOutputStream::Stderr + } else { + ExecOutputStream::Stdout + }, + chunk: ByteBuf::from(chunk), + }); let event = Event { id: stream.sub_id.clone(), msg, diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index 8d26eda9..3be0c92c 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -324,11 +324,8 @@ pub enum EventMsg { /// Notification that the server is about to execute a command. ExecCommandBegin(ExecCommandBeginEvent), - /// Incremental chunk of stdout from a running command. - ExecCommandStdoutDelta(ExecCommandStdoutDeltaEvent), - - /// Incremental chunk of stderr from a running command. - ExecCommandStderrDelta(ExecCommandStderrDeltaEvent), + /// Incremental chunk of output from a running command. + ExecCommandOutputDelta(ExecCommandOutputDeltaEvent), ExecCommandEnd(ExecCommandEndEvent), @@ -484,19 +481,19 @@ pub struct ExecCommandEndEvent { } #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ExecCommandStdoutDeltaEvent { - /// Identifier for the ExecCommandBegin that produced this chunk. - pub call_id: String, - /// Raw stdout bytes (may not be valid UTF-8). - #[serde(with = "serde_bytes")] - pub chunk: ByteBuf, +#[serde(rename_all = "snake_case")] +pub enum ExecOutputStream { + Stdout, + Stderr, } #[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ExecCommandStderrDeltaEvent { +pub struct ExecCommandOutputDeltaEvent { /// Identifier for the ExecCommandBegin that produced this chunk. pub call_id: String, - /// Raw stderr bytes (may not be valid UTF-8). + /// Which stream produced this chunk. + pub stream: ExecOutputStream, + /// Raw bytes from the stream (may not be valid UTF-8). #[serde(with = "serde_bytes")] pub chunk: ByteBuf, } diff --git a/codex-rs/core/tests/exec_stream_events.rs b/codex-rs/core/tests/exec_stream_events.rs index 059b69a0..50f6888f 100644 --- a/codex-rs/core/tests/exec_stream_events.rs +++ b/codex-rs/core/tests/exec_stream_events.rs @@ -11,15 +11,19 @@ use codex_core::exec::StdoutStream; use codex_core::exec::process_exec_tool_call; use codex_core::protocol::Event; use codex_core::protocol::EventMsg; -use codex_core::protocol::ExecCommandStderrDeltaEvent; -use codex_core::protocol::ExecCommandStdoutDeltaEvent; +use codex_core::protocol::ExecCommandOutputDeltaEvent; +use codex_core::protocol::ExecOutputStream; use codex_core::protocol::SandboxPolicy; use tokio::sync::Notify; fn collect_stdout_events(rx: Receiver) -> Vec { let mut out = Vec::new(); while let Ok(ev) = rx.try_recv() { - if let EventMsg::ExecCommandStdoutDelta(ExecCommandStdoutDeltaEvent { chunk, .. }) = ev.msg + if let EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent { + stream: ExecOutputStream::Stdout, + chunk, + .. + }) = ev.msg { out.extend_from_slice(&chunk); } @@ -126,7 +130,11 @@ async fn test_exec_stderr_stream_events_echo() { // Collect only stderr delta events let mut err = Vec::new(); while let Ok(ev) = rx.try_recv() { - if let EventMsg::ExecCommandStderrDelta(ExecCommandStderrDeltaEvent { chunk, .. }) = ev.msg + if let EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent { + stream: ExecOutputStream::Stderr, + chunk, + .. + }) = ev.msg { err.extend_from_slice(&chunk); } 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 05643c62..7393ab72 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -239,8 +239,7 @@ impl EventProcessor for EventProcessorWithHumanOutput { cwd.to_string_lossy(), ); } - EventMsg::ExecCommandStdoutDelta(_) => {} - EventMsg::ExecCommandStderrDelta(_) => {} + EventMsg::ExecCommandOutputDelta(_) => {} EventMsg::ExecCommandEnd(ExecCommandEndEvent { call_id, stdout, diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 94d47f74..d489ffe0 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -258,8 +258,7 @@ async fn run_codex_tool_session_inner( | EventMsg::McpToolCallBegin(_) | EventMsg::McpToolCallEnd(_) | EventMsg::ExecCommandBegin(_) - | EventMsg::ExecCommandStdoutDelta(_) - | EventMsg::ExecCommandStderrDelta(_) + | EventMsg::ExecCommandOutputDelta(_) | EventMsg::ExecCommandEnd(_) | EventMsg::BackgroundEvent(_) | EventMsg::PatchApplyBegin(_) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index d6454c82..374a4b56 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -374,7 +374,7 @@ impl ChatWidget<'_> { ); self.add_to_history(HistoryCell::new_active_exec_command(command)); } - EventMsg::ExecCommandStdoutDelta(_) => {} + EventMsg::ExecCommandOutputDelta(_) => {} EventMsg::PatchApplyBegin(PatchApplyBeginEvent { call_id: _, auto_approved,