feat: better UI for unified_exec (#6515)

<img width="376" height="132" alt="Screenshot 2025-11-12 at 17 36 22"
src="https://github.com/user-attachments/assets/ce693f0d-5ca0-462e-b170-c20811dcc8d5"
/>
This commit is contained in:
jif-oai
2025-11-14 16:31:12 +01:00
committed by GitHub
parent 4788fb179a
commit 63c8c01f40
17 changed files with 563 additions and 129 deletions

View File

@@ -4,17 +4,13 @@ use crate::tools::TELEMETRY_PREVIEW_MAX_BYTES;
use crate::tools::TELEMETRY_PREVIEW_MAX_LINES;
use crate::tools::TELEMETRY_PREVIEW_TRUNCATION_NOTICE;
use crate::turn_diff_tracker::TurnDiffTracker;
use codex_otel::otel_event_manager::OtelEventManager;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ShellToolCallParams;
use codex_protocol::protocol::FileChange;
use codex_utils_string::take_bytes_at_char_boundary;
use mcp_types::CallToolResult;
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -244,25 +240,3 @@ mod tests {
assert_eq!(lines.last(), Some(&TELEMETRY_PREVIEW_TRUNCATION_NOTICE));
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub(crate) struct ExecCommandContext {
pub(crate) turn: Arc<TurnContext>,
pub(crate) call_id: String,
pub(crate) command_for_display: Vec<String>,
pub(crate) cwd: PathBuf,
pub(crate) apply_patch: Option<ApplyPatchCommandContext>,
pub(crate) tool_name: String,
pub(crate) otel_event_manager: OtelEventManager,
// TODO(abhisek-oai): Find a better way to track this.
// https://github.com/openai/codex/pull/2471/files#r2470352242
pub(crate) is_user_shell_command: bool,
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub(crate) struct ApplyPatchCommandContext {
pub(crate) user_explicitly_approved_this_action: bool,
pub(crate) changes: HashMap<PathBuf, FileChange>,
}

View File

@@ -8,6 +8,7 @@ use crate::parse_command::parse_command;
use crate::protocol::EventMsg;
use crate::protocol::ExecCommandBeginEvent;
use crate::protocol::ExecCommandEndEvent;
use crate::protocol::ExecCommandSource;
use crate::protocol::FileChange;
use crate::protocol::PatchApplyBeginEvent;
use crate::protocol::PatchApplyEndEvent;
@@ -60,7 +61,8 @@ pub(crate) async fn emit_exec_command_begin(
ctx: ToolEventCtx<'_>,
command: &[String],
cwd: &Path,
is_user_shell_command: bool,
source: ExecCommandSource,
interaction_input: Option<String>,
) {
ctx.session
.send_event(
@@ -70,7 +72,8 @@ pub(crate) async fn emit_exec_command_begin(
command: command.to_vec(),
cwd: cwd.to_path_buf(),
parsed_cmd: parse_command(command),
is_user_shell_command,
source,
interaction_input,
}),
)
.await;
@@ -80,7 +83,7 @@ pub(crate) enum ToolEmitter {
Shell {
command: Vec<String>,
cwd: PathBuf,
is_user_shell_command: bool,
source: ExecCommandSource,
},
ApplyPatch {
changes: HashMap<PathBuf, FileChange>,
@@ -89,18 +92,17 @@ pub(crate) enum ToolEmitter {
UnifiedExec {
command: Vec<String>,
cwd: PathBuf,
// True for `exec_command` and false for `write_stdin`.
#[allow(dead_code)]
is_startup_command: bool,
source: ExecCommandSource,
interaction_input: Option<String>,
},
}
impl ToolEmitter {
pub fn shell(command: Vec<String>, cwd: PathBuf, is_user_shell_command: bool) -> Self {
pub fn shell(command: Vec<String>, cwd: PathBuf, source: ExecCommandSource) -> Self {
Self::Shell {
command,
cwd,
is_user_shell_command,
source,
}
}
@@ -111,11 +113,17 @@ impl ToolEmitter {
}
}
pub fn unified_exec(command: &[String], cwd: PathBuf, is_startup_command: bool) -> Self {
pub fn unified_exec(
command: &[String],
cwd: PathBuf,
source: ExecCommandSource,
interaction_input: Option<String>,
) -> Self {
Self::UnifiedExec {
command: command.to_vec(),
cwd,
is_startup_command,
source,
interaction_input,
}
}
@@ -125,11 +133,11 @@ impl ToolEmitter {
Self::Shell {
command,
cwd,
is_user_shell_command,
source,
},
ToolEventStage::Begin,
) => {
emit_exec_command_begin(ctx, command, cwd.as_path(), *is_user_shell_command).await;
emit_exec_command_begin(ctx, command, cwd.as_path(), *source, None).await;
}
(Self::Shell { .. }, ToolEventStage::Success(output)) => {
emit_exec_end(
@@ -217,8 +225,23 @@ impl ToolEmitter {
) => {
emit_patch_end(ctx, String::new(), (*message).to_string(), false).await;
}
(Self::UnifiedExec { command, cwd, .. }, ToolEventStage::Begin) => {
emit_exec_command_begin(ctx, command, cwd.as_path(), false).await;
(
Self::UnifiedExec {
command,
cwd,
source,
interaction_input,
},
ToolEventStage::Begin,
) => {
emit_exec_command_begin(
ctx,
command,
cwd.as_path(),
*source,
interaction_input.clone(),
)
.await;
}
(Self::UnifiedExec { .. }, ToolEventStage::Success(output)) => {
emit_exec_end(

View File

@@ -11,6 +11,7 @@ use crate::exec::ExecParams;
use crate::exec_env::create_env;
use crate::function_tool::FunctionCallError;
use crate::is_safe_command::is_known_safe_command;
use crate::protocol::ExecCommandSource;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolOutput;
use crate::tools::context::ToolPayload;
@@ -285,11 +286,13 @@ impl ShellHandler {
}
// Regular shell execution path.
let emitter = ToolEmitter::shell(
exec_params.command.clone(),
exec_params.cwd.clone(),
is_user_shell_command,
);
let source = if is_user_shell_command {
ExecCommandSource::UserShell
} else {
ExecCommandSource::Agent
};
let emitter =
ToolEmitter::shell(exec_params.command.clone(), exec_params.cwd.clone(), source);
let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None);
emitter.begin(event_ctx).await;

View File

@@ -4,6 +4,7 @@ use crate::function_tool::FunctionCallError;
use crate::is_safe_command::is_known_safe_command;
use crate::protocol::EventMsg;
use crate::protocol::ExecCommandOutputDeltaEvent;
use crate::protocol::ExecCommandSource;
use crate::protocol::ExecOutputStream;
use crate::shell::get_shell_by_model_provided_path;
use crate::tools::context::ToolInvocation;
@@ -162,8 +163,12 @@ impl ToolHandler for UnifiedExecHandler {
&context.call_id,
None,
);
let emitter = ToolEmitter::unified_exec(&command, cwd.clone(), true);
let emitter = ToolEmitter::unified_exec(
&command,
cwd.clone(),
ExecCommandSource::UnifiedExecStartup,
None,
);
emitter.emit(event_ctx, ToolEventStage::Begin).await;
manager
@@ -191,6 +196,7 @@ impl ToolHandler for UnifiedExecHandler {
})?;
manager
.write_stdin(WriteStdinRequest {
call_id: &call_id,
session_id: args.session_id,
input: &args.chars,
yield_time_ms: args.yield_time_ms,