1pub mod acp;
2mod agent_configuration;
3mod agent_diff;
4mod agent_model_selector;
5mod agent_panel;
6mod buffer_codegen;
7mod completion_provider;
8mod context;
9mod context_server_configuration;
10mod favorite_models;
11mod inline_assistant;
12mod inline_prompt_editor;
13mod language_model_selector;
14mod mention_set;
15mod profile_selector;
16mod slash_command;
17mod slash_command_picker;
18mod terminal_codegen;
19mod terminal_inline_assistant;
20mod text_thread_editor;
21mod ui;
22
23use std::rc::Rc;
24use std::sync::Arc;
25
26use agent_settings::{AgentProfileId, AgentSettings};
27use assistant_slash_command::SlashCommandRegistry;
28use client::Client;
29use command_palette_hooks::CommandPaletteFilter;
30use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt as _};
31use fs::Fs;
32use gpui::{Action, App, Entity, SharedString, actions};
33use language::{
34 LanguageRegistry,
35 language_settings::{AllLanguageSettings, EditPredictionProvider},
36};
37use language_model::{
38 ConfiguredModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
39};
40use project::DisableAiSettings;
41use prompt_store::PromptBuilder;
42use schemars::JsonSchema;
43use serde::{Deserialize, Serialize};
44use settings::{LanguageModelSelection, Settings as _, SettingsStore};
45use std::any::TypeId;
46
47use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
48pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
49pub use crate::inline_assistant::InlineAssistant;
50pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
51pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
52use zed_actions;
53
54actions!(
55 agent,
56 [
57 /// Creates a new text-based conversation thread.
58 NewTextThread,
59 /// Toggles the menu to create new agent threads.
60 ToggleNewThreadMenu,
61 /// Toggles the navigation menu for switching between threads and views.
62 ToggleNavigationMenu,
63 /// Toggles the options menu for agent settings and preferences.
64 ToggleOptionsMenu,
65 /// Deletes the recently opened thread from history.
66 DeleteRecentlyOpenThread,
67 /// Toggles the profile or mode selector for switching between agent profiles.
68 ToggleProfileSelector,
69 /// Cycles through available session modes.
70 CycleModeSelector,
71 /// Cycles through favorited models in the ACP model selector.
72 CycleFavoriteModels,
73 /// Expands the message editor to full size.
74 ExpandMessageEditor,
75 /// Removes all thread history.
76 RemoveHistory,
77 /// Opens the conversation history view.
78 OpenHistory,
79 /// Adds a context server to the configuration.
80 AddContextServer,
81 /// Removes the currently selected thread.
82 RemoveSelectedThread,
83 /// Starts a chat conversation with follow-up enabled.
84 ChatWithFollow,
85 /// Cycles to the next inline assist suggestion.
86 CycleNextInlineAssist,
87 /// Cycles to the previous inline assist suggestion.
88 CyclePreviousInlineAssist,
89 /// Moves focus up in the interface.
90 FocusUp,
91 /// Moves focus down in the interface.
92 FocusDown,
93 /// Moves focus left in the interface.
94 FocusLeft,
95 /// Moves focus right in the interface.
96 FocusRight,
97 /// Opens the active thread as a markdown file.
98 OpenActiveThreadAsMarkdown,
99 /// Opens the agent diff view to review changes.
100 OpenAgentDiff,
101 /// Keeps the current suggestion or change.
102 Keep,
103 /// Rejects the current suggestion or change.
104 Reject,
105 /// Rejects all suggestions or changes.
106 RejectAll,
107 /// Keeps all suggestions or changes.
108 KeepAll,
109 /// Allow this operation only this time.
110 AllowOnce,
111 /// Allow this operation and remember the choice.
112 AllowAlways,
113 /// Reject this operation only this time.
114 RejectOnce,
115 /// Follows the agent's suggestions.
116 Follow,
117 /// Resets the trial upsell notification.
118 ResetTrialUpsell,
119 /// Resets the trial end upsell notification.
120 ResetTrialEndUpsell,
121 /// Continues the current thread.
122 ContinueThread,
123 /// Continues the thread with burn mode enabled.
124 ContinueWithBurnMode,
125 /// Toggles burn mode for faster responses.
126 ToggleBurnMode,
127 ]
128);
129
130/// Creates a new conversation thread, optionally based on an existing thread.
131#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
132#[action(namespace = agent)]
133#[serde(deny_unknown_fields)]
134pub struct NewThread;
135
136/// Creates a new external agent conversation thread.
137#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
138#[action(namespace = agent)]
139#[serde(deny_unknown_fields)]
140pub struct NewExternalAgentThread {
141 /// Which agent to use for the conversation.
142 agent: Option<ExternalAgent>,
143}
144
145#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)]
146#[action(namespace = agent)]
147#[serde(deny_unknown_fields)]
148pub struct NewNativeAgentThreadFromSummary {
149 from_session_id: agent_client_protocol::SessionId,
150}
151
152// TODO unify this with AgentType
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
154#[serde(rename_all = "snake_case")]
155pub enum ExternalAgent {
156 Gemini,
157 ClaudeCode,
158 Codex,
159 NativeAgent,
160 Custom { name: SharedString },
161}
162
163impl ExternalAgent {
164 pub fn server(
165 &self,
166 fs: Arc<dyn fs::Fs>,
167 history: Entity<agent::HistoryStore>,
168 ) -> Rc<dyn agent_servers::AgentServer> {
169 match self {
170 Self::Gemini => Rc::new(agent_servers::Gemini),
171 Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
172 Self::Codex => Rc::new(agent_servers::Codex),
173 Self::NativeAgent => Rc::new(agent::NativeAgentServer::new(fs, history)),
174 Self::Custom { name } => Rc::new(agent_servers::CustomAgentServer::new(name.clone())),
175 }
176 }
177}
178
179/// Opens the profile management interface for configuring agent tools and settings.
180#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
181#[action(namespace = agent)]
182#[serde(deny_unknown_fields)]
183pub struct ManageProfiles {
184 #[serde(default)]
185 pub customize_tools: Option<AgentProfileId>,
186}
187
188impl ManageProfiles {
189 pub fn customize_tools(profile_id: AgentProfileId) -> Self {
190 Self {
191 customize_tools: Some(profile_id),
192 }
193 }
194}
195
196#[derive(Clone)]
197pub(crate) enum ModelUsageContext {
198 InlineAssistant,
199}
200
201impl ModelUsageContext {
202 pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
203 match self {
204 Self::InlineAssistant => {
205 LanguageModelRegistry::read_global(cx).inline_assistant_model()
206 }
207 }
208 }
209}
210
211/// Initializes the `agent` crate.
212pub fn init(
213 fs: Arc<dyn Fs>,
214 client: Arc<Client>,
215 prompt_builder: Arc<PromptBuilder>,
216 language_registry: Arc<LanguageRegistry>,
217 is_eval: bool,
218 cx: &mut App,
219) {
220 assistant_text_thread::init(client, cx);
221 rules_library::init(cx);
222 if !is_eval {
223 // Initializing the language model from the user settings messes with the eval, so we only initialize them when
224 // we're not running inside of the eval.
225 init_language_model_settings(cx);
226 }
227 assistant_slash_command::init(cx);
228 agent_panel::init(cx);
229 context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
230 TextThreadEditor::init(cx);
231
232 register_slash_commands(cx);
233 inline_assistant::init(fs.clone(), prompt_builder.clone(), cx);
234 terminal_inline_assistant::init(fs.clone(), prompt_builder, cx);
235 cx.observe_new(move |workspace, window, cx| {
236 ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
237 })
238 .detach();
239 cx.observe_new(ManageProfilesModal::register).detach();
240
241 // Update command palette filter based on AI settings
242 update_command_palette_filter(cx);
243
244 // Watch for settings changes
245 cx.observe_global::<SettingsStore>(|app_cx| {
246 // When settings change, update the command palette filter
247 update_command_palette_filter(app_cx);
248 })
249 .detach();
250
251 cx.on_flags_ready(|_, cx| {
252 update_command_palette_filter(cx);
253 })
254 .detach();
255}
256
257fn update_command_palette_filter(cx: &mut App) {
258 let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
259 let agent_enabled = AgentSettings::get_global(cx).enabled;
260 let agent_v2_enabled = cx.has_flag::<AgentV2FeatureFlag>();
261 let edit_prediction_provider = AllLanguageSettings::get_global(cx)
262 .edit_predictions
263 .provider;
264
265 CommandPaletteFilter::update_global(cx, |filter, _| {
266 use editor::actions::{
267 AcceptEditPrediction, AcceptNextLineEditPrediction, AcceptNextWordEditPrediction,
268 NextEditPrediction, PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
269 };
270 let edit_prediction_actions = [
271 TypeId::of::<AcceptEditPrediction>(),
272 TypeId::of::<AcceptNextWordEditPrediction>(),
273 TypeId::of::<AcceptNextLineEditPrediction>(),
274 TypeId::of::<AcceptEditPrediction>(),
275 TypeId::of::<ShowEditPrediction>(),
276 TypeId::of::<NextEditPrediction>(),
277 TypeId::of::<PreviousEditPrediction>(),
278 TypeId::of::<ToggleEditPrediction>(),
279 ];
280
281 if disable_ai {
282 filter.hide_namespace("agent");
283 filter.hide_namespace("agents");
284 filter.hide_namespace("assistant");
285 filter.hide_namespace("copilot");
286 filter.hide_namespace("supermaven");
287 filter.hide_namespace("zed_predict_onboarding");
288 filter.hide_namespace("edit_prediction");
289
290 filter.hide_action_types(&edit_prediction_actions);
291 filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
292 } else {
293 if agent_enabled {
294 filter.show_namespace("agent");
295 filter.show_namespace("agents");
296 } else {
297 filter.hide_namespace("agent");
298 filter.hide_namespace("agents");
299 }
300
301 filter.show_namespace("assistant");
302
303 match edit_prediction_provider {
304 EditPredictionProvider::None => {
305 filter.hide_namespace("edit_prediction");
306 filter.hide_namespace("copilot");
307 filter.hide_namespace("supermaven");
308 filter.hide_action_types(&edit_prediction_actions);
309 }
310 EditPredictionProvider::Copilot => {
311 filter.show_namespace("edit_prediction");
312 filter.show_namespace("copilot");
313 filter.hide_namespace("supermaven");
314 filter.show_action_types(edit_prediction_actions.iter());
315 }
316 EditPredictionProvider::Supermaven => {
317 filter.show_namespace("edit_prediction");
318 filter.hide_namespace("copilot");
319 filter.show_namespace("supermaven");
320 filter.show_action_types(edit_prediction_actions.iter());
321 }
322 EditPredictionProvider::Zed
323 | EditPredictionProvider::Codestral
324 | EditPredictionProvider::Experimental(_) => {
325 filter.show_namespace("edit_prediction");
326 filter.hide_namespace("copilot");
327 filter.hide_namespace("supermaven");
328 filter.show_action_types(edit_prediction_actions.iter());
329 }
330 }
331
332 filter.show_namespace("zed_predict_onboarding");
333 filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
334 if !agent_v2_enabled {
335 filter.hide_action_types(&[TypeId::of::<zed_actions::agent::ToggleAgentPane>()]);
336 }
337 }
338 });
339}
340
341fn init_language_model_settings(cx: &mut App) {
342 update_active_language_model_from_settings(cx);
343
344 cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
345 .detach();
346 cx.subscribe(
347 &LanguageModelRegistry::global(cx),
348 |_, event: &language_model::Event, cx| match event {
349 language_model::Event::ProviderStateChanged(_)
350 | language_model::Event::AddedProvider(_)
351 | language_model::Event::RemovedProvider(_)
352 | language_model::Event::ProvidersChanged => {
353 update_active_language_model_from_settings(cx);
354 }
355 _ => {}
356 },
357 )
358 .detach();
359}
360
361fn update_active_language_model_from_settings(cx: &mut App) {
362 let settings = AgentSettings::get_global(cx);
363
364 fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
365 language_model::SelectedModel {
366 provider: LanguageModelProviderId::from(selection.provider.0.clone()),
367 model: LanguageModelId::from(selection.model.clone()),
368 }
369 }
370
371 // Filter out models from providers that are not authenticated
372 fn is_provider_authenticated(
373 selection: &LanguageModelSelection,
374 registry: &LanguageModelRegistry,
375 cx: &App,
376 ) -> bool {
377 let provider_id = LanguageModelProviderId::from(selection.provider.0.clone());
378 registry
379 .provider(&provider_id)
380 .map_or(false, |provider| provider.is_authenticated(cx))
381 }
382
383 let registry = LanguageModelRegistry::global(cx);
384 let registry_ref = registry.read(cx);
385
386 let default = settings
387 .default_model
388 .as_ref()
389 .filter(|s| is_provider_authenticated(s, registry_ref, cx))
390 .map(to_selected_model);
391 let inline_assistant = settings
392 .inline_assistant_model
393 .as_ref()
394 .filter(|s| is_provider_authenticated(s, registry_ref, cx))
395 .map(to_selected_model);
396 let commit_message = settings
397 .commit_message_model
398 .as_ref()
399 .filter(|s| is_provider_authenticated(s, registry_ref, cx))
400 .map(to_selected_model);
401 let thread_summary = settings
402 .thread_summary_model
403 .as_ref()
404 .filter(|s| is_provider_authenticated(s, registry_ref, cx))
405 .map(to_selected_model);
406 let inline_alternatives = settings
407 .inline_alternatives
408 .iter()
409 .filter(|s| is_provider_authenticated(s, registry_ref, cx))
410 .map(to_selected_model)
411 .collect::<Vec<_>>();
412
413 registry.update(cx, |registry, cx| {
414 registry.select_default_model(default.as_ref(), cx);
415 registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
416 registry.select_commit_message_model(commit_message.as_ref(), cx);
417 registry.select_thread_summary_model(thread_summary.as_ref(), cx);
418 registry.select_inline_alternative_models(inline_alternatives, cx);
419 });
420}
421
422fn register_slash_commands(cx: &mut App) {
423 let slash_command_registry = SlashCommandRegistry::global(cx);
424
425 slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
426 slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
427 slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
428 slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
429 slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
430 slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
431 slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
432 slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
433 slash_command_registry
434 .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
435 slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
436
437 cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
438 move |is_enabled, _cx| {
439 if is_enabled {
440 slash_command_registry.register_command(
441 assistant_slash_commands::StreamingExampleSlashCommand,
442 false,
443 );
444 }
445 }
446 })
447 .detach();
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
454 use command_palette_hooks::CommandPaletteFilter;
455 use editor::actions::AcceptEditPrediction;
456 use gpui::{BorrowAppContext, TestAppContext, px};
457 use project::DisableAiSettings;
458 use settings::{
459 DefaultAgentView, DockPosition, DockSide, NotifyWhenAgentWaiting, Settings, SettingsStore,
460 };
461
462 #[gpui::test]
463 fn test_agent_command_palette_visibility(cx: &mut TestAppContext) {
464 // Init settings
465 cx.update(|cx| {
466 let store = SettingsStore::test(cx);
467 cx.set_global(store);
468 command_palette_hooks::init(cx);
469 AgentSettings::register(cx);
470 DisableAiSettings::register(cx);
471 AllLanguageSettings::register(cx);
472 });
473
474 let agent_settings = AgentSettings {
475 enabled: true,
476 button: true,
477 dock: DockPosition::Right,
478 agents_panel_dock: DockSide::Left,
479 default_width: px(300.),
480 default_height: px(600.),
481 default_model: None,
482 inline_assistant_model: None,
483 inline_assistant_use_streaming_tools: false,
484 commit_message_model: None,
485 thread_summary_model: None,
486 inline_alternatives: vec![],
487 favorite_models: vec![],
488 default_profile: AgentProfileId::default(),
489 default_view: DefaultAgentView::Thread,
490 profiles: Default::default(),
491 always_allow_tool_actions: false,
492 notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
493 play_sound_when_agent_done: false,
494 single_file_review: false,
495 model_parameters: vec![],
496 preferred_completion_mode: CompletionMode::Normal,
497 enable_feedback: false,
498 expand_edit_card: true,
499 expand_terminal_card: true,
500 use_modifier_to_send: true,
501 message_editor_min_lines: 1,
502 };
503
504 cx.update(|cx| {
505 AgentSettings::override_global(agent_settings.clone(), cx);
506 DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx);
507
508 // Initial update
509 update_command_palette_filter(cx);
510 });
511
512 // Assert visible
513 cx.update(|cx| {
514 let filter = CommandPaletteFilter::try_global(cx).unwrap();
515 assert!(
516 !filter.is_hidden(&NewThread),
517 "NewThread should be visible by default"
518 );
519 });
520
521 // Disable agent
522 cx.update(|cx| {
523 let mut new_settings = agent_settings.clone();
524 new_settings.enabled = false;
525 AgentSettings::override_global(new_settings, cx);
526
527 // Trigger update
528 update_command_palette_filter(cx);
529 });
530
531 // Assert hidden
532 cx.update(|cx| {
533 let filter = CommandPaletteFilter::try_global(cx).unwrap();
534 assert!(
535 filter.is_hidden(&NewThread),
536 "NewThread should be hidden when agent is disabled"
537 );
538 });
539
540 // Test EditPredictionProvider
541 // Enable EditPredictionProvider::Copilot
542 cx.update(|cx| {
543 cx.update_global::<SettingsStore, _>(|store, cx| {
544 store.update_user_settings(cx, |s| {
545 s.project
546 .all_languages
547 .features
548 .get_or_insert(Default::default())
549 .edit_prediction_provider = Some(EditPredictionProvider::Copilot);
550 });
551 });
552 update_command_palette_filter(cx);
553 });
554
555 cx.update(|cx| {
556 let filter = CommandPaletteFilter::try_global(cx).unwrap();
557 assert!(
558 !filter.is_hidden(&AcceptEditPrediction),
559 "EditPrediction should be visible when provider is Copilot"
560 );
561 });
562
563 // Disable EditPredictionProvider (None)
564 cx.update(|cx| {
565 cx.update_global::<SettingsStore, _>(|store, cx| {
566 store.update_user_settings(cx, |s| {
567 s.project
568 .all_languages
569 .features
570 .get_or_insert(Default::default())
571 .edit_prediction_provider = Some(EditPredictionProvider::None);
572 });
573 });
574 update_command_palette_filter(cx);
575 });
576
577 cx.update(|cx| {
578 let filter = CommandPaletteFilter::try_global(cx).unwrap();
579 assert!(
580 filter.is_hidden(&AcceptEditPrediction),
581 "EditPrediction should be hidden when provider is None"
582 );
583 });
584 }
585}