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