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