assistant2: Add tool lists for each context server (#27029)

This PR updates the list of context servers with the ability to view the
tools provided by the context server:

<img width="1394" alt="Screenshot 2025-03-18 at 5 53 05 PM"
src="https://github.com/user-attachments/assets/4ffe93dd-f9e9-44e7-877f-656ebf45a326"
/>

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-03-18 18:04:47 -04:00 committed by GitHub
parent 985ac4e5f2
commit a2ae6a1c77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 81 additions and 13 deletions

View file

@ -1,22 +1,26 @@
use std::sync::Arc; use std::sync::Arc;
use assistant_tool::{ToolSource, ToolWorkingSet};
use collections::HashMap; use collections::HashMap;
use context_server::manager::ContextServerManager; use context_server::manager::ContextServerManager;
use gpui::{Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription}; use gpui::{Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry}; use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, Divider, DividerColor, ElevationIndex, Indicator}; use ui::{prelude::*, Disclosure, Divider, DividerColor, ElevationIndex, Indicator};
use zed_actions::assistant::DeployPromptLibrary; use zed_actions::assistant::DeployPromptLibrary;
pub struct AssistantConfiguration { pub struct AssistantConfiguration {
focus_handle: FocusHandle, focus_handle: FocusHandle,
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>, configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
context_server_manager: Entity<ContextServerManager>, context_server_manager: Entity<ContextServerManager>,
expanded_context_server_tools: HashMap<Arc<str>, bool>,
tools: Arc<ToolWorkingSet>,
_registry_subscription: Subscription, _registry_subscription: Subscription,
} }
impl AssistantConfiguration { impl AssistantConfiguration {
pub fn new( pub fn new(
context_server_manager: Entity<ContextServerManager>, context_server_manager: Entity<ContextServerManager>,
tools: Arc<ToolWorkingSet>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
@ -43,6 +47,8 @@ impl AssistantConfiguration {
focus_handle, focus_handle,
configuration_views_by_provider: HashMap::default(), configuration_views_by_provider: HashMap::default(),
context_server_manager, context_server_manager,
expanded_context_server_tools: HashMap::default(),
tools,
_registry_subscription: registry_subscription, _registry_subscription: registry_subscription,
}; };
this.build_provider_configuration_views(window, cx); this.build_provider_configuration_views(window, cx);
@ -153,6 +159,8 @@ impl AssistantConfiguration {
fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement { fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let context_servers = self.context_server_manager.read(cx).servers().clone(); let context_servers = self.context_server_manager.read(cx).servers().clone();
let tools_by_source = self.tools.tools_by_source(cx);
let empty = Vec::new();
const SUBHEADING: &str = "Connect to context servers via the Model Context Protocol either via Zed extensions or directly."; const SUBHEADING: &str = "Connect to context servers via the Model Context Protocol either via Zed extensions or directly.";
@ -169,21 +177,75 @@ impl AssistantConfiguration {
) )
.children(context_servers.into_iter().map(|context_server| { .children(context_servers.into_iter().map(|context_server| {
let is_running = context_server.client().is_some(); let is_running = context_server.client().is_some();
let are_tools_expanded = self
.expanded_context_server_tools
.get(&context_server.id())
.copied()
.unwrap_or_default();
h_flex() let tools = tools_by_source
.gap_2() .get(&ToolSource::ContextServer {
.px_2() id: context_server.id().into(),
.py_1() })
.unwrap_or_else(|| &empty);
let tool_count = tools.len();
v_flex()
.border_1() .border_1()
.rounded_sm() .rounded_sm()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.child(Indicator::dot().color(if is_running { .child(
Color::Success h_flex()
} else { .gap_2()
Color::Error .px_2()
})) .py_1()
.child(Label::new(context_server.id())) .when(are_tools_expanded, |element| {
element
.border_b_1()
.border_color(cx.theme().colors().border)
})
.child(
Disclosure::new("tool-list-disclosure", are_tools_expanded)
.on_click(cx.listener({
let context_server_id = context_server.id();
move |this, _event, _window, _cx| {
let is_open = this
.expanded_context_server_tools
.entry(context_server_id.clone())
.or_insert(false);
*is_open = !*is_open;
}
})),
)
.child(Indicator::dot().color(if is_running {
Color::Success
} else {
Color::Error
}))
.child(Label::new(context_server.id()))
.child(Label::new(format!("{tool_count} tools")).color(Color::Muted)),
)
.map(|parent| {
if !are_tools_expanded {
return parent;
}
parent.child(v_flex().children(tools.into_iter().enumerate().map(
|(ix, tool)| {
h_flex()
.px_2()
.py_1()
.when(ix < tool_count - 1, |element| {
element
.border_b_1()
.border_color(cx.theme().colors().border)
})
.child(Label::new(tool.name()))
},
)))
})
})) }))
} }
} }

View file

@ -416,10 +416,12 @@ impl AssistantPanel {
pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) { pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let context_server_manager = self.thread_store.read(cx).context_server_manager(); let context_server_manager = self.thread_store.read(cx).context_server_manager();
let tools = self.thread_store.read(cx).tools();
self.active_view = ActiveView::Configuration; self.active_view = ActiveView::Configuration;
self.configuration = self.configuration = Some(
Some(cx.new(|cx| AssistantConfiguration::new(context_server_manager, window, cx))); cx.new(|cx| AssistantConfiguration::new(context_server_manager, tools, window, cx)),
);
if let Some(configuration) = self.configuration.as_ref() { if let Some(configuration) = self.configuration.as_ref() {
self.configuration_subscription = Some(cx.subscribe_in( self.configuration_subscription = Some(cx.subscribe_in(

View file

@ -69,6 +69,10 @@ impl ThreadStore {
self.context_server_manager.clone() self.context_server_manager.clone()
} }
pub fn tools(&self) -> Arc<ToolWorkingSet> {
self.tools.clone()
}
/// Returns the number of threads. /// Returns the number of threads.
pub fn thread_count(&self) -> usize { pub fn thread_count(&self) -> usize {
self.threads.len() self.threads.len()