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