acp: Update to `0.10.2` (#51280)

Bennet Bo Fenner created

Updates to `0.10.2`, most notable change is implementing
`session/close`.

Release Notes:

- N/A

Change summary

Cargo.lock                             | 35 ++++++++++++++++++++-----
Cargo.toml                             |  2 
crates/acp_thread/src/acp_thread.rs    |  2 
crates/acp_thread/src/connection.rs    |  6 +++
crates/agent/src/agent.rs              |  6 +++
crates/agent_servers/src/acp.rs        | 39 ++++++++++++++++++++++-----
crates/agent_ui/src/connection_view.rs | 26 ++++++++++++------
7 files changed, 89 insertions(+), 27 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -227,9 +227,9 @@ dependencies = [
 
 [[package]]
 name = "agent-client-protocol"
-version = "0.9.4"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2659b1089101b15db31137710159421cb44785ecdb5ba784be3b4a6f8cb8a475"
+checksum = "9c56a59cf6315e99f874d2c1f96c69d2da5ffe0087d211297fc4a41f849770a2"
 dependencies = [
  "agent-client-protocol-schema",
  "anyhow",
@@ -244,16 +244,16 @@ dependencies = [
 
 [[package]]
 name = "agent-client-protocol-schema"
-version = "0.10.8"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44bc1fef9c32f03bce2ab44af35b6f483bfd169bf55cc59beeb2e3b1a00ae4d1"
+checksum = "e0497b9a95a404e35799904835c57c6f8c69b9d08ccfd3cb5b7d746425cd6789"
 dependencies = [
  "anyhow",
  "derive_more",
  "schemars",
  "serde",
  "serde_json",
- "strum 0.27.2",
+ "strum 0.28.0",
 ]
 
 [[package]]
@@ -7151,7 +7151,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_yaml",
- "strum_macros",
+ "strum_macros 0.27.2",
 ]
 
 [[package]]
@@ -16544,7 +16544,16 @@ version = "0.27.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
 dependencies = [
- "strum_macros",
+ "strum_macros 0.27.2",
+]
+
+[[package]]
+name = "strum"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
+dependencies = [
+ "strum_macros 0.28.0",
 ]
 
 [[package]]
@@ -16559,6 +16568,18 @@ dependencies = [
  "syn 2.0.117",
 ]
 
+[[package]]
+name = "strum_macros"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
 [[package]]
 name = "subtle"
 version = "2.6.1"

Cargo.toml 🔗

@@ -473,7 +473,7 @@ ztracing_macro = { path = "crates/ztracing_macro" }
 # External crates
 #
 
-agent-client-protocol = { version = "=0.9.4", features = ["unstable"] }
+agent-client-protocol = { version = "=0.10.2", features = ["unstable"] }
 aho-corasick = "1.1"
 alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "9d9640d4" }
 any_vec = "0.14"

crates/acp_thread/src/acp_thread.rs 🔗

@@ -4027,7 +4027,7 @@ mod tests {
         }
 
         fn authenticate(&self, method: acp::AuthMethodId, _cx: &mut App) -> Task<gpui::Result<()>> {
-            if self.auth_methods().iter().any(|m| m.id == method) {
+            if self.auth_methods().iter().any(|m| m.id() == &method) {
                 Task::ready(Ok(()))
             } else {
                 Task::ready(Err(anyhow!("Invalid Auth Method")))

crates/acp_thread/src/connection.rs 🔗

@@ -60,7 +60,11 @@ pub trait AgentConnection {
     }
 
     /// Close an existing session. Allows the agent to free the session from memory.
-    fn close_session(&self, _session_id: &acp::SessionId, _cx: &mut App) -> Task<Result<()>> {
+    fn close_session(
+        self: Rc<Self>,
+        _session_id: &acp::SessionId,
+        _cx: &mut App,
+    ) -> Task<Result<()>> {
         Task::ready(Err(anyhow::Error::msg("Closing sessions is not supported")))
     }
 

crates/agent/src/agent.rs 🔗

@@ -1418,7 +1418,11 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
         true
     }
 
-    fn close_session(&self, session_id: &acp::SessionId, cx: &mut App) -> Task<Result<()>> {
+    fn close_session(
+        self: Rc<Self>,
+        session_id: &acp::SessionId,
+        cx: &mut App,
+    ) -> Task<Result<()>> {
         self.0.update(cx, |agent, _cx| {
             let project_id = agent.sessions.get(session_id).map(|s| s.project_id);
             agent.sessions.remove(session_id);

crates/agent_servers/src/acp.rs 🔗

@@ -279,7 +279,7 @@ impl AcpConnection {
                 acp::InitializeRequest::new(acp::ProtocolVersion::V1)
                     .client_capabilities(
                         acp::ClientCapabilities::new()
-                            .fs(acp::FileSystemCapability::new()
+                            .fs(acp::FileSystemCapabilities::new()
                                 .read_text_file(true)
                                 .write_text_file(true))
                             .terminal(true)
@@ -331,11 +331,11 @@ impl AcpConnection {
                 "env": command.env.clone().unwrap_or_default(),
             });
             let meta = acp::Meta::from_iter([("terminal-auth".to_string(), value)]);
-            vec![
-                acp::AuthMethod::new("spawn-gemini-cli", "Login")
+            vec![acp::AuthMethod::Agent(
+                acp::AuthMethodAgent::new("spawn-gemini-cli", "Login")
                     .description("Login with your Google or Vertex AI account")
                     .meta(meta),
-            ]
+            )]
         } else {
             response.auth_methods
         };
@@ -744,6 +744,31 @@ impl AgentConnection for AcpConnection {
         })
     }
 
+    fn supports_close_session(&self) -> bool {
+        self.agent_capabilities.session_capabilities.close.is_some()
+    }
+
+    fn close_session(
+        self: Rc<Self>,
+        session_id: &acp::SessionId,
+        cx: &mut App,
+    ) -> Task<Result<()>> {
+        if !self.agent_capabilities.session_capabilities.close.is_none() {
+            return Task::ready(Err(anyhow!(LoadError::Other(
+                "Closing sessions is not supported by this agent.".into()
+            ))));
+        }
+
+        let conn = self.connection.clone();
+        let session_id = session_id.clone();
+        cx.foreground_executor().spawn(async move {
+            conn.close_session(acp::CloseSessionRequest::new(session_id.clone()))
+                .await?;
+            self.sessions.borrow_mut().remove(&session_id);
+            Ok(())
+        })
+    }
+
     fn auth_methods(&self) -> &[acp::AuthMethod] {
         &self.auth_methods
     }
@@ -1373,10 +1398,10 @@ impl acp::Client for ClientDelegate {
         Ok(acp::CreateTerminalResponse::new(terminal_id))
     }
 
-    async fn kill_terminal_command(
+    async fn kill_terminal(
         &self,
-        args: acp::KillTerminalCommandRequest,
-    ) -> Result<acp::KillTerminalCommandResponse, acp::Error> {
+        args: acp::KillTerminalRequest,
+    ) -> Result<acp::KillTerminalResponse, acp::Error> {
         self.session_thread(&args.session_id)?
             .update(&mut self.cx.clone(), |thread, cx| {
                 thread.kill_terminal(args.terminal_id, cx)

crates/agent_ui/src/connection_view.rs 🔗

@@ -463,7 +463,7 @@ impl ConnectedServerState {
         let tasks = self
             .threads
             .keys()
-            .map(|id| self.connection.close_session(id, cx));
+            .map(|id| self.connection.clone().close_session(id, cx));
         let task = futures::future::join_all(tasks);
         cx.background_spawn(async move {
             task.await;
@@ -1431,7 +1431,7 @@ impl ConnectionView {
                     .connection()
                     .auth_methods()
                     .iter()
-                    .any(|method| method.id.0.as_ref() == "claude-login")
+                    .any(|method| method.id().0.as_ref() == "claude-login")
                 {
                     available_commands.push(acp::AvailableCommand::new("login", "Authenticate"));
                     available_commands.push(acp::AvailableCommand::new("logout", "Authenticate"));
@@ -1495,10 +1495,15 @@ impl ConnectionView {
         let agent_telemetry_id = connection.telemetry_id();
 
         // Check for the experimental "terminal-auth" _meta field
-        let auth_method = connection.auth_methods().iter().find(|m| m.id == method);
+        let auth_method = connection.auth_methods().iter().find(|m| m.id() == &method);
 
         if let Some(terminal_auth) = auth_method
-            .and_then(|a| a.meta.as_ref())
+            .and_then(|a| match a {
+                acp::AuthMethod::EnvVar(env_var) => env_var.meta.as_ref(),
+                acp::AuthMethod::Terminal(terminal) => terminal.meta.as_ref(),
+                acp::AuthMethod::Agent(agent) => agent.meta.as_ref(),
+                _ => None,
+            })
             .and_then(|m| m.get("terminal-auth"))
         {
             // Extract terminal auth details from meta
@@ -1882,7 +1887,7 @@ impl ConnectionView {
                     .enumerate()
                     .rev()
                     .map(|(ix, method)| {
-                        let (method_id, name) = (method.id.0.clone(), method.name.clone());
+                        let (method_id, name) = (method.id().0.clone(), method.name().to_string());
                         let agent_telemetry_id = connection.telemetry_id();
 
                         Button::new(method_id.clone(), name)
@@ -1894,8 +1899,8 @@ impl ConnectionView {
                                     this.style(ButtonStyle::Outlined)
                                 }
                             })
-                            .when_some(method.description.clone(), |this, description| {
-                                this.tooltip(Tooltip::text(description))
+                            .when_some(method.description(), |this, description| {
+                                this.tooltip(Tooltip::text(description.to_string()))
                             })
                             .on_click({
                                 cx.listener(move |this, _, window, cx| {
@@ -4074,7 +4079,10 @@ pub(crate) mod tests {
         fn new() -> Self {
             Self {
                 authenticated: Arc::new(Mutex::new(false)),
-                auth_method: acp::AuthMethod::new(Self::AUTH_METHOD_ID, "Test Login"),
+                auth_method: acp::AuthMethod::Agent(acp::AuthMethodAgent::new(
+                    Self::AUTH_METHOD_ID,
+                    "Test Login",
+                )),
             }
         }
     }
@@ -4127,7 +4135,7 @@ pub(crate) mod tests {
             method_id: acp::AuthMethodId,
             _cx: &mut App,
         ) -> Task<gpui::Result<()>> {
-            if method_id == self.auth_method.id {
+            if &method_id == self.auth_method.id() {
                 *self.authenticated.lock() = true;
                 Task::ready(Ok(()))
             } else {