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