mirror of
https://github.com/openai/codex.git
synced 2026-04-24 22:54:54 +00:00
chore: rework unified exec events (#7775)
This commit is contained in:
@@ -566,6 +566,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
||||
EventMsg::WebSearchBegin(_)
|
||||
| EventMsg::ExecApprovalRequest(_)
|
||||
| EventMsg::ApplyPatchApprovalRequest(_)
|
||||
| EventMsg::TerminalInteraction(_)
|
||||
| EventMsg::ExecCommandOutputDelta(_)
|
||||
| EventMsg::GetHistoryEntryResponse(_)
|
||||
| EventMsg::McpListToolsResponse(_)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user