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