custom.rs

  1use crate::{AgentServerDelegate, load_proxy_env};
  2use acp_thread::AgentConnection;
  3use agent_client_protocol as acp;
  4use anyhow::{Context as _, Result};
  5use collections::HashMap;
  6use fs::Fs;
  7use gpui::{App, AppContext as _, SharedString, Task};
  8use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
  9use settings::{SettingsStore, update_settings_file};
 10use std::{path::Path, rc::Rc, sync::Arc};
 11use ui::IconName;
 12
 13/// A generic agent server implementation for custom user-defined agents
 14pub struct CustomAgentServer {
 15    name: SharedString,
 16}
 17
 18impl CustomAgentServer {
 19    pub fn new(name: SharedString) -> Self {
 20        Self { name }
 21    }
 22}
 23
 24impl crate::AgentServer for CustomAgentServer {
 25    fn telemetry_id(&self) -> &'static str {
 26        "custom"
 27    }
 28
 29    fn name(&self) -> SharedString {
 30        self.name.clone()
 31    }
 32
 33    fn logo(&self) -> IconName {
 34        IconName::Terminal
 35    }
 36
 37    fn local_login_commands(&self) -> Vec<String> {
 38        vec![]
 39    }
 40
 41    fn remote_login_commands(&self) -> Vec<String> {
 42        vec![]
 43    }
 44
 45    fn local_logout_commands(&self) -> Vec<String> {
 46        vec![]
 47    }
 48
 49    fn remote_logout_commands(&self) -> Vec<String> {
 50        vec![]
 51    }
 52
 53    fn default_mode(&self, cx: &mut App) -> Option<acp::SessionModeId> {
 54        let settings = cx.read_global(|settings: &SettingsStore, _| {
 55            settings
 56                .get::<AllAgentServersSettings>(None)
 57                .custom
 58                .get(&self.name())
 59                .cloned()
 60        });
 61
 62        settings
 63            .as_ref()
 64            .and_then(|s| s.default_mode.clone().map(|m| acp::SessionModeId(m.into())))
 65    }
 66
 67    fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
 68        let name = self.name();
 69        update_settings_file(fs, cx, move |settings, _| {
 70            settings
 71                .agent_servers
 72                .get_or_insert_default()
 73                .custom
 74                .get_mut(&name)
 75                .unwrap()
 76                .default_mode = mode_id.map(|m| m.to_string())
 77        });
 78    }
 79
 80    fn connect(
 81        &self,
 82        root_dir: Option<&Path>,
 83        delegate: AgentServerDelegate,
 84        cx: &mut App,
 85    ) -> Task<
 86        Result<(
 87            Rc<dyn AgentConnection>,
 88            HashMap<String, task::SpawnInTerminal>,
 89        )>,
 90    > {
 91        let name = self.name();
 92        let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
 93        let is_remote = delegate.project.read(cx).is_via_remote_server();
 94        let default_mode = self.default_mode(cx);
 95        let store = delegate.store.downgrade();
 96        let extra_env = load_proxy_env(cx);
 97
 98        cx.spawn(async move |cx| {
 99            let (command, root_dir, auth_commands) = store
100                .update(cx, |store, cx| {
101                    let agent = store
102                        .get_external_agent(&ExternalAgentServerName(name.clone()))
103                        .with_context(|| {
104                            format!("Custom agent server `{}` is not registered", name)
105                        })?;
106                    anyhow::Ok(agent.get_command(
107                        root_dir.as_deref(),
108                        extra_env,
109                        delegate.status_tx,
110                        delegate.new_version_available,
111                        &mut cx.to_async(),
112                    ))
113                })??
114                .await?;
115            let connection = crate::acp::connect(
116                name,
117                command,
118                root_dir.as_ref(),
119                default_mode,
120                is_remote,
121                cx,
122            )
123            .await?;
124            Ok((connection, auth_commands))
125        })
126    }
127
128    fn into_any(self: Rc<Self>) -> Rc<dyn std::any::Any> {
129        self
130    }
131}