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