From 74e80993a37383cd00008d0d2e893ad688d4bbb6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Oct 2025 12:21:12 -0400 Subject: [PATCH] Move login metehods out of AgentServer --- crates/agent/src/native_agent_server.rs | 16 --- crates/agent_servers/src/agent_servers.rs | 44 ++++---- crates/agent_servers/src/claude.rs | 36 +++--- crates/agent_servers/src/codex.rs | 36 +++--- crates/agent_servers/src/custom.rs | 40 +++---- crates/agent_servers/src/gemini.rs | 40 +++---- crates/agent_ui/src/acp/thread_view.rs | 129 +++++++++++++--------- 7 files changed, 180 insertions(+), 161 deletions(-) diff --git a/crates/agent/src/native_agent_server.rs b/crates/agent/src/native_agent_server.rs index 6ad3230d2c5d472dad1a72ef1c2a6e86e633fc32..b5c86c5a655807104eac6756163637f7613fcdae 100644 --- a/crates/agent/src/native_agent_server.rs +++ b/crates/agent/src/native_agent_server.rs @@ -34,22 +34,6 @@ impl AgentServer for NativeAgentServer { ui::IconName::ZedAgent } - fn local_login_commands(&self) -> Vec { - vec![] - } - - fn remote_login_commands(&self) -> Vec { - vec![] - } - - fn local_logout_commands(&self) -> Vec { - vec![] - } - - fn remote_logout_commands(&self) -> Vec { - vec![] - } - fn connect( &self, _root_dir: Option<&Path>, diff --git a/crates/agent_servers/src/agent_servers.rs b/crates/agent_servers/src/agent_servers.rs index a2ed9c168634c37d6399c3c4bcd5489a04e03ae1..160f242de406480d6f4921a6f2494aed0a04cb11 100644 --- a/crates/agent_servers/src/agent_servers.rs +++ b/crates/agent_servers/src/agent_servers.rs @@ -68,26 +68,6 @@ pub trait AgentServer: Send { ) { } - /// Returns the list of slash commands that should trigger Zed's authentication UI - /// when running locally (e.g., "/login"). - /// These commands will be intercepted by Zed to show the auth method selection UI. - fn local_login_commands(&self) -> Vec; - - /// Returns the list of slash commands that should trigger Zed's authentication UI - /// when running remotely (e.g., "/login"). - /// These commands will be intercepted by Zed to show the auth method selection UI. - fn remote_login_commands(&self) -> Vec; - - /// Returns the list of logout-related slash commands that should be sent to the agent - /// when running locally to let it reset internal state (e.g., "/logout"). - /// These commands will be added to available_commands and passed through to the agent. - fn local_logout_commands(&self) -> Vec; - - /// Returns the list of logout-related slash commands that should be sent to the agent - /// when running remotely to let it reset internal state (e.g., "/logout"). - /// These commands will be added to available_commands and passed through to the agent. - fn remote_logout_commands(&self) -> Vec; - fn connect( &self, root_dir: Option<&Path>, @@ -109,6 +89,30 @@ impl dyn AgentServer { } } +/// Extension trait for ACP-specific agent capabilities. +/// This trait is only implemented by agents that use the Agent Client Protocol (ACP). +pub trait AcpAgentServer: AgentServer { + /// Returns the list of slash commands that should trigger Zed's authentication UI + /// when running locally (e.g., "/login"). + /// These commands will be intercepted by Zed to show the auth method selection UI. + fn local_login_commands(&self) -> Vec; + + /// Returns the list of slash commands that should trigger Zed's authentication UI + /// when running remotely (e.g., "/login"). + /// These commands will be intercepted by Zed to show the auth method selection UI. + fn remote_login_commands(&self) -> Vec; + + /// Returns the list of logout-related slash commands that should be sent to the agent + /// when running locally to let it reset internal state (e.g., "/logout"). + /// These commands will be added to available_commands and passed through to the agent. + fn local_logout_commands(&self) -> Vec; + + /// Returns the list of logout-related slash commands that should be sent to the agent + /// when running remotely to let it reset internal state (e.g., "/logout"). + /// These commands will be added to available_commands and passed through to the agent. + fn remote_logout_commands(&self) -> Vec; +} + /// Load the default proxy environment variables to pass through to the agent pub fn load_proxy_env(cx: &mut App) -> HashMap { let proxy_url = cx diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index 0164378efa97cb65fae641c1b64d783bd4d4d883..d632caa61749d11442456b3f8f88ec6217e709f4 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -11,7 +11,7 @@ use collections::HashMap; use gpui::{App, AppContext as _, SharedString, Task}; use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME}; -use crate::{AgentServer, AgentServerDelegate, load_proxy_env}; +use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; #[derive(Clone)] @@ -56,22 +56,6 @@ impl AgentServer for ClaudeCode { }); } - fn local_login_commands(&self) -> Vec { - vec!["login".to_string()] - } - - fn remote_login_commands(&self) -> Vec { - vec!["login".to_string()] - } - - fn local_logout_commands(&self) -> Vec { - vec!["logout".to_string()] - } - - fn remote_logout_commands(&self) -> Vec { - vec!["logout".to_string()] - } - fn connect( &self, root_dir: Option<&Path>, @@ -122,3 +106,21 @@ impl AgentServer for ClaudeCode { self } } + +impl AcpAgentServer for ClaudeCode { + fn local_login_commands(&self) -> Vec { + vec!["login".to_string()] + } + + fn remote_login_commands(&self) -> Vec { + vec!["login".to_string()] + } + + fn local_logout_commands(&self) -> Vec { + vec!["logout".to_string()] + } + + fn remote_logout_commands(&self) -> Vec { + vec!["logout".to_string()] + } +} diff --git a/crates/agent_servers/src/codex.rs b/crates/agent_servers/src/codex.rs index bd728649b6266f440de91b52c4d9bd081bf73660..fb9f3883329107dcf7b418672849ce91db43b655 100644 --- a/crates/agent_servers/src/codex.rs +++ b/crates/agent_servers/src/codex.rs @@ -11,7 +11,7 @@ use gpui::{App, AppContext as _, SharedString, Task}; use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME}; use settings::{SettingsStore, update_settings_file}; -use crate::{AgentServer, AgentServerDelegate, load_proxy_env}; +use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env}; #[derive(Clone)] pub struct Codex; @@ -36,22 +36,6 @@ impl AgentServer for Codex { ui::IconName::AiOpenAi } - fn local_login_commands(&self) -> Vec { - vec![] - } - - fn remote_login_commands(&self) -> Vec { - vec![] - } - - fn local_logout_commands(&self) -> Vec { - vec![] - } - - fn remote_logout_commands(&self) -> Vec { - vec![] - } - fn default_mode(&self, cx: &mut App) -> Option { let settings = cx.read_global(|settings: &SettingsStore, _| { settings.get::(None).codex.clone() @@ -126,3 +110,21 @@ impl AgentServer for Codex { self } } + +impl AcpAgentServer for Codex { + fn local_login_commands(&self) -> Vec { + vec![] + } + + fn remote_login_commands(&self) -> Vec { + vec![] + } + + fn local_logout_commands(&self) -> Vec { + vec![] + } + + fn remote_logout_commands(&self) -> Vec { + vec![] + } +} diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index 1dfddc9f5050c034f040f1d9cef87074cd90629c..a5a644b133fd4e193a3a056e043ea06dcc911140 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -1,4 +1,4 @@ -use crate::{AgentServerDelegate, load_proxy_env}; +use crate::{AcpAgentServer, AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; use agent_client_protocol as acp; use anyhow::{Context as _, Result}; @@ -7,7 +7,7 @@ use fs::Fs; use gpui::{App, AppContext as _, SharedString, Task}; use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName}; use settings::{SettingsStore, update_settings_file}; -use std::{path::Path, rc::Rc, sync::Arc}; +use std::{any::Any, path::Path, rc::Rc, sync::Arc}; use ui::IconName; /// A generic agent server implementation for custom user-defined agents @@ -34,22 +34,6 @@ impl crate::AgentServer for CustomAgentServer { IconName::Terminal } - fn local_login_commands(&self) -> Vec { - vec![] - } - - fn remote_login_commands(&self) -> Vec { - vec![] - } - - fn local_logout_commands(&self) -> Vec { - vec![] - } - - fn remote_logout_commands(&self) -> Vec { - vec![] - } - fn default_mode(&self, cx: &mut App) -> Option { let settings = cx.read_global(|settings: &SettingsStore, _| { settings @@ -125,7 +109,25 @@ impl crate::AgentServer for CustomAgentServer { }) } - fn into_any(self: Rc) -> Rc { + fn into_any(self: Rc) -> Rc { self } } + +impl AcpAgentServer for CustomAgentServer { + fn local_login_commands(&self) -> Vec { + vec![] + } + + fn remote_login_commands(&self) -> Vec { + vec![] + } + + fn local_logout_commands(&self) -> Vec { + vec![] + } + + fn remote_logout_commands(&self) -> Vec { + vec![] + } +} diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index 59a8e154c4a738d77dd44a59ee584f2c5939a64f..19f17e5ab40f731a6cbc8e30dfc5ea26b3f28684 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use std::{any::Any, path::Path}; -use crate::{AgentServer, AgentServerDelegate, load_proxy_env}; +use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env}; use acp_thread::AgentConnection; use anyhow::{Context as _, Result}; use collections::HashMap; @@ -25,24 +25,6 @@ impl AgentServer for Gemini { ui::IconName::AiGemini } - fn local_login_commands(&self) -> Vec { - vec!["login".to_string()] - } - - fn remote_login_commands(&self) -> Vec { - // When remote, OAuth doesn't work, so login is handled via the - // auth_commands mapping (oauth-personal -> spawn-gemini-cli) - vec![] - } - - fn local_logout_commands(&self) -> Vec { - vec![] - } - - fn remote_logout_commands(&self) -> Vec { - vec![] - } - fn connect( &self, root_dir: Option<&Path>, @@ -104,6 +86,26 @@ impl AgentServer for Gemini { } } +impl AcpAgentServer for Gemini { + fn local_login_commands(&self) -> Vec { + vec!["login".to_string()] + } + + fn remote_login_commands(&self) -> Vec { + // When remote, OAuth doesn't work, so login is handled via the + // auth_commands mapping (oauth-personal -> spawn-gemini-cli) + vec![] + } + + fn local_logout_commands(&self) -> Vec { + vec![] + } + + fn remote_logout_commands(&self) -> Vec { + vec![] + } +} + #[cfg(test)] pub(crate) mod tests { use project::agent_server_store::AgentServerCommand; diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 26ba07855fb67b81d6469cad4d3b3f7229553f57..bf26548d08de9a873bcb601a4ae8e73bacdcdec2 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -260,6 +260,7 @@ impl ThreadFeedbackState { pub struct AcpThreadView { agent: Rc, + acp_agent: Option>, workspace: WeakEntity, project: Entity, thread_state: ThreadState, @@ -403,8 +404,38 @@ impl AcpThreadView { let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref()) == Some(crate::ExternalAgent::Codex); + // Try to downcast to AcpAgentServer for ACP-specific functionality + let acp_agent = agent + .clone() + .into_any() + .downcast::() + .map(|a| a as Rc) + .or_else(|_| { + agent + .clone() + .into_any() + .downcast::() + .map(|a| a as Rc) + }) + .or_else(|_| { + agent + .clone() + .into_any() + .downcast::() + .map(|a| a as Rc) + }) + .or_else(|_| { + agent + .clone() + .into_any() + .downcast::() + .map(|a| a as Rc) + }) + .ok(); + Self { agent: agent.clone(), + acp_agent, workspace: workspace.clone(), project: project.clone(), entry_view_state, @@ -1053,18 +1084,23 @@ impl AcpThreadView { let text = self.message_editor.read(cx).text(cx); let text = text.trim(); - // Check if this is a login or logout command + // Check if this is a login or logout command (only for ACP agents) let command_name = text.strip_prefix('/'); let is_remote = self.project.read(cx).is_via_remote_server(); - let login_commands = if is_remote { - self.agent.remote_login_commands() - } else { - self.agent.local_login_commands() - }; - let logout_commands = if is_remote { - self.agent.remote_logout_commands() + let (login_commands, logout_commands) = if let Some(acp_agent) = &self.acp_agent { + let login = if is_remote { + acp_agent.remote_login_commands() + } else { + acp_agent.local_login_commands() + }; + let logout = if is_remote { + acp_agent.remote_logout_commands() + } else { + acp_agent.local_logout_commands() + }; + (login, logout) } else { - self.agent.local_logout_commands() + (vec![], vec![]) }; let is_login_command = if let Some(cmd) = command_name { login_commands.iter().any(|c| c == cmd) @@ -1449,36 +1485,39 @@ impl AcpThreadView { AcpThreadEvent::AvailableCommandsUpdated(available_commands) => { let mut available_commands = available_commands.clone(); - let is_remote = self.project.read(cx).is_via_remote_server(); - let login_commands = if is_remote { - self.agent.remote_login_commands() - } else { - self.agent.local_login_commands() - }; - let logout_commands = if is_remote { - self.agent.remote_logout_commands() - } else { - self.agent.local_logout_commands() - }; + // Add auth commands only for ACP agents + if let Some(acp_agent) = &self.acp_agent { + let is_remote = self.project.read(cx).is_via_remote_server(); + let login_commands = if is_remote { + acp_agent.remote_login_commands() + } else { + acp_agent.local_login_commands() + }; + let logout_commands = if is_remote { + acp_agent.remote_logout_commands() + } else { + acp_agent.local_logout_commands() + }; - // Add login commands from the agent - for command_name in login_commands { - available_commands.push(acp::AvailableCommand { - name: command_name, - description: "Authenticate".to_owned(), - input: None, - meta: None, - }); - } + // Add login commands from the agent + for command_name in login_commands { + available_commands.push(acp::AvailableCommand { + name: command_name, + description: "Authenticate".to_owned(), + input: None, + meta: None, + }); + } - // Add logout commands from the agent - for command_name in logout_commands { - available_commands.push(acp::AvailableCommand { - name: command_name, - description: "Authenticate".to_owned(), - input: None, - meta: None, - }); + // Add logout commands from the agent + for command_name in logout_commands { + available_commands.push(acp::AvailableCommand { + name: command_name, + description: "Authenticate".to_owned(), + input: None, + meta: None, + }); + } } self.available_commands.replace(available_commands); @@ -6050,22 +6089,6 @@ pub(crate) mod tests { "Test".into() } - fn local_login_commands(&self) -> Vec { - vec![] - } - - fn remote_login_commands(&self) -> Vec { - vec![] - } - - fn local_logout_commands(&self) -> Vec { - vec![] - } - - fn remote_logout_commands(&self) -> Vec { - vec![] - } - fn connect( &self, _root_dir: Option<&Path>,