Minor cleanup of codex exec output (#4585)

<img width="850" height="723" alt="image"
src="https://github.com/user-attachments/assets/2ae067bf-ba6b-47bf-9ffe-d1c3f3aa1870"
/>
<img width="872" height="547" alt="image"
src="https://github.com/user-attachments/assets/9058be24-6513-4423-9dae-2d5fd4cbf162"
/>
This commit is contained in:
pakrym-oai
2025-10-02 14:17:42 -07:00
committed by GitHub
parent ed5d656fa8
commit f895d4cbb3
5 changed files with 65 additions and 146 deletions

1
codex-rs/Cargo.lock generated
View File

@@ -913,7 +913,6 @@ version = "0.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
"chrono",
"clap", "clap",
"codex-arg0", "codex-arg0",
"codex-common", "codex-common",

View File

@@ -16,7 +16,6 @@ workspace = true
[dependencies] [dependencies]
anyhow = { workspace = true } anyhow = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive"] }
codex-arg0 = { workspace = true } codex-arg0 = { workspace = true }
codex-common = { workspace = true, features = [ codex-common = { workspace = true, features = [
@@ -27,6 +26,7 @@ codex-common = { workspace = true, features = [
codex-core = { workspace = true } codex-core = { workspace = true }
codex-ollama = { workspace = true } codex-ollama = { workspace = true }
codex-protocol = { workspace = true } codex-protocol = { workspace = true }
opentelemetry-appender-tracing = { workspace = true }
owo-colors = { workspace = true } owo-colors = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
@@ -40,7 +40,6 @@ tokio = { workspace = true, features = [
] } ] }
tracing = { workspace = true, features = ["log"] } tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] } tracing-subscriber = { workspace = true, features = ["env-filter"] }
opentelemetry-appender-tracing = { workspace = true }
ts-rs = { workspace = true, features = [ ts-rs = { workspace = true, features = [
"uuid-impl", "uuid-impl",
"serde-json-impl", "serde-json-impl",
@@ -52,10 +51,10 @@ ts-rs = { workspace = true, features = [
assert_cmd = { workspace = true } assert_cmd = { workspace = true }
core_test_support = { workspace = true } core_test_support = { workspace = true }
libc = { workspace = true } libc = { workspace = true }
mcp-types = { workspace = true }
predicates = { workspace = true } predicates = { workspace = true }
pretty_assertions = { workspace = true } pretty_assertions = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }
walkdir = { workspace = true } walkdir = { workspace = true }
wiremock = { workspace = true } wiremock = { workspace = true }
mcp-types = { workspace = true }

View File

@@ -21,6 +21,8 @@ pub(crate) trait EventProcessor {
/// Handle a single event emitted by the agent. /// Handle a single event emitted by the agent.
fn process_event(&mut self, event: Event) -> CodexStatus; fn process_event(&mut self, event: Event) -> CodexStatus;
fn print_final_output(&mut self) {}
} }
pub(crate) fn handle_last_message(last_agent_message: Option<&str>, output_file: &Path) { pub(crate) fn handle_last_message(last_agent_message: Option<&str>, output_file: &Path) {

View File

@@ -2,10 +2,7 @@ use codex_common::elapsed::format_duration;
use codex_common::elapsed::format_elapsed; use codex_common::elapsed::format_elapsed;
use codex_core::config::Config; use codex_core::config::Config;
use codex_core::plan_tool::UpdatePlanArgs; use codex_core::plan_tool::UpdatePlanArgs;
use codex_core::protocol::AgentMessageDeltaEvent;
use codex_core::protocol::AgentMessageEvent; use codex_core::protocol::AgentMessageEvent;
use codex_core::protocol::AgentReasoningDeltaEvent;
use codex_core::protocol::AgentReasoningRawContentDeltaEvent;
use codex_core::protocol::AgentReasoningRawContentEvent; use codex_core::protocol::AgentReasoningRawContentEvent;
use codex_core::protocol::BackgroundEventEvent; use codex_core::protocol::BackgroundEventEvent;
use codex_core::protocol::ErrorEvent; use codex_core::protocol::ErrorEvent;
@@ -31,7 +28,6 @@ use owo_colors::OwoColorize;
use owo_colors::Style; use owo_colors::Style;
use shlex::try_join; use shlex::try_join;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Instant; use std::time::Instant;
@@ -44,7 +40,6 @@ use codex_common::create_config_summary_entries;
/// a limit so they can see the full transcript. /// a limit so they can see the full transcript.
const MAX_OUTPUT_LINES_FOR_EXEC_TOOL_CALL: usize = 20; const MAX_OUTPUT_LINES_FOR_EXEC_TOOL_CALL: usize = 20;
pub(crate) struct EventProcessorWithHumanOutput { pub(crate) struct EventProcessorWithHumanOutput {
call_id_to_command: HashMap<String, ExecCommandBegin>,
call_id_to_patch: HashMap<String, PatchApplyBegin>, call_id_to_patch: HashMap<String, PatchApplyBegin>,
// To ensure that --color=never is respected, ANSI escapes _must_ be added // To ensure that --color=never is respected, ANSI escapes _must_ be added
@@ -62,10 +57,8 @@ pub(crate) struct EventProcessorWithHumanOutput {
/// Whether to include `AgentReasoning` events in the output. /// Whether to include `AgentReasoning` events in the output.
show_agent_reasoning: bool, show_agent_reasoning: bool,
show_raw_agent_reasoning: bool, show_raw_agent_reasoning: bool,
answer_started: bool,
reasoning_started: bool,
raw_reasoning_started: bool,
last_message_path: Option<PathBuf>, last_message_path: Option<PathBuf>,
last_total_token_usage: Option<codex_core::protocol::TokenUsageInfo>,
} }
impl EventProcessorWithHumanOutput { impl EventProcessorWithHumanOutput {
@@ -74,12 +67,10 @@ impl EventProcessorWithHumanOutput {
config: &Config, config: &Config,
last_message_path: Option<PathBuf>, last_message_path: Option<PathBuf>,
) -> Self { ) -> Self {
let call_id_to_command = HashMap::new();
let call_id_to_patch = HashMap::new(); let call_id_to_patch = HashMap::new();
if with_ansi { if with_ansi {
Self { Self {
call_id_to_command,
call_id_to_patch, call_id_to_patch,
bold: Style::new().bold(), bold: Style::new().bold(),
italic: Style::new().italic(), italic: Style::new().italic(),
@@ -90,14 +81,11 @@ impl EventProcessorWithHumanOutput {
cyan: Style::new().cyan(), cyan: Style::new().cyan(),
show_agent_reasoning: !config.hide_agent_reasoning, show_agent_reasoning: !config.hide_agent_reasoning,
show_raw_agent_reasoning: config.show_raw_agent_reasoning, show_raw_agent_reasoning: config.show_raw_agent_reasoning,
answer_started: false,
reasoning_started: false,
raw_reasoning_started: false,
last_message_path, last_message_path,
last_total_token_usage: None,
} }
} else { } else {
Self { Self {
call_id_to_command,
call_id_to_patch, call_id_to_patch,
bold: Style::new(), bold: Style::new(),
italic: Style::new(), italic: Style::new(),
@@ -108,19 +96,13 @@ impl EventProcessorWithHumanOutput {
cyan: Style::new(), cyan: Style::new(),
show_agent_reasoning: !config.hide_agent_reasoning, show_agent_reasoning: !config.hide_agent_reasoning,
show_raw_agent_reasoning: config.show_raw_agent_reasoning, show_raw_agent_reasoning: config.show_raw_agent_reasoning,
answer_started: false,
reasoning_started: false,
raw_reasoning_started: false,
last_message_path, last_message_path,
last_total_token_usage: None,
} }
} }
} }
} }
struct ExecCommandBegin {
command: Vec<String>,
}
struct PatchApplyBegin { struct PatchApplyBegin {
start_time: Instant, start_time: Instant,
auto_approved: bool, auto_approved: bool,
@@ -130,9 +112,6 @@ struct PatchApplyBegin {
#[macro_export] #[macro_export]
macro_rules! ts_println { macro_rules! ts_println {
($self:ident, $($arg:tt)*) => {{ ($self:ident, $($arg:tt)*) => {{
let now = chrono::Utc::now();
let formatted = now.format("[%Y-%m-%dT%H:%M:%S]");
print!("{} ", formatted.style($self.dimmed));
println!($($arg)*); println!($($arg)*);
}}; }};
} }
@@ -141,7 +120,12 @@ impl EventProcessor for EventProcessorWithHumanOutput {
/// Print a concise summary of the effective configuration that will be used /// Print a concise summary of the effective configuration that will be used
/// for the session. This mirrors the information shown in the TUI welcome /// for the session. This mirrors the information shown in the TUI welcome
/// screen. /// screen.
fn print_config_summary(&mut self, config: &Config, prompt: &str, _: &SessionConfiguredEvent) { fn print_config_summary(
&mut self,
config: &Config,
prompt: &str,
session_configured_event: &SessionConfiguredEvent,
) {
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
ts_println!( ts_println!(
self, self,
@@ -149,7 +133,11 @@ impl EventProcessor for EventProcessorWithHumanOutput {
VERSION VERSION
); );
let entries = create_config_summary_entries(config); let mut entries = create_config_summary_entries(config);
entries.push((
"session id",
session_configured_event.session_id.to_string(),
));
for (key, value) in entries { for (key, value) in entries {
println!("{} {}", format!("{key}:").style(self.bold), value); println!("{} {}", format!("{key}:").style(self.bold), value);
@@ -160,12 +148,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
// Echo the prompt that will be sent to the agent so it is visible in the // Echo the prompt that will be sent to the agent so it is visible in the
// transcript/logs before any events come in. Note the prompt may have been // transcript/logs before any events come in. Note the prompt may have been
// read from stdin, so it may not be visible in the terminal otherwise. // read from stdin, so it may not be visible in the terminal otherwise.
ts_println!( ts_println!(self, "{}\n{}", "user".style(self.cyan), prompt);
self,
"{}\n{}",
"User instructions:".style(self.bold).style(self.cyan),
prompt
);
} }
fn process_event(&mut self, event: Event) -> CodexStatus { fn process_event(&mut self, event: Event) -> CodexStatus {
@@ -191,126 +174,49 @@ impl EventProcessor for EventProcessorWithHumanOutput {
return CodexStatus::InitiateShutdown; return CodexStatus::InitiateShutdown;
} }
EventMsg::TokenCount(ev) => { EventMsg::TokenCount(ev) => {
if let Some(usage_info) = ev.info { self.last_total_token_usage = ev.info;
ts_println!(
self,
"tokens used: {}",
format_with_separators(usage_info.total_token_usage.blended_total())
);
}
}
EventMsg::AgentMessageDelta(AgentMessageDeltaEvent { delta }) => {
if !self.answer_started {
ts_println!(self, "{}\n", "codex".style(self.italic).style(self.magenta));
self.answer_started = true;
}
print!("{delta}");
#[expect(clippy::expect_used)]
std::io::stdout().flush().expect("could not flush stdout");
}
EventMsg::AgentReasoningDelta(AgentReasoningDeltaEvent { delta }) => {
if !self.show_agent_reasoning {
return CodexStatus::Running;
}
if !self.reasoning_started {
ts_println!(
self,
"{}\n",
"thinking".style(self.italic).style(self.magenta),
);
self.reasoning_started = true;
}
print!("{delta}");
#[expect(clippy::expect_used)]
std::io::stdout().flush().expect("could not flush stdout");
} }
EventMsg::AgentReasoningSectionBreak(_) => { EventMsg::AgentReasoningSectionBreak(_) => {
if !self.show_agent_reasoning { if !self.show_agent_reasoning {
return CodexStatus::Running; return CodexStatus::Running;
} }
println!(); println!();
#[expect(clippy::expect_used)]
std::io::stdout().flush().expect("could not flush stdout");
} }
EventMsg::AgentReasoningRawContent(AgentReasoningRawContentEvent { text }) => { EventMsg::AgentReasoningRawContent(AgentReasoningRawContentEvent { text }) => {
if !self.show_raw_agent_reasoning { if self.show_raw_agent_reasoning {
return CodexStatus::Running;
}
if !self.raw_reasoning_started {
print!("{text}");
#[expect(clippy::expect_used)]
std::io::stdout().flush().expect("could not flush stdout");
} else {
println!();
self.raw_reasoning_started = false;
}
}
EventMsg::AgentReasoningRawContentDelta(AgentReasoningRawContentDeltaEvent {
delta,
}) => {
if !self.show_raw_agent_reasoning {
return CodexStatus::Running;
}
if !self.raw_reasoning_started {
self.raw_reasoning_started = true;
}
print!("{delta}");
#[expect(clippy::expect_used)]
std::io::stdout().flush().expect("could not flush stdout");
}
EventMsg::AgentMessage(AgentMessageEvent { message }) => {
// if answer_started is false, this means we haven't received any
// delta. Thus, we need to print the message as a new answer.
if !self.answer_started {
ts_println!( ts_println!(
self, self,
"{}\n{}", "{}\n{}",
"codex".style(self.italic).style(self.magenta), "thinking".style(self.italic).style(self.magenta),
message, text,
); );
} else {
println!();
self.answer_started = false;
} }
} }
EventMsg::ExecCommandBegin(ExecCommandBeginEvent { EventMsg::AgentMessage(AgentMessageEvent { message }) => {
call_id,
command,
cwd,
parsed_cmd: _,
}) => {
self.call_id_to_command.insert(
call_id,
ExecCommandBegin {
command: command.clone(),
},
);
ts_println!( ts_println!(
self, self,
"{} {} in {}", "{}\n{}",
"exec".style(self.magenta), "codex".style(self.italic).style(self.magenta),
message,
);
}
EventMsg::ExecCommandBegin(ExecCommandBeginEvent { command, cwd, .. }) => {
print!(
"{}\n{} in {}",
"exec".style(self.italic).style(self.magenta),
escape_command(&command).style(self.bold), escape_command(&command).style(self.bold),
cwd.to_string_lossy(), cwd.to_string_lossy(),
); );
} }
EventMsg::ExecCommandOutputDelta(_) => {} EventMsg::ExecCommandOutputDelta(_) => {}
EventMsg::ExecCommandEnd(ExecCommandEndEvent { EventMsg::ExecCommandEnd(ExecCommandEndEvent {
call_id,
aggregated_output, aggregated_output,
duration, duration,
exit_code, exit_code,
.. ..
}) => { }) => {
let exec_command = self.call_id_to_command.remove(&call_id); let duration = format!(" in {}", format_duration(duration));
let (duration, call) = if let Some(ExecCommandBegin { command, .. }) = exec_command
{
(
format!(" in {}", format_duration(duration)),
format!("{}", escape_command(&command).style(self.bold)),
)
} else {
("".to_string(), format!("exec('{call_id}')"))
};
let truncated_output = aggregated_output let truncated_output = aggregated_output
.lines() .lines()
@@ -319,11 +225,11 @@ impl EventProcessor for EventProcessorWithHumanOutput {
.join("\n"); .join("\n");
match exit_code { match exit_code {
0 => { 0 => {
let title = format!("{call} succeeded{duration}:"); let title = format!(" succeeded{duration}:");
ts_println!(self, "{}", title.style(self.green)); ts_println!(self, "{}", title.style(self.green));
} }
_ => { _ => {
let title = format!("{call} exited {exit_code}{duration}:"); let title = format!(" exited {exit_code}{duration}:");
ts_println!(self, "{}", title.style(self.red)); ts_println!(self, "{}", title.style(self.red));
} }
} }
@@ -391,9 +297,8 @@ impl EventProcessor for EventProcessorWithHumanOutput {
ts_println!( ts_println!(
self, self,
"{} auto_approved={}:", "{}",
"apply_patch".style(self.magenta), "file update".style(self.magenta).style(self.italic),
auto_approved,
); );
// Pretty-print the patch summary with colored diff markers so // Pretty-print the patch summary with colored diff markers so
@@ -492,7 +397,11 @@ impl EventProcessor for EventProcessorWithHumanOutput {
} }
} }
EventMsg::TurnDiff(TurnDiffEvent { unified_diff }) => { EventMsg::TurnDiff(TurnDiffEvent { unified_diff }) => {
ts_println!(self, "{}", "turn diff:".style(self.magenta)); ts_println!(
self,
"{}",
"file update:".style(self.magenta).style(self.italic)
);
println!("{unified_diff}"); println!("{unified_diff}");
} }
EventMsg::ExecApprovalRequest(_) => { EventMsg::ExecApprovalRequest(_) => {
@@ -503,17 +412,12 @@ impl EventProcessor for EventProcessorWithHumanOutput {
} }
EventMsg::AgentReasoning(agent_reasoning_event) => { EventMsg::AgentReasoning(agent_reasoning_event) => {
if self.show_agent_reasoning { if self.show_agent_reasoning {
if !self.reasoning_started { ts_println!(
ts_println!( self,
self, "{}\n{}",
"{}\n{}", "thinking".style(self.italic).style(self.magenta),
"codex".style(self.italic).style(self.magenta), agent_reasoning_event.text,
agent_reasoning_event.text, );
);
} else {
println!();
self.reasoning_started = false;
}
} }
} }
EventMsg::SessionConfigured(session_configured_event) => { EventMsg::SessionConfigured(session_configured_event) => {
@@ -604,9 +508,23 @@ impl EventProcessor for EventProcessorWithHumanOutput {
EventMsg::UserMessage(_) => {} EventMsg::UserMessage(_) => {}
EventMsg::EnteredReviewMode(_) => {} EventMsg::EnteredReviewMode(_) => {}
EventMsg::ExitedReviewMode(_) => {} EventMsg::ExitedReviewMode(_) => {}
EventMsg::AgentMessageDelta(_) => {}
EventMsg::AgentReasoningDelta(_) => {}
EventMsg::AgentReasoningRawContentDelta(_) => {}
} }
CodexStatus::Running CodexStatus::Running
} }
fn print_final_output(&mut self) {
if let Some(usage_info) = &self.last_total_token_usage {
ts_println!(
self,
"{}\n{}",
"tokens used".style(self.magenta).style(self.italic),
format_with_separators(usage_info.total_token_usage.blended_total())
);
}
}
} }
fn escape_command(command: &[String]) -> String { fn escape_command(command: &[String]) -> String {

View File

@@ -364,6 +364,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
} }
} }
} }
event_processor.print_final_output();
if error_seen { if error_seen {
std::process::exit(1); std::process::exit(1);
} }