From 79c2cd8bdca287df517ca3c1ee88877d7b74d262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=89=91=E5=87=9B?= <2993354@qq.com> Date: Fri, 26 Jun 2026 18:48:46 +0800 Subject: [PATCH] fix: launch Codex npm shim through node on Windows (#1960) --- crates/dbx-core/src/ai_codex_cli.rs | 70 ++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/crates/dbx-core/src/ai_codex_cli.rs b/crates/dbx-core/src/ai_codex_cli.rs index abc2cbe61..98aafc023 100644 --- a/crates/dbx-core/src/ai_codex_cli.rs +++ b/crates/dbx-core/src/ai_codex_cli.rs @@ -29,15 +29,44 @@ async fn resolve_codex_command(config: &AiConfig) -> CodexCommandSpec { let configured = codex_program(config); if is_path_like_program(&configured) { let expanded = expand_tilde(&configured); - return CodexCommandSpec { program: direct_program_path(&expanded).unwrap_or(expanded), args: Vec::new() }; + return codex_command_for_program(direct_program_path(&expanded).unwrap_or(expanded)); } if let Some(path) = resolve_program_path(&configured).await { - CodexCommandSpec { program: path, args: Vec::new() } + codex_command_for_program(path) } else { CodexCommandSpec { program: configured, args: Vec::new() } } } +fn codex_command_for_program(program: String) -> CodexCommandSpec { + #[cfg(windows)] + if let Some(command) = windows_npm_codex_shim_command(&program) { + return command; + } + + CodexCommandSpec { program, args: Vec::new() } +} + +#[cfg(windows)] +fn windows_npm_codex_shim_command(program: &str) -> Option { + let path = Path::new(program); + let extension = path.extension()?.to_str()?.to_ascii_lowercase(); + if extension != "cmd" && extension != "bat" { + return None; + } + + let parent = path.parent()?; + let codex_js = parent.join("node_modules").join("@openai").join("codex").join("bin").join("codex.js"); + if !codex_js.is_file() { + return None; + } + + let bundled_node = parent.join("node.exe"); + let node = if bundled_node.is_file() { bundled_node.to_string_lossy().to_string() } else { "node".to_string() }; + + Some(CodexCommandSpec { program: node, args: vec![codex_js.to_string_lossy().to_string()] }) +} + fn codex_process_env(config: &AiConfig, command: &CodexCommandSpec) -> Result, String> { let mut env = BTreeMap::from_iter(codex_cli_env(config)?); if let Some(dir) = command.parent_dir() { @@ -74,10 +103,9 @@ fn direct_program_path(program: &str) -> Option { let path = Path::new(program); if path.is_absolute() && path.is_file() { #[cfg(windows)] - { - return windows_launchable_program_path(path); - } - Some(path.to_string_lossy().to_string()) + return windows_launchable_program_path(path); + #[cfg(not(windows))] + return Some(path.to_string_lossy().to_string()); } else { None } @@ -392,6 +420,7 @@ pub async fn list_codex_models(config: &AiConfig) -> Result, St validate_codex_program(config)?; let command = resolve_codex_command(config).await; let output = cli_command(&command.program) + .args(command.args.iter().map(String::as_str)) .args(["debug", "models"]) .envs(codex_process_env(config, &command)?.iter().map(|(key, value)| (key.as_str(), value.as_str()))) .output() @@ -444,6 +473,7 @@ pub async fn test_codex_connection(config: &AiConfig) -> Result