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