mirror of
https://github.com/openai/codex.git
synced 2026-09-16 12:13:30 +00:00
fix: change EventMsg enum so every variant takes a single struct
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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(_),
|
||||
}
|
||||
)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
@@ -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 human‑readable 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 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<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"}}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
Reference in New Issue
Block a user