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