diff --git a/crates/agent_servers/src/codex.rs b/crates/agent_servers/src/codex.rs new file mode 100644 index 0000000000..871b8f57fd --- /dev/null +++ b/crates/agent_servers/src/codex.rs @@ -0,0 +1,118 @@ +use crate::stdio_agent_server::{StdioAgentServer, find_bin_in_path}; +use crate::{AgentServerCommand, AgentServerVersion}; +use anyhow::{Context as _, Result}; +use gpui::{AsyncApp, Entity}; +use project::Project; +use settings::SettingsStore; + +use crate::AllAgentServersSettings; + +#[derive(Clone)] +pub struct Codex; + +const ACP_ARG: &str = "acp"; + +impl StdioAgentServer for Codex { + fn name(&self) -> &'static str { + "Codex" + } + + fn empty_state_headline(&self) -> &'static str { + "Welcome to Codex" + } + + fn empty_state_message(&self) -> &'static str { + "Ask questions, edit files, run commands.\nBe specific for the best results." + } + + fn logo(&self) -> ui::IconName { + ui::IconName::AiOpenAi + } + + async fn command( + &self, + project: &Entity, + cx: &mut AsyncApp, + ) -> Result { + let custom_command = cx.read_global(|settings: &SettingsStore, _| { + let settings = settings.get::(None); + settings + .codex + .as_ref() + .map(|codex_settings| AgentServerCommand { + path: codex_settings.command.path.clone(), + args: codex_settings + .command + .args + .iter() + .cloned() + .chain(std::iter::once(ACP_ARG.into())) + .collect(), + env: codex_settings.command.env.clone(), + }) + })?; + + if let Some(custom_command) = custom_command { + return Ok(custom_command); + } + + if let Some(path) = find_bin_in_path("codex", project, cx).await { + return Ok(AgentServerCommand { + path, + args: vec![ACP_ARG.into()], + env: None, + }); + } + + todo!() + // let (fs, node_runtime) = project.update(cx, |project, _| { + // (project.fs().clone(), project.node_runtime().cloned()) + // })?; + // let node_runtime = node_runtime.context("codex not found on path")?; + + // let directory = ::paths::agent_servers_dir().join("codex"); + // fs.create_dir(&directory).await?; + // node_runtime + // .npm_install_packages(&directory, &[("@google/gemini-cli", "latest")]) + // .await?; + // let path = directory.join("node_modules/.bin/gemini"); + + // Ok(AgentServerCommand { + // path, + // args: vec![ACP_ARG.into()], + // env: None, + // }) + } + + async fn version(&self, command: &AgentServerCommand) -> Result { + let version_fut = util::command::new_smol_command(&command.path) + .args(command.args.iter()) + .arg("--version") + .kill_on_drop(true) + .output(); + + let help_fut = util::command::new_smol_command(&command.path) + .args(command.args.iter()) + .arg("--help") + .kill_on_drop(true) + .output(); + + let (version_output, help_output) = futures::future::join(version_fut, help_fut).await; + + let current_version = String::from_utf8(version_output?.stdout)?; + let supported = String::from_utf8(help_output?.stdout)?.contains(ACP_ARG); + + if supported { + Ok(AgentServerVersion::Supported) + } else { + Ok(AgentServerVersion::Unsupported { + error_message: format!( + "Your installed version of Codex {} doesn't support the Agentic Coding Protocol (ACP).", + current_version + ).into(), + upgrade_message: "Upgrade Codex to Latest".into(), + upgrade_command: "npm install -g @openai/codex@latest".into(), + }) + } + } +}