claude.rs

  1use agent_client_protocol as acp;
  2use collections::HashSet;
  3use fs::Fs;
  4use settings::{SettingsStore, update_settings_file};
  5use std::rc::Rc;
  6use std::sync::Arc;
  7use std::{any::Any, path::PathBuf};
  8
  9use anyhow::{Context as _, Result};
 10use gpui::{App, AppContext as _, SharedString, Task};
 11use project::agent_server_store::{AllAgentServersSettings, CLAUDE_AGENT_NAME};
 12
 13use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
 14use acp_thread::AgentConnection;
 15
 16#[derive(Clone)]
 17pub struct ClaudeCode;
 18
 19pub struct AgentServerLoginCommand {
 20    pub path: PathBuf,
 21    pub arguments: Vec<String>,
 22}
 23
 24impl AgentServer for ClaudeCode {
 25    fn name(&self) -> SharedString {
 26        "Claude Agent".into()
 27    }
 28
 29    fn logo(&self) -> ui::IconName {
 30        ui::IconName::AiClaude
 31    }
 32
 33    fn default_mode(&self, cx: &App) -> Option<acp::SessionModeId> {
 34        let settings = cx.read_global(|settings: &SettingsStore, _| {
 35            settings.get::<AllAgentServersSettings>(None).claude.clone()
 36        });
 37
 38        settings
 39            .as_ref()
 40            .and_then(|s| s.default_mode.clone().map(acp::SessionModeId::new))
 41    }
 42
 43    fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
 44        update_settings_file(fs, cx, |settings, _| {
 45            settings
 46                .agent_servers
 47                .get_or_insert_default()
 48                .claude
 49                .get_or_insert_default()
 50                .default_mode = mode_id.map(|m| m.to_string())
 51        });
 52    }
 53
 54    fn default_model(&self, cx: &App) -> Option<acp::ModelId> {
 55        let settings = cx.read_global(|settings: &SettingsStore, _| {
 56            settings.get::<AllAgentServersSettings>(None).claude.clone()
 57        });
 58
 59        settings
 60            .as_ref()
 61            .and_then(|s| s.default_model.clone().map(acp::ModelId::new))
 62    }
 63
 64    fn set_default_model(&self, model_id: Option<acp::ModelId>, fs: Arc<dyn Fs>, cx: &mut App) {
 65        update_settings_file(fs, cx, |settings, _| {
 66            settings
 67                .agent_servers
 68                .get_or_insert_default()
 69                .claude
 70                .get_or_insert_default()
 71                .default_model = model_id.map(|m| m.to_string())
 72        });
 73    }
 74
 75    fn favorite_model_ids(&self, cx: &mut App) -> HashSet<acp::ModelId> {
 76        let settings = cx.read_global(|settings: &SettingsStore, _| {
 77            settings.get::<AllAgentServersSettings>(None).claude.clone()
 78        });
 79
 80        settings
 81            .as_ref()
 82            .map(|s| {
 83                s.favorite_models
 84                    .iter()
 85                    .map(|id| acp::ModelId::new(id.clone()))
 86                    .collect()
 87            })
 88            .unwrap_or_default()
 89    }
 90
 91    fn toggle_favorite_model(
 92        &self,
 93        model_id: acp::ModelId,
 94        should_be_favorite: bool,
 95        fs: Arc<dyn Fs>,
 96        cx: &App,
 97    ) {
 98        update_settings_file(fs, cx, move |settings, _| {
 99            let favorite_models = &mut settings
100                .agent_servers
101                .get_or_insert_default()
102                .claude
103                .get_or_insert_default()
104                .favorite_models;
105
106            let model_id_str = model_id.to_string();
107            if should_be_favorite {
108                if !favorite_models.contains(&model_id_str) {
109                    favorite_models.push(model_id_str);
110                }
111            } else {
112                favorite_models.retain(|id| id != &model_id_str);
113            }
114        });
115    }
116
117    fn default_config_option(&self, config_id: &str, cx: &App) -> Option<String> {
118        let settings = cx.read_global(|settings: &SettingsStore, _| {
119            settings.get::<AllAgentServersSettings>(None).claude.clone()
120        });
121
122        settings
123            .as_ref()
124            .and_then(|s| s.default_config_options.get(config_id).cloned())
125    }
126
127    fn set_default_config_option(
128        &self,
129        config_id: &str,
130        value_id: Option<&str>,
131        fs: Arc<dyn Fs>,
132        cx: &mut App,
133    ) {
134        let config_id = config_id.to_string();
135        let value_id = value_id.map(|s| s.to_string());
136        update_settings_file(fs, cx, move |settings, _| {
137            let config_options = &mut settings
138                .agent_servers
139                .get_or_insert_default()
140                .claude
141                .get_or_insert_default()
142                .default_config_options;
143
144            if let Some(value) = value_id.clone() {
145                config_options.insert(config_id.clone(), value);
146            } else {
147                config_options.remove(&config_id);
148            }
149        });
150    }
151
152    fn favorite_config_option_value_ids(
153        &self,
154        config_id: &acp::SessionConfigId,
155        cx: &mut App,
156    ) -> HashSet<acp::SessionConfigValueId> {
157        let settings = cx.read_global(|settings: &SettingsStore, _| {
158            settings.get::<AllAgentServersSettings>(None).claude.clone()
159        });
160
161        settings
162            .as_ref()
163            .and_then(|s| s.favorite_config_option_values.get(config_id.0.as_ref()))
164            .map(|values| {
165                values
166                    .iter()
167                    .cloned()
168                    .map(acp::SessionConfigValueId::new)
169                    .collect()
170            })
171            .unwrap_or_default()
172    }
173
174    fn toggle_favorite_config_option_value(
175        &self,
176        config_id: acp::SessionConfigId,
177        value_id: acp::SessionConfigValueId,
178        should_be_favorite: bool,
179        fs: Arc<dyn Fs>,
180        cx: &App,
181    ) {
182        let config_id = config_id.to_string();
183        let value_id = value_id.to_string();
184
185        update_settings_file(fs, cx, move |settings, _| {
186            let favorites = &mut settings
187                .agent_servers
188                .get_or_insert_default()
189                .claude
190                .get_or_insert_default()
191                .favorite_config_option_values;
192
193            let entry = favorites.entry(config_id.clone()).or_insert_with(Vec::new);
194
195            if should_be_favorite {
196                if !entry.iter().any(|v| v == &value_id) {
197                    entry.push(value_id.clone());
198                }
199            } else {
200                entry.retain(|v| v != &value_id);
201                if entry.is_empty() {
202                    favorites.remove(&config_id);
203                }
204            }
205        });
206    }
207
208    fn connect(
209        &self,
210        delegate: AgentServerDelegate,
211        cx: &mut App,
212    ) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
213        let name = self.name();
214        let store = delegate.store.downgrade();
215        let extra_env = load_proxy_env(cx);
216        let default_mode = self.default_mode(cx);
217        let default_model = self.default_model(cx);
218        let default_config_options = cx.read_global(|settings: &SettingsStore, _| {
219            settings
220                .get::<AllAgentServersSettings>(None)
221                .claude
222                .as_ref()
223                .map(|s| s.default_config_options.clone())
224                .unwrap_or_default()
225        });
226
227        cx.spawn(async move |cx| {
228            let (command, login) = store
229                .update(cx, |store, cx| {
230                    let agent = store
231                        .get_external_agent(&CLAUDE_AGENT_NAME.into())
232                        .context("Claude Agent is not registered")?;
233                    anyhow::Ok(agent.get_command(
234                        extra_env,
235                        delegate.status_tx,
236                        delegate.new_version_available,
237                        &mut cx.to_async(),
238                    ))
239                })??
240                .await?;
241            let connection = crate::acp::connect(
242                name.clone(),
243                name,
244                command,
245                default_mode,
246                default_model,
247                default_config_options,
248                cx,
249            )
250            .await?;
251            Ok((connection, login))
252        })
253    }
254
255    fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
256        self
257    }
258}