Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 23 additions & 24 deletions openaide-rs/app-server/src/agent/codex_acp_provisioner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ impl CodexAcpProvisioner {
.map_err(provisioning_installer_error)
.and_then(|_| {
validate_package(&staging)?;
validate_platform_launcher(&staging, self.windows)?;
validate_platform_runtime(&staging, self.windows)?;
fs::write(staging.join(MANAGED_MARKER), managed_marker())
.map_err(provisioning_io_error)?;
fs::rename(&staging, version_root).map_err(provisioning_io_error)
Expand All @@ -226,26 +226,23 @@ impl CodexAcpProvisioner {

let lease = Arc::new(open_lock_file(&version_root.join(".lease"))?);
FileExt::lock_shared(lease.as_ref()).map_err(provisioning_io_error)?;
let (command, args) = if self.windows {
(
windows_package_launcher(version_root)
let entrypoint = package_root(version_root).join("dist/index.js");
let mut config = config;
config.command = resolved_command_or_name("node");
config.args = vec![entrypoint.to_string_lossy().into_owned()];
if self.windows {
// The @openai/codex Node launcher can terminate when nested under
// codex-acp on Windows. Use its pinned native binary directly.
config.env.retain(|(name, _)| name != "CODEX_PATH");
config.env.push((
"CODEX_PATH".to_string(),
windows_codex_binary(version_root)
.to_string_lossy()
.into_owned(),
Vec::new(),
)
} else {
let entrypoint = package_root(version_root).join("dist/index.js");
(
resolved_command_or_name("node"),
vec![entrypoint.to_string_lossy().into_owned()],
)
};
));
}
Ok(PreparedCodexAcpLaunch {
config: AcpAgentConfig {
command,
args,
..config
},
config,
lease: Some(lease),
})
}
Expand Down Expand Up @@ -336,17 +333,19 @@ fn valid_installation(version_root: &Path, windows: bool) -> bool {
.as_deref()
== Some(managed_marker().as_str())
&& validate_package(version_root).is_ok()
&& validate_platform_launcher(version_root, windows).is_ok()
&& validate_platform_runtime(version_root, windows).is_ok()
}

fn windows_package_launcher(version_root: &Path) -> PathBuf {
version_root.join("node_modules/.bin/codex-acp.cmd")
fn windows_codex_binary(version_root: &Path) -> PathBuf {
version_root
.join("node_modules/@openai/codex-win32-x64")
.join("vendor/x86_64-pc-windows-msvc/bin/codex.exe")
}

fn validate_platform_launcher(version_root: &Path, windows: bool) -> Result<(), RuntimeError> {
if windows && !windows_package_launcher(version_root).is_file() {
fn validate_platform_runtime(version_root: &Path, windows: bool) -> Result<(), RuntimeError> {
if windows && !windows_codex_binary(version_root).is_file() {
return Err(provisioning_error(
"installed Codex integration did not provide its Windows launcher".to_string(),
"installed Codex integration did not provide its native Windows runtime".to_string(),
));
}
Ok(())
Expand Down
39 changes: 22 additions & 17 deletions openaide-rs/app-server/src/agent/codex_acp_provisioner_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ impl CodexAcpInstaller for RecordingInstaller {
.expect("write managed package manifest");
fs::write(package_root.join("dist/index.js"), "#!/usr/bin/env node\n")
.expect("write managed package entrypoint");
let bin_root = destination.join("node_modules/.bin");
fs::create_dir_all(&bin_root).expect("create managed package launchers");
fs::write(bin_root.join("codex-acp.cmd"), "@echo off\r\n")
.expect("write managed Windows package launcher");
let codex_root = destination
.join("node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc/bin");
fs::create_dir_all(&codex_root).expect("create managed native Codex fixture");
fs::write(codex_root.join("codex.exe"), "native Codex fixture")
.expect("write managed native Codex fixture");
Ok(())
}
}
Expand Down Expand Up @@ -151,7 +152,7 @@ fn explicit_codex_launch_installs_the_locked_integration_once_and_reuses_it() {
}

#[test]
fn windows_launch_uses_the_managed_batch_entrypoint() {
fn windows_launch_uses_the_managed_native_codex_binary() {
let storage = TempDir::new().expect("temporary storage root");
let provisioner = CodexAcpProvisioner::with_installer_for_platform(
storage.path().to_path_buf(),
Expand All @@ -163,20 +164,24 @@ fn windows_launch_uses_the_managed_batch_entrypoint() {
.prepare(AcpAgentConfig::codex())
.expect("managed Windows Codex launch");

let command = std::path::Path::new(&launch.config.command);
let expected_codex = storage
.path()
.join("agent-runtimes/codex-acp")
.join(CODEX_ACP_VERSION)
.join("node_modules/@openai/codex-win32-x64")
.join("vendor/x86_64-pc-windows-msvc/bin/codex.exe");
assert_eq!(
command.file_name().and_then(std::ffi::OsStr::to_str),
Some("codex-acp.cmd")
launch
.config
.env
.iter()
.find(|(name, _)| name == "CODEX_PATH")
.map(|(_, value)| value.as_str()),
Some(expected_codex.to_string_lossy().as_ref()),
);
assert_eq!(
command
.parent()
.and_then(std::path::Path::file_name)
.and_then(std::ffi::OsStr::to_str),
Some(".bin"),
);
assert!(launch.config.args.is_empty());
assert!(command.is_file());
assert_eq!(launch.config.args.len(), 1);
assert!(launch.config.args[0].ends_with("node_modules/@openaide/codex-acp/dist/index.js"));
assert!(!launch.config.command.ends_with(".cmd"));
}

#[test]
Expand Down