Compare commits

...

1 Commits

Author SHA1 Message Date
Joe Gershenson
eb8f9d070c Fix Windows marketplace paths and shell command strings 2026-04-16 15:43:10 -07:00
4 changed files with 56 additions and 5 deletions

View File

@@ -37,6 +37,7 @@ pub use responses::create_final_assistant_message_sse_response;
pub use responses::create_request_permissions_sse_response;
pub use responses::create_request_user_input_sse_response;
pub use responses::create_shell_command_sse_response;
pub use responses::create_shell_command_sse_response_from_command;
pub use rollout::create_fake_rollout;
pub use rollout::create_fake_rollout_with_source;
pub use rollout::create_fake_rollout_with_text_elements;

View File

@@ -10,11 +10,37 @@ pub fn create_shell_command_sse_response(
) -> anyhow::Result<String> {
// The `arguments` for the `shell_command` tool is a serialized JSON object.
let command_str = shlex::try_join(command.iter().map(String::as_str))?;
let tool_call_arguments = serde_json::to_string(&json!({
"command": command_str,
create_shell_command_sse_response_from_command(
&command_str,
workdir,
timeout_ms,
call_id,
/*login*/ None,
)
}
pub fn create_shell_command_sse_response_from_command(
command: &str,
workdir: Option<&Path>,
timeout_ms: Option<u64>,
call_id: &str,
login: Option<bool>,
) -> anyhow::Result<String> {
// Use this when a test already has a shell command string. It fixes string
// quoting for those callers by avoiding a rebuild from argv with POSIX
// rules, which can change how Windows PowerShell parses the command; for
// sleep-based tests, nested parsing can add another PowerShell startup
// before the requested sleep even begins.
let mut tool_call_arguments = json!({
"command": command,
"workdir": workdir.map(|w| w.to_string_lossy()),
"timeout_ms": timeout_ms
}))?;
});
if let Some(login) = login {
tool_call_arguments["login"] = json!(login);
}
let tool_call_arguments = serde_json::to_string(&tool_call_arguments)?;
Ok(responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_function_call(call_id, "shell_command", &tool_call_arguments),

View File

@@ -276,12 +276,18 @@ mod tests {
let config = fs::read_to_string(codex_home.path().join(codex_config::CONFIG_TOML_FILE))?;
let config: toml::Value = toml::from_str(&config)?;
let marketplace = config
.get("marketplaces")
.and_then(toml::Value::as_table)
.and_then(|marketplaces| marketplaces.get("debug"))
.and_then(toml::Value::as_table)
.expect("debug marketplace should be present in config");
assert_eq!(
config["marketplaces"]["debug"]["source_type"].as_str(),
marketplace.get("source_type").and_then(toml::Value::as_str),
Some("local")
);
assert_eq!(
config["marketplaces"]["debug"]["source"].as_str(),
marketplace.get("source").and_then(toml::Value::as_str),
Some(expected_source.as_str())
);
Ok(())

View File

@@ -133,6 +133,16 @@ fn looks_like_local_path(source: &str) -> bool {
|| source.starts_with("~/")
|| source == "."
|| source == ".."
|| {
#[cfg(windows)]
{
source.starts_with(".\\") || source.starts_with("..\\") || source.starts_with('\\')
}
#[cfg(not(windows))]
{
false
}
}
}
fn looks_like_windows_absolute_path(source: &str) -> bool {
@@ -322,6 +332,14 @@ mod tests {
assert!(path.is_absolute());
}
#[cfg(windows)]
#[test]
fn windows_backslash_relative_path_looks_local() {
assert!(looks_like_local_path(r".\marketplace"));
assert!(looks_like_local_path(r"..\marketplace"));
assert!(looks_like_local_path(r"\marketplace"));
}
#[test]
fn windows_absolute_paths_look_like_local_paths_on_every_host() {
assert!(looks_like_local_path(r"C:\Users\alice\marketplace"));