agent_ui.rs

  1mod acp;
  2mod active_thread;
  3mod agent_configuration;
  4mod agent_diff;
  5mod agent_model_selector;
  6mod agent_panel;
  7mod buffer_codegen;
  8mod burn_mode_tooltip;
  9mod context_picker;
 10mod context_server_configuration;
 11mod context_strip;
 12mod debug;
 13mod inline_assistant;
 14mod inline_prompt_editor;
 15mod language_model_selector;
 16mod message_editor;
 17mod profile_selector;
 18mod slash_command;
 19mod slash_command_picker;
 20mod slash_command_settings;
 21mod terminal_codegen;
 22mod terminal_inline_assistant;
 23mod text_thread_editor;
 24mod thread_history;
 25mod tool_compatibility;
 26mod ui;
 27
 28use std::rc::Rc;
 29use std::sync::Arc;
 30
 31use agent::{Thread, ThreadId};
 32use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
 33use assistant_slash_command::SlashCommandRegistry;
 34use client::Client;
 35use command_palette_hooks::CommandPaletteFilter;
 36use feature_flags::FeatureFlagAppExt as _;
 37use fs::Fs;
 38use gpui::{Action, App, Entity, actions};
 39use language::LanguageRegistry;
 40use language_model::{
 41    ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
 42};
 43use project::DisableAiSettings;
 44use prompt_store::PromptBuilder;
 45use schemars::JsonSchema;
 46use serde::{Deserialize, Serialize};
 47use settings::{Settings as _, SettingsStore};
 48use std::any::TypeId;
 49
 50pub use crate::active_thread::ActiveThread;
 51use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
 52pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
 53pub use crate::inline_assistant::InlineAssistant;
 54use crate::slash_command_settings::SlashCommandSettings;
 55pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
 56pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
 57pub use ui::preview::{all_agent_previews, get_agent_preview};
 58use zed_actions;
 59
 60actions!(
 61    agent,
 62    [
 63        /// Creates a new text-based conversation thread.
 64        NewTextThread,
 65        /// Toggles the context picker interface for adding files, symbols, or other context.
 66        ToggleContextPicker,
 67        /// Toggles the menu to create new agent threads.
 68        ToggleNewThreadMenu,
 69        /// Toggles the navigation menu for switching between threads and views.
 70        ToggleNavigationMenu,
 71        /// Toggles the options menu for agent settings and preferences.
 72        ToggleOptionsMenu,
 73        /// Deletes the recently opened thread from history.
 74        DeleteRecentlyOpenThread,
 75        /// Toggles the profile selector for switching between agent profiles.
 76        ToggleProfileSelector,
 77        /// Removes all added context from the current conversation.
 78        RemoveAllContext,
 79        /// Expands the message editor to full size.
 80        ExpandMessageEditor,
 81        /// Opens the conversation history view.
 82        OpenHistory,
 83        /// Adds a context server to the configuration.
 84        AddContextServer,
 85        /// Removes the currently selected thread.
 86        RemoveSelectedThread,
 87        /// Starts a chat conversation with follow-up enabled.
 88        ChatWithFollow,
 89        /// Cycles to the next inline assist suggestion.
 90        CycleNextInlineAssist,
 91        /// Cycles to the previous inline assist suggestion.
 92        CyclePreviousInlineAssist,
 93        /// Moves focus up in the interface.
 94        FocusUp,
 95        /// Moves focus down in the interface.
 96        FocusDown,
 97        /// Moves focus left in the interface.
 98        FocusLeft,
 99        /// Moves focus right in the interface.
100        FocusRight,
101        /// Removes the currently focused context item.
102        RemoveFocusedContext,
103        /// Accepts the suggested context item.
104        AcceptSuggestedContext,
105        /// Opens the active thread as a markdown file.
106        OpenActiveThreadAsMarkdown,
107        /// Opens the agent diff view to review changes.
108        OpenAgentDiff,
109        /// Keeps the current suggestion or change.
110        Keep,
111        /// Rejects the current suggestion or change.
112        Reject,
113        /// Rejects all suggestions or changes.
114        RejectAll,
115        /// Keeps all suggestions or changes.
116        KeepAll,
117        /// Follows the agent's suggestions.
118        Follow,
119        /// Resets the trial upsell notification.
120        ResetTrialUpsell,
121        /// Resets the trial end upsell notification.
122        ResetTrialEndUpsell,
123        /// Continues the current thread.
124        ContinueThread,
125        /// Continues the thread with burn mode enabled.
126        ContinueWithBurnMode,
127        /// Toggles burn mode for faster responses.
128        ToggleBurnMode,
129    ]
130);
131
132/// Creates a new conversation thread, optionally based on an existing thread.
133#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
134#[action(namespace = agent)]
135#[serde(deny_unknown_fields)]
136pub struct NewThread {
137    #[serde(default)]
138    from_thread_id: Option<ThreadId>,
139}
140
141/// Creates a new external agent conversation thread.
142#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
143#[action(namespace = agent)]
144#[serde(deny_unknown_fields)]
145pub struct NewExternalAgentThread {
146    /// Which agent to use for the conversation.
147    agent: Option<ExternalAgent>,
148}
149
150#[derive(Default, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
151#[serde(rename_all = "snake_case")]
152enum ExternalAgent {
153    #[default]
154    Gemini,
155    ClaudeCode,
156    NativeAgent,
157}
158
159impl ExternalAgent {
160    pub fn server(&self, fs: Arc<dyn fs::Fs>) -> Rc<dyn agent_servers::AgentServer> {
161        match self {
162            ExternalAgent::Gemini => Rc::new(agent_servers::Gemini),
163            ExternalAgent::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
164            ExternalAgent::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs)),
165        }
166    }
167}
168
169/// Opens the profile management interface for configuring agent tools and settings.
170#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
171#[action(namespace = agent)]
172#[serde(deny_unknown_fields)]
173pub struct ManageProfiles {
174    #[serde(default)]
175    pub customize_tools: Option<AgentProfileId>,
176}
177
178impl ManageProfiles {
179    pub fn customize_tools(profile_id: AgentProfileId) -> Self {
180        Self {
181            customize_tools: Some(profile_id),
182        }
183    }
184}
185
186#[derive(Clone)]
187pub(crate) enum ModelUsageContext {
188    Thread(Entity<Thread>),
189    InlineAssistant,
190}
191
192impl ModelUsageContext {
193    pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
194        match self {
195            Self::Thread(thread) => thread.read(cx).configured_model(),
196            Self::InlineAssistant => {
197                LanguageModelRegistry::read_global(cx).inline_assistant_model()
198            }
199        }
200    }
201
202    pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
203        self.configured_model(cx)
204            .map(|configured_model| configured_model.model)
205    }
206}
207
208/// Initializes the `agent` crate.
209pub fn init(
210    fs: Arc<dyn Fs>,
211    client: Arc<Client>,
212    prompt_builder: Arc<PromptBuilder>,
213    language_registry: Arc<LanguageRegistry>,
214    is_eval: bool,
215    cx: &mut App,
216) {
217    AgentSettings::register(cx);
218    SlashCommandSettings::register(cx);
219
220    assistant_context::init(client.clone(), cx);
221    rules_library::init(cx);
222    if !is_eval {
223        // Initializing the language model from the user settings messes with the eval, so we only initialize them when
224        // we're not running inside of the eval.
225        init_language_model_settings(cx);
226    }
227    assistant_slash_command::init(cx);
228    agent::init(cx);
229    agent_panel::init(cx);
230    context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
231    TextThreadEditor::init(cx);
232
233    register_slash_commands(cx);
234    inline_assistant::init(
235        fs.clone(),
236        prompt_builder.clone(),
237        client.telemetry().clone(),
238        cx,
239    );
240    terminal_inline_assistant::init(
241        fs.clone(),
242        prompt_builder.clone(),
243        client.telemetry().clone(),
244        cx,
245    );
246    indexed_docs::init(cx);
247    cx.observe_new(move |workspace, window, cx| {
248        ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
249    })
250    .detach();
251    cx.observe_new(ManageProfilesModal::register).detach();
252
253    // Update command palette filter based on AI settings
254    update_command_palette_filter(cx);
255
256    // Watch for settings changes
257    cx.observe_global::<SettingsStore>(|app_cx| {
258        // When settings change, update the command palette filter
259        update_command_palette_filter(app_cx);
260    })
261    .detach();
262}
263
264fn update_command_palette_filter(cx: &mut App) {
265    let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
266    CommandPaletteFilter::update_global(cx, |filter, _| {
267        if disable_ai {
268            filter.hide_namespace("agent");
269            filter.hide_namespace("assistant");
270            filter.hide_namespace("copilot");
271            filter.hide_namespace("supermaven");
272            filter.hide_namespace("zed_predict_onboarding");
273            filter.hide_namespace("edit_prediction");
274
275            use editor::actions::{
276                AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
277                PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
278            };
279            let edit_prediction_actions = [
280                TypeId::of::<AcceptEditPrediction>(),
281                TypeId::of::<AcceptPartialEditPrediction>(),
282                TypeId::of::<ShowEditPrediction>(),
283                TypeId::of::<NextEditPrediction>(),
284                TypeId::of::<PreviousEditPrediction>(),
285                TypeId::of::<ToggleEditPrediction>(),
286            ];
287            filter.hide_action_types(&edit_prediction_actions);
288            filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
289        } else {
290            filter.show_namespace("agent");
291            filter.show_namespace("assistant");
292            filter.show_namespace("copilot");
293            filter.show_namespace("zed_predict_onboarding");
294
295            filter.show_namespace("edit_prediction");
296
297            use editor::actions::{
298                AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
299                PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
300            };
301            let edit_prediction_actions = [
302                TypeId::of::<AcceptEditPrediction>(),
303                TypeId::of::<AcceptPartialEditPrediction>(),
304                TypeId::of::<ShowEditPrediction>(),
305                TypeId::of::<NextEditPrediction>(),
306                TypeId::of::<PreviousEditPrediction>(),
307                TypeId::of::<ToggleEditPrediction>(),
308            ];
309            filter.show_action_types(edit_prediction_actions.iter());
310
311            filter
312                .show_action_types([TypeId::of::<zed_actions::OpenZedPredictOnboarding>()].iter());
313        }
314    });
315}
316
317fn init_language_model_settings(cx: &mut App) {
318    update_active_language_model_from_settings(cx);
319
320    cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
321        .detach();
322    cx.subscribe(
323        &LanguageModelRegistry::global(cx),
324        |_, event: &language_model::Event, cx| match event {
325            language_model::Event::ProviderStateChanged
326            | language_model::Event::AddedProvider(_)
327            | language_model::Event::RemovedProvider(_) => {
328                update_active_language_model_from_settings(cx);
329            }
330            _ => {}
331        },
332    )
333    .detach();
334}
335
336fn update_active_language_model_from_settings(cx: &mut App) {
337    let settings = AgentSettings::get_global(cx);
338
339    fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
340        language_model::SelectedModel {
341            provider: LanguageModelProviderId::from(selection.provider.0.clone()),
342            model: LanguageModelId::from(selection.model.clone()),
343        }
344    }
345
346    let default = settings.default_model.as_ref().map(to_selected_model);
347    let inline_assistant = settings
348        .inline_assistant_model
349        .as_ref()
350        .map(to_selected_model);
351    let commit_message = settings
352        .commit_message_model
353        .as_ref()
354        .map(to_selected_model);
355    let thread_summary = settings
356        .thread_summary_model
357        .as_ref()
358        .map(to_selected_model);
359    let inline_alternatives = settings
360        .inline_alternatives
361        .iter()
362        .map(to_selected_model)
363        .collect::<Vec<_>>();
364
365    LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
366        registry.select_default_model(default.as_ref(), cx);
367        registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
368        registry.select_commit_message_model(commit_message.as_ref(), cx);
369        registry.select_thread_summary_model(thread_summary.as_ref(), cx);
370        registry.select_inline_alternative_models(inline_alternatives, cx);
371    });
372}
373
374fn register_slash_commands(cx: &mut App) {
375    let slash_command_registry = SlashCommandRegistry::global(cx);
376
377    slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
378    slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
379    slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
380    slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
381    slash_command_registry
382        .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
383    slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
384    slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
385    slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
386    slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
387    slash_command_registry
388        .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
389    slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
390
391    cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
392        let slash_command_registry = slash_command_registry.clone();
393        move |is_enabled, _cx| {
394            if is_enabled {
395                slash_command_registry.register_command(
396                    assistant_slash_commands::StreamingExampleSlashCommand,
397                    false,
398                );
399            }
400        }
401    })
402    .detach();
403
404    update_slash_commands_from_settings(cx);
405    cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
406        .detach();
407}
408
409fn update_slash_commands_from_settings(cx: &mut App) {
410    let slash_command_registry = SlashCommandRegistry::global(cx);
411    let settings = SlashCommandSettings::get_global(cx);
412
413    if settings.docs.enabled {
414        slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
415    } else {
416        slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
417    }
418
419    if settings.cargo_workspace.enabled {
420        slash_command_registry
421            .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
422    } else {
423        slash_command_registry
424            .unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
425    }
426}