context_server_configuration.rs

  1use std::sync::Arc;
  2
  3use anyhow::Context as _;
  4use context_server::ContextServerId;
  5use extension::{ContextServerConfiguration, ExtensionManifest};
  6use gpui::Task;
  7use language::LanguageRegistry;
  8use project::context_server_store::registry::ContextServerDescriptorRegistry;
  9use ui::prelude::*;
 10use util::ResultExt;
 11use workspace::Workspace;
 12
 13use crate::agent_configuration::ConfigureContextServerModal;
 14
 15pub(crate) fn init(language_registry: Arc<LanguageRegistry>, cx: &mut App) {
 16    cx.observe_new(move |_: &mut Workspace, window, cx| {
 17        let Some(window) = window else {
 18            return;
 19        };
 20
 21        if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
 22            cx.subscribe_in(extension_events, window, {
 23                let language_registry = language_registry.clone();
 24                move |workspace, _, event, window, cx| match event {
 25                    extension::Event::ExtensionInstalled(manifest) => {
 26                        show_configure_mcp_modal(
 27                            language_registry.clone(),
 28                            manifest,
 29                            workspace,
 30                            window,
 31                            cx,
 32                        );
 33                    }
 34                    extension::Event::ConfigureExtensionRequested(manifest) => {
 35                        if !manifest.context_servers.is_empty() {
 36                            show_configure_mcp_modal(
 37                                language_registry.clone(),
 38                                manifest,
 39                                workspace,
 40                                window,
 41                                cx,
 42                            );
 43                        }
 44                    }
 45                    _ => {}
 46                }
 47            })
 48            .detach();
 49        } else {
 50            log::info!(
 51                "No extension events global found. Skipping context server configuration wizard"
 52            );
 53        }
 54    })
 55    .detach();
 56}
 57
 58pub enum Configuration {
 59    NotAvailable(ContextServerId, Option<SharedString>),
 60    Required(
 61        ContextServerId,
 62        Option<SharedString>,
 63        ContextServerConfiguration,
 64    ),
 65}
 66
 67fn show_configure_mcp_modal(
 68    language_registry: Arc<LanguageRegistry>,
 69    manifest: &Arc<ExtensionManifest>,
 70    workspace: &mut Workspace,
 71    window: &mut Window,
 72    cx: &mut Context<'_, Workspace>,
 73) {
 74    if !window.is_window_active() {
 75        return;
 76    }
 77
 78    let context_server_store = workspace.project().read(cx).context_server_store();
 79    let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());
 80
 81    let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
 82    let worktree_store = workspace.project().read(cx).worktree_store();
 83    let configuration_tasks = manifest
 84        .context_servers
 85        .keys()
 86        .cloned()
 87        .map({
 88            |key| {
 89                let Some(descriptor) = registry.context_server_descriptor(&key) else {
 90                    return Task::ready(Configuration::NotAvailable(
 91                        ContextServerId(key),
 92                        repository.clone(),
 93                    ));
 94                };
 95                cx.spawn({
 96                    let repository_url = repository.clone();
 97                    let worktree_store = worktree_store.clone();
 98                    async move |_, cx| {
 99                        let configuration = descriptor
100                            .configuration(worktree_store.clone(), &cx)
101                            .await
102                            .context("Failed to resolve context server configuration")
103                            .log_err()
104                            .flatten();
105
106                        match configuration {
107                            Some(config) => Configuration::Required(
108                                ContextServerId(key),
109                                repository_url,
110                                config,
111                            ),
112                            None => {
113                                Configuration::NotAvailable(ContextServerId(key), repository_url)
114                            }
115                        }
116                    }
117                })
118            }
119        })
120        .collect::<Vec<_>>();
121
122    let jsonc_language = language_registry.language_for_name("jsonc");
123
124    cx.spawn_in(window, async move |this, cx| {
125        let configurations = futures::future::join_all(configuration_tasks).await;
126        let jsonc_language = jsonc_language.await.ok();
127
128        this.update_in(cx, |this, window, cx| {
129            let workspace = cx.entity().downgrade();
130            this.toggle_modal(window, cx, |window, cx| {
131                ConfigureContextServerModal::new(
132                    configurations.into_iter(),
133                    context_server_store,
134                    jsonc_language,
135                    language_registry,
136                    workspace,
137                    window,
138                    cx,
139                )
140            });
141        })
142    })
143    .detach();
144}