1use std::any::Any;
2use std::rc::Rc;
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 delegate: AgentServerDelegate,
49 cx: &mut App,
50 ) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
51 let name = self.name();
52 let store = delegate.store.downgrade();
53 let mut extra_env = load_proxy_env(cx);
54 let default_mode = self.default_mode(cx);
55 let default_model = self.default_model(cx);
56 let default_config_options = cx.read_global(|settings: &SettingsStore, _| {
57 settings
58 .get::<AllAgentServersSettings>(None)
59 .gemini
60 .as_ref()
61 .map(|s| s.default_config_options.clone())
62 .unwrap_or_default()
63 });
64
65 cx.spawn(async move |cx| {
66 extra_env.insert("SURFACE".to_owned(), "zed".to_owned());
67
68 if let Some(api_key) = cx.update(api_key_for_gemini_cli).await.ok() {
69 extra_env.insert("GEMINI_API_KEY".into(), api_key);
70 }
71 let (command, login) = store
72 .update(cx, |store, cx| {
73 let agent = store
74 .get_external_agent(&GEMINI_NAME.into())
75 .context("Gemini CLI is not registered")?;
76 anyhow::Ok(agent.get_command(
77 extra_env,
78 delegate.status_tx,
79 delegate.new_version_available,
80 &mut cx.to_async(),
81 ))
82 })??
83 .await?;
84
85 let connection = crate::acp::connect(
86 name.clone(),
87 name,
88 command,
89 default_mode,
90 default_model,
91 default_config_options,
92 cx,
93 )
94 .await?;
95 Ok((connection, login))
96 })
97 }
98
99 fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
100 self
101 }
102}
103
104#[cfg(test)]
105pub(crate) mod tests {
106 use project::agent_server_store::AgentServerCommand;
107
108 use super::*;
109 use std::path::Path;
110
111 crate::common_e2e_tests!(async |_, _| Gemini, allow_option_id = "proceed_once");
112
113 pub fn local_command() -> AgentServerCommand {
114 let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
115 .join("../../../gemini-cli/packages/cli")
116 .to_string_lossy()
117 .to_string();
118
119 AgentServerCommand {
120 path: "node".into(),
121 args: vec![cli_path],
122 env: None,
123 }
124 }
125}