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