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(
365 selection: &LanguageModelSelection,
366 registry: &LanguageModelRegistry,
367 cx: &App,
368 ) -> Option<language_model::SelectedModel> {
369 let provider_id = LanguageModelProviderId::from(selection.provider.0.clone());
370
371 if registry
372 .provider(&provider_id)
373 .map_or(false, |provider| provider.is_authenticated(cx))
374 {
375 Some(language_model::SelectedModel {
376 provider: LanguageModelProviderId::from(selection.provider.0.clone()),
377 model: LanguageModelId::from(selection.model.clone()),
378 })
379 } else {
380 None
381 }
382 }
383
384 let registry = LanguageModelRegistry::global(cx);
385 let registry_ref = registry.read(cx);
386
387 let default = settings
388 .default_model
389 .as_ref()
390 .and_then(|s| to_selected_model(s, registry_ref, cx));
391 let inline_assistant = settings
392 .inline_assistant_model
393 .as_ref()
394 .and_then(|s| to_selected_model(s, registry_ref, cx));
395 let commit_message = settings
396 .commit_message_model
397 .as_ref()
398 .and_then(|s| to_selected_model(s, registry_ref, cx));
399 let thread_summary = settings
400 .thread_summary_model
401 .as_ref()
402 .and_then(|s| to_selected_model(s, registry_ref, cx));
403 let inline_alternatives = settings
404 .inline_alternatives
405 .iter()
406 .filter_map(|s| to_selected_model(s, registry_ref, cx))
407 .collect::<Vec<_>>();
408
409 registry.update(cx, |registry, cx| {
410 registry.select_default_model(default.as_ref(), cx);
411 registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
412 registry.select_commit_message_model(commit_message.as_ref(), cx);
413 registry.select_thread_summary_model(thread_summary.as_ref(), cx);
414 registry.select_inline_alternative_models(inline_alternatives, cx);
415 });
416}
417
418fn register_slash_commands(cx: &mut App) {
419 let slash_command_registry = SlashCommandRegistry::global(cx);
420
421 slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
422 slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
423 slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
424 slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
425 slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
426 slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
427 slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
428 slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
429 slash_command_registry
430 .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
431 slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
432
433 cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
434 move |is_enabled, _cx| {
435 if is_enabled {
436 slash_command_registry.register_command(
437 assistant_slash_commands::StreamingExampleSlashCommand,
438 false,
439 );
440 }
441 }
442 })
443 .detach();
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449 use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
450 use command_palette_hooks::CommandPaletteFilter;
451 use editor::actions::AcceptEditPrediction;
452 use gpui::{BorrowAppContext, TestAppContext, px};
453 use project::DisableAiSettings;
454 use settings::{
455 DefaultAgentView, DockPosition, DockSide, NotifyWhenAgentWaiting, Settings, SettingsStore,
456 };
457
458 #[gpui::test]
459 fn test_agent_command_palette_visibility(cx: &mut TestAppContext) {
460 // Init settings
461 cx.update(|cx| {
462 let store = SettingsStore::test(cx);
463 cx.set_global(store);
464 command_palette_hooks::init(cx);
465 AgentSettings::register(cx);
466 DisableAiSettings::register(cx);
467 AllLanguageSettings::register(cx);
468 });
469
470 let agent_settings = AgentSettings {
471 enabled: true,
472 button: true,
473 dock: DockPosition::Right,
474 agents_panel_dock: DockSide::Left,
475 default_width: px(300.),
476 default_height: px(600.),
477 default_model: None,
478 inline_assistant_model: None,
479 inline_assistant_use_streaming_tools: false,
480 commit_message_model: None,
481 thread_summary_model: None,
482 inline_alternatives: vec![],
483 favorite_models: vec![],
484 default_profile: AgentProfileId::default(),
485 default_view: DefaultAgentView::Thread,
486 profiles: Default::default(),
487 always_allow_tool_actions: false,
488 notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
489 play_sound_when_agent_done: false,
490 single_file_review: false,
491 model_parameters: vec![],
492 preferred_completion_mode: CompletionMode::Normal,
493 enable_feedback: false,
494 expand_edit_card: true,
495 expand_terminal_card: true,
496 use_modifier_to_send: true,
497 message_editor_min_lines: 1,
498 };
499
500 cx.update(|cx| {
501 AgentSettings::override_global(agent_settings.clone(), cx);
502 DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx);
503
504 // Initial update
505 update_command_palette_filter(cx);
506 });
507
508 // Assert visible
509 cx.update(|cx| {
510 let filter = CommandPaletteFilter::try_global(cx).unwrap();
511 assert!(
512 !filter.is_hidden(&NewThread),
513 "NewThread should be visible by default"
514 );
515 });
516
517 // Disable agent
518 cx.update(|cx| {
519 let mut new_settings = agent_settings.clone();
520 new_settings.enabled = false;
521 AgentSettings::override_global(new_settings, cx);
522
523 // Trigger update
524 update_command_palette_filter(cx);
525 });
526
527 // Assert hidden
528 cx.update(|cx| {
529 let filter = CommandPaletteFilter::try_global(cx).unwrap();
530 assert!(
531 filter.is_hidden(&NewThread),
532 "NewThread should be hidden when agent is disabled"
533 );
534 });
535
536 // Test EditPredictionProvider
537 // Enable EditPredictionProvider::Copilot
538 cx.update(|cx| {
539 cx.update_global::<SettingsStore, _>(|store, cx| {
540 store.update_user_settings(cx, |s| {
541 s.project
542 .all_languages
543 .features
544 .get_or_insert(Default::default())
545 .edit_prediction_provider = Some(EditPredictionProvider::Copilot);
546 });
547 });
548 update_command_palette_filter(cx);
549 });
550
551 cx.update(|cx| {
552 let filter = CommandPaletteFilter::try_global(cx).unwrap();
553 assert!(
554 !filter.is_hidden(&AcceptEditPrediction),
555 "EditPrediction should be visible when provider is Copilot"
556 );
557 });
558
559 // Disable EditPredictionProvider (None)
560 cx.update(|cx| {
561 cx.update_global::<SettingsStore, _>(|store, cx| {
562 store.update_user_settings(cx, |s| {
563 s.project
564 .all_languages
565 .features
566 .get_or_insert(Default::default())
567 .edit_prediction_provider = Some(EditPredictionProvider::None);
568 });
569 });
570 update_command_palette_filter(cx);
571 });
572
573 cx.update(|cx| {
574 let filter = CommandPaletteFilter::try_global(cx).unwrap();
575 assert!(
576 filter.is_hidden(&AcceptEditPrediction),
577 "EditPrediction should be hidden when provider is None"
578 );
579 });
580 }
581}