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