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