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