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}