Skip to content

Commit 15330de

Browse files
ConradIrwinJosephTLyons
authored andcommitted
acp: Require gemini version 0.2.0 (#36960)
Release Notes: - N/A
1 parent fe7e793 commit 15330de

File tree

8 files changed

+134
-98
lines changed

8 files changed

+134
-98
lines changed

crates/acp_thread/src/acp_thread.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -789,15 +789,10 @@ pub enum ThreadStatus {
789789

790790
#[derive(Debug, Clone)]
791791
pub enum LoadError {
792-
NotInstalled {
793-
error_message: SharedString,
794-
install_message: SharedString,
795-
install_command: String,
796-
},
792+
NotInstalled,
797793
Unsupported {
798-
error_message: SharedString,
799-
upgrade_message: SharedString,
800-
upgrade_command: String,
794+
command: SharedString,
795+
current_version: SharedString,
801796
},
802797
Exited {
803798
status: ExitStatus,
@@ -808,9 +803,12 @@ pub enum LoadError {
808803
impl Display for LoadError {
809804
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
810805
match self {
811-
LoadError::NotInstalled { error_message, .. }
812-
| LoadError::Unsupported { error_message, .. } => {
813-
write!(f, "{error_message}")
806+
LoadError::NotInstalled => write!(f, "not installed"),
807+
LoadError::Unsupported {
808+
command: path,
809+
current_version,
810+
} => {
811+
write!(f, "version {current_version} from {path} is not supported")
814812
}
815813
LoadError::Exited { status } => write!(f, "Server exited with status {status}"),
816814
LoadError::Other(msg) => write!(f, "{}", msg),

crates/agent2/src/native_agent_server.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ impl AgentServer for NativeAgentServer {
4242
ui::IconName::ZedAgent
4343
}
4444

45+
fn install_command(&self) -> Option<&'static str> {
46+
None
47+
}
48+
4549
fn connect(
4650
&self,
4751
_root_dir: &Path,

crates/agent_servers/src/acp.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl AcpConnection {
5656
root_dir: &Path,
5757
cx: &mut AsyncApp,
5858
) -> Result<Self> {
59-
let mut child = util::command::new_smol_command(&command.path)
59+
let mut child = util::command::new_smol_command(command.path)
6060
.args(command.args.iter().map(|arg| arg.as_str()))
6161
.envs(command.env.iter().flatten())
6262
.current_dir(root_dir)
@@ -150,6 +150,10 @@ impl AcpConnection {
150150
_io_task: io_task,
151151
})
152152
}
153+
154+
pub fn prompt_capabilities(&self) -> &acp::PromptCapabilities {
155+
&self.prompt_capabilities
156+
}
153157
}
154158

155159
impl AgentConnection for AcpConnection {

crates/agent_servers/src/agent_servers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub trait AgentServer: Send {
4646
) -> Task<Result<Rc<dyn AgentConnection>>>;
4747

4848
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
49+
50+
fn install_command(&self) -> Option<&'static str>;
4951
}
5052

5153
impl dyn AgentServer {

crates/agent_servers/src/claude.rs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ impl AgentServer for ClaudeCode {
6363
ui::IconName::AiClaude
6464
}
6565

66+
fn install_command(&self) -> Option<&'static str> {
67+
Some("npm install -g @anthropic-ai/claude-code@latest")
68+
}
69+
6670
fn connect(
6771
&self,
6872
_root_dir: &Path,
@@ -108,11 +112,7 @@ impl AgentConnection for ClaudeAgentConnection {
108112
)
109113
.await
110114
else {
111-
return Err(LoadError::NotInstalled {
112-
error_message: "Failed to find Claude Code binary".into(),
113-
install_message: "Install Claude Code".into(),
114-
install_command: "npm install -g @anthropic-ai/claude-code@latest".into(),
115-
}.into());
115+
return Err(LoadError::NotInstalled.into());
116116
};
117117

118118
let api_key =
@@ -230,17 +230,8 @@ impl AgentConnection for ClaudeAgentConnection {
230230
|| !help.contains("--session-id"))
231231
{
232232
LoadError::Unsupported {
233-
error_message: format!(
234-
"Your installed version of Claude Code ({}, version {}) does not have required features for use with Zed.",
235-
command.path.to_string_lossy(),
236-
version,
237-
)
238-
.into(),
239-
upgrade_message: "Upgrade Claude Code to latest".into(),
240-
upgrade_command: format!(
241-
"{} update",
242-
command.path.to_string_lossy()
243-
),
233+
command: command.path.to_string_lossy().to_string().into(),
234+
current_version: version.to_string().into(),
244235
}
245236
} else {
246237
LoadError::Exited { status }

crates/agent_servers/src/custom.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ impl crate::AgentServer for CustomAgentServer {
5757
})
5858
}
5959

60+
fn install_command(&self) -> Option<&'static str> {
61+
None
62+
}
63+
6064
fn into_any(self: Rc<Self>) -> Rc<dyn std::any::Any> {
6165
self
6266
}

crates/agent_servers/src/gemini.rs

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::rc::Rc;
22
use std::{any::Any, path::Path};
33

4+
use crate::acp::AcpConnection;
45
use crate::{AgentServer, AgentServerCommand};
56
use acp_thread::{AgentConnection, LoadError};
67
use anyhow::Result;
@@ -37,6 +38,10 @@ impl AgentServer for Gemini {
3738
ui::IconName::AiGemini
3839
}
3940

41+
fn install_command(&self) -> Option<&'static str> {
42+
Some("npm install -g @google/gemini-cli@latest")
43+
}
44+
4045
fn connect(
4146
&self,
4247
root_dir: &Path,
@@ -52,48 +57,73 @@ impl AgentServer for Gemini {
5257
})?;
5358

5459
let Some(mut command) =
55-
AgentServerCommand::resolve("gemini", &[ACP_ARG], None, settings, &project, cx).await
60+
AgentServerCommand::resolve("gemini", &[ACP_ARG], None, settings, &project, cx)
61+
.await
5662
else {
57-
return Err(LoadError::NotInstalled {
58-
error_message: "Failed to find Gemini CLI binary".into(),
59-
install_message: "Install Gemini CLI".into(),
60-
install_command: Self::install_command().into(),
61-
}.into());
63+
return Err(LoadError::NotInstalled.into());
6264
};
6365

64-
if let Some(api_key)= cx.update(GoogleLanguageModelProvider::api_key)?.await.ok() {
65-
command.env.get_or_insert_default().insert("GEMINI_API_KEY".to_owned(), api_key.key);
66+
if let Some(api_key) = cx.update(GoogleLanguageModelProvider::api_key)?.await.ok() {
67+
command
68+
.env
69+
.get_or_insert_default()
70+
.insert("GEMINI_API_KEY".to_owned(), api_key.key);
6671
}
6772

6873
let result = crate::acp::connect(server_name, command.clone(), &root_dir, cx).await;
69-
if result.is_err() {
70-
let version_fut = util::command::new_smol_command(&command.path)
71-
.args(command.args.iter())
72-
.arg("--version")
73-
.kill_on_drop(true)
74-
.output();
75-
76-
let help_fut = util::command::new_smol_command(&command.path)
77-
.args(command.args.iter())
78-
.arg("--help")
79-
.kill_on_drop(true)
80-
.output();
81-
82-
let (version_output, help_output) = futures::future::join(version_fut, help_fut).await;
83-
84-
let current_version = String::from_utf8(version_output?.stdout)?;
85-
let supported = String::from_utf8(help_output?.stdout)?.contains(ACP_ARG);
86-
87-
if !supported {
88-
return Err(LoadError::Unsupported {
89-
error_message: format!(
90-
"Your installed version of Gemini CLI ({}, version {}) doesn't support the Agentic Coding Protocol (ACP).",
91-
command.path.to_string_lossy(),
92-
current_version
93-
).into(),
94-
upgrade_message: "Upgrade Gemini CLI to latest".into(),
95-
upgrade_command: Self::upgrade_command().into(),
96-
}.into())
74+
match &result {
75+
Ok(connection) => {
76+
if let Some(connection) = connection.clone().downcast::<AcpConnection>()
77+
&& !connection.prompt_capabilities().image
78+
{
79+
let version_output = util::command::new_smol_command(&command.path)
80+
.args(command.args.iter())
81+
.arg("--version")
82+
.kill_on_drop(true)
83+
.output()
84+
.await;
85+
let current_version =
86+
String::from_utf8(version_output?.stdout)?.trim().to_owned();
87+
if !connection.prompt_capabilities().image {
88+
return Err(LoadError::Unsupported {
89+
current_version: current_version.into(),
90+
command: format!(
91+
"{} {}",
92+
command.path.to_string_lossy(),
93+
command.args.join(" ")
94+
)
95+
.into(),
96+
}
97+
.into());
98+
}
99+
}
100+
}
101+
Err(_) => {
102+
let version_fut = util::command::new_smol_command(&command.path)
103+
.args(command.args.iter())
104+
.arg("--version")
105+
.kill_on_drop(true)
106+
.output();
107+
108+
let help_fut = util::command::new_smol_command(&command.path)
109+
.args(command.args.iter())
110+
.arg("--help")
111+
.kill_on_drop(true)
112+
.output();
113+
114+
let (version_output, help_output) =
115+
futures::future::join(version_fut, help_fut).await;
116+
117+
let current_version = String::from_utf8(version_output?.stdout)?;
118+
let supported = String::from_utf8(help_output?.stdout)?.contains(ACP_ARG);
119+
120+
if !supported {
121+
return Err(LoadError::Unsupported {
122+
current_version: current_version.into(),
123+
command: command.path.to_string_lossy().to_string().into(),
124+
}
125+
.into());
126+
}
97127
}
98128
}
99129
result
@@ -111,11 +141,11 @@ impl Gemini {
111141
}
112142

113143
pub fn install_command() -> &'static str {
114-
"npm install -g @google/gemini-cli@preview"
144+
"npm install -g @google/gemini-cli@latest"
115145
}
116146

117147
pub fn upgrade_command() -> &'static str {
118-
"npm install -g @google/gemini-cli@preview"
148+
"npm install -g @google/gemini-cli@latest"
119149
}
120150
}
121151

crates/agent_ui/src/acp/thread_view.rs

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2825,19 +2825,14 @@ impl AcpThreadView {
28252825
cx: &mut Context<Self>,
28262826
) -> AnyElement {
28272827
let (message, action_slot): (SharedString, _) = match e {
2828-
LoadError::NotInstalled {
2829-
error_message: _,
2830-
install_message: _,
2831-
install_command,
2832-
} => {
2833-
return self.render_not_installed(install_command.clone(), false, window, cx);
2828+
LoadError::NotInstalled => {
2829+
return self.render_not_installed(None, window, cx);
28342830
}
28352831
LoadError::Unsupported {
2836-
error_message: _,
2837-
upgrade_message: _,
2838-
upgrade_command,
2832+
command: path,
2833+
current_version,
28392834
} => {
2840-
return self.render_not_installed(upgrade_command.clone(), true, window, cx);
2835+
return self.render_not_installed(Some((path, current_version)), window, cx);
28412836
}
28422837
LoadError::Exited { .. } => ("Server exited with status {status}".into(), None),
28432838
LoadError::Other(msg) => (
@@ -2855,8 +2850,11 @@ impl AcpThreadView {
28552850
.into_any_element()
28562851
}
28572852

2858-
fn install_agent(&self, install_command: String, window: &mut Window, cx: &mut Context<Self>) {
2853+
fn install_agent(&self, window: &mut Window, cx: &mut Context<Self>) {
28592854
telemetry::event!("Agent Install CLI", agent = self.agent.telemetry_id());
2855+
let Some(install_command) = self.agent.install_command().map(|s| s.to_owned()) else {
2856+
return;
2857+
};
28602858
let task = self
28612859
.workspace
28622860
.update(cx, |workspace, cx| {
@@ -2899,32 +2897,35 @@ impl AcpThreadView {
28992897

29002898
fn render_not_installed(
29012899
&self,
2902-
install_command: String,
2903-
is_upgrade: bool,
2900+
existing_version: Option<(&SharedString, &SharedString)>,
29042901
window: &mut Window,
29052902
cx: &mut Context<Self>,
29062903
) -> AnyElement {
2904+
let install_command = self.agent.install_command().unwrap_or_default();
2905+
29072906
self.install_command_markdown.update(cx, |markdown, cx| {
29082907
if !markdown.source().contains(&install_command) {
29092908
markdown.replace(format!("```\n{}\n```", install_command), cx);
29102909
}
29112910
});
29122911

2913-
let (heading_label, description_label, button_label, or_label) = if is_upgrade {
2914-
(
2915-
"Upgrade Gemini CLI in Zed",
2916-
"Get access to the latest version with support for Zed.",
2917-
"Upgrade Gemini CLI",
2918-
"Or, to upgrade it manually:",
2919-
)
2920-
} else {
2921-
(
2922-
"Get Started with Gemini CLI in Zed",
2923-
"Use Google's new coding agent directly in Zed.",
2924-
"Install Gemini CLI",
2925-
"Or, to install it manually:",
2926-
)
2927-
};
2912+
let (heading_label, description_label, button_label) =
2913+
if let Some((path, version)) = existing_version {
2914+
(
2915+
format!("Upgrade {} to work with Zed", self.agent.name()),
2916+
format!(
2917+
"Currently using {}, which is only version {}",
2918+
path, version
2919+
),
2920+
format!("Upgrade {}", self.agent.name()),
2921+
)
2922+
} else {
2923+
(
2924+
format!("Get Started with {} in Zed", self.agent.name()),
2925+
"Use Google's new coding agent directly in Zed.".to_string(),
2926+
format!("Install {}", self.agent.name()),
2927+
)
2928+
};
29282929

29292930
v_flex()
29302931
.w_full()
@@ -2954,12 +2955,10 @@ impl AcpThreadView {
29542955
.icon_color(Color::Muted)
29552956
.icon_size(IconSize::Small)
29562957
.icon_position(IconPosition::Start)
2957-
.on_click(cx.listener(move |this, _, window, cx| {
2958-
this.install_agent(install_command.clone(), window, cx)
2959-
})),
2958+
.on_click(cx.listener(|this, _, window, cx| this.install_agent(window, cx))),
29602959
)
29612960
.child(
2962-
Label::new(or_label)
2961+
Label::new("Or, run the following command in your terminal:")
29632962
.size(LabelSize::Small)
29642963
.color(Color::Muted),
29652964
)
@@ -5403,6 +5402,10 @@ pub(crate) mod tests {
54035402
"Test".into()
54045403
}
54055404

5405+
fn install_command(&self) -> Option<&'static str> {
5406+
None
5407+
}
5408+
54065409
fn connect(
54075410
&self,
54085411
_root_dir: &Path,

0 commit comments

Comments
 (0)