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