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