gemini.rs

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