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: 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}
71
72impl AgentSettings {
73 pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
74 let settings = Self::get_global(cx);
75 settings
76 .model_parameters
77 .iter()
78 .rfind(|setting| setting.matches(model))
79 .and_then(|m| m.temperature)
80 }
81
82 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
83 self.inline_assistant_model = Some(LanguageModelSelection {
84 provider: provider.into(),
85 model,
86 });
87 }
88
89 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
90 self.commit_message_model = Some(LanguageModelSelection {
91 provider: provider.into(),
92 model,
93 });
94 }
95
96 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
97 self.thread_summary_model = Some(LanguageModelSelection {
98 provider: provider.into(),
99 model,
100 });
101 }
102}
103
104#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
105pub struct LanguageModelParameters {
106 pub provider: Option<LanguageModelProviderSetting>,
107 pub model: Option<SharedString>,
108 pub temperature: Option<f32>,
109}
110
111impl LanguageModelParameters {
112 pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
113 if let Some(provider) = &self.provider {
114 if provider.0 != model.provider_id().0 {
115 return false;
116 }
117 }
118 if let Some(setting_model) = &self.model {
119 if *setting_model != model.id().0 {
120 return false;
121 }
122 }
123 true
124 }
125}
126
127impl AgentSettingsContent {
128 pub fn set_dock(&mut self, dock: AgentDockPosition) {
129 self.dock = Some(dock);
130 }
131
132 pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
133 let model = language_model.id().0.to_string();
134 let provider = language_model.provider_id().0.to_string();
135
136 self.default_model = Some(LanguageModelSelection {
137 provider: provider.into(),
138 model,
139 });
140 }
141
142 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
143 self.inline_assistant_model = Some(LanguageModelSelection {
144 provider: provider.into(),
145 model,
146 });
147 }
148
149 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
150 self.commit_message_model = Some(LanguageModelSelection {
151 provider: provider.into(),
152 model,
153 });
154 }
155
156 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
157 self.thread_summary_model = Some(LanguageModelSelection {
158 provider: provider.into(),
159 model,
160 });
161 }
162
163 pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
164 self.always_allow_tool_actions = Some(allow);
165 }
166
167 pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
168 self.play_sound_when_agent_done = Some(allow);
169 }
170
171 pub fn set_single_file_review(&mut self, allow: bool) {
172 self.single_file_review = Some(allow);
173 }
174
175 pub fn set_profile(&mut self, profile_id: AgentProfileId) {
176 self.default_profile = Some(profile_id);
177 }
178
179 pub fn create_profile(
180 &mut self,
181 profile_id: AgentProfileId,
182 profile_settings: AgentProfileSettings,
183 ) -> Result<()> {
184 let profiles = self.profiles.get_or_insert_default();
185 if profiles.contains_key(&profile_id) {
186 bail!("profile with ID '{profile_id}' already exists");
187 }
188
189 profiles.insert(
190 profile_id,
191 AgentProfileContent {
192 name: profile_settings.name.into(),
193 tools: profile_settings.tools,
194 enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
195 context_servers: profile_settings
196 .context_servers
197 .into_iter()
198 .map(|(server_id, preset)| {
199 (
200 server_id,
201 ContextServerPresetContent {
202 tools: preset.tools,
203 },
204 )
205 })
206 .collect(),
207 },
208 );
209
210 Ok(())
211 }
212}
213
214#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
215#[schemars(deny_unknown_fields)]
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}
296
297#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
298#[serde(rename_all = "snake_case")]
299pub enum CompletionMode {
300 #[default]
301 Normal,
302 #[serde(alias = "max")]
303 Burn,
304}
305
306impl From<CompletionMode> for zed_llm_client::CompletionMode {
307 fn from(value: CompletionMode) -> Self {
308 match value {
309 CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
310 CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
311 }
312 }
313}
314
315#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
316pub struct LanguageModelSelection {
317 pub provider: LanguageModelProviderSetting,
318 pub model: String,
319}
320
321#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
322pub struct LanguageModelProviderSetting(pub String);
323
324impl JsonSchema for LanguageModelProviderSetting {
325 fn schema_name() -> Cow<'static, str> {
326 "LanguageModelProviderSetting".into()
327 }
328
329 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
330 json_schema!({
331 "enum": [
332 "anthropic",
333 "amazon-bedrock",
334 "google",
335 "lmstudio",
336 "ollama",
337 "openai",
338 "zed.dev",
339 "copilot_chat",
340 "deepseek",
341 "openrouter",
342 "mistral",
343 "vercel"
344 ]
345 })
346 }
347}
348
349impl From<String> for LanguageModelProviderSetting {
350 fn from(provider: String) -> Self {
351 Self(provider)
352 }
353}
354
355impl From<&str> for LanguageModelProviderSetting {
356 fn from(provider: &str) -> Self {
357 Self(provider.to_string())
358 }
359}
360
361impl Default for LanguageModelSelection {
362 fn default() -> Self {
363 Self {
364 provider: LanguageModelProviderSetting("openai".to_string()),
365 model: "gpt-4".to_string(),
366 }
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 merge(&mut settings.default_model, value.default_model.clone());
414 settings.inline_assistant_model = value
415 .inline_assistant_model
416 .clone()
417 .or(settings.inline_assistant_model.take());
418 settings.commit_message_model = value
419 .clone()
420 .commit_message_model
421 .or(settings.commit_message_model.take());
422 settings.thread_summary_model = value
423 .clone()
424 .thread_summary_model
425 .or(settings.thread_summary_model.take());
426 merge(
427 &mut settings.inline_alternatives,
428 value.inline_alternatives.clone(),
429 );
430 merge(
431 &mut settings.always_allow_tool_actions,
432 value.always_allow_tool_actions,
433 );
434 merge(
435 &mut settings.notify_when_agent_waiting,
436 value.notify_when_agent_waiting,
437 );
438 merge(
439 &mut settings.play_sound_when_agent_done,
440 value.play_sound_when_agent_done,
441 );
442 merge(&mut settings.stream_edits, value.stream_edits);
443 merge(&mut settings.single_file_review, value.single_file_review);
444 merge(&mut settings.default_profile, value.default_profile.clone());
445 merge(&mut settings.default_view, value.default_view);
446 merge(
447 &mut settings.preferred_completion_mode,
448 value.preferred_completion_mode,
449 );
450 merge(&mut settings.enable_feedback, value.enable_feedback);
451
452 settings
453 .model_parameters
454 .extend_from_slice(&value.model_parameters);
455
456 if let Some(profiles) = value.profiles.as_ref() {
457 settings
458 .profiles
459 .extend(profiles.into_iter().map(|(id, profile)| {
460 (
461 id.clone(),
462 AgentProfileSettings {
463 name: profile.name.clone().into(),
464 tools: profile.tools.clone(),
465 enable_all_context_servers: profile
466 .enable_all_context_servers
467 .unwrap_or_default(),
468 context_servers: profile
469 .context_servers
470 .iter()
471 .map(|(context_server_id, preset)| {
472 (
473 context_server_id.clone(),
474 ContextServerPreset {
475 tools: preset.tools.clone(),
476 },
477 )
478 })
479 .collect(),
480 },
481 )
482 }));
483 }
484 }
485
486 Ok(settings)
487 }
488
489 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
490 if let Some(b) = vscode
491 .read_value("chat.agent.enabled")
492 .and_then(|b| b.as_bool())
493 {
494 current.enabled = Some(b);
495 current.button = Some(b);
496 }
497 }
498}
499
500fn merge<T>(target: &mut T, value: Option<T>) {
501 if let Some(value) = value {
502 *target = value;
503 }
504}