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