1use crate::stdio_agent_server::StdioAgentServer;
2use crate::{AgentServerCommand, AgentServerVersion};
3use anyhow::{Context as _, Result};
4use gpui::{AsyncApp, Entity};
5use project::Project;
6use settings::SettingsStore;
7
8use crate::AllAgentServersSettings;
9
10#[derive(Clone)]
11pub struct Codex;
12
13const ACP_ARG: &str = "experimental-acp";
14
15impl StdioAgentServer for Codex {
16 fn name(&self) -> &'static str {
17 "Codex"
18 }
19
20 fn empty_state_headline(&self) -> &'static str {
21 "Welcome to Codex"
22 }
23
24 fn empty_state_message(&self) -> &'static str {
25 ""
26 }
27
28 fn supports_always_allow(&self) -> bool {
29 true
30 }
31
32 fn logo(&self) -> ui::IconName {
33 ui::IconName::AiOpenAi
34 }
35
36 async fn command(
37 &self,
38 project: &Entity<Project>,
39 cx: &mut AsyncApp,
40 ) -> Result<AgentServerCommand> {
41 let settings = cx.read_global(|settings: &SettingsStore, _| {
42 settings.get::<AllAgentServersSettings>(None).codex.clone()
43 })?;
44
45 if let Some(command) =
46 AgentServerCommand::resolve("codex", &[ACP_ARG], settings, &project, cx).await
47 {
48 return Ok(command);
49 };
50
51 let (fs, node_runtime) = project.update(cx, |project, _| {
52 (project.fs().clone(), project.node_runtime().cloned())
53 })?;
54 let node_runtime = node_runtime.context("codex not found on path")?;
55
56 let directory = ::paths::agent_servers_dir().join("codex");
57 fs.create_dir(&directory).await?;
58 node_runtime
59 .npm_install_packages(&directory, &[("@openai/codex", "latest")])
60 .await?;
61 let path = directory.join("node_modules/.bin/codex");
62
63 Ok(AgentServerCommand {
64 path,
65 args: vec![ACP_ARG.into()],
66 env: None,
67 })
68 }
69
70 async fn version(&self, command: &AgentServerCommand) -> Result<AgentServerVersion> {
71 let version_fut = util::command::new_smol_command(&command.path)
72 .args(command.args.iter())
73 .arg("--version")
74 .kill_on_drop(true)
75 .output();
76
77 let help_fut = util::command::new_smol_command(&command.path)
78 .args(command.args.iter())
79 .arg("--help")
80 .kill_on_drop(true)
81 .output();
82
83 let (version_output, help_output) = futures::future::join(version_fut, help_fut).await;
84
85 let current_version = String::from_utf8(version_output?.stdout)?;
86 let supported = String::from_utf8(help_output?.stdout)?.contains(ACP_ARG);
87
88 if supported {
89 Ok(AgentServerVersion::Supported)
90 } else {
91 Ok(AgentServerVersion::Unsupported {
92 error_message: format!(
93 "Your installed version of Codex {} doesn't support the Agentic Coding Protocol (ACP).",
94 current_version
95 ).into(),
96 upgrade_message: "Upgrade Codex to Latest".into(),
97 upgrade_command: "npm install -g @openai/codex@latest".into(),
98 })
99 }
100 }
101}
102
103#[cfg(test)]
104pub(crate) mod tests {
105 use super::*;
106 use crate::AgentServerCommand;
107 use std::path::Path;
108
109 crate::common_e2e_tests!(Codex);
110
111 pub fn local_command() -> AgentServerCommand {
112 let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
113 .join("../../../codex/codex-rs/target/debug/codex");
114
115 AgentServerCommand {
116 path: cli_path,
117 args: vec![],
118 env: None,
119 }
120 }
121}