agent_servers.rs

  1mod acp;
  2mod claude;
  3mod codex;
  4mod custom;
  5mod gemini;
  6
  7#[cfg(any(test, feature = "test-support"))]
  8pub mod e2e_tests;
  9
 10pub use claude::*;
 11use client::ProxySettings;
 12pub use codex::*;
 13use collections::HashMap;
 14pub use custom::*;
 15use fs::Fs;
 16pub use gemini::*;
 17use http_client::read_no_proxy_from_env;
 18use project::agent_server_store::AgentServerStore;
 19
 20use acp_thread::AgentConnection;
 21use anyhow::Result;
 22use gpui::{App, AppContext, Entity, SharedString, Task};
 23use project::Project;
 24use settings::SettingsStore;
 25use std::{any::Any, path::Path, rc::Rc, sync::Arc};
 26
 27pub use acp::AcpConnection;
 28
 29pub struct AgentServerDelegate {
 30    store: Entity<AgentServerStore>,
 31    project: Entity<Project>,
 32    status_tx: Option<watch::Sender<SharedString>>,
 33    new_version_available: Option<watch::Sender<Option<String>>>,
 34}
 35
 36impl AgentServerDelegate {
 37    pub fn new(
 38        store: Entity<AgentServerStore>,
 39        project: Entity<Project>,
 40        status_tx: Option<watch::Sender<SharedString>>,
 41        new_version_tx: Option<watch::Sender<Option<String>>>,
 42    ) -> Self {
 43        Self {
 44            store,
 45            project,
 46            status_tx,
 47            new_version_available: new_version_tx,
 48        }
 49    }
 50
 51    pub fn project(&self) -> &Entity<Project> {
 52        &self.project
 53    }
 54}
 55
 56pub trait AgentServer: Send {
 57    fn logo(&self) -> ui::IconName;
 58    fn name(&self) -> SharedString;
 59    fn telemetry_id(&self) -> &'static str;
 60    fn default_mode(&self, _cx: &mut App) -> Option<agent_client_protocol::SessionModeId> {
 61        None
 62    }
 63    fn set_default_mode(
 64        &self,
 65        _mode_id: Option<agent_client_protocol::SessionModeId>,
 66        _fs: Arc<dyn Fs>,
 67        _cx: &mut App,
 68    ) {
 69    }
 70
 71    fn connect(
 72        &self,
 73        root_dir: Option<&Path>,
 74        delegate: AgentServerDelegate,
 75        cx: &mut App,
 76    ) -> Task<
 77        Result<(
 78            Rc<dyn AgentConnection>,
 79            HashMap<String, task::SpawnInTerminal>,
 80        )>,
 81    >;
 82
 83    fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
 84}
 85
 86impl dyn AgentServer {
 87    pub fn downcast<T: 'static + AgentServer + Sized>(self: Rc<Self>) -> Option<Rc<T>> {
 88        self.into_any().downcast().ok()
 89    }
 90}
 91
 92/// Extension trait for ACP-specific agent capabilities.
 93/// This trait is only implemented by agents that use the Agent Client Protocol (ACP).
 94pub trait AcpAgentServer: AgentServer {
 95    /// Returns the list of slash commands that should trigger Zed's authentication UI
 96    /// when running locally (e.g., "/login").
 97    /// These commands will be intercepted by Zed to show the auth method selection UI.
 98    fn local_login_commands(&self) -> Vec<String>;
 99
100    /// Returns the list of slash commands that should trigger Zed's authentication UI
101    /// when running remotely (e.g., "/login").
102    /// These commands will be intercepted by Zed to show the auth method selection UI.
103    fn remote_login_commands(&self) -> Vec<String>;
104
105    /// Returns the list of logout-related slash commands that should be sent to the agent
106    /// when running locally to let it reset internal state (e.g., "/logout").
107    /// These commands will be added to available_commands and passed through to the agent.
108    fn local_logout_commands(&self) -> Vec<String>;
109
110    /// Returns the list of logout-related slash commands that should be sent to the agent
111    /// when running remotely to let it reset internal state (e.g., "/logout").
112    /// These commands will be added to available_commands and passed through to the agent.
113    fn remote_logout_commands(&self) -> Vec<String>;
114}
115
116/// Load the default proxy environment variables to pass through to the agent
117pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
118    let proxy_url = cx
119        .read_global(|settings: &SettingsStore, _| settings.get::<ProxySettings>(None).proxy_url());
120    let mut env = HashMap::default();
121
122    if let Some(proxy_url) = &proxy_url {
123        let env_var = if proxy_url.scheme() == "https" {
124            "HTTPS_PROXY"
125        } else {
126            "HTTP_PROXY"
127        };
128        env.insert(env_var.to_owned(), proxy_url.to_string());
129    }
130
131    if let Some(no_proxy) = read_no_proxy_from_env() {
132        env.insert("NO_PROXY".to_owned(), no_proxy);
133    } else if proxy_url.is_some() {
134        // We sometimes need local MCP servers that we don't want to proxy
135        env.insert("NO_PROXY".to_owned(), "localhost,127.0.0.1".to_owned());
136    }
137
138    env
139}