feat: add --json flag to codex exec

This commit is contained in:
Michael Bolin
2025-07-17 14:41:54 -07:00
parent bb30ab9e96
commit 65395df3ec
4 changed files with 66 additions and 9 deletions

View File

@@ -51,6 +51,10 @@ pub struct Cli {
#[arg(long = "color", value_enum, default_value_t = Color::Auto)]
pub color: Color,
/// Print events to stdout as JSONL.
#[arg(long = "json", default_value_t = false)]
pub json: bool,
/// Specifies file where the last message from the agent should be written.
#[arg(long = "output-last-message")]
pub last_message_file: Option<PathBuf>,

View File

@@ -30,7 +30,15 @@ use std::time::Instant;
/// a limit so they can see the full transcript.
const MAX_OUTPUT_LINES_FOR_EXEC_TOOL_CALL: usize = 20;
pub(crate) struct EventProcessor {
pub(crate) trait EventProcessor {
/// Print summary of effective configuration and user prompt.
fn print_config_summary(&mut self, config: &Config, prompt: &str);
/// Handle a single event emitted by the agent.
fn process_event(&mut self, event: Event);
}
pub(crate) struct EventProcessorWithHumanOutput {
call_id_to_command: HashMap<String, ExecCommandBegin>,
call_id_to_patch: HashMap<String, PatchApplyBegin>,
@@ -57,7 +65,7 @@ pub(crate) struct EventProcessor {
reasoning_started: bool,
}
impl EventProcessor {
impl EventProcessorWithHumanOutput {
pub(crate) fn create_with_ansi(with_ansi: bool, config: &Config) -> Self {
let call_id_to_command = HashMap::new();
let call_id_to_patch = HashMap::new();
@@ -128,11 +136,11 @@ macro_rules! ts_println {
}};
}
impl EventProcessor {
impl EventProcessor for EventProcessorWithHumanOutput {
/// Print a concise summary of the effective configuration that will be used
/// for the session. This mirrors the information shown in the TUI welcome
/// screen.
pub(crate) fn print_config_summary(&mut self, config: &Config, prompt: &str) {
fn print_config_summary(&mut self, config: &Config, prompt: &str) {
const VERSION: &str = env!("CARGO_PKG_VERSION");
ts_println!(
self,
@@ -177,7 +185,7 @@ impl EventProcessor {
);
}
pub(crate) fn process_event(&mut self, event: Event) {
fn process_event(&mut self, event: Event) {
let Event { id: _, msg } = event;
match msg {
EventMsg::Error(ErrorEvent { message }) => {

View File

@@ -0,0 +1,33 @@
use codex_core::config::Config;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
use crate::event_processor::EventProcessor;
pub(crate) struct EventProcessorWithJsonOutput;
impl EventProcessorWithJsonOutput {
pub fn new() -> Self {
Self {}
}
}
impl EventProcessor for EventProcessorWithJsonOutput {
fn print_config_summary(&mut self, _config: &Config, _prompt: &str) {
let _ = _config;
// Intentionally left blank human summary not needed in JSON mode.
}
fn process_event(&mut self, event: Event) {
match event.msg {
EventMsg::AgentMessageDelta(_) | EventMsg::AgentReasoningDelta(_) => {
// Suppress streaming events in JSON mode.
}
_ => {
if let Ok(line) = serde_json::to_string(&event) {
println!("{line}");
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
mod cli;
mod event_processor;
mod event_processor_with_json_output;
use std::io::IsTerminal;
use std::io::Read;
@@ -19,12 +20,15 @@ use codex_core::protocol::InputItem;
use codex_core::protocol::Op;
use codex_core::protocol::TaskCompleteEvent;
use codex_core::util::is_inside_git_repo;
use event_processor::EventProcessor;
use event_processor::EventProcessorWithHumanOutput;
use event_processor_with_json_output::EventProcessorWithJsonOutput;
use tracing::debug;
use tracing::error;
use tracing::info;
use tracing_subscriber::EnvFilter;
use crate::event_processor::EventProcessor;
pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()> {
let Cli {
images,
@@ -36,6 +40,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
skip_git_repo_check,
color,
last_message_file,
json: json_mode,
sandbox_mode: sandbox_mode_cli_arg,
prompt,
config_overrides,
@@ -115,9 +120,16 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
};
let config = Config::load_with_cli_overrides(cli_kv_overrides, overrides)?;
let mut event_processor = EventProcessor::create_with_ansi(stdout_with_ansi, &config);
// Print the effective configuration and prompt so users can see what Codex
// is using.
let mut event_processor: Box<dyn EventProcessor> = if json_mode {
Box::new(EventProcessorWithJsonOutput::new())
} else {
Box::new(EventProcessorWithHumanOutput::create_with_ansi(
stdout_with_ansi,
&config,
))
};
// Human processor prints summary; JSON processor is a no-op.
event_processor.print_config_summary(&config, &prompt);
if !skip_git_repo_check && !is_inside_git_repo(&config) {