mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
protocol: retain executor cwd in command events
This commit is contained in:
@@ -23,6 +23,7 @@ use crate::protocol::v2::PatchApplyStatus;
|
||||
use crate::protocol::v2::PatchChangeKind;
|
||||
use crate::protocol::v2::ThreadItem;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use codex_protocol::protocol::ExecApprovalRequestEvent;
|
||||
use codex_protocol::protocol::ExecCommandBeginEvent;
|
||||
@@ -42,8 +43,37 @@ use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn native_command_cwd(cwd: &AbsolutePathBuf) -> ApiPathString {
|
||||
ApiPathString::from_path_uri(&PathUri::from_abs_path(cwd), PathConvention::native())
|
||||
.unwrap_or_else(|_| ApiPathString::new(cwd.to_string_lossy().into_owned()))
|
||||
native_command_cwd_from_uri(&PathUri::from_abs_path(cwd), PathConvention::native())
|
||||
}
|
||||
|
||||
fn native_command_cwd_from_uri(cwd: &PathUri, path_convention: PathConvention) -> ApiPathString {
|
||||
ApiPathString::from_path_uri(cwd, path_convention)
|
||||
.unwrap_or_else(|_| ApiPathString::new(cwd.to_string()))
|
||||
}
|
||||
|
||||
fn command_actions_for_event(
|
||||
parsed_cmd: &[ParsedCommand],
|
||||
cwd: &PathUri,
|
||||
path_convention: PathConvention,
|
||||
) -> Vec<CommandAction> {
|
||||
let host_cwd = (path_convention == PathConvention::native())
|
||||
.then(|| cwd.to_abs_path().ok())
|
||||
.flatten();
|
||||
parsed_cmd
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|parsed| match host_cwd.as_ref() {
|
||||
Some(cwd) => CommandAction::from_core_with_cwd(parsed, cwd),
|
||||
None => CommandAction::Unknown {
|
||||
command: match parsed {
|
||||
ParsedCommand::Read { cmd, .. }
|
||||
| ParsedCommand::ListFiles { cmd, .. }
|
||||
| ParsedCommand::Search { cmd, .. }
|
||||
| ParsedCommand::Unknown { cmd } => cmd,
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn build_file_change_approval_request_item(
|
||||
@@ -98,16 +128,15 @@ pub fn build_command_execution_begin_item(payload: &ExecCommandBeginEvent) -> Th
|
||||
ThreadItem::CommandExecution {
|
||||
id: payload.call_id.clone(),
|
||||
command: shlex_join(&payload.command),
|
||||
cwd: native_command_cwd(&payload.cwd),
|
||||
cwd: native_command_cwd_from_uri(&payload.cwd, payload.path_convention),
|
||||
process_id: payload.process_id.clone(),
|
||||
source: payload.source.into(),
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
command_actions: payload
|
||||
.parsed_cmd
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &payload.cwd))
|
||||
.collect(),
|
||||
command_actions: command_actions_for_event(
|
||||
&payload.parsed_cmd,
|
||||
&payload.cwd,
|
||||
payload.path_convention,
|
||||
),
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
duration_ms: None,
|
||||
@@ -125,16 +154,15 @@ pub fn build_command_execution_end_item(payload: &ExecCommandEndEvent) -> Thread
|
||||
ThreadItem::CommandExecution {
|
||||
id: payload.call_id.clone(),
|
||||
command: shlex_join(&payload.command),
|
||||
cwd: native_command_cwd(&payload.cwd),
|
||||
cwd: native_command_cwd_from_uri(&payload.cwd, payload.path_convention),
|
||||
process_id: payload.process_id.clone(),
|
||||
source: payload.source.into(),
|
||||
status: (&payload.status).into(),
|
||||
command_actions: payload
|
||||
.parsed_cmd
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &payload.cwd))
|
||||
.collect(),
|
||||
command_actions: command_actions_for_event(
|
||||
&payload.parsed_cmd,
|
||||
&payload.cwd,
|
||||
payload.path_convention,
|
||||
),
|
||||
aggregated_output,
|
||||
exit_code: Some(payload.exit_code),
|
||||
duration_ms: Some(duration_ms),
|
||||
@@ -324,3 +352,7 @@ fn format_file_change_diff(change: &FileChange) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "item_builders_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
use super::*;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::protocol::ExecCommandBeginEvent;
|
||||
use codex_protocol::protocol::ExecCommandSource;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn begin_event(
|
||||
cwd: PathUri,
|
||||
path_convention: PathConvention,
|
||||
parsed_cmd: Vec<ParsedCommand>,
|
||||
) -> ExecCommandBeginEvent {
|
||||
ExecCommandBeginEvent {
|
||||
call_id: "exec-1".to_string(),
|
||||
process_id: None,
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at_ms: 0,
|
||||
command: vec!["cat".to_string(), "notes.txt".to_string()],
|
||||
cwd,
|
||||
path_convention,
|
||||
parsed_cmd,
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_command_event_renders_windows_native_cwd() {
|
||||
let event = begin_event(
|
||||
PathUri::parse("file:///C:/Research/space%20%23%25").expect("Windows cwd URI"),
|
||||
PathConvention::Windows,
|
||||
vec![ParsedCommand::Unknown {
|
||||
cmd: "cat notes.txt".to_string(),
|
||||
}],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
build_command_execution_begin_item(&event),
|
||||
ThreadItem::CommandExecution {
|
||||
id: "exec-1".to_string(),
|
||||
command: "cat notes.txt".to_string(),
|
||||
cwd: ApiPathString::new(r"C:\Research\space #%"),
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
command_actions: vec![CommandAction::Unknown {
|
||||
command: "cat notes.txt".to_string(),
|
||||
}],
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
duration_ms: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreign_command_event_does_not_project_read_path_onto_host() {
|
||||
let (cwd, path_convention, native_cwd) = match PathConvention::native() {
|
||||
PathConvention::Posix => (
|
||||
PathUri::parse("file:///C:/workspace").expect("Windows cwd URI"),
|
||||
PathConvention::Windows,
|
||||
ApiPathString::new(r"C:\workspace"),
|
||||
),
|
||||
PathConvention::Windows => (
|
||||
PathUri::parse("file:///workspace").expect("POSIX cwd URI"),
|
||||
PathConvention::Posix,
|
||||
ApiPathString::new("/workspace"),
|
||||
),
|
||||
};
|
||||
let event = begin_event(
|
||||
cwd,
|
||||
path_convention,
|
||||
vec![ParsedCommand::Read {
|
||||
cmd: "cat notes.txt".to_string(),
|
||||
name: "notes.txt".to_string(),
|
||||
path: PathBuf::from("notes.txt"),
|
||||
}],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
build_command_execution_begin_item(&event),
|
||||
ThreadItem::CommandExecution {
|
||||
id: "exec-1".to_string(),
|
||||
command: "cat notes.txt".to_string(),
|
||||
cwd: native_cwd,
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
command_actions: vec![CommandAction::Unknown {
|
||||
command: "cat notes.txt".to_string(),
|
||||
}],
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
duration_ms: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1266,6 +1266,8 @@ mod tests {
|
||||
use codex_protocol::protocol::WebSearchEndEvent;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
@@ -2013,7 +2015,8 @@ mod tests {
|
||||
turn_id: "turn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["echo".into(), "hello world".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: PathUri::from_abs_path(&test_path_buf("/tmp").abs()),
|
||||
path_convention: PathConvention::native(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown {
|
||||
cmd: "echo hello world".into(),
|
||||
}],
|
||||
@@ -2253,7 +2256,8 @@ mod tests {
|
||||
turn_id: "turn-1".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["ls".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: PathUri::from_abs_path(&test_path_buf("/tmp").abs()),
|
||||
path_convention: PathConvention::native(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown { cmd: "ls".into() }],
|
||||
source: ExecCommandSource::Agent,
|
||||
interaction_input: None,
|
||||
@@ -2519,7 +2523,8 @@ mod tests {
|
||||
turn_id: "turn-a".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["echo".into(), "done".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: PathUri::from_abs_path(&test_path_buf("/tmp").abs()),
|
||||
path_convention: PathConvention::native(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown {
|
||||
cmd: "echo done".into(),
|
||||
}],
|
||||
@@ -2617,7 +2622,8 @@ mod tests {
|
||||
turn_id: "turn-missing".into(),
|
||||
completed_at_ms: 0,
|
||||
command: vec!["echo".into(), "done".into()],
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: PathUri::from_abs_path(&test_path_buf("/tmp").abs()),
|
||||
path_convention: PathConvention::native(),
|
||||
parsed_cmd: vec![ParsedCommand::Unknown {
|
||||
cmd: "echo done".into(),
|
||||
}],
|
||||
|
||||
Reference in New Issue
Block a user