gemini.rs

  1use std::rc::Rc;
  2use std::{any::Any, path::Path};
  3
  4use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
  5use acp_thread::AgentConnection;
  6use anyhow::{Context as _, Result};
  7use collections::HashMap;
  8use gpui::{App, SharedString, Task};
  9use language_models::provider::google::GoogleLanguageModelProvider;
 10use project::agent_server_store::GEMINI_NAME;
 11
 12#[derive(Clone)]
 13pub struct Gemini;
 14
 15impl AgentServer for Gemini {
 16    fn telemetry_id(&self) -> &'static str {
 17        "gemini-cli"
 18    }
 19
 20    fn name(&self) -> SharedString {
 21        "Gemini CLI".into()
 22    }
 23
 24    fn logo(&self) -> ui::IconName {
 25        ui::IconName::AiGemini
 26    }
 27
 28    fn login_commands(&self) -> &'static [&'static str] {
 29        &["login"]
 30    }
 31
 32    fn connect(
 33        &self,
 34        root_dir: Option<&Path>,
 35        delegate: AgentServerDelegate,
 36        cx: &mut App,
 37    ) -> Task<
 38        Result<(
 39            Rc<dyn AgentConnection>,
 40            HashMap<String, task::SpawnInTerminal>,
 41        )>,
 42    > {
 43        let name = self.name();
 44        let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
 45        let is_remote = delegate.project.read(cx).is_via_remote_server();
 46        let store = delegate.store.downgrade();
 47        let mut extra_env = load_proxy_env(cx);
 48        let default_mode = self.default_mode(cx);
 49
 50        cx.spawn(async move |cx| {
 51            extra_env.insert("SURFACE".to_owned(), "zed".to_owned());
 52
 53            if let Some(api_key) = cx
 54                .update(GoogleLanguageModelProvider::api_key_for_gemini_cli)?
 55                .await
 56                .ok()
 57            {
 58                extra_env.insert("GEMINI_API_KEY".into(), api_key);
 59            }
 60            let (command, root_dir, mut auth_commands) = store
 61                .update(cx, |store, cx| {
 62                    let agent = store
 63                        .get_external_agent(&GEMINI_NAME.into())
 64                        .context("Gemini CLI is not registered")?;
 65                    anyhow::Ok(agent.get_command(
 66                        root_dir.as_deref(),
 67                        extra_env,
 68                        delegate.status_tx,
 69                        delegate.new_version_available,
 70                        &mut cx.to_async(),
 71                    ))
 72                })??
 73                .await?;
 74
 75            // When remote, OAuth doesn't work, so we need to use the terminal-based login
 76            // for oauth-personal. Map it to the same terminal command as spawn-gemini-cli.
 77            if is_remote {
 78                if let Some(spawn_gemini_cli) = auth_commands.get("spawn-gemini-cli").cloned() {
 79                    auth_commands.insert("oauth-personal".to_string(), spawn_gemini_cli);
 80                }
 81            }
 82
 83            let connection = crate::acp::connect(
 84                name,
 85                command,
 86                root_dir.as_ref(),
 87                default_mode,
 88                is_remote,
 89                cx,
 90            )
 91            .await?;
 92            Ok((connection, auth_commands))
 93        })
 94    }
 95
 96    fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
 97        self
 98    }
 99}
100
101#[cfg(test)]
102pub(crate) mod tests {
103    use project::agent_server_store::AgentServerCommand;
104
105    use super::*;
106    use std::path::Path;
107
108    crate::common_e2e_tests!(async |_, _, _| Gemini, allow_option_id = "proceed_once");
109
110    pub fn local_command() -> AgentServerCommand {
111        let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
112            .join("../../../gemini-cli/packages/cli")
113            .to_string_lossy()
114            .to_string();
115
116        AgentServerCommand {
117            path: "node".into(),
118            args: vec![cli_path],
119            env: None,
120        }
121    }
122}