Split out login_commands and logout_commands

Richard Feldman created

Change summary

crates/agent_servers/src/agent_servers.rs | 14 ++++++++
crates/agent_servers/src/claude.rs        |  8 +++++
crates/agent_servers/src/gemini.rs        |  4 ++
crates/agent_ui/src/acp/thread_view.rs    | 39 +++++++++++++++++-------
4 files changed, 53 insertions(+), 12 deletions(-)

Detailed changes

crates/agent_servers/src/agent_servers.rs 🔗

@@ -68,6 +68,20 @@ pub trait AgentServer: Send {
     ) {
     }
 
+    /// Returns the list of slash commands that should trigger Zed's authentication UI
+    /// when the user types them (e.g., "/login").
+    /// These commands will be intercepted by Zed to show the auth method selection UI.
+    fn login_commands(&self) -> Vec<&'static str> {
+        Vec::new()
+    }
+
+    /// Returns the list of logout-related slash commands that should be sent to the agent
+    /// to let it reset internal state (e.g., "/logout").
+    /// These commands will be added to available_commands and passed through to the agent.
+    fn logout_commands(&self) -> Vec<&'static str> {
+        Vec::new()
+    }
+
     fn connect(
         &self,
         root_dir: Option<&Path>,

crates/agent_servers/src/claude.rs 🔗

@@ -56,6 +56,14 @@ impl AgentServer for ClaudeCode {
         });
     }
 
+    fn login_commands(&self) -> Vec<&'static str> {
+        vec!["login"]
+    }
+
+    fn logout_commands(&self) -> Vec<&'static str> {
+        vec!["logout"]
+    }
+
     fn connect(
         &self,
         root_dir: Option<&Path>,

crates/agent_servers/src/gemini.rs 🔗

@@ -25,6 +25,10 @@ impl AgentServer for Gemini {
         ui::IconName::AiGemini
     }
 
+    fn login_commands(&self) -> Vec<&'static str> {
+        vec!["login"]
+    }
+
     fn connect(
         &self,
         root_dir: Option<&Path>,

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -1052,20 +1052,36 @@ impl AcpThreadView {
 
         let text = self.message_editor.read(cx).text(cx);
         let text = text.trim();
-        if text == "/login" || text == "/logout" {
+
+        // Check if this is a login or logout command
+        let command_name = text.strip_prefix('/');
+        let is_login_command = if let Some(cmd) = command_name {
+            self.agent.login_commands().contains(&cmd)
+        } else {
+            false
+        };
+        let is_logout_command = if let Some(cmd) = command_name {
+            self.agent.logout_commands().contains(&cmd)
+        } else {
+            false
+        };
+
+        if is_login_command || is_logout_command {
             let ThreadState::Ready { thread, .. } = &self.thread_state else {
                 return;
             };
 
             let connection = thread.read(cx).connection().clone();
             let can_login = !connection.auth_methods().is_empty() || !self.auth_commands.is_empty();
+
             // Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
-            let logout_supported = text == "/logout"
+            let logout_supported = is_logout_command
                 && self
                     .available_commands
                     .borrow()
                     .iter()
-                    .any(|command| command.name == "logout");
+                    .any(|command| command_name == Some(command.name.as_str()));
+
             if can_login && !logout_supported {
                 self.message_editor
                     .update(cx, |editor, cx| editor.clear(window, cx));
@@ -1422,21 +1438,20 @@ impl AcpThreadView {
             AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
                 let mut available_commands = available_commands.clone();
 
-                if thread
-                    .read(cx)
-                    .connection()
-                    .auth_methods()
-                    .iter()
-                    .any(|method| method.id.0.as_ref() == "claude-login")
-                {
+                // Add login commands from the agent
+                for command_name in self.agent.login_commands() {
                     available_commands.push(acp::AvailableCommand {
-                        name: "login".to_owned(),
+                        name: command_name.to_owned(),
                         description: "Authenticate".to_owned(),
                         input: None,
                         meta: None,
                     });
+                }
+
+                // Add logout commands from the agent
+                for command_name in self.agent.logout_commands() {
                     available_commands.push(acp::AvailableCommand {
-                        name: "logout".to_owned(),
+                        name: command_name.to_owned(),
                         description: "Authenticate".to_owned(),
                         input: None,
                         meta: None,