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