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}
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)]
215pub struct AgentSettingsContent {
216 /// Whether the Agent is enabled.
217 ///
218 /// Default: true
219 enabled: Option<bool>,
220 /// Whether to show the agent panel button in the status bar.
221 ///
222 /// Default: true
223 button: Option<bool>,
224 /// Where to dock the agent panel.
225 ///
226 /// Default: right
227 dock: Option<AgentDockPosition>,
228 /// Default width in pixels when the agent panel is docked to the left or right.
229 ///
230 /// Default: 640
231 default_width: Option<f32>,
232 /// Default height in pixels when the agent panel is docked to the bottom.
233 ///
234 /// Default: 320
235 default_height: Option<f32>,
236 /// The default model to use when creating new chats and for other features when a specific model is not specified.
237 default_model: Option<LanguageModelSelection>,
238 /// Model to use for the inline assistant. Defaults to default_model when not specified.
239 inline_assistant_model: Option<LanguageModelSelection>,
240 /// Model to use for generating git commit messages. Defaults to default_model when not specified.
241 commit_message_model: Option<LanguageModelSelection>,
242 /// Model to use for generating thread summaries. Defaults to default_model when not specified.
243 thread_summary_model: Option<LanguageModelSelection>,
244 /// Additional models with which to generate alternatives when performing inline assists.
245 inline_alternatives: Option<Vec<LanguageModelSelection>>,
246 /// The default profile to use in the Agent.
247 ///
248 /// Default: write
249 default_profile: Option<AgentProfileId>,
250 /// Which view type to show by default in the agent panel.
251 ///
252 /// Default: "thread"
253 default_view: Option<DefaultView>,
254 /// The available agent profiles.
255 pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
256 /// Whenever a tool action would normally wait for your confirmation
257 /// that you allow it, always choose to allow it.
258 ///
259 /// Default: false
260 always_allow_tool_actions: Option<bool>,
261 /// Where to show a popup notification when the agent is waiting for user input.
262 ///
263 /// Default: "primary_screen"
264 notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
265 /// Whether to play a sound when the agent has either completed its response, or needs user input.
266 ///
267 /// Default: false
268 play_sound_when_agent_done: Option<bool>,
269 /// Whether to stream edits from the agent as they are received.
270 ///
271 /// Default: false
272 stream_edits: Option<bool>,
273 /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
274 ///
275 /// Default: true
276 single_file_review: Option<bool>,
277 /// Additional parameters for language model requests. When making a request
278 /// to a model, parameters will be taken from the last entry in this list
279 /// that matches the model's provider and name. In each entry, both provider
280 /// and model are optional, so that you can specify parameters for either
281 /// one.
282 ///
283 /// Default: []
284 #[serde(default)]
285 model_parameters: Vec<LanguageModelParameters>,
286 /// What completion mode to enable for new threads
287 ///
288 /// Default: normal
289 preferred_completion_mode: Option<CompletionMode>,
290 /// Whether to show thumb buttons for feedback in the agent panel.
291 ///
292 /// Default: true
293 enable_feedback: Option<bool>,
294}
295
296#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
297#[serde(rename_all = "snake_case")]
298pub enum CompletionMode {
299 #[default]
300 Normal,
301 #[serde(alias = "max")]
302 Burn,
303}
304
305impl From<CompletionMode> for zed_llm_client::CompletionMode {
306 fn from(value: CompletionMode) -> Self {
307 match value {
308 CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
309 CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
310 }
311 }
312}
313
314#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
315pub struct LanguageModelSelection {
316 pub provider: LanguageModelProviderSetting,
317 pub model: String,
318}
319
320#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
321pub struct LanguageModelProviderSetting(pub String);
322
323impl JsonSchema for LanguageModelProviderSetting {
324 fn schema_name() -> Cow<'static, str> {
325 "LanguageModelProviderSetting".into()
326 }
327
328 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
329 json_schema!({
330 "enum": [
331 "anthropic",
332 "amazon-bedrock",
333 "google",
334 "lmstudio",
335 "ollama",
336 "openai",
337 "zed.dev",
338 "copilot_chat",
339 "deepseek",
340 "openrouter",
341 "mistral",
342 "vercel"
343 ]
344 })
345 }
346}
347
348impl From<String> for LanguageModelProviderSetting {
349 fn from(provider: String) -> Self {
350 Self(provider)
351 }
352}
353
354impl From<&str> for LanguageModelProviderSetting {
355 fn from(provider: &str) -> Self {
356 Self(provider.to_string())
357 }
358}
359
360#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
361pub struct AgentProfileContent {
362 pub name: Arc<str>,
363 #[serde(default)]
364 pub tools: IndexMap<Arc<str>, bool>,
365 /// Whether all context servers are enabled by default.
366 pub enable_all_context_servers: Option<bool>,
367 #[serde(default)]
368 pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
369}
370
371#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
372pub struct ContextServerPresetContent {
373 pub tools: IndexMap<Arc<str>, bool>,
374}
375
376impl Settings for AgentSettings {
377 const KEY: Option<&'static str> = Some("agent");
378
379 const FALLBACK_KEY: Option<&'static str> = Some("assistant");
380
381 const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
382
383 type FileContent = AgentSettingsContent;
384
385 fn load(
386 sources: SettingsSources<Self::FileContent>,
387 _: &mut gpui::App,
388 ) -> anyhow::Result<Self> {
389 let mut settings = AgentSettings::default();
390
391 for value in sources.defaults_and_customizations() {
392 merge(&mut settings.enabled, value.enabled);
393 merge(&mut settings.button, value.button);
394 merge(&mut settings.dock, value.dock);
395 merge(
396 &mut settings.default_width,
397 value.default_width.map(Into::into),
398 );
399 merge(
400 &mut settings.default_height,
401 value.default_height.map(Into::into),
402 );
403 settings.default_model = value
404 .default_model
405 .clone()
406 .or(settings.default_model.take());
407 settings.inline_assistant_model = value
408 .inline_assistant_model
409 .clone()
410 .or(settings.inline_assistant_model.take());
411 settings.commit_message_model = value
412 .clone()
413 .commit_message_model
414 .or(settings.commit_message_model.take());
415 settings.thread_summary_model = value
416 .clone()
417 .thread_summary_model
418 .or(settings.thread_summary_model.take());
419 merge(
420 &mut settings.inline_alternatives,
421 value.inline_alternatives.clone(),
422 );
423 merge(
424 &mut settings.always_allow_tool_actions,
425 value.always_allow_tool_actions,
426 );
427 merge(
428 &mut settings.notify_when_agent_waiting,
429 value.notify_when_agent_waiting,
430 );
431 merge(
432 &mut settings.play_sound_when_agent_done,
433 value.play_sound_when_agent_done,
434 );
435 merge(&mut settings.stream_edits, value.stream_edits);
436 merge(&mut settings.single_file_review, value.single_file_review);
437 merge(&mut settings.default_profile, value.default_profile.clone());
438 merge(&mut settings.default_view, value.default_view);
439 merge(
440 &mut settings.preferred_completion_mode,
441 value.preferred_completion_mode,
442 );
443 merge(&mut settings.enable_feedback, value.enable_feedback);
444
445 settings
446 .model_parameters
447 .extend_from_slice(&value.model_parameters);
448
449 if let Some(profiles) = value.profiles.as_ref() {
450 settings
451 .profiles
452 .extend(profiles.into_iter().map(|(id, profile)| {
453 (
454 id.clone(),
455 AgentProfileSettings {
456 name: profile.name.clone().into(),
457 tools: profile.tools.clone(),
458 enable_all_context_servers: profile
459 .enable_all_context_servers
460 .unwrap_or_default(),
461 context_servers: profile
462 .context_servers
463 .iter()
464 .map(|(context_server_id, preset)| {
465 (
466 context_server_id.clone(),
467 ContextServerPreset {
468 tools: preset.tools.clone(),
469 },
470 )
471 })
472 .collect(),
473 },
474 )
475 }));
476 }
477 }
478
479 Ok(settings)
480 }
481
482 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
483 if let Some(b) = vscode
484 .read_value("chat.agent.enabled")
485 .and_then(|b| b.as_bool())
486 {
487 current.enabled = Some(b);
488 current.button = Some(b);
489 }
490 }
491}
492
493fn merge<T>(target: &mut T, value: Option<T>) {
494 if let Some(value) = value {
495 *target = value;
496 }
497}