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, Deserializer, Serialize, Serializer};
11use settings::{Settings, SettingsSources};
12use std::borrow::Cow;
13use vim_mode_setting::EditorMode;
14
15pub use crate::agent_profile::*;
16
17pub const SUMMARIZE_THREAD_PROMPT: &str =
18 include_str!("../../agent/src/prompts/summarize_thread_prompt.txt");
19pub const SUMMARIZE_THREAD_DETAILED_PROMPT: &str =
20 include_str!("../../agent/src/prompts/summarize_thread_detailed_prompt.txt");
21
22pub fn init(cx: &mut App) {
23 AgentSettings::register(cx);
24}
25
26#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
27#[serde(rename_all = "snake_case")]
28pub enum AgentDockPosition {
29 Left,
30 #[default]
31 Right,
32 Bottom,
33}
34
35#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
36#[serde(rename_all = "snake_case")]
37pub enum DefaultView {
38 #[default]
39 Thread,
40 TextThread,
41}
42
43#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
44#[serde(rename_all = "snake_case")]
45pub enum NotifyWhenAgentWaiting {
46 #[default]
47 PrimaryScreen,
48 AllScreens,
49 Never,
50}
51
52#[derive(Copy, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
53pub enum AgentEditorMode {
54 EditorModeOverride(EditorMode),
55 #[default]
56 Inherit,
57}
58
59impl<'de> Deserialize<'de> for AgentEditorMode {
60 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
61 where
62 D: Deserializer<'de>,
63 {
64 let s = String::deserialize(deserializer)?;
65 if s == "inherit" {
66 Ok(AgentEditorMode::Inherit)
67 } else {
68 let mode = EditorMode::deserialize(serde::de::value::StringDeserializer::new(s))?;
69 Ok(AgentEditorMode::EditorModeOverride(mode))
70 }
71 }
72}
73
74impl Serialize for AgentEditorMode {
75 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76 where
77 S: Serializer,
78 {
79 match self {
80 AgentEditorMode::EditorModeOverride(mode) => mode.serialize(serializer),
81 AgentEditorMode::Inherit => serializer.serialize_str("inherit"),
82 }
83 }
84}
85
86impl From<EditorMode> for AgentEditorMode {
87 fn from(b: EditorMode) -> Self {
88 AgentEditorMode::EditorModeOverride(b)
89 }
90}
91
92#[derive(Default, Clone, Debug)]
93pub struct AgentSettings {
94 pub enabled: bool,
95 pub button: bool,
96 pub dock: AgentDockPosition,
97 pub default_width: Pixels,
98 pub default_height: Pixels,
99 pub default_model: Option<LanguageModelSelection>,
100 pub inline_assistant_model: Option<LanguageModelSelection>,
101 pub commit_message_model: Option<LanguageModelSelection>,
102 pub thread_summary_model: Option<LanguageModelSelection>,
103 pub inline_alternatives: Vec<LanguageModelSelection>,
104 pub using_outdated_settings_version: bool,
105 pub default_profile: AgentProfileId,
106 pub default_view: DefaultView,
107 pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
108 pub always_allow_tool_actions: bool,
109 pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
110 pub play_sound_when_agent_done: bool,
111 pub stream_edits: bool,
112 pub single_file_review: bool,
113 pub model_parameters: Vec<LanguageModelParameters>,
114 pub preferred_completion_mode: CompletionMode,
115 pub enable_feedback: bool,
116 pub expand_edit_card: bool,
117 pub expand_terminal_card: bool,
118 pub use_modifier_to_send: bool,
119 pub editor_mode: AgentEditorMode,
120}
121
122impl AgentSettings {
123 pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
124 let settings = Self::get_global(cx);
125 settings
126 .model_parameters
127 .iter()
128 .rfind(|setting| setting.matches(model))
129 .and_then(|m| m.temperature)
130 }
131
132 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
133 self.inline_assistant_model = Some(LanguageModelSelection {
134 provider: provider.into(),
135 model,
136 });
137 }
138
139 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
140 self.commit_message_model = Some(LanguageModelSelection {
141 provider: provider.into(),
142 model,
143 });
144 }
145
146 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
147 self.thread_summary_model = Some(LanguageModelSelection {
148 provider: provider.into(),
149 model,
150 });
151 }
152}
153
154#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
155pub struct LanguageModelParameters {
156 pub provider: Option<LanguageModelProviderSetting>,
157 pub model: Option<SharedString>,
158 pub temperature: Option<f32>,
159}
160
161impl LanguageModelParameters {
162 pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
163 if let Some(provider) = &self.provider
164 && provider.0 != model.provider_id().0
165 {
166 return false;
167 }
168 if let Some(setting_model) = &self.model
169 && *setting_model != model.id().0
170 {
171 return false;
172 }
173 true
174 }
175}
176
177impl AgentSettingsContent {
178 pub fn set_dock(&mut self, dock: AgentDockPosition) {
179 self.dock = Some(dock);
180 }
181
182 pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
183 let model = language_model.id().0.to_string();
184 let provider = language_model.provider_id().0.to_string();
185
186 self.default_model = Some(LanguageModelSelection {
187 provider: provider.into(),
188 model,
189 });
190 }
191
192 pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
193 self.inline_assistant_model = Some(LanguageModelSelection {
194 provider: provider.into(),
195 model,
196 });
197 }
198
199 pub fn set_commit_message_model(&mut self, provider: String, model: String) {
200 self.commit_message_model = Some(LanguageModelSelection {
201 provider: provider.into(),
202 model,
203 });
204 }
205
206 pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
207 self.thread_summary_model = Some(LanguageModelSelection {
208 provider: provider.into(),
209 model,
210 });
211 }
212
213 pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
214 self.always_allow_tool_actions = Some(allow);
215 }
216
217 pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
218 self.play_sound_when_agent_done = Some(allow);
219 }
220
221 pub fn set_single_file_review(&mut self, allow: bool) {
222 self.single_file_review = Some(allow);
223 }
224
225 pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
226 self.use_modifier_to_send = Some(always_use);
227 }
228
229 pub fn set_profile(&mut self, profile_id: AgentProfileId) {
230 self.default_profile = Some(profile_id);
231 }
232
233 pub fn create_profile(
234 &mut self,
235 profile_id: AgentProfileId,
236 profile_settings: AgentProfileSettings,
237 ) -> Result<()> {
238 let profiles = self.profiles.get_or_insert_default();
239 if profiles.contains_key(&profile_id) {
240 bail!("profile with ID '{profile_id}' already exists");
241 }
242
243 profiles.insert(
244 profile_id,
245 AgentProfileContent {
246 name: profile_settings.name.into(),
247 tools: profile_settings.tools,
248 enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
249 context_servers: profile_settings
250 .context_servers
251 .into_iter()
252 .map(|(server_id, preset)| {
253 (
254 server_id,
255 ContextServerPresetContent {
256 tools: preset.tools,
257 },
258 )
259 })
260 .collect(),
261 },
262 );
263
264 Ok(())
265 }
266}
267
268#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
269pub struct AgentSettingsContent {
270 /// Whether the Agent is enabled.
271 ///
272 /// Default: true
273 enabled: Option<bool>,
274 /// Whether to show the agent panel button in the status bar.
275 ///
276 /// Default: true
277 button: Option<bool>,
278 /// Where to dock the agent panel.
279 ///
280 /// Default: right
281 dock: Option<AgentDockPosition>,
282 /// Default width in pixels when the agent panel is docked to the left or right.
283 ///
284 /// Default: 640
285 default_width: Option<f32>,
286 /// Default height in pixels when the agent panel is docked to the bottom.
287 ///
288 /// Default: 320
289 default_height: Option<f32>,
290 /// The default model to use when creating new chats and for other features when a specific model is not specified.
291 default_model: Option<LanguageModelSelection>,
292 /// Model to use for the inline assistant. Defaults to default_model when not specified.
293 inline_assistant_model: Option<LanguageModelSelection>,
294 /// Model to use for generating git commit messages. Defaults to default_model when not specified.
295 commit_message_model: Option<LanguageModelSelection>,
296 /// Model to use for generating thread summaries. Defaults to default_model when not specified.
297 thread_summary_model: Option<LanguageModelSelection>,
298 /// Additional models with which to generate alternatives when performing inline assists.
299 inline_alternatives: Option<Vec<LanguageModelSelection>>,
300 /// The default profile to use in the Agent.
301 ///
302 /// Default: write
303 default_profile: Option<AgentProfileId>,
304 /// Which view type to show by default in the agent panel.
305 ///
306 /// Default: "thread"
307 default_view: Option<DefaultView>,
308 /// The available agent profiles.
309 pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
310 /// Whenever a tool action would normally wait for your confirmation
311 /// that you allow it, always choose to allow it.
312 ///
313 /// Default: false
314 always_allow_tool_actions: Option<bool>,
315 /// Where to show a popup notification when the agent is waiting for user input.
316 ///
317 /// Default: "primary_screen"
318 notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
319 /// Whether to play a sound when the agent has either completed its response, or needs user input.
320 ///
321 /// Default: false
322 play_sound_when_agent_done: Option<bool>,
323 /// Whether to stream edits from the agent as they are received.
324 ///
325 /// Default: false
326 stream_edits: Option<bool>,
327 /// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
328 ///
329 /// Default: true
330 single_file_review: Option<bool>,
331 /// Additional parameters for language model requests. When making a request
332 /// to a model, parameters will be taken from the last entry in this list
333 /// that matches the model's provider and name. In each entry, both provider
334 /// and model are optional, so that you can specify parameters for either
335 /// one.
336 ///
337 /// Default: []
338 #[serde(default)]
339 model_parameters: Vec<LanguageModelParameters>,
340 /// What completion mode to enable for new threads
341 ///
342 /// Default: normal
343 preferred_completion_mode: Option<CompletionMode>,
344 /// Whether to show thumb buttons for feedback in the agent panel.
345 ///
346 /// Default: true
347 enable_feedback: Option<bool>,
348 /// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
349 ///
350 /// Default: true
351 expand_edit_card: Option<bool>,
352 /// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
353 ///
354 /// Default: true
355 expand_terminal_card: Option<bool>,
356 /// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
357 ///
358 /// Default: false
359 use_modifier_to_send: Option<bool>,
360 /// Weather to inherit or override the editor mode for the agent panel.
361 ///
362 /// Default: inherit
363 editor_mode: Option<AgentEditorMode>,
364}
365
366#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
367#[serde(rename_all = "snake_case")]
368pub enum CompletionMode {
369 #[default]
370 Normal,
371 #[serde(alias = "max")]
372 Burn,
373}
374
375impl From<CompletionMode> for cloud_llm_client::CompletionMode {
376 fn from(value: CompletionMode) -> Self {
377 match value {
378 CompletionMode::Normal => cloud_llm_client::CompletionMode::Normal,
379 CompletionMode::Burn => cloud_llm_client::CompletionMode::Max,
380 }
381 }
382}
383
384#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
385pub struct LanguageModelSelection {
386 pub provider: LanguageModelProviderSetting,
387 pub model: String,
388}
389
390#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
391pub struct LanguageModelProviderSetting(pub String);
392
393impl JsonSchema for LanguageModelProviderSetting {
394 fn schema_name() -> Cow<'static, str> {
395 "LanguageModelProviderSetting".into()
396 }
397
398 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
399 json_schema!({
400 "enum": [
401 "anthropic",
402 "amazon-bedrock",
403 "google",
404 "lmstudio",
405 "ollama",
406 "openai",
407 "zed.dev",
408 "copilot_chat",
409 "deepseek",
410 "openrouter",
411 "mistral",
412 "vercel"
413 ]
414 })
415 }
416}
417
418impl From<String> for LanguageModelProviderSetting {
419 fn from(provider: String) -> Self {
420 Self(provider)
421 }
422}
423
424impl From<&str> for LanguageModelProviderSetting {
425 fn from(provider: &str) -> Self {
426 Self(provider.to_string())
427 }
428}
429
430#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
431pub struct AgentProfileContent {
432 pub name: Arc<str>,
433 #[serde(default)]
434 pub tools: IndexMap<Arc<str>, bool>,
435 /// Whether all context servers are enabled by default.
436 pub enable_all_context_servers: Option<bool>,
437 #[serde(default)]
438 pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
439}
440
441#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
442pub struct ContextServerPresetContent {
443 pub tools: IndexMap<Arc<str>, bool>,
444}
445
446impl Settings for AgentSettings {
447 const KEY: Option<&'static str> = Some("agent");
448
449 const FALLBACK_KEY: Option<&'static str> = Some("assistant");
450
451 const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
452
453 type FileContent = AgentSettingsContent;
454
455 fn load(
456 sources: SettingsSources<Self::FileContent>,
457 _: &mut gpui::App,
458 ) -> anyhow::Result<Self> {
459 let mut settings = AgentSettings::default();
460
461 for value in sources.defaults_and_customizations() {
462 merge(&mut settings.enabled, value.enabled);
463 merge(&mut settings.button, value.button);
464 merge(&mut settings.dock, value.dock);
465 merge(
466 &mut settings.default_width,
467 value.default_width.map(Into::into),
468 );
469 merge(
470 &mut settings.default_height,
471 value.default_height.map(Into::into),
472 );
473 settings.default_model = value
474 .default_model
475 .clone()
476 .or(settings.default_model.take());
477 settings.inline_assistant_model = value
478 .inline_assistant_model
479 .clone()
480 .or(settings.inline_assistant_model.take());
481 settings.commit_message_model = value
482 .clone()
483 .commit_message_model
484 .or(settings.commit_message_model.take());
485 settings.thread_summary_model = value
486 .clone()
487 .thread_summary_model
488 .or(settings.thread_summary_model.take());
489 merge(
490 &mut settings.inline_alternatives,
491 value.inline_alternatives.clone(),
492 );
493 merge(
494 &mut settings.notify_when_agent_waiting,
495 value.notify_when_agent_waiting,
496 );
497 merge(
498 &mut settings.play_sound_when_agent_done,
499 value.play_sound_when_agent_done,
500 );
501 merge(&mut settings.stream_edits, value.stream_edits);
502 merge(&mut settings.single_file_review, value.single_file_review);
503 merge(&mut settings.default_profile, value.default_profile.clone());
504 merge(&mut settings.default_view, value.default_view);
505 merge(
506 &mut settings.preferred_completion_mode,
507 value.preferred_completion_mode,
508 );
509 merge(&mut settings.enable_feedback, value.enable_feedback);
510 merge(&mut settings.expand_edit_card, value.expand_edit_card);
511 merge(
512 &mut settings.expand_terminal_card,
513 value.expand_terminal_card,
514 );
515 merge(
516 &mut settings.use_modifier_to_send,
517 value.use_modifier_to_send,
518 );
519
520 settings
521 .model_parameters
522 .extend_from_slice(&value.model_parameters);
523
524 if let Some(profiles) = value.profiles.as_ref() {
525 settings
526 .profiles
527 .extend(profiles.into_iter().map(|(id, profile)| {
528 (
529 id.clone(),
530 AgentProfileSettings {
531 name: profile.name.clone().into(),
532 tools: profile.tools.clone(),
533 enable_all_context_servers: profile
534 .enable_all_context_servers
535 .unwrap_or_default(),
536 context_servers: profile
537 .context_servers
538 .iter()
539 .map(|(context_server_id, preset)| {
540 (
541 context_server_id.clone(),
542 ContextServerPreset {
543 tools: preset.tools.clone(),
544 },
545 )
546 })
547 .collect(),
548 },
549 )
550 }));
551 }
552 }
553
554 debug_assert!(
555 !sources.default.always_allow_tool_actions.unwrap_or(false),
556 "For security, agent.always_allow_tool_actions should always be false in default.json. If it's true, that is a bug that should be fixed!"
557 );
558
559 // For security reasons, only trust the user's global settings for whether to always allow tool actions.
560 // If this could be overridden locally, an attacker could (e.g. by committing to source control and
561 // convincing you to switch branches) modify your project-local settings to disable the agent's safety checks.
562 settings.always_allow_tool_actions = sources
563 .user
564 .and_then(|setting| setting.always_allow_tool_actions)
565 .unwrap_or(false);
566
567 Ok(settings)
568 }
569
570 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
571 if let Some(b) = vscode
572 .read_value("chat.agent.enabled")
573 .and_then(|b| b.as_bool())
574 {
575 current.enabled = Some(b);
576 current.button = Some(b);
577 }
578 }
579}
580
581fn merge<T>(target: &mut T, value: Option<T>) {
582 if let Some(value) = value {
583 *target = value;
584 }
585}