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}