1mod agent_profile;
2
3use std::sync::Arc;
4
5use anyhow::{Result, bail};
6use collections::IndexMap;
7use gpui::{App, Pixels, SharedString};
8use language_model::LanguageModel;
9use schemars::{JsonSchema, json_schema};
10use serde::{Deserialize, Serialize};
11use settings::{Settings, SettingsSources};
12use std::borrow::Cow;
13
14pub use crate::agent_profile::*;
15
16pub fn init(cx: &mut App) {
17 AgentSettings::register(cx);
18}
19
20#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
21#[serde(rename_all = "snake_case")]
22pub enum AgentDockPosition {
23 Left,
24 #[default]
25 Right,
26 Bottom,
27}
28
29#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
30#[serde(rename_all = "snake_case")]
31pub enum DefaultView {
32 #[default]
33 Thread,
34 TextThread,
35}
36
37#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
38#[serde(rename_all = "snake_case")]
39pub enum NotifyWhenAgentWaiting {
40 #[default]
41 PrimaryScreen,
42 AllScreens,
43 Never,
44}
45
46#[derive(Default, Clone, Debug)]
47pub struct AgentSettings {
48 pub enabled: bool,
49 pub button: bool,
50 pub dock: AgentDockPosition,
51 pub default_width: Pixels,
52 pub default_height: Pixels,
53 pub default_model: Option<LanguageModelSelection>,
54 pub inline_assistant_model: Option<LanguageModelSelection>,
55 pub commit_message_model: Option<LanguageModelSelection>,
56 pub thread_summary_model: Option<LanguageModelSelection>,
57 pub inline_alternatives: Vec<LanguageModelSelection>,
58 pub using_outdated_settings_version: bool,
59 pub default_profile: AgentProfileId,
60 pub default_view: DefaultView,
61 pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
62 pub always_allow_tool_actions: bool,
63 pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
64 pub play_sound_when_agent_done: bool,
65 pub stream_edits: bool,
66 pub single_file_review: bool,
67 pub model_parameters: Vec<LanguageModelParameters>,
68 pub preferred_completion_mode: CompletionMode,
69 pub enable_feedback: bool,
70 pub expand_edit_card: bool,
71 pub expand_terminal_card: bool,
72 pub use_modifier_to_send: bool,
73}
74
75impl AgentSettings {
76 pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
77 let settings = Self::get_global(cx);
78 settings
79 .model_parameters
80 .iter()
81 .rfind(|setting| setting.matches(model))
82 .and_then(|m| m.temperature)
83 }
84
85 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
86 self.inline_assistant_model = Some(LanguageModelSelection {
87 provider: provider.into(),
88 model,
89 });
90 }
91
92 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
93 self.commit_message_model = Some(LanguageModelSelection {
94 provider: provider.into(),
95 model,
96 });
97 }
98
99 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
100 self.thread_summary_model = Some(LanguageModelSelection {
101 provider: provider.into(),
102 model,
103 });
104 }
105}
106
107#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
108pub struct LanguageModelParameters {
109 pub provider: Option<LanguageModelProviderSetting>,
110 pub model: Option<SharedString>,
111 pub temperature: Option<f32>,
112}
113
114impl LanguageModelParameters {
115 pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
116 if let Some(provider) = &self.provider {
117 if provider.0 != model.provider_id().0 {
118 return false;
119 }
120 }
121 if let Some(setting_model) = &self.model {
122 if *setting_model != model.id().0 {
123 return false;
124 }
125 }
126 true
127 }
128}
129
130impl AgentSettingsContent {
131 pub fn set_dock(&mut self, dock: AgentDockPosition) {
132 self.dock = Some(dock);
133 }
134
135 pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
136 let model = language_model.id().0.to_string();
137 let provider = language_model.provider_id().0.to_string();
138
139 self.default_model = Some(LanguageModelSelection {
140 provider: provider.into(),
141 model,
142 });
143 }
144
145 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
146 self.inline_assistant_model = Some(LanguageModelSelection {
147 provider: provider.into(),
148 model,
149 });
150 }
151
152 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
153 self.commit_message_model = Some(LanguageModelSelection {
154 provider: provider.into(),
155 model,
156 });
157 }
158
159 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
160 self.thread_summary_model = Some(LanguageModelSelection {
161 provider: provider.into(),
162 model,
163 });
164 }
165
166 pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
167 self.always_allow_tool_actions = Some(allow);
168 }
169
170 pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
171 self.play_sound_when_agent_done = Some(allow);
172 }
173
174 pub fn set_single_file_review(&mut self, allow: bool) {
175 self.single_file_review = Some(allow);
176 }
177
178 pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
179 self.use_modifier_to_send = Some(always_use);
180 }
181
182 pub fn set_profile(&mut self, profile_id: AgentProfileId) {
183 self.default_profile = Some(profile_id);
184 }
185
186 pub fn create_profile(
187 &mut self,
188 profile_id: AgentProfileId,
189 profile_settings: AgentProfileSettings,
190 ) -> Result<()> {
191 let profiles = self.profiles.get_or_insert_default();
192 if profiles.contains_key(&profile_id) {
193 bail!("profile with ID '{profile_id}' already exists");
194 }
195
196 profiles.insert(
197 profile_id,
198 AgentProfileContent {
199 name: profile_settings.name.into(),
200 tools: profile_settings.tools,
201 enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
202 context_servers: profile_settings
203 .context_servers
204 .into_iter()
205 .map(|(server_id, preset)| {
206 (
207 server_id,
208 ContextServerPresetContent {
209 tools: preset.tools,
210 },
211 )
212 })
213 .collect(),
214 },
215 );
216
217 Ok(())
218 }
219}
220
221#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
222pub struct AgentSettingsContent {
223 /// Whether the Agent is enabled.
224 ///
225 /// Default: true
226 enabled: Option<bool>,
227 /// Whether to show the agent panel button in the status bar.
228 ///
229 /// Default: true
230 button: Option<bool>,
231 /// Where to dock the agent panel.
232 ///
233 /// Default: right
234 dock: Option<AgentDockPosition>,
235 /// Default width in pixels when the agent panel is docked to the left or right.
236 ///
237 /// Default: 640
238 default_width: Option<f32>,
239 /// Default height in pixels when the agent panel is docked to the bottom.
240 ///
241 /// Default: 320
242 default_height: Option<f32>,
243 /// The default model to use when creating new chats and for other features when a specific model is not specified.
244 default_model: Option<LanguageModelSelection>,
245 /// Model to use for the inline assistant. Defaults to default_model when not specified.
246 inline_assistant_model: Option<LanguageModelSelection>,
247 /// Model to use for generating git commit messages. Defaults to default_model when not specified.
248 commit_message_model: Option<LanguageModelSelection>,
249 /// Model to use for generating thread summaries. Defaults to default_model when not specified.
250 thread_summary_model: Option<LanguageModelSelection>,
251 /// Additional models with which to generate alternatives when performing inline assists.
252 inline_alternatives: Option<Vec<LanguageModelSelection>>,
253 /// The default profile to use in the Agent.
254 ///
255 /// Default: write
256 default_profile: Option<AgentProfileId>,
257 /// Which view type to show by default in the agent panel.
258 ///
259 /// Default: "thread"
260 default_view: Option<DefaultView>,
261 /// The available agent profiles.
262 pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
263 /// Whenever a tool action would normally wait for your confirmation
264 /// that you allow it, always choose to allow it.
265 ///
266 /// Default: false
267 always_allow_tool_actions: Option<bool>,
268 /// Where to show a popup notification when the agent is waiting for user input.
269 ///
270 /// Default: "primary_screen"
271 notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
272 /// Whether to play a sound when the agent has either completed its response, or needs user input.
273 ///
274 /// Default: false
275 play_sound_when_agent_done: Option<bool>,
276 /// Whether to stream edits from the agent as they are received.
277 ///
278 /// Default: false
279 stream_edits: Option<bool>,
280 /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
281 ///
282 /// Default: true
283 single_file_review: Option<bool>,
284 /// Additional parameters for language model requests. When making a request
285 /// to a model, parameters will be taken from the last entry in this list
286 /// that matches the model's provider and name. In each entry, both provider
287 /// and model are optional, so that you can specify parameters for either
288 /// one.
289 ///
290 /// Default: []
291 #[serde(default)]
292 model_parameters: Vec<LanguageModelParameters>,
293 /// What completion mode to enable for new threads
294 ///
295 /// Default: normal
296 preferred_completion_mode: Option<CompletionMode>,
297 /// Whether to show thumb buttons for feedback in the agent panel.
298 ///
299 /// Default: true
300 enable_feedback: Option<bool>,
301 /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
302 ///
303 /// Default: true
304 expand_edit_card: Option<bool>,
305 /// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
306 ///
307 /// Default: true
308 expand_terminal_card: Option<bool>,
309 /// Whether to always use cmd-enter (or ctrl-enter on Linux) to send messages in the agent panel.
310 ///
311 /// Default: false
312 use_modifier_to_send: Option<bool>,
313}
314
315#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
316#[serde(rename_all = "snake_case")]
317pub enum CompletionMode {
318 #[default]
319 Normal,
320 #[serde(alias = "max")]
321 Burn,
322}
323
324impl From<CompletionMode> for zed_llm_client::CompletionMode {
325 fn from(value: CompletionMode) -> Self {
326 match value {
327 CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
328 CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
329 }
330 }
331}
332
333#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
334pub struct LanguageModelSelection {
335 pub provider: LanguageModelProviderSetting,
336 pub model: String,
337}
338
339#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
340pub struct LanguageModelProviderSetting(pub String);
341
342impl JsonSchema for LanguageModelProviderSetting {
343 fn schema_name() -> Cow<'static, str> {
344 "LanguageModelProviderSetting".into()
345 }
346
347 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
348 json_schema!({
349 "enum": [
350 "anthropic",
351 "amazon-bedrock",
352 "google",
353 "lmstudio",
354 "ollama",
355 "openai",
356 "zed.dev",
357 "copilot_chat",
358 "deepseek",
359 "openrouter",
360 "mistral",
361 "vercel"
362 ]
363 })
364 }
365}
366
367impl From<String> for LanguageModelProviderSetting {
368 fn from(provider: String) -> Self {
369 Self(provider)
370 }
371}
372
373impl From<&str> for LanguageModelProviderSetting {
374 fn from(provider: &str) -> Self {
375 Self(provider.to_string())
376 }
377}
378
379#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
380pub struct AgentProfileContent {
381 pub name: Arc<str>,
382 #[serde(default)]
383 pub tools: IndexMap<Arc<str>, bool>,
384 /// Whether all context servers are enabled by default.
385 pub enable_all_context_servers: Option<bool>,
386 #[serde(default)]
387 pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
388}
389
390#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
391pub struct ContextServerPresetContent {
392 pub tools: IndexMap<Arc<str>, bool>,
393}
394
395impl Settings for AgentSettings {
396 const KEY: Option<&'static str> = Some("agent");
397
398 const FALLBACK_KEY: Option<&'static str> = Some("assistant");
399
400 const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
401
402 type FileContent = AgentSettingsContent;
403
404 fn load(
405 sources: SettingsSources<Self::FileContent>,
406 _: &mut gpui::App,
407 ) -> anyhow::Result<Self> {
408 let mut settings = AgentSettings::default();
409
410 for value in sources.defaults_and_customizations() {
411 merge(&mut settings.enabled, value.enabled);
412 merge(&mut settings.button, value.button);
413 merge(&mut settings.dock, value.dock);
414 merge(
415 &mut settings.default_width,
416 value.default_width.map(Into::into),
417 );
418 merge(
419 &mut settings.default_height,
420 value.default_height.map(Into::into),
421 );
422 settings.default_model = value
423 .default_model
424 .clone()
425 .or(settings.default_model.take());
426 settings.inline_assistant_model = value
427 .inline_assistant_model
428 .clone()
429 .or(settings.inline_assistant_model.take());
430 settings.commit_message_model = value
431 .clone()
432 .commit_message_model
433 .or(settings.commit_message_model.take());
434 settings.thread_summary_model = value
435 .clone()
436 .thread_summary_model
437 .or(settings.thread_summary_model.take());
438 merge(
439 &mut settings.inline_alternatives,
440 value.inline_alternatives.clone(),
441 );
442 merge(
443 &mut settings.always_allow_tool_actions,
444 value.always_allow_tool_actions,
445 );
446 merge(
447 &mut settings.notify_when_agent_waiting,
448 value.notify_when_agent_waiting,
449 );
450 merge(
451 &mut settings.play_sound_when_agent_done,
452 value.play_sound_when_agent_done,
453 );
454 merge(&mut settings.stream_edits, value.stream_edits);
455 merge(&mut settings.single_file_review, value.single_file_review);
456 merge(&mut settings.default_profile, value.default_profile.clone());
457 merge(&mut settings.default_view, value.default_view);
458 merge(
459 &mut settings.preferred_completion_mode,
460 value.preferred_completion_mode,
461 );
462 merge(&mut settings.enable_feedback, value.enable_feedback);
463 merge(&mut settings.expand_edit_card, value.expand_edit_card);
464 merge(
465 &mut settings.expand_terminal_card,
466 value.expand_terminal_card,
467 );
468 merge(
469 &mut settings.use_modifier_to_send,
470 value.use_modifier_to_send,
471 );
472
473 settings
474 .model_parameters
475 .extend_from_slice(&value.model_parameters);
476
477 if let Some(profiles) = value.profiles.as_ref() {
478 settings
479 .profiles
480 .extend(profiles.into_iter().map(|(id, profile)| {
481 (
482 id.clone(),
483 AgentProfileSettings {
484 name: profile.name.clone().into(),
485 tools: profile.tools.clone(),
486 enable_all_context_servers: profile
487 .enable_all_context_servers
488 .unwrap_or_default(),
489 context_servers: profile
490 .context_servers
491 .iter()
492 .map(|(context_server_id, preset)| {
493 (
494 context_server_id.clone(),
495 ContextServerPreset {
496 tools: preset.tools.clone(),
497 },
498 )
499 })
500 .collect(),
501 },
502 )
503 }));
504 }
505 }
506
507 Ok(settings)
508 }
509
510 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
511 if let Some(b) = vscode
512 .read_value("chat.agent.enabled")
513 .and_then(|b| b.as_bool())
514 {
515 current.enabled = Some(b);
516 current.button = Some(b);
517 }
518 }
519}
520
521fn merge<T>(target: &mut T, value: Option<T>) {
522 if let Some(value) = value {
523 *target = value;
524 }
525}