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.
This commit is contained in:
Michael Bolin
2025-05-13 20:44:42 -07:00
committed by GitHub
parent e6c206d19d
commit a5f3a34827
9 changed files with 288 additions and 183 deletions

View File

@@ -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<String>) {
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<Session>, sub_id: String, input: Vec<InputItem>) {
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;

View File

@@ -24,7 +24,7 @@ pub async fn init_codex(config: Config) -> anyhow::Result<(Codex, Event, Arc<Not
&event,
Event {
id: _id,
msg: EventMsg::SessionConfigured { .. },
msg: EventMsg::SessionConfigured(_),
}
)
{

View File

@@ -7,6 +7,8 @@ use crate::models::FunctionCallOutputPayload;
use crate::models::ResponseInputItem;
use crate::protocol::Event;
use crate::protocol::EventMsg;
use crate::protocol::McpToolCallBeginEvent;
use crate::protocol::McpToolCallEndEvent;
/// Handles the specified tool call dispatches the appropriate
/// `McpToolCallBegin` and `McpToolCallEnd` events to the `Session`.
@@ -39,12 +41,12 @@ pub(crate) async fn handle_mcp_tool_call(
}
};
let tool_call_begin_event = EventMsg::McpToolCallBegin {
let tool_call_begin_event = EventMsg::McpToolCallBegin(McpToolCallBeginEvent {
call_id: call_id.clone(),
server: server.clone(),
tool: tool_name.clone(),
arguments: arguments_value.clone(),
};
});
notify_mcp_tool_call_event(sess, sub_id, tool_call_begin_event).await;
// Perform the tool call.
@@ -53,29 +55,29 @@ pub(crate) async fn handle_mcp_tool_call(
.await
{
Ok(result) => (
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");
};

View File

@@ -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<serde_json::Value>,
},
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<CallToolResult>,
},
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<String>,
/// 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<String>,
/// The command's working directory.
cwd: PathBuf,
/// Optional humanreadable reason for the approval (e.g. retry without
/// sandbox).
#[serde(skip_serializing_if = "Option::is_none")]
reason: Option<String>,
},
ExecApprovalRequest(ExecApprovalRequestEvent),
ApplyPatchApprovalRequest {
changes: HashMap<PathBuf, FileChange>,
/// Optional explanatory reason (e.g. request for extra write access).
#[serde(skip_serializing_if = "Option::is_none")]
reason: Option<String>,
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<PathBuf>,
},
BackgroundEvent {
message: String,
},
BackgroundEvent(BackgroundEventEvent),
/// Notification that the agent is about to apply a code patch. Mirrors
/// `ExecCommandBegin` so frontends 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<PathBuf, FileChange>,
},
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<serde_json::Value>,
}
#[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<CallToolResult>,
}
#[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<String>,
/// 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<String>,
/// 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<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ApplyPatchApprovalRequestEvent {
pub changes: HashMap<PathBuf, FileChange>,
/// Optional explanatory reason (e.g. request for extra write access).
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
/// 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<PathBuf>,
}
#[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<PathBuf, FileChange>,
}
#[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<String>,
pub inserted_lines: Vec<String>,
}
#[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"}}"#
);
}
}

View File

@@ -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 nonzero 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}")
}
_ => (),
}
}

View File

@@ -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}")
}
_ => (),

View File

@@ -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?
}
_ => {

View File

@@ -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");
}
_ => {}

View File

@@ -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 autoapproved 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()?;