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, schema::Schema};
10use serde::{Deserialize, Serialize};
11use settings::{Settings, SettingsSources};
12
13pub use crate::agent_profile::*;
14
15pub fn init(cx: &mut App) {
16 AgentSettings::register(cx);
17}
18
19#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
20#[serde(rename_all = "snake_case")]
21pub enum AgentDockPosition {
22 Left,
23 #[default]
24 Right,
25 Bottom,
26}
27
28#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
29#[serde(rename_all = "snake_case")]
30pub enum DefaultView {
31 #[default]
32 Thread,
33 TextThread,
34}
35
36#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
37#[serde(rename_all = "snake_case")]
38pub enum NotifyWhenAgentWaiting {
39 #[default]
40 PrimaryScreen,
41 AllScreens,
42 Never,
43}
44
45#[derive(Default, Clone, Debug)]
46pub struct AgentSettings {
47 pub enabled: bool,
48 pub button: bool,
49 pub dock: AgentDockPosition,
50 pub default_width: Pixels,
51 pub default_height: Pixels,
52 pub default_model: LanguageModelSelection,
53 pub inline_assistant_model: Option<LanguageModelSelection>,
54 pub commit_message_model: Option<LanguageModelSelection>,
55 pub thread_summary_model: Option<LanguageModelSelection>,
56 pub inline_alternatives: Vec<LanguageModelSelection>,
57 pub using_outdated_settings_version: bool,
58 pub default_profile: AgentProfileId,
59 pub default_view: DefaultView,
60 pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
61 pub always_allow_tool_actions: bool,
62 pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
63 pub play_sound_when_agent_done: bool,
64 pub stream_edits: bool,
65 pub single_file_review: bool,
66 pub model_parameters: Vec<LanguageModelParameters>,
67 pub preferred_completion_mode: CompletionMode,
68 pub enable_feedback: bool,
69}
70
71impl AgentSettings {
72 pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
73 let settings = Self::get_global(cx);
74 settings
75 .model_parameters
76 .iter()
77 .rfind(|setting| setting.matches(model))
78 .and_then(|m| m.temperature)
79 }
80
81 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
82 self.inline_assistant_model = Some(LanguageModelSelection {
83 provider: provider.into(),
84 model,
85 });
86 }
87
88 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
89 self.commit_message_model = Some(LanguageModelSelection {
90 provider: provider.into(),
91 model,
92 });
93 }
94
95 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
96 self.thread_summary_model = Some(LanguageModelSelection {
97 provider: provider.into(),
98 model,
99 });
100 }
101}
102
103#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
104pub struct LanguageModelParameters {
105 pub provider: Option<LanguageModelProviderSetting>,
106 pub model: Option<SharedString>,
107 pub temperature: Option<f32>,
108}
109
110impl LanguageModelParameters {
111 pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
112 if let Some(provider) = &self.provider {
113 if provider.0 != model.provider_id().0 {
114 return false;
115 }
116 }
117 if let Some(setting_model) = &self.model {
118 if *setting_model != model.id().0 {
119 return false;
120 }
121 }
122 true
123 }
124}
125
126impl AgentSettingsContent {
127 pub fn set_dock(&mut self, dock: AgentDockPosition) {
128 self.dock = Some(dock);
129 }
130
131 pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
132 let model = language_model.id().0.to_string();
133 let provider = language_model.provider_id().0.to_string();
134
135 self.default_model = Some(LanguageModelSelection {
136 provider: provider.into(),
137 model,
138 });
139 }
140
141 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
142 self.inline_assistant_model = Some(LanguageModelSelection {
143 provider: provider.into(),
144 model,
145 });
146 }
147
148 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
149 self.commit_message_model = Some(LanguageModelSelection {
150 provider: provider.into(),
151 model,
152 });
153 }
154
155 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
156 self.thread_summary_model = Some(LanguageModelSelection {
157 provider: provider.into(),
158 model,
159 });
160 }
161
162 pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
163 self.always_allow_tool_actions = Some(allow);
164 }
165
166 pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
167 self.play_sound_when_agent_done = Some(allow);
168 }
169
170 pub fn set_single_file_review(&mut self, allow: bool) {
171 self.single_file_review = Some(allow);
172 }
173
174 pub fn set_profile(&mut self, profile_id: AgentProfileId) {
175 self.default_profile = Some(profile_id);
176 }
177
178 pub fn create_profile(
179 &mut self,
180 profile_id: AgentProfileId,
181 profile_settings: AgentProfileSettings,
182 ) -> Result<()> {
183 let profiles = self.profiles.get_or_insert_default();
184 if profiles.contains_key(&profile_id) {
185 bail!("profile with ID '{profile_id}' already exists");
186 }
187
188 profiles.insert(
189 profile_id,
190 AgentProfileContent {
191 name: profile_settings.name.into(),
192 tools: profile_settings.tools,
193 enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
194 context_servers: profile_settings
195 .context_servers
196 .into_iter()
197 .map(|(server_id, preset)| {
198 (
199 server_id,
200 ContextServerPresetContent {
201 tools: preset.tools,
202 },
203 )
204 })
205 .collect(),
206 },
207 );
208
209 Ok(())
210 }
211}
212
213#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
214#[schemars(deny_unknown_fields)]
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() -> String {
325 "LanguageModelProviderSetting".into()
326 }
327
328 fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
329 schemars::schema::SchemaObject {
330 enum_values: Some(vec![
331 "anthropic".into(),
332 "amazon-bedrock".into(),
333 "google".into(),
334 "lmstudio".into(),
335 "ollama".into(),
336 "openai".into(),
337 "zed.dev".into(),
338 "copilot_chat".into(),
339 "deepseek".into(),
340 "openrouter".into(),
341 "mistral".into(),
342 "vercel".into(),
343 ]),
344 ..Default::default()
345 }
346 .into()
347 }
348}
349
350impl From<String> for LanguageModelProviderSetting {
351 fn from(provider: String) -> Self {
352 Self(provider)
353 }
354}
355
356impl From<&str> for LanguageModelProviderSetting {
357 fn from(provider: &str) -> Self {
358 Self(provider.to_string())
359 }
360}
361
362impl Default for LanguageModelSelection {
363 fn default() -> Self {
364 Self {
365 provider: LanguageModelProviderSetting("openai".to_string()),
366 model: "gpt-4".to_string(),
367 }
368 }
369}
370
371#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
372pub struct AgentProfileContent {
373 pub name: Arc<str>,
374 #[serde(default)]
375 pub tools: IndexMap<Arc<str>, bool>,
376 /// Whether all context servers are enabled by default.
377 pub enable_all_context_servers: Option<bool>,
378 #[serde(default)]
379 pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
380}
381
382#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
383pub struct ContextServerPresetContent {
384 pub tools: IndexMap<Arc<str>, bool>,
385}
386
387impl Settings for AgentSettings {
388 const KEY: Option<&'static str> = Some("agent");
389
390 const FALLBACK_KEY: Option<&'static str> = Some("assistant");
391
392 const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
393
394 type FileContent = AgentSettingsContent;
395
396 fn load(
397 sources: SettingsSources<Self::FileContent>,
398 _: &mut gpui::App,
399 ) -> anyhow::Result<Self> {
400 let mut settings = AgentSettings::default();
401
402 for value in sources.defaults_and_customizations() {
403 merge(&mut settings.enabled, value.enabled);
404 merge(&mut settings.button, value.button);
405 merge(&mut settings.dock, value.dock);
406 merge(
407 &mut settings.default_width,
408 value.default_width.map(Into::into),
409 );
410 merge(
411 &mut settings.default_height,
412 value.default_height.map(Into::into),
413 );
414 merge(&mut settings.default_model, value.default_model.clone());
415 settings.inline_assistant_model = value
416 .inline_assistant_model
417 .clone()
418 .or(settings.inline_assistant_model.take());
419 settings.commit_message_model = value
420 .clone()
421 .commit_message_model
422 .or(settings.commit_message_model.take());
423 settings.thread_summary_model = value
424 .clone()
425 .thread_summary_model
426 .or(settings.thread_summary_model.take());
427 merge(
428 &mut settings.inline_alternatives,
429 value.inline_alternatives.clone(),
430 );
431 merge(
432 &mut settings.always_allow_tool_actions,
433 value.always_allow_tool_actions,
434 );
435 merge(
436 &mut settings.notify_when_agent_waiting,
437 value.notify_when_agent_waiting,
438 );
439 merge(
440 &mut settings.play_sound_when_agent_done,
441 value.play_sound_when_agent_done,
442 );
443 merge(&mut settings.stream_edits, value.stream_edits);
444 merge(&mut settings.single_file_review, value.single_file_review);
445 merge(&mut settings.default_profile, value.default_profile.clone());
446 merge(&mut settings.default_view, value.default_view);
447 merge(
448 &mut settings.preferred_completion_mode,
449 value.preferred_completion_mode,
450 );
451 merge(&mut settings.enable_feedback, value.enable_feedback);
452
453 settings
454 .model_parameters
455 .extend_from_slice(&value.model_parameters);
456
457 if let Some(profiles) = value.profiles.as_ref() {
458 settings
459 .profiles
460 .extend(profiles.into_iter().map(|(id, profile)| {
461 (
462 id.clone(),
463 AgentProfileSettings {
464 name: profile.name.clone().into(),
465 tools: profile.tools.clone(),
466 enable_all_context_servers: profile
467 .enable_all_context_servers
468 .unwrap_or_default(),
469 context_servers: profile
470 .context_servers
471 .iter()
472 .map(|(context_server_id, preset)| {
473 (
474 context_server_id.clone(),
475 ContextServerPreset {
476 tools: preset.tools.clone(),
477 },
478 )
479 })
480 .collect(),
481 },
482 )
483 }));
484 }
485 }
486
487 Ok(settings)
488 }
489
490 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
491 if let Some(b) = vscode
492 .read_value("chat.agent.enabled")
493 .and_then(|b| b.as_bool())
494 {
495 current.enabled = Some(b);
496 current.button = Some(b);
497 }
498 }
499}
500
501fn merge<T>(target: &mut T, value: Option<T>) {
502 if let Some(value) = value {
503 *target = value;
504 }
505}