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