mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Consolidate command execution output into aggregated_output (#50402)
## What changed - Remove `stdout`, `stderr`, and `formatted_output` from `CommandExecutionItem` and `ExecCommandEndEvent`, retaining `aggregated_output` as the output field. - Update command event producers, legacy conversion, rollout traces, and the precomputed schema. Use aggregated output for terminal trace results. - Clear `interaction_input` in emitted and migrated command completion items. ## Testing Update command execution and trace tests to use aggregated output, and assert that legacy rollout migration preserves `aggregatedOutput`. GitOrigin-RevId: ed59827d4a5ec9a767084ee4feac72c2042440f2
This commit is contained in:
BIN
Binary file not shown.
@@ -2408,12 +2408,9 @@ mod tests {
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
status: CoreCommandExecutionStatus::Completed,
|
||||
stdout: Some("hello world\n".to_string()),
|
||||
stderr: Some(String::new()),
|
||||
aggregated_output: Some("hello world\n".to_string()),
|
||||
exit_code: Some(0),
|
||||
duration: Some(Duration::from_millis(12)),
|
||||
formatted_output: Some("hello world\n".to_string()),
|
||||
});
|
||||
let events = vec![
|
||||
EventMsg::TurnStarted(TurnStartedEvent {
|
||||
@@ -2456,12 +2453,9 @@ mod tests {
|
||||
parsed_cmd,
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
stdout: "hello world\n".to_string(),
|
||||
stderr: String::new(),
|
||||
aggregated_output: "hello world\n".to_string(),
|
||||
exit_code: 0,
|
||||
duration: Duration::from_millis(12),
|
||||
formatted_output: "hello world\n".to_string(),
|
||||
status: CoreExecCommandStatus::Completed,
|
||||
}),
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
@@ -3145,12 +3139,9 @@ mod tests {
|
||||
}],
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
stdout: String::new(),
|
||||
stderr: String::new(),
|
||||
aggregated_output: "hello world\n".into(),
|
||||
exit_code: 0,
|
||||
duration: Duration::from_millis(12),
|
||||
formatted_output: String::new(),
|
||||
status: CoreExecCommandStatus::Completed,
|
||||
}),
|
||||
EventMsg::McpToolCallEnd(McpToolCallEndEvent {
|
||||
@@ -3439,12 +3430,9 @@ mod tests {
|
||||
parsed_cmd: vec![ParsedCommand::Unknown { cmd: "ls".into() }],
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
stdout: String::new(),
|
||||
stderr: "exec command rejected by user".into(),
|
||||
aggregated_output: "exec command rejected by user".into(),
|
||||
exit_code: -1,
|
||||
duration: Duration::ZERO,
|
||||
formatted_output: String::new(),
|
||||
status: CoreExecCommandStatus::Declined,
|
||||
}),
|
||||
EventMsg::PatchApplyEnd(PatchApplyEndEvent {
|
||||
@@ -3833,12 +3821,9 @@ mod tests {
|
||||
}],
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
stdout: "done\n".into(),
|
||||
stderr: String::new(),
|
||||
aggregated_output: "done\n".into(),
|
||||
exit_code: 0,
|
||||
duration: Duration::from_millis(5),
|
||||
formatted_output: "done\n".into(),
|
||||
status: CoreExecCommandStatus::Completed,
|
||||
}),
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
@@ -3943,12 +3928,9 @@ mod tests {
|
||||
}],
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
stdout: "done\n".into(),
|
||||
stderr: String::new(),
|
||||
aggregated_output: "done\n".into(),
|
||||
exit_code: 0,
|
||||
duration: Duration::from_millis(5),
|
||||
formatted_output: "done\n".into(),
|
||||
status: CoreExecCommandStatus::Completed,
|
||||
}),
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
|
||||
@@ -3330,12 +3330,9 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
|
||||
source: CoreExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
status: CoreCommandExecutionStatus::Completed,
|
||||
stdout: Some("done\n".to_string()),
|
||||
stderr: Some(String::new()),
|
||||
aggregated_output: Some("done\n".to_string()),
|
||||
exit_code: Some(0),
|
||||
duration: Some(Duration::from_millis(5)),
|
||||
formatted_output: Some("done\n".to_string()),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -1710,7 +1710,7 @@ async fn user_shell_commands_do_not_inherit_managed_network_proxy() -> anyhow::R
|
||||
let event = rx.recv().await.expect("channel open");
|
||||
if let EventMsg::ExecCommandEnd(event) = event.msg {
|
||||
assert_eq!(event.exit_code, 0);
|
||||
assert_eq!(event.stdout.trim(), "not-set");
|
||||
assert_eq!(event.aggregated_output.trim(), "not-set");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ use crate::session::TurnInput;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::shell::Shell;
|
||||
use crate::state::TaskKind;
|
||||
use crate::tools::format_exec_output_str;
|
||||
use crate::tools::runtimes::RuntimePathPrepends;
|
||||
#[cfg(unix)]
|
||||
use crate::tools::runtimes::apply_package_path_prepend;
|
||||
@@ -200,12 +199,9 @@ pub(crate) async fn execute_user_shell_command(
|
||||
source: ExecCommandSource::UserShell,
|
||||
interaction_input: None,
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
duration: None,
|
||||
formatted_output: None,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
@@ -281,12 +277,9 @@ pub(crate) async fn execute_user_shell_command(
|
||||
source: ExecCommandSource::UserShell,
|
||||
interaction_input: None,
|
||||
status: CommandExecutionStatus::Failed,
|
||||
stdout: Some(String::new()),
|
||||
stderr: Some(aborted_message.clone()),
|
||||
aggregated_output: Some(aborted_message.clone()),
|
||||
exit_code: Some(-1),
|
||||
duration: Some(Duration::ZERO),
|
||||
formatted_output: Some(aborted_message),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
@@ -312,15 +305,9 @@ pub(crate) async fn execute_user_shell_command(
|
||||
} else {
|
||||
CommandExecutionStatus::Failed
|
||||
},
|
||||
stdout: Some(output.stdout.text.clone()),
|
||||
stderr: Some(output.stderr.text.clone()),
|
||||
aggregated_output: Some(output.aggregated_output.text.clone()),
|
||||
exit_code: Some(output.exit_code),
|
||||
duration: Some(output.duration),
|
||||
formatted_output: Some(format_exec_output_str(
|
||||
&output,
|
||||
turn_context.model_info().truncation_policy.into(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
@@ -355,15 +342,9 @@ pub(crate) async fn execute_user_shell_command(
|
||||
source: ExecCommandSource::UserShell,
|
||||
interaction_input: None,
|
||||
status: CommandExecutionStatus::Failed,
|
||||
stdout: Some(exec_output.stdout.text.clone()),
|
||||
stderr: Some(exec_output.stderr.text.clone()),
|
||||
aggregated_output: Some(exec_output.aggregated_output.text.clone()),
|
||||
exit_code: Some(exec_output.exit_code),
|
||||
duration: Some(exec_output.duration),
|
||||
formatted_output: Some(format_exec_output_str(
|
||||
&exec_output,
|
||||
turn_context.model_info().truncation_policy.into(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -34,8 +34,6 @@ use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::format_exec_output_str;
|
||||
|
||||
const REJECTION_MESSAGE_MAX_TOKENS: usize = 900;
|
||||
|
||||
pub(super) fn truncate_rejection_message(message: &str) -> String {
|
||||
@@ -185,12 +183,9 @@ async fn emit_exec_command_begin(ctx: ToolEventCtx<'_>, exec_input: &ExecCommand
|
||||
source: exec_input.source,
|
||||
interaction_input: exec_input.interaction_input.map(str::to_owned),
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
duration: None,
|
||||
formatted_output: None,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
@@ -511,12 +506,9 @@ impl<'a> ExecCommandInput<'a> {
|
||||
}
|
||||
|
||||
struct ExecCommandResult {
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
aggregated_output: String,
|
||||
exit_code: i32,
|
||||
duration: Duration,
|
||||
formatted_output: String,
|
||||
status: ExecCommandStatus,
|
||||
}
|
||||
|
||||
@@ -532,15 +524,9 @@ async fn emit_exec_stage(
|
||||
ToolEventStage::Success { output, .. }
|
||||
| ToolEventStage::Failure(ToolEventFailure::Output(output)) => {
|
||||
let exec_result = ExecCommandResult {
|
||||
stdout: output.stdout.text.clone(),
|
||||
stderr: output.stderr.text.clone(),
|
||||
aggregated_output: output.aggregated_output.text.clone(),
|
||||
aggregated_output: output.aggregated_output.text,
|
||||
exit_code: output.exit_code,
|
||||
duration: output.duration,
|
||||
formatted_output: format_exec_output_str(
|
||||
&output,
|
||||
ctx.model_info.truncation_policy.into(),
|
||||
),
|
||||
status: if output.exit_code == 0 {
|
||||
ExecCommandStatus::Completed
|
||||
} else {
|
||||
@@ -552,12 +538,9 @@ async fn emit_exec_stage(
|
||||
ToolEventStage::Failure(ToolEventFailure::Message(message)) => {
|
||||
let text = message.to_string();
|
||||
let exec_result = ExecCommandResult {
|
||||
stdout: String::new(),
|
||||
stderr: text.clone(),
|
||||
aggregated_output: text.clone(),
|
||||
aggregated_output: text,
|
||||
exit_code: -1,
|
||||
duration: Duration::ZERO,
|
||||
formatted_output: text,
|
||||
status: ExecCommandStatus::Failed,
|
||||
};
|
||||
emit_exec_end(ctx, exec_input, exec_result).await;
|
||||
@@ -565,12 +548,9 @@ async fn emit_exec_stage(
|
||||
ToolEventStage::Failure(ToolEventFailure::Rejected { message, .. }) => {
|
||||
let text = message.to_string();
|
||||
let exec_result = ExecCommandResult {
|
||||
stdout: String::new(),
|
||||
stderr: text.clone(),
|
||||
aggregated_output: text.clone(),
|
||||
aggregated_output: text,
|
||||
exit_code: -1,
|
||||
duration: Duration::ZERO,
|
||||
formatted_output: text,
|
||||
status: ExecCommandStatus::Declined,
|
||||
};
|
||||
emit_exec_end(ctx, exec_input, exec_result).await;
|
||||
@@ -598,14 +578,11 @@ async fn emit_exec_end(
|
||||
cwd: exec_input.cwd.clone(),
|
||||
parsed_cmd: exec_input.parsed_cmd.to_vec(),
|
||||
source: exec_input.source,
|
||||
interaction_input: exec_input.interaction_input.map(str::to_owned),
|
||||
interaction_input: None,
|
||||
status: exec_result.status.into(),
|
||||
stdout: Some(exec_result.stdout),
|
||||
stderr: Some(exec_result.stderr),
|
||||
aggregated_output: Some(exec_result.aggregated_output),
|
||||
exit_code: Some(exec_result.exit_code),
|
||||
duration: Some(exec_result.duration),
|
||||
formatted_output: Some(exec_result.formatted_output),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -357,13 +357,6 @@ async fn exit_watcher_waits_for_late_network_denial_before_classifying_end() ->
|
||||
Some("LATE_DENIAL")
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
item.formatted_output,
|
||||
Some(codex_utils_output_truncation::formatted_truncate_text(
|
||||
"LATE_DENIAL",
|
||||
codex_utils_output_truncation::TruncationPolicy::Bytes(4),
|
||||
))
|
||||
);
|
||||
assert!(
|
||||
elapsed >= Duration::from_millis(10) && elapsed < TRAILING_OUTPUT_GRACE,
|
||||
"completion should wait for denial without falling back to the output grace: {elapsed:?}"
|
||||
|
||||
@@ -905,7 +905,7 @@ async fn shell_snapshot_v2_filters_profile_secrets_without_creating_files() -> R
|
||||
|
||||
assert_eq!(end.exit_code, 0);
|
||||
assert_eq!(
|
||||
normalize_newlines(&end.stdout).trim(),
|
||||
normalize_newlines(&end.aggregated_output).trim(),
|
||||
"helper|path|policy|missing"
|
||||
);
|
||||
}
|
||||
@@ -953,7 +953,7 @@ async fn shell_snapshot_v2_preserves_legacy_snapshots_for_user_shell() -> Result
|
||||
.await;
|
||||
|
||||
assert_eq!(end.exit_code, 0);
|
||||
assert_eq!(normalize_newlines(&end.stdout).trim(), "legacy");
|
||||
assert_eq!(normalize_newlines(&end.aggregated_output).trim(), "legacy");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -962,7 +962,7 @@ async fn shell_snapshot_v2_preserves_legacy_snapshots_for_user_shell() -> Result
|
||||
async fn linux_unified_exec_uses_shell_snapshot() -> Result<()> {
|
||||
let command = "echo snapshot-linux";
|
||||
let run = run_snapshot_command(command).await?;
|
||||
let stdout = normalize_newlines(&run.end.stdout);
|
||||
let output = normalize_newlines(&run.end.aggregated_output);
|
||||
|
||||
assert_eq!(run.begin.command.get(1).map(String::as_str), Some("-lc"));
|
||||
assert_eq!(run.begin.command.get(2).map(String::as_str), Some(command));
|
||||
@@ -971,8 +971,8 @@ async fn linux_unified_exec_uses_shell_snapshot() -> Result<()> {
|
||||
assert_posix_snapshot_sections(&run.snapshot_content);
|
||||
assert_eq!(run.end.exit_code, 0);
|
||||
assert!(
|
||||
stdout.contains("snapshot-linux"),
|
||||
"stdout should contain snapshot marker; stdout={stdout:?}"
|
||||
output.contains("snapshot-linux"),
|
||||
"output should contain snapshot marker; output={output:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -1018,7 +1018,7 @@ async fn unified_exec_snapshot_preserves_shell_environment_policy_set() -> Resul
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
normalize_newlines(&end.stdout).trim(),
|
||||
normalize_newlines(&end.aggregated_output).trim(),
|
||||
POLICY_SUCCESS_OUTPUT
|
||||
);
|
||||
assert_eq!(end.exit_code, 0);
|
||||
@@ -1239,10 +1239,13 @@ async fn macos_unified_exec_resolves_command_from_tied_path_snapshot(
|
||||
|
||||
assert_eq!(
|
||||
end.exit_code, 0,
|
||||
"tied-path command failed: stderr={:?}",
|
||||
end.stderr
|
||||
"tied-path command failed: output={:?}",
|
||||
end.aggregated_output
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_newlines(&end.aggregated_output).trim(),
|
||||
"tied-path-command"
|
||||
);
|
||||
assert_eq!(normalize_newlines(&end.stdout).trim(), "tied-path-command");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1274,7 +1277,10 @@ async fn macos_unified_exec_uses_shell_snapshot() -> Result<()> {
|
||||
|
||||
assert!(run.snapshot_path.starts_with(&run.codex_home));
|
||||
assert_posix_snapshot_sections(&run.snapshot_content);
|
||||
assert_eq!(normalize_newlines(&run.end.stdout).trim(), "snapshot-macos");
|
||||
assert_eq!(
|
||||
normalize_newlines(&run.end.aggregated_output).trim(),
|
||||
"snapshot-macos"
|
||||
);
|
||||
assert_eq!(run.end.exit_code, 0);
|
||||
|
||||
Ok(())
|
||||
@@ -1308,7 +1314,7 @@ async fn windows_unified_exec_uses_shell_snapshot() -> Result<()> {
|
||||
assert!(run.snapshot_content.contains("# aliases "));
|
||||
assert!(run.snapshot_content.contains("# exports "));
|
||||
assert_eq!(
|
||||
normalize_newlines(&run.end.stdout).trim(),
|
||||
normalize_newlines(&run.end.aggregated_output).trim(),
|
||||
"snapshot-windows"
|
||||
);
|
||||
assert_eq!(run.end.exit_code, 0);
|
||||
|
||||
@@ -2815,8 +2815,6 @@ async fn captured_step_controls_exec_completion_and_write_stdin_output() -> Resu
|
||||
.await;
|
||||
let end = end.expect("exec completion");
|
||||
|
||||
use codex_utils_output_truncation::TruncationPolicy;
|
||||
use codex_utils_output_truncation::formatted_truncate_text;
|
||||
let requests = responses.requests();
|
||||
let exec = requests[2]
|
||||
.function_call_output_text("exec-b")
|
||||
@@ -2836,13 +2834,7 @@ async fn captured_step_controls_exec_completion_and_write_stdin_output() -> Resu
|
||||
"{stdin}"
|
||||
);
|
||||
assert_eq!(end.exit_code, 7);
|
||||
// The formatting check needs an untruncated chunk, independent of how the executor
|
||||
// aggregates output across the initial command and later stdin interactions.
|
||||
assert!(end.aggregated_output.contains(&output));
|
||||
assert_eq!(
|
||||
end.formatted_output,
|
||||
formatted_truncate_text(&end.aggregated_output, TruncationPolicy::Bytes(400))
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1113,12 +1113,8 @@ async fn unified_exec_emits_output_delta_for_exec_command() -> Result<()> {
|
||||
);
|
||||
let end_event = end_event.expect("expected command completion");
|
||||
assert_eq!(
|
||||
(
|
||||
end_event.exit_code,
|
||||
end_event.stdout,
|
||||
end_event.aggregated_output
|
||||
),
|
||||
(0, "HELLO-UEXECé�".to_string(), "HELLO-UEXECé�".to_string())
|
||||
(end_event.exit_code, end_event.aggregated_output),
|
||||
(0, "HELLO-UEXECé�".to_string())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -77,15 +77,17 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() {
|
||||
.unwrap();
|
||||
let msg = wait_for_event(&codex, |ev| matches!(ev, EventMsg::ExecCommandEnd(_))).await;
|
||||
let EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
stdout, exit_code, ..
|
||||
aggregated_output,
|
||||
exit_code,
|
||||
..
|
||||
}) = msg
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
assert_eq!(exit_code, 0);
|
||||
assert!(
|
||||
stdout.contains(file_name),
|
||||
"ls output should include {file_name}, got: {stdout:?}"
|
||||
aggregated_output.contains(file_name),
|
||||
"ls output should include {file_name}, got: {aggregated_output:?}"
|
||||
);
|
||||
|
||||
// 2) shell command should print the file contents verbatim
|
||||
@@ -99,7 +101,7 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() {
|
||||
.unwrap();
|
||||
let msg = wait_for_event(&codex, |ev| matches!(ev, EventMsg::ExecCommandEnd(_))).await;
|
||||
let EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
mut stdout,
|
||||
mut aggregated_output,
|
||||
exit_code,
|
||||
..
|
||||
}) = msg
|
||||
@@ -109,9 +111,9 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() {
|
||||
assert_eq!(exit_code, 0);
|
||||
if cfg!(windows) {
|
||||
// Windows shells emit CRLF line endings; normalize so the assertion remains portable.
|
||||
stdout = stdout.replace("\r\n", "\n");
|
||||
aggregated_output = aggregated_output.replace("\r\n", "\n");
|
||||
}
|
||||
assert_eq!(stdout, contents);
|
||||
assert_eq!(aggregated_output, contents);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -447,7 +449,7 @@ async fn user_shell_command_history_is_persisted_and_shared_with_model() -> anyh
|
||||
})
|
||||
.await;
|
||||
assert_eq!(end_event.exit_code, 0);
|
||||
assert_eq!(end_event.stdout.trim(), "not-set");
|
||||
assert_eq!(end_event.aggregated_output.trim(), "not-set");
|
||||
|
||||
let _ = wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
|
||||
|
||||
@@ -507,8 +509,7 @@ async fn user_shell_command_does_not_set_network_sandbox_env_var() -> anyhow::Re
|
||||
|
||||
let ExecCommandEndEvent {
|
||||
exit_code,
|
||||
stdout,
|
||||
stderr,
|
||||
aggregated_output,
|
||||
..
|
||||
} = wait_for_event_match(&test.codex, |ev| match ev {
|
||||
EventMsg::ExecCommandEnd(event) => Some(event.clone()),
|
||||
@@ -518,9 +519,9 @@ async fn user_shell_command_does_not_set_network_sandbox_env_var() -> anyhow::Re
|
||||
|
||||
assert_eq!(
|
||||
exit_code, 0,
|
||||
"shell command should execute successfully. stdout=`{stdout}`, stderr=`{stderr}`",
|
||||
"shell command should execute successfully. output=`{aggregated_output}`",
|
||||
);
|
||||
assert_eq!(stdout.trim(), "not-set");
|
||||
assert_eq!(aggregated_output.trim(), "not-set");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -271,12 +271,6 @@ pub struct CommandExecutionItem {
|
||||
pub status: CommandExecutionStatus,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub stdout: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub stderr: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub aggregated_output: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -284,9 +278,6 @@ pub struct CommandExecutionItem {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(type = "string", optional)]
|
||||
pub duration: Option<Duration>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub formatted_output: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)]
|
||||
|
||||
@@ -222,12 +222,9 @@ impl CommandExecutionItem {
|
||||
parsed_cmd: self.parsed_cmd.clone(),
|
||||
source: self.source,
|
||||
interaction_input: self.interaction_input.clone(),
|
||||
stdout: self.stdout.clone().unwrap_or_default(),
|
||||
stderr: self.stderr.clone().unwrap_or_default(),
|
||||
aggregated_output: self.aggregated_output.clone().unwrap_or_default(),
|
||||
exit_code: self.exit_code.unwrap_or_default(),
|
||||
duration: self.duration.unwrap_or_default(),
|
||||
formatted_output: self.formatted_output.clone().unwrap_or_default(),
|
||||
status,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -3625,10 +3625,6 @@ pub struct ExecCommandEndEvent {
|
||||
#[ts(optional)]
|
||||
pub interaction_input: Option<String>,
|
||||
|
||||
/// Captured stdout
|
||||
pub stdout: String,
|
||||
/// Captured stderr
|
||||
pub stderr: String,
|
||||
/// Captured aggregated output
|
||||
#[serde(default)]
|
||||
pub aggregated_output: String,
|
||||
@@ -3637,8 +3633,6 @@ pub struct ExecCommandEndEvent {
|
||||
/// The duration of the command execution.
|
||||
#[ts(type = "string")]
|
||||
pub duration: Duration,
|
||||
/// Formatted output from the command, as seen by the model.
|
||||
pub formatted_output: String,
|
||||
/// Completion status for this command execution.
|
||||
pub status: ExecCommandStatus,
|
||||
}
|
||||
@@ -5598,12 +5592,9 @@ mod tests {
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
duration: None,
|
||||
formatted_output: None,
|
||||
}),
|
||||
};
|
||||
let completed = ItemCompletedEvent {
|
||||
@@ -5626,12 +5617,9 @@ mod tests {
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
status: CommandExecutionStatus::Completed,
|
||||
stdout: Some("done\n".into()),
|
||||
stderr: Some(String::new()),
|
||||
aggregated_output: Some("done\n".into()),
|
||||
exit_code: Some(0),
|
||||
duration: Some(Duration::from_millis(5)),
|
||||
formatted_output: Some("done\n".into()),
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -220,12 +220,9 @@ struct ExecCommandEndTracePayload<'a> {
|
||||
source: ExecCommandSource,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
interaction_input: Option<&'a str>,
|
||||
stdout: &'a str,
|
||||
stderr: &'a str,
|
||||
aggregated_output: &'a str,
|
||||
exit_code: i32,
|
||||
duration: Duration,
|
||||
formatted_output: &'a str,
|
||||
status: &'a ExecCommandStatus,
|
||||
}
|
||||
|
||||
@@ -243,12 +240,9 @@ impl<'a> From<&'a ExecCommandEndEvent> for ExecCommandEndTracePayload<'a> {
|
||||
parsed_cmd,
|
||||
source,
|
||||
interaction_input,
|
||||
stdout,
|
||||
stderr,
|
||||
aggregated_output,
|
||||
exit_code,
|
||||
duration,
|
||||
formatted_output,
|
||||
status,
|
||||
} = event;
|
||||
Self {
|
||||
@@ -263,12 +257,9 @@ impl<'a> From<&'a ExecCommandEndEvent> for ExecCommandEndTracePayload<'a> {
|
||||
parsed_cmd,
|
||||
source: *source,
|
||||
interaction_input: interaction_input.as_deref(),
|
||||
stdout,
|
||||
stderr,
|
||||
aggregated_output,
|
||||
exit_code: *exit_code,
|
||||
duration: *duration,
|
||||
formatted_output,
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +93,9 @@ fn exec_command_trace_payloads_use_inferred_native_cwd() -> anyhow::Result<()> {
|
||||
parsed_cmd: Vec::new(),
|
||||
source: ExecCommandSource::UnifiedExecInteraction,
|
||||
interaction_input: Some("input".to_string()),
|
||||
stdout: "output".to_string(),
|
||||
stderr: String::new(),
|
||||
aggregated_output: "output".to_string(),
|
||||
aggregated_output: "combined output".to_string(),
|
||||
exit_code: 0,
|
||||
duration: Duration::from_millis(250),
|
||||
formatted_output: "output".to_string(),
|
||||
status: ExecCommandStatus::Completed,
|
||||
});
|
||||
|
||||
@@ -138,12 +135,9 @@ fn exec_command_trace_payloads_use_inferred_native_cwd() -> anyhow::Result<()> {
|
||||
"parsed_cmd": [],
|
||||
"source": "unified_exec_interaction",
|
||||
"interaction_input": "input",
|
||||
"stdout": "output",
|
||||
"stderr": "",
|
||||
"aggregated_output": "output",
|
||||
"aggregated_output": "combined output",
|
||||
"exit_code": 0,
|
||||
"duration": {"secs": 0, "nanos": 250000000},
|
||||
"formatted_output": "output",
|
||||
"status": "completed"
|
||||
})
|
||||
);
|
||||
|
||||
@@ -450,9 +450,9 @@ fn parse_protocol_terminal_response(payload: ExecCommandEndPayload) -> ParsedTer
|
||||
terminal_id: payload.process_id,
|
||||
result: TerminalResult {
|
||||
exit_code: Some(payload.exit_code),
|
||||
stdout: payload.stdout,
|
||||
stderr: payload.stderr,
|
||||
formatted_output: Some(payload.formatted_output),
|
||||
stdout: payload.aggregated_output,
|
||||
stderr: String::new(),
|
||||
formatted_output: None,
|
||||
original_token_count: None,
|
||||
chunk_id: None,
|
||||
},
|
||||
@@ -557,10 +557,9 @@ struct ExecCommandBeginPayload {
|
||||
#[derive(Deserialize)]
|
||||
struct ExecCommandEndPayload {
|
||||
process_id: Option<String>,
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
#[serde(default)]
|
||||
aggregated_output: String,
|
||||
exit_code: i32,
|
||||
formatted_output: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -81,10 +81,8 @@ fn exec_tool_reduces_to_terminal_operation_and_session() -> anyhow::Result<()> {
|
||||
"turn_id": "turn-1",
|
||||
"command": ["cargo", "test"],
|
||||
"cwd": "/repo",
|
||||
"stdout": "ok\n",
|
||||
"stderr": "",
|
||||
"aggregated_output": "ok\n",
|
||||
"exit_code": 0,
|
||||
"formatted_output": "ok\n",
|
||||
"status": "completed"
|
||||
}),
|
||||
)?;
|
||||
@@ -180,7 +178,7 @@ fn exec_tool_reduces_to_terminal_operation_and_session() -> anyhow::Result<()> {
|
||||
exit_code: Some(0),
|
||||
stdout: "ok\n".to_string(),
|
||||
stderr: String::new(),
|
||||
formatted_output: Some("ok\n".to_string()),
|
||||
formatted_output: None,
|
||||
original_token_count: None,
|
||||
chunk_id: None,
|
||||
}),
|
||||
|
||||
@@ -277,16 +277,12 @@ pub(super) fn completed_item(
|
||||
cwd: event.cwd.clone(),
|
||||
parsed_cmd: event.parsed_cmd.clone(),
|
||||
source: event.source,
|
||||
interaction_input: event.interaction_input.clone(),
|
||||
interaction_input: None,
|
||||
status: event.status.clone().into(),
|
||||
stdout: (!event.stdout.is_empty()).then(|| event.stdout.clone()),
|
||||
stderr: (!event.stderr.is_empty()).then(|| event.stderr.clone()),
|
||||
aggregated_output: (!event.aggregated_output.is_empty())
|
||||
.then(|| event.aggregated_output.clone()),
|
||||
exit_code: Some(event.exit_code),
|
||||
duration: Some(event.duration),
|
||||
formatted_output: (!event.formatted_output.is_empty())
|
||||
.then(|| event.formatted_output.clone()),
|
||||
}),
|
||||
Some(event.turn_id.clone()),
|
||||
)),
|
||||
|
||||
@@ -471,6 +471,7 @@ async fn migration_projects_explicit_and_implicit_legacy_completed_items() {
|
||||
let reasoning: serde_json::Value =
|
||||
serde_json::from_slice(&items.items[1].item_json).expect("parse projected reasoning");
|
||||
assert_eq!(command["type"], "commandExecution");
|
||||
assert_eq!(command["aggregatedOutput"], "ok");
|
||||
assert_eq!(reasoning["type"], "reasoning");
|
||||
assert_eq!(reasoning["summary"], json!(["summary"]));
|
||||
assert_eq!(reasoning["content"], json!(["raw"]));
|
||||
|
||||
Reference in New Issue
Block a user