chore: rework unified exec events (#7775)

This commit is contained in:
jif-oai
2025-12-10 10:30:38 +00:00
committed by GitHub
parent d1c5db5796
commit 0ad54982ae
20 changed files with 876 additions and 174 deletions

View File

@@ -566,6 +566,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
EventMsg::WebSearchBegin(_)
| EventMsg::ExecApprovalRequest(_)
| EventMsg::ApplyPatchApprovalRequest(_)
| EventMsg::TerminalInteraction(_)
| EventMsg::ExecCommandOutputDelta(_)
| EventMsg::GetHistoryEntryResponse(_)
| EventMsg::McpListToolsResponse(_)

View File

@@ -48,6 +48,7 @@ use codex_core::protocol::PatchApplyEndEvent;
use codex_core::protocol::SessionConfiguredEvent;
use codex_core::protocol::TaskCompleteEvent;
use codex_core::protocol::TaskStartedEvent;
use codex_core::protocol::TerminalInteractionEvent;
use codex_core::protocol::WebSearchEndEvent;
use codex_protocol::plan_tool::StepStatus;
use codex_protocol::plan_tool::UpdatePlanArgs;
@@ -72,6 +73,7 @@ pub struct EventProcessorWithJsonOutput {
struct RunningCommand {
command: String,
item_id: String,
aggregated_output: String,
}
#[derive(Debug, Clone)]
@@ -109,6 +111,10 @@ impl EventProcessorWithJsonOutput {
EventMsg::AgentReasoning(ev) => self.handle_reasoning_event(ev),
EventMsg::ExecCommandBegin(ev) => self.handle_exec_command_begin(ev),
EventMsg::ExecCommandEnd(ev) => self.handle_exec_command_end(ev),
EventMsg::TerminalInteraction(ev) => self.handle_terminal_interaction(ev),
EventMsg::ExecCommandOutputDelta(ev) => {
self.handle_output_chunk(&ev.call_id, &ev.chunk)
}
EventMsg::McpToolCallBegin(ev) => self.handle_mcp_tool_call_begin(ev),
EventMsg::McpToolCallEnd(ev) => self.handle_mcp_tool_call_end(ev),
EventMsg::PatchApplyBegin(ev) => self.handle_patch_apply_begin(ev),
@@ -172,6 +178,16 @@ impl EventProcessorWithJsonOutput {
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent { item })]
}
fn handle_output_chunk(&mut self, _call_id: &str, _chunk: &[u8]) -> Vec<ThreadEvent> {
//TODO see how we want to process them
vec![]
}
fn handle_terminal_interaction(&mut self, _ev: &TerminalInteractionEvent) -> Vec<ThreadEvent> {
//TODO see how we want to process them
vec![]
}
fn handle_agent_message(&self, payload: &AgentMessageEvent) -> Vec<ThreadEvent> {
let item = ThreadItem {
id: self.get_next_item_id(),
@@ -214,6 +230,7 @@ impl EventProcessorWithJsonOutput {
RunningCommand {
command: command_string.clone(),
item_id: item_id.clone(),
aggregated_output: String::new(),
},
);
@@ -366,7 +383,11 @@ impl EventProcessorWithJsonOutput {
}
fn handle_exec_command_end(&mut self, ev: &ExecCommandEndEvent) -> Vec<ThreadEvent> {
let Some(RunningCommand { command, item_id }) = self.running_commands.remove(&ev.call_id)
let Some(RunningCommand {
command,
item_id,
aggregated_output,
}) = self.running_commands.remove(&ev.call_id)
else {
warn!(
call_id = ev.call_id,
@@ -379,12 +400,17 @@ impl EventProcessorWithJsonOutput {
} else {
CommandExecutionStatus::Failed
};
let aggregated_output = if ev.aggregated_output.is_empty() {
aggregated_output
} else {
ev.aggregated_output.clone()
};
let item = ThreadItem {
id: item_id,
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
command,
aggregated_output: ev.aggregated_output.clone(),
aggregated_output,
exit_code: Some(ev.exit_code),
status,
}),
@@ -455,6 +481,21 @@ impl EventProcessorWithJsonOutput {
items.push(ThreadEvent::ItemCompleted(ItemCompletedEvent { item }));
}
if !self.running_commands.is_empty() {
for (_, running) in self.running_commands.drain() {
let item = ThreadItem {
id: running.item_id,
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
command: running.command,
aggregated_output: running.aggregated_output,
exit_code: None,
status: CommandExecutionStatus::Completed,
}),
};
items.push(ThreadEvent::ItemCompleted(ItemCompletedEvent { item }));
}
}
if let Some(error) = self.last_critical_error.take() {
items.push(ThreadEvent::TurnFailed(TurnFailedEvent { error }));
} else {

View File

@@ -48,6 +48,8 @@ use codex_protocol::plan_tool::PlanItemArg;
use codex_protocol::plan_tool::StepStatus;
use codex_protocol::plan_tool::UpdatePlanArgs;
use codex_protocol::protocol::CodexErrorInfo;
use codex_protocol::protocol::ExecCommandOutputDeltaEvent;
use codex_protocol::protocol::ExecOutputStream;
use mcp_types::CallToolResult;
use mcp_types::ContentBlock;
use mcp_types::TextContent;
@@ -699,6 +701,93 @@ fn exec_command_end_success_produces_completed_command_item() {
);
}
#[test]
fn command_execution_output_delta_updates_item_progress() {
let mut ep = EventProcessorWithJsonOutput::new(None);
let command = vec![
"bash".to_string(),
"-lc".to_string(),
"echo delta".to_string(),
];
let cwd = std::env::current_dir().unwrap();
let parsed_cmd = Vec::new();
let begin = event(
"d1",
EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
call_id: "delta-1".to_string(),
process_id: Some("42".to_string()),
turn_id: "turn-1".to_string(),
command: command.clone(),
cwd: cwd.clone(),
parsed_cmd: parsed_cmd.clone(),
source: ExecCommandSource::Agent,
interaction_input: None,
}),
);
let out_begin = ep.collect_thread_events(&begin);
assert_eq!(
out_begin,
vec![ThreadEvent::ItemStarted(ItemStartedEvent {
item: ThreadItem {
id: "item_0".to_string(),
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
command: "bash -lc 'echo delta'".to_string(),
aggregated_output: String::new(),
exit_code: None,
status: CommandExecutionStatus::InProgress,
}),
},
})]
);
let delta = event(
"d2",
EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent {
call_id: "delta-1".to_string(),
stream: ExecOutputStream::Stdout,
chunk: b"partial output\n".to_vec(),
}),
);
let out_delta = ep.collect_thread_events(&delta);
assert_eq!(out_delta, Vec::<ThreadEvent>::new());
let end = event(
"d3",
EventMsg::ExecCommandEnd(ExecCommandEndEvent {
call_id: "delta-1".to_string(),
process_id: Some("42".to_string()),
turn_id: "turn-1".to_string(),
command,
cwd,
parsed_cmd,
source: ExecCommandSource::Agent,
interaction_input: None,
stdout: String::new(),
stderr: String::new(),
aggregated_output: String::new(),
exit_code: 0,
duration: Duration::from_millis(3),
formatted_output: String::new(),
}),
);
let out_end = ep.collect_thread_events(&end);
assert_eq!(
out_end,
vec![ThreadEvent::ItemCompleted(ItemCompletedEvent {
item: ThreadItem {
id: "item_0".to_string(),
details: ThreadItemDetails::CommandExecution(CommandExecutionItem {
command: "bash -lc 'echo delta'".to_string(),
aggregated_output: String::new(),
exit_code: Some(0),
status: CommandExecutionStatus::Completed,
}),
},
})]
);
}
#[test]
fn exec_command_end_failure_produces_failed_command_item() {
let mut ep = EventProcessorWithJsonOutput::new(None);