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