From a47a387186ac7a88af3d342cb83e1c2ec459ad81 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Oct 2025 11:31:13 -0400 Subject: [PATCH] Separate out local and remote login/logout --- crates/agent/src/native_agent_server.rs | 16 +++++++ crates/agent_servers/src/agent_servers.rs | 18 ++++++-- crates/agent_servers/src/claude.rs | 12 +++++- crates/agent_servers/src/codex.rs | 16 +++++++ crates/agent_servers/src/custom.rs | 16 +++++++ crates/agent_servers/src/gemini.rs | 26 +++++++----- crates/agent_ui/src/acp/thread_view.rs | 51 ++++++++++++++++++++--- 7 files changed, 133 insertions(+), 22 deletions(-) diff --git a/crates/agent/src/native_agent_server.rs b/crates/agent/src/native_agent_server.rs index b5c86c5a655807104eac6756163637f7613fcdae..e4e52445d98bdd76e40584c7d7cbcf2ba0c5e318 100644 --- a/crates/agent/src/native_agent_server.rs +++ b/crates/agent/src/native_agent_server.rs @@ -34,6 +34,22 @@ impl AgentServer for NativeAgentServer { ui::IconName::ZedAgent } + fn local_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn local_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_logout_commands(&self) -> Vec<&'static str> { + 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 77ea37578253faddaf006bda71a54dd0030306d3..1ecbaddf21b8eec45650793e2fb46e6145a98435 100644 --- a/crates/agent_servers/src/agent_servers.rs +++ b/crates/agent_servers/src/agent_servers.rs @@ -69,14 +69,24 @@ 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"). + /// when running locally (e.g., "/login"). /// These commands will be intercepted by Zed to show the auth method selection UI. - fn login_commands(&self) -> Vec<&'static str>; + fn local_login_commands(&self) -> Vec<&'static str>; + + /// 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<&'static str>; + + /// 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<&'static str>; /// Returns the list of logout-related slash commands that should be sent to the agent - /// to let it reset internal state (e.g., "/logout"). + /// 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 logout_commands(&self) -> Vec<&'static str>; + fn remote_logout_commands(&self) -> Vec<&'static str>; fn connect( &self, diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index 914b43805dea5f5e0c3588b924a81c616904395c..c70b476d8d858de3b817e821b6a07d6415ad9ad5 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -56,11 +56,19 @@ impl AgentServer for ClaudeCode { }); } - fn login_commands(&self) -> Vec<&'static str> { + fn local_login_commands(&self) -> Vec<&'static str> { vec!["login"] } - fn logout_commands(&self) -> Vec<&'static str> { + fn remote_login_commands(&self) -> Vec<&'static str> { + vec!["login"] + } + + fn local_logout_commands(&self) -> Vec<&'static str> { + vec!["logout"] + } + + fn remote_logout_commands(&self) -> Vec<&'static str> { vec!["logout"] } diff --git a/crates/agent_servers/src/codex.rs b/crates/agent_servers/src/codex.rs index 76600ba35bae721d8045484b5ae232add60b6282..4b53335be35530a49ffba5ba776df62fd5f65cd9 100644 --- a/crates/agent_servers/src/codex.rs +++ b/crates/agent_servers/src/codex.rs @@ -36,6 +36,22 @@ impl AgentServer for Codex { ui::IconName::AiOpenAi } + fn local_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn local_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + fn default_mode(&self, cx: &mut App) -> Option { let settings = cx.read_global(|settings: &SettingsStore, _| { settings.get::(None).codex.clone() diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index 0a72db9ae3db48c8060a7c19dda17e582770d9ff..1912998401495fd8fa668a1616d43ffb1cdd1b3c 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -34,6 +34,22 @@ impl crate::AgentServer for CustomAgentServer { IconName::Terminal } + fn local_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn local_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + fn default_mode(&self, cx: &mut App) -> Option { let settings = cx.read_global(|settings: &SettingsStore, _| { settings diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index 1fba2cb39df5793500c03944cb2221aabfb105c9..b213b171f756e9dd700b9bb368049c0b7cf45861 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -25,10 +25,24 @@ impl AgentServer for Gemini { ui::IconName::AiGemini } - fn login_commands(&self) -> Vec<&'static str> { + fn local_login_commands(&self) -> Vec<&'static str> { vec!["login"] } + fn remote_login_commands(&self) -> Vec<&'static str> { + // 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<&'static str> { + vec![] + } + + fn remote_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + fn connect( &self, root_dir: Option<&Path>, @@ -57,7 +71,7 @@ impl AgentServer for Gemini { { extra_env.insert("GEMINI_API_KEY".into(), api_key); } - let (command, root_dir, mut auth_commands) = store + let (command, root_dir, auth_commands) = store .update(cx, |store, cx| { let agent = store .get_external_agent(&GEMINI_NAME.into()) @@ -72,14 +86,6 @@ impl AgentServer for Gemini { })?? .await?; - // When remote, OAuth doesn't work, so we need to use the terminal-based login - // for oauth-personal. Map it to the same terminal command as spawn-gemini-cli. - if is_remote { - if let Some(spawn_gemini_cli) = auth_commands.get("spawn-gemini-cli").cloned() { - auth_commands.insert("oauth-personal".to_string(), spawn_gemini_cli); - } - } - let connection = crate::acp::connect( name, command, diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index c25464e9f3c16f95f7a887c6a64a66d74b0d4618..2adc7cd728b1542f748cee877110635230b42966 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -1055,13 +1055,24 @@ impl AcpThreadView { // Check if this is a login or logout command 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() + } else { + self.agent.local_logout_commands() + }; let is_login_command = if let Some(cmd) = command_name { - self.agent.login_commands().contains(&cmd) + login_commands.contains(&cmd) } else { false }; let is_logout_command = if let Some(cmd) = command_name { - self.agent.logout_commands().contains(&cmd) + logout_commands.contains(&cmd) } else { false }; @@ -1438,10 +1449,22 @@ 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 login commands from the agent - for command_name in self.agent.login_commands() { + for command_name in login_commands { available_commands.push(acp::AvailableCommand { - name: command_name.to_owned(), + name: command_name.to_string(), description: "Authenticate".to_owned(), input: None, meta: None, @@ -1449,9 +1472,9 @@ impl AcpThreadView { } // Add logout commands from the agent - for command_name in self.agent.logout_commands() { + for command_name in logout_commands { available_commands.push(acp::AvailableCommand { - name: command_name.to_owned(), + name: command_name.to_string(), description: "Authenticate".to_owned(), input: None, meta: None, @@ -6027,6 +6050,22 @@ pub(crate) mod tests { "Test".into() } + fn local_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_login_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn local_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + + fn remote_logout_commands(&self) -> Vec<&'static str> { + vec![] + } + fn connect( &self, _root_dir: Option<&Path>,