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 /// Queues a message to be sent when generation completes.
128 QueueMessage,
129 /// Sends the next queued message immediately.
130 SendNextQueuedMessage,
131 /// Clears all messages from the queue.
132 ClearMessageQueue,
133 ]
134);
135
136/// Creates a new conversation thread, optionally based on an existing thread.
137#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
138#[action(namespace = agent)]
139#[serde(deny_unknown_fields)]
140pub struct NewThread;
141
142/// Creates a new external agent conversation thread.
143#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
144#[action(namespace = agent)]
145#[serde(deny_unknown_fields)]
146pub struct NewExternalAgentThread {
147 /// Which agent to use for the conversation.
148 agent: Option<ExternalAgent>,
149}
150
151#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)]
152#[action(namespace = agent)]
153#[serde(deny_unknown_fields)]
154pub struct NewNativeAgentThreadFromSummary {
155 from_session_id: agent_client_protocol::SessionId,
156}
157
158// TODO unify this with AgentType
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
160#[serde(rename_all = "snake_case")]
161pub enum ExternalAgent {
162 Gemini,
163 ClaudeCode,
164 Codex,
165 NativeAgent,
166 Custom { name: SharedString },
167}
168
169impl ExternalAgent {
170 pub fn server(
171 &self,
172 fs: Arc<dyn fs::Fs>,
173 history: Entity<agent::HistoryStore>,
174 ) -> Rc<dyn agent_servers::AgentServer> {
175 match self {
176 Self::Gemini => Rc::new(agent_servers::Gemini),
177 Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
178 Self::Codex => Rc::new(agent_servers::Codex),
179 Self::NativeAgent => Rc::new(agent::NativeAgentServer::new(fs, history)),
180 Self::Custom { name } => Rc::new(agent_servers::CustomAgentServer::new(name.clone())),
181 }
182 }
183}
184
185/// Opens the profile management interface for configuring agent tools and settings.
186#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
187#[action(namespace = agent)]
188#[serde(deny_unknown_fields)]
189pub struct ManageProfiles {
190 #[serde(default)]
191 pub customize_tools: Option<AgentProfileId>,
192}
193
194impl ManageProfiles {
195 pub fn customize_tools(profile_id: AgentProfileId) -> Self {
196 Self {
197 customize_tools: Some(profile_id),
198 }
199 }
200}
201
202#[derive(Clone)]
203pub(crate) enum ModelUsageContext {
204 InlineAssistant,
205}
206
207impl ModelUsageContext {
208 pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
209 match self {
210 Self::InlineAssistant => {
211 LanguageModelRegistry::read_global(cx).inline_assistant_model()
212 }
213 }
214 }
215}
216
217/// Initializes the `agent` crate.
218pub fn init(
219 fs: Arc<dyn Fs>,
220 client: Arc<Client>,
221 prompt_builder: Arc<PromptBuilder>,
222 language_registry: Arc<LanguageRegistry>,
223 is_eval: bool,
224 cx: &mut App,
225) {
226 assistant_text_thread::init(client, cx);
227 rules_library::init(cx);
228 if !is_eval {
229 // Initializing the language model from the user settings messes with the eval, so we only initialize them when
230 // we're not running inside of the eval.
231 init_language_model_settings(cx);
232 }
233 assistant_slash_command::init(cx);
234 agent_panel::init(cx);
235 context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
236 TextThreadEditor::init(cx);
237
238 register_slash_commands(cx);
239 inline_assistant::init(fs.clone(), prompt_builder.clone(), cx);
240 terminal_inline_assistant::init(fs.clone(), prompt_builder, cx);
241 cx.observe_new(move |workspace, window, cx| {
242 ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
243 })
244 .detach();
245 cx.observe_new(ManageProfilesModal::register).detach();
246
247 // Update command palette filter based on AI settings
248 update_command_palette_filter(cx);
249
250 // Watch for settings changes
251 cx.observe_global::<SettingsStore>(|app_cx| {
252 // When settings change, update the command palette filter
253 update_command_palette_filter(app_cx);
254 })
255 .detach();
256
257 cx.on_flags_ready(|_, cx| {
258 update_command_palette_filter(cx);
259 })
260 .detach();
261}
262
263fn update_command_palette_filter(cx: &mut App) {
264 let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
265 let agent_enabled = AgentSettings::get_global(cx).enabled;
266 let agent_v2_enabled = cx.has_flag::<AgentV2FeatureFlag>();
267 let edit_prediction_provider = AllLanguageSettings::get_global(cx)
268 .edit_predictions
269 .provider;
270
271 CommandPaletteFilter::update_global(cx, |filter, _| {
272 use editor::actions::{
273 AcceptEditPrediction, AcceptNextLineEditPrediction, AcceptNextWordEditPrediction,
274 NextEditPrediction, PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
275 };
276 let edit_prediction_actions = [
277 TypeId::of::<AcceptEditPrediction>(),
278 TypeId::of::<AcceptNextWordEditPrediction>(),
279 TypeId::of::<AcceptNextLineEditPrediction>(),
280 TypeId::of::<AcceptEditPrediction>(),
281 TypeId::of::<ShowEditPrediction>(),
282 TypeId::of::<NextEditPrediction>(),
283 TypeId::of::<PreviousEditPrediction>(),
284 TypeId::of::<ToggleEditPrediction>(),
285 ];
286
287 if disable_ai {
288 filter.hide_namespace("agent");
289 filter.hide_namespace("agents");
290 filter.hide_namespace("assistant");
291 filter.hide_namespace("copilot");
292 filter.hide_namespace("supermaven");
293 filter.hide_namespace("zed_predict_onboarding");
294 filter.hide_namespace("edit_prediction");
295
296 filter.hide_action_types(&edit_prediction_actions);
297 filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
298 } else {
299 if agent_enabled {
300 filter.show_namespace("agent");
301 filter.show_namespace("agents");
302 } else {
303 filter.hide_namespace("agent");
304 filter.hide_namespace("agents");
305 }
306
307 filter.show_namespace("assistant");
308
309 match edit_prediction_provider {
310 EditPredictionProvider::None => {
311 filter.hide_namespace("edit_prediction");
312 filter.hide_namespace("copilot");
313 filter.hide_namespace("supermaven");
314 filter.hide_action_types(&edit_prediction_actions);
315 }
316 EditPredictionProvider::Copilot => {
317 filter.show_namespace("edit_prediction");
318 filter.show_namespace("copilot");
319 filter.hide_namespace("supermaven");
320 filter.show_action_types(edit_prediction_actions.iter());
321 }
322 EditPredictionProvider::Supermaven => {
323 filter.show_namespace("edit_prediction");
324 filter.hide_namespace("copilot");
325 filter.show_namespace("supermaven");
326 filter.show_action_types(edit_prediction_actions.iter());
327 }
328 EditPredictionProvider::Zed
329 | EditPredictionProvider::Codestral
330 | EditPredictionProvider::Experimental(_) => {
331 filter.show_namespace("edit_prediction");
332 filter.hide_namespace("copilot");
333 filter.hide_namespace("supermaven");
334 filter.show_action_types(edit_prediction_actions.iter());
335 }
336 }
337
338 filter.show_namespace("zed_predict_onboarding");
339 filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
340 if !agent_v2_enabled {
341 filter.hide_action_types(&[TypeId::of::<zed_actions::agent::ToggleAgentPane>()]);
342 }
343 }
344 });
345}
346
347fn init_language_model_settings(cx: &mut App) {
348 update_active_language_model_from_settings(cx);
349
350 cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
351 .detach();
352 cx.subscribe(
353 &LanguageModelRegistry::global(cx),
354 |_, event: &language_model::Event, cx| match event {
355 language_model::Event::ProviderStateChanged(_)
356 | language_model::Event::AddedProvider(_)
357 | language_model::Event::RemovedProvider(_)
358 | language_model::Event::ProvidersChanged => {
359 update_active_language_model_from_settings(cx);
360 }
361 _ => {}
362 },
363 )
364 .detach();
365}
366
367fn update_active_language_model_from_settings(cx: &mut App) {
368 let settings = AgentSettings::get_global(cx);
369
370 fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
371 language_model::SelectedModel {
372 provider: LanguageModelProviderId::from(selection.provider.0.clone()),
373 model: LanguageModelId::from(selection.model.clone()),
374 }
375 }
376
377 let default = settings.default_model.as_ref().map(to_selected_model);
378 let inline_assistant = settings
379 .inline_assistant_model
380 .as_ref()
381 .map(to_selected_model);
382 let commit_message = settings
383 .commit_message_model
384 .as_ref()
385 .map(to_selected_model);
386 let thread_summary = settings
387 .thread_summary_model
388 .as_ref()
389 .map(to_selected_model);
390 let inline_alternatives = settings
391 .inline_alternatives
392 .iter()
393 .map(to_selected_model)
394 .collect::<Vec<_>>();
395
396 LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
397 registry.select_default_model(default.as_ref(), cx);
398 registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
399 registry.select_commit_message_model(commit_message.as_ref(), cx);
400 registry.select_thread_summary_model(thread_summary.as_ref(), cx);
401 registry.select_inline_alternative_models(inline_alternatives, cx);
402 });
403}
404
405fn register_slash_commands(cx: &mut App) {
406 let slash_command_registry = SlashCommandRegistry::global(cx);
407
408 slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
409 slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
410 slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
411 slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
412 slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
413 slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
414 slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
415 slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
416 slash_command_registry
417 .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
418 slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
419
420 cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
421 move |is_enabled, _cx| {
422 if is_enabled {
423 slash_command_registry.register_command(
424 assistant_slash_commands::StreamingExampleSlashCommand,
425 false,
426 );
427 }
428 }
429 })
430 .detach();
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
437 use command_palette_hooks::CommandPaletteFilter;
438 use editor::actions::AcceptEditPrediction;
439 use gpui::{BorrowAppContext, TestAppContext, px};
440 use project::DisableAiSettings;
441 use settings::{
442 DefaultAgentView, DockPosition, DockSide, NotifyWhenAgentWaiting, Settings, SettingsStore,
443 };
444
445 #[gpui::test]
446 fn test_agent_command_palette_visibility(cx: &mut TestAppContext) {
447 // Init settings
448 cx.update(|cx| {
449 let store = SettingsStore::test(cx);
450 cx.set_global(store);
451 command_palette_hooks::init(cx);
452 AgentSettings::register(cx);
453 DisableAiSettings::register(cx);
454 AllLanguageSettings::register(cx);
455 });
456
457 let agent_settings = AgentSettings {
458 enabled: true,
459 button: true,
460 dock: DockPosition::Right,
461 agents_panel_dock: DockSide::Left,
462 default_width: px(300.),
463 default_height: px(600.),
464 default_model: None,
465 inline_assistant_model: None,
466 inline_assistant_use_streaming_tools: false,
467 commit_message_model: None,
468 thread_summary_model: None,
469 inline_alternatives: vec![],
470 favorite_models: vec![],
471 default_profile: AgentProfileId::default(),
472 default_view: DefaultAgentView::Thread,
473 profiles: Default::default(),
474 always_allow_tool_actions: false,
475 notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
476 play_sound_when_agent_done: false,
477 single_file_review: false,
478 model_parameters: vec![],
479 preferred_completion_mode: CompletionMode::Normal,
480 enable_feedback: false,
481 expand_edit_card: true,
482 expand_terminal_card: true,
483 use_modifier_to_send: true,
484 message_editor_min_lines: 1,
485 };
486
487 cx.update(|cx| {
488 AgentSettings::override_global(agent_settings.clone(), cx);
489 DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx);
490
491 // Initial update
492 update_command_palette_filter(cx);
493 });
494
495 // Assert visible
496 cx.update(|cx| {
497 let filter = CommandPaletteFilter::try_global(cx).unwrap();
498 assert!(
499 !filter.is_hidden(&NewThread),
500 "NewThread should be visible by default"
501 );
502 });
503
504 // Disable agent
505 cx.update(|cx| {
506 let mut new_settings = agent_settings.clone();
507 new_settings.enabled = false;
508 AgentSettings::override_global(new_settings, cx);
509
510 // Trigger update
511 update_command_palette_filter(cx);
512 });
513
514 // Assert hidden
515 cx.update(|cx| {
516 let filter = CommandPaletteFilter::try_global(cx).unwrap();
517 assert!(
518 filter.is_hidden(&NewThread),
519 "NewThread should be hidden when agent is disabled"
520 );
521 });
522
523 // Test EditPredictionProvider
524 // Enable EditPredictionProvider::Copilot
525 cx.update(|cx| {
526 cx.update_global::<SettingsStore, _>(|store, cx| {
527 store.update_user_settings(cx, |s| {
528 s.project
529 .all_languages
530 .features
531 .get_or_insert(Default::default())
532 .edit_prediction_provider = Some(EditPredictionProvider::Copilot);
533 });
534 });
535 update_command_palette_filter(cx);
536 });
537
538 cx.update(|cx| {
539 let filter = CommandPaletteFilter::try_global(cx).unwrap();
540 assert!(
541 !filter.is_hidden(&AcceptEditPrediction),
542 "EditPrediction should be visible when provider is Copilot"
543 );
544 });
545
546 // Disable EditPredictionProvider (None)
547 cx.update(|cx| {
548 cx.update_global::<SettingsStore, _>(|store, cx| {
549 store.update_user_settings(cx, |s| {
550 s.project
551 .all_languages
552 .features
553 .get_or_insert(Default::default())
554 .edit_prediction_provider = Some(EditPredictionProvider::None);
555 });
556 });
557 update_command_palette_filter(cx);
558 });
559
560 cx.update(|cx| {
561 let filter = CommandPaletteFilter::try_global(cx).unwrap();
562 assert!(
563 filter.is_hidden(&AcceptEditPrediction),
564 "EditPrediction should be hidden when provider is None"
565 );
566 });
567 }
568}