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