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 /// Toggles fast mode for models that support it.
164 ToggleFastMode,
165 ]
166);
167
168/// Action to authorize a tool call with a specific permission option.
169/// This is used by the permission granularity dropdown to authorize tool calls.
170#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)]
171#[action(namespace = agent)]
172#[serde(deny_unknown_fields)]
173pub struct AuthorizeToolCall {
174 /// The tool call ID to authorize.
175 pub tool_call_id: String,
176 /// The permission option ID to use.
177 pub option_id: String,
178 /// The kind of permission option (serialized as string).
179 pub option_kind: String,
180}
181
182/// Action to select a permission granularity option from the dropdown.
183/// This updates the selected granularity without triggering authorization.
184#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)]
185#[action(namespace = agent)]
186#[serde(deny_unknown_fields)]
187pub struct SelectPermissionGranularity {
188 /// The tool call ID for which to select the granularity.
189 pub tool_call_id: String,
190 /// The index of the selected granularity option.
191 pub index: usize,
192}
193
194/// Creates a new conversation thread, optionally based on an existing thread.
195#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
196#[action(namespace = agent)]
197#[serde(deny_unknown_fields)]
198pub struct NewThread;
199
200/// Creates a new external agent conversation thread.
201#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
202#[action(namespace = agent)]
203#[serde(deny_unknown_fields)]
204pub struct NewExternalAgentThread {
205 /// Which agent to use for the conversation.
206 agent: Option<ExternalAgent>,
207}
208
209#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)]
210#[action(namespace = agent)]
211#[serde(deny_unknown_fields)]
212pub struct NewNativeAgentThreadFromSummary {
213 from_session_id: agent_client_protocol::SessionId,
214}
215
216// TODO unify this with AgentType
217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
218#[serde(rename_all = "snake_case")]
219pub enum ExternalAgent {
220 NativeAgent,
221 Custom { name: SharedString },
222}
223
224impl ExternalAgent {
225 pub fn server(
226 &self,
227 fs: Arc<dyn fs::Fs>,
228 thread_store: Entity<agent::ThreadStore>,
229 ) -> Rc<dyn agent_servers::AgentServer> {
230 match self {
231 Self::NativeAgent => Rc::new(agent::NativeAgentServer::new(fs, thread_store)),
232 Self::Custom { name } => Rc::new(agent_servers::CustomAgentServer::new(name.clone())),
233 }
234 }
235}
236
237/// Content to initialize new external agent with.
238pub enum AgentInitialContent {
239 ThreadSummary(acp_thread::AgentSessionInfo),
240 ContentBlock {
241 blocks: Vec<agent_client_protocol::ContentBlock>,
242 auto_submit: bool,
243 },
244}
245
246/// Opens the profile management interface for configuring agent tools and settings.
247#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
248#[action(namespace = agent)]
249#[serde(deny_unknown_fields)]
250pub struct ManageProfiles {
251 #[serde(default)]
252 pub customize_tools: Option<AgentProfileId>,
253}
254
255impl ManageProfiles {
256 pub fn customize_tools(profile_id: AgentProfileId) -> Self {
257 Self {
258 customize_tools: Some(profile_id),
259 }
260 }
261}
262
263#[derive(Clone)]
264pub(crate) enum ModelUsageContext {
265 InlineAssistant,
266}
267
268impl ModelUsageContext {
269 pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
270 match self {
271 Self::InlineAssistant => {
272 LanguageModelRegistry::read_global(cx).inline_assistant_model()
273 }
274 }
275 }
276}
277
278/// Initializes the `agent` crate.
279pub fn init(
280 fs: Arc<dyn Fs>,
281 client: Arc<Client>,
282 prompt_builder: Arc<PromptBuilder>,
283 language_registry: Arc<LanguageRegistry>,
284 is_eval: bool,
285 cx: &mut App,
286) {
287 agent::ThreadStore::init_global(cx);
288 assistant_text_thread::init(client, cx);
289 rules_library::init(cx);
290 if !is_eval {
291 // Initializing the language model from the user settings messes with the eval, so we only initialize them when
292 // we're not running inside of the eval.
293 init_language_model_settings(cx);
294 }
295 assistant_slash_command::init(cx);
296 agent_panel::init(cx);
297 context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
298 TextThreadEditor::init(cx);
299
300 register_slash_commands(cx);
301 inline_assistant::init(fs.clone(), prompt_builder.clone(), cx);
302 terminal_inline_assistant::init(fs.clone(), prompt_builder, cx);
303 cx.observe_new(move |workspace, window, cx| {
304 ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
305 })
306 .detach();
307 cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
308 workspace.register_action(
309 move |workspace: &mut Workspace,
310 _: &zed_actions::AcpRegistry,
311 window: &mut Window,
312 cx: &mut Context<Workspace>| {
313 let existing = workspace
314 .active_pane()
315 .read(cx)
316 .items()
317 .find_map(|item| item.downcast::<AgentRegistryPage>());
318
319 if let Some(existing) = existing {
320 existing.update(cx, |_, cx| {
321 project::AgentRegistryStore::global(cx)
322 .update(cx, |store, cx| store.refresh(cx));
323 });
324 workspace.activate_item(&existing, true, true, window, cx);
325 } else {
326 let registry_page = AgentRegistryPage::new(workspace, window, cx);
327 workspace.add_item_to_active_pane(
328 Box::new(registry_page),
329 None,
330 true,
331 window,
332 cx,
333 );
334 }
335 },
336 );
337 })
338 .detach();
339 cx.observe_new(ManageProfilesModal::register).detach();
340
341 // Update command palette filter based on AI settings
342 update_command_palette_filter(cx);
343
344 // Watch for settings changes
345 cx.observe_global::<SettingsStore>(|app_cx| {
346 // When settings change, update the command palette filter
347 update_command_palette_filter(app_cx);
348 })
349 .detach();
350
351 cx.on_flags_ready(|_, cx| {
352 update_command_palette_filter(cx);
353 })
354 .detach();
355}
356
357fn update_command_palette_filter(cx: &mut App) {
358 let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
359 let agent_enabled = AgentSettings::get_global(cx).enabled;
360 let agent_v2_enabled = cx.has_flag::<AgentV2FeatureFlag>();
361 let edit_prediction_provider = AllLanguageSettings::get_global(cx)
362 .edit_predictions
363 .provider;
364
365 CommandPaletteFilter::update_global(cx, |filter, _| {
366 use editor::actions::{
367 AcceptEditPrediction, AcceptNextLineEditPrediction, AcceptNextWordEditPrediction,
368 NextEditPrediction, PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
369 };
370 let edit_prediction_actions = [
371 TypeId::of::<AcceptEditPrediction>(),
372 TypeId::of::<AcceptNextWordEditPrediction>(),
373 TypeId::of::<AcceptNextLineEditPrediction>(),
374 TypeId::of::<AcceptEditPrediction>(),
375 TypeId::of::<ShowEditPrediction>(),
376 TypeId::of::<NextEditPrediction>(),
377 TypeId::of::<PreviousEditPrediction>(),
378 TypeId::of::<ToggleEditPrediction>(),
379 ];
380
381 if disable_ai {
382 filter.hide_namespace("agent");
383 filter.hide_namespace("agents");
384 filter.hide_namespace("assistant");
385 filter.hide_namespace("copilot");
386 filter.hide_namespace("supermaven");
387 filter.hide_namespace("zed_predict_onboarding");
388 filter.hide_namespace("edit_prediction");
389
390 filter.hide_action_types(&edit_prediction_actions);
391 filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
392 } else {
393 if agent_enabled {
394 filter.show_namespace("agent");
395 filter.show_namespace("agents");
396 filter.show_namespace("assistant");
397 } else {
398 filter.hide_namespace("agent");
399 filter.hide_namespace("agents");
400 filter.hide_namespace("assistant");
401 }
402
403 match edit_prediction_provider {
404 EditPredictionProvider::None => {
405 filter.hide_namespace("edit_prediction");
406 filter.hide_namespace("copilot");
407 filter.hide_namespace("supermaven");
408 filter.hide_action_types(&edit_prediction_actions);
409 }
410 EditPredictionProvider::Copilot => {
411 filter.show_namespace("edit_prediction");
412 filter.show_namespace("copilot");
413 filter.hide_namespace("supermaven");
414 filter.show_action_types(edit_prediction_actions.iter());
415 }
416 EditPredictionProvider::Supermaven => {
417 filter.show_namespace("edit_prediction");
418 filter.hide_namespace("copilot");
419 filter.show_namespace("supermaven");
420 filter.show_action_types(edit_prediction_actions.iter());
421 }
422 EditPredictionProvider::Zed
423 | EditPredictionProvider::Codestral
424 | EditPredictionProvider::Ollama
425 | EditPredictionProvider::OpenAiCompatibleApi
426 | EditPredictionProvider::Sweep
427 | EditPredictionProvider::Mercury
428 | EditPredictionProvider::Experimental(_) => {
429 filter.show_namespace("edit_prediction");
430 filter.hide_namespace("copilot");
431 filter.hide_namespace("supermaven");
432 filter.show_action_types(edit_prediction_actions.iter());
433 }
434 }
435
436 filter.show_namespace("zed_predict_onboarding");
437 filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
438 }
439
440 if agent_v2_enabled {
441 filter.show_namespace("multi_workspace");
442 } else {
443 filter.hide_namespace("multi_workspace");
444 }
445 });
446}
447
448fn init_language_model_settings(cx: &mut App) {
449 update_active_language_model_from_settings(cx);
450
451 cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
452 .detach();
453 cx.subscribe(
454 &LanguageModelRegistry::global(cx),
455 |_, event: &language_model::Event, cx| match event {
456 language_model::Event::ProviderStateChanged(_)
457 | language_model::Event::AddedProvider(_)
458 | language_model::Event::RemovedProvider(_)
459 | language_model::Event::ProvidersChanged => {
460 update_active_language_model_from_settings(cx);
461 }
462 _ => {}
463 },
464 )
465 .detach();
466}
467
468fn update_active_language_model_from_settings(cx: &mut App) {
469 let settings = AgentSettings::get_global(cx);
470
471 fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
472 language_model::SelectedModel {
473 provider: LanguageModelProviderId::from(selection.provider.0.clone()),
474 model: LanguageModelId::from(selection.model.clone()),
475 }
476 }
477
478 let default = settings.default_model.as_ref().map(to_selected_model);
479 let inline_assistant = settings
480 .inline_assistant_model
481 .as_ref()
482 .map(to_selected_model);
483 let commit_message = settings
484 .commit_message_model
485 .as_ref()
486 .map(to_selected_model);
487 let thread_summary = settings
488 .thread_summary_model
489 .as_ref()
490 .map(to_selected_model);
491 let inline_alternatives = settings
492 .inline_alternatives
493 .iter()
494 .map(to_selected_model)
495 .collect::<Vec<_>>();
496
497 LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
498 registry.select_default_model(default.as_ref(), cx);
499 registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
500 registry.select_commit_message_model(commit_message.as_ref(), cx);
501 registry.select_thread_summary_model(thread_summary.as_ref(), cx);
502 registry.select_inline_alternative_models(inline_alternatives, cx);
503 });
504}
505
506fn register_slash_commands(cx: &mut App) {
507 let slash_command_registry = SlashCommandRegistry::global(cx);
508
509 slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
510 slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
511 slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
512 slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
513 slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
514 slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
515 slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
516 slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
517 slash_command_registry
518 .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
519 slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
520
521 cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
522 move |is_enabled, _cx| {
523 if is_enabled {
524 slash_command_registry.register_command(
525 assistant_slash_commands::StreamingExampleSlashCommand,
526 false,
527 );
528 }
529 }
530 })
531 .detach();
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537 use agent_settings::{AgentProfileId, AgentSettings};
538 use command_palette_hooks::CommandPaletteFilter;
539 use editor::actions::AcceptEditPrediction;
540 use gpui::{BorrowAppContext, TestAppContext, px};
541 use project::DisableAiSettings;
542 use settings::{
543 DefaultAgentView, DockPosition, NotifyWhenAgentWaiting, Settings, SettingsStore,
544 };
545
546 #[gpui::test]
547 fn test_agent_command_palette_visibility(cx: &mut TestAppContext) {
548 // Init settings
549 cx.update(|cx| {
550 let store = SettingsStore::test(cx);
551 cx.set_global(store);
552 command_palette_hooks::init(cx);
553 AgentSettings::register(cx);
554 DisableAiSettings::register(cx);
555 AllLanguageSettings::register(cx);
556 });
557
558 let agent_settings = AgentSettings {
559 enabled: true,
560 button: true,
561 dock: DockPosition::Right,
562 default_width: px(300.),
563 default_height: px(600.),
564 default_model: None,
565 inline_assistant_model: None,
566 inline_assistant_use_streaming_tools: false,
567 commit_message_model: None,
568 thread_summary_model: None,
569 inline_alternatives: vec![],
570 favorite_models: vec![],
571 default_profile: AgentProfileId::default(),
572 default_view: DefaultAgentView::Thread,
573 profiles: Default::default(),
574
575 notify_when_agent_waiting: NotifyWhenAgentWaiting::default(),
576 play_sound_when_agent_done: false,
577 single_file_review: false,
578 model_parameters: vec![],
579 enable_feedback: false,
580 expand_edit_card: true,
581 expand_terminal_card: true,
582 cancel_generation_on_terminal_stop: true,
583 use_modifier_to_send: true,
584 message_editor_min_lines: 1,
585 tool_permissions: Default::default(),
586 show_turn_stats: false,
587 };
588
589 cx.update(|cx| {
590 AgentSettings::override_global(agent_settings.clone(), cx);
591 DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx);
592
593 // Initial update
594 update_command_palette_filter(cx);
595 });
596
597 // Assert visible
598 cx.update(|cx| {
599 let filter = CommandPaletteFilter::try_global(cx).unwrap();
600 assert!(
601 !filter.is_hidden(&NewThread),
602 "NewThread should be visible by default"
603 );
604 assert!(
605 !filter.is_hidden(&text_thread_editor::CopyCode),
606 "CopyCode should be visible when agent is enabled"
607 );
608 });
609
610 // Disable agent
611 cx.update(|cx| {
612 let mut new_settings = agent_settings.clone();
613 new_settings.enabled = false;
614 AgentSettings::override_global(new_settings, cx);
615
616 // Trigger update
617 update_command_palette_filter(cx);
618 });
619
620 // Assert hidden
621 cx.update(|cx| {
622 let filter = CommandPaletteFilter::try_global(cx).unwrap();
623 assert!(
624 filter.is_hidden(&NewThread),
625 "NewThread should be hidden when agent is disabled"
626 );
627 assert!(
628 filter.is_hidden(&text_thread_editor::CopyCode),
629 "CopyCode should be hidden when agent is disabled"
630 );
631 });
632
633 // Test EditPredictionProvider
634 // Enable EditPredictionProvider::Copilot
635 cx.update(|cx| {
636 cx.update_global::<SettingsStore, _>(|store, cx| {
637 store.update_user_settings(cx, |s| {
638 s.project
639 .all_languages
640 .edit_predictions
641 .get_or_insert(Default::default())
642 .provider = Some(EditPredictionProvider::Copilot);
643 });
644 });
645 update_command_palette_filter(cx);
646 });
647
648 cx.update(|cx| {
649 let filter = CommandPaletteFilter::try_global(cx).unwrap();
650 assert!(
651 !filter.is_hidden(&AcceptEditPrediction),
652 "EditPrediction should be visible when provider is Copilot"
653 );
654 });
655
656 // Disable EditPredictionProvider (None)
657 cx.update(|cx| {
658 cx.update_global::<SettingsStore, _>(|store, cx| {
659 store.update_user_settings(cx, |s| {
660 s.project
661 .all_languages
662 .edit_predictions
663 .get_or_insert(Default::default())
664 .provider = Some(EditPredictionProvider::None);
665 });
666 });
667 update_command_palette_filter(cx);
668 });
669
670 cx.update(|cx| {
671 let filter = CommandPaletteFilter::try_global(cx).unwrap();
672 assert!(
673 filter.is_hidden(&AcceptEditPrediction),
674 "EditPrediction should be hidden when provider is None"
675 );
676 });
677 }
678}