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 credentials_provider::CredentialsProvider;
8use gpui::{App, AppContext as _, SharedString, Task};
9use language_model::{ApiKey, EnvVar};
10use project::agent_server_store::{AllAgentServersSettings, GEMINI_NAME};
11use settings::SettingsStore;
12
13const GEMINI_API_KEY_VAR_NAME: &str = "GEMINI_API_KEY";
14const GOOGLE_AI_API_KEY_VAR_NAME: &str = "GOOGLE_AI_API_KEY";
15
16fn api_key_for_gemini_cli(cx: &mut App) -> Task<Result<String>> {
17 let env_var = EnvVar::new(GEMINI_API_KEY_VAR_NAME.into())
18 .or(EnvVar::new(GOOGLE_AI_API_KEY_VAR_NAME.into()));
19 if let Some(key) = env_var.value {
20 return Task::ready(Ok(key));
21 }
22 let credentials_provider = <dyn CredentialsProvider>::global(cx);
23 let api_url = google_ai::API_URL.to_string();
24 cx.spawn(async move |cx| {
25 Ok(
26 ApiKey::load_from_system_keychain(&api_url, credentials_provider.as_ref(), cx)
27 .await?
28 .key()
29 .to_string(),
30 )
31 })
32}
33
34#[derive(Clone)]
35pub struct Gemini;
36
37impl AgentServer for Gemini {
38 fn name(&self) -> SharedString {
39 "Gemini CLI".into()
40 }
41
42 fn logo(&self) -> ui::IconName {
43 ui::IconName::AiGemini
44 }
45
46 fn connect(
47 &self,
48 root_dir: Option<&Path>,
49 delegate: AgentServerDelegate,
50 cx: &mut App,
51 ) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
52 let name = self.name();
53 let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
54 let is_remote = delegate.project.read(cx).is_via_remote_server();
55 let store = delegate.store.downgrade();
56 let mut extra_env = load_proxy_env(cx);
57 let default_mode = self.default_mode(cx);
58 let default_model = self.default_model(cx);
59 let default_config_options = cx.read_global(|settings: &SettingsStore, _| {
60 settings
61 .get::<AllAgentServersSettings>(None)
62 .gemini
63 .as_ref()
64 .map(|s| s.default_config_options.clone())
65 .unwrap_or_default()
66 });
67
68 cx.spawn(async move |cx| {
69 extra_env.insert("SURFACE".to_owned(), "zed".to_owned());
70
71 if let Some(api_key) = cx.update(api_key_for_gemini_cli).await.ok() {
72 extra_env.insert("GEMINI_API_KEY".into(), api_key);
73 }
74 let (command, root_dir, login) = store
75 .update(cx, |store, cx| {
76 let agent = store
77 .get_external_agent(&GEMINI_NAME.into())
78 .context("Gemini CLI is not registered")?;
79 anyhow::Ok(agent.get_command(
80 root_dir.as_deref(),
81 extra_env,
82 delegate.status_tx,
83 delegate.new_version_available,
84 &mut cx.to_async(),
85 ))
86 })??
87 .await?;
88
89 let connection = crate::acp::connect(
90 name,
91 command,
92 root_dir.as_ref(),
93 default_mode,
94 default_model,
95 default_config_options,
96 is_remote,
97 cx,
98 )
99 .await?;
100 Ok((connection, login))
101 })
102 }
103
104 fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
105 self
106 }
107}
108
109#[cfg(test)]
110pub(crate) mod tests {
111 use project::agent_server_store::AgentServerCommand;
112
113 use super::*;
114 use std::path::Path;
115
116 crate::common_e2e_tests!(async |_, _| Gemini, allow_option_id = "proceed_once");
117
118 pub fn local_command() -> AgentServerCommand {
119 let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
120 .join("../../../gemini-cli/packages/cli")
121 .to_string_lossy()
122 .to_string();
123
124 AgentServerCommand {
125 path: "node".into(),
126 args: vec![cli_path],
127 env: None,
128 }
129 }
130}