Factor tool definitions out of `assistant` (#21189)

Marshall Bowers created

This PR factors the tool definitions out of the `assistant` crate so
that they can be shared between `assistant` and `assistant2`.

`ToolWorkingSet` now lives in `assistant_tool`. The tool definitions
themselves live in `assistant_tools`, with the exception of the
`ContextServerTool`, which has been moved to the `context_server` crate.

As part of this refactoring I needed to extract the
`ContextServerSettings` to a separate `context_server_settings` crate so
that the `extension_host`β€”which is referenced by the `remote_server`β€”can
name the `ContextServerSettings` type without pulling in some undesired
dependencies.

Release Notes:

- N/A

Change summary

Cargo.lock                                                    | 42 +++
Cargo.toml                                                    |  8 
crates/assistant/Cargo.toml                                   |  2 
crates/assistant/src/assistant.rs                             | 12 
crates/assistant/src/assistant_panel.rs                       |  4 
crates/assistant/src/context.rs                               |  2 
crates/assistant/src/context/context_tests.rs                 |  2 
crates/assistant/src/context_store.rs                         | 19 
crates/assistant/src/slash_command/context_server_command.rs  | 12 
crates/assistant/src/tools.rs                                 |  2 
crates/assistant_tool/src/assistant_tool.rs                   |  4 
crates/assistant_tool/src/tool_working_set.rs                 |  6 
crates/assistant_tools/Cargo.toml                             | 22 +
crates/assistant_tools/LICENSE-GPL                            |  0 
crates/assistant_tools/src/assistant_tools.rs                 | 13 +
crates/assistant_tools/src/now_tool.rs                        |  0 
crates/collab/Cargo.toml                                      |  3 
crates/collab/src/tests/integration_tests.rs                  |  7 
crates/context_server/Cargo.toml                              |  9 
crates/context_server/LICENSE-GPL                             |  1 
crates/context_server/src/client.rs                           |  0 
crates/context_server/src/context_server.rs                   |  7 
crates/context_server/src/context_server_tool.rs              |  5 
crates/context_server/src/extension_context_server.rs         |  3 
crates/context_server/src/manager.rs                          | 56 ----
crates/context_server/src/protocol.rs                         |  0 
crates/context_server/src/registry.rs                         |  2 
crates/context_server/src/types.rs                            |  0 
crates/context_server_settings/Cargo.toml                     | 21 +
crates/context_server_settings/LICENSE-GPL                    |  1 
crates/context_server_settings/src/context_server_settings.rs | 61 +++++
crates/extension_host/Cargo.toml                              |  2 
crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs       |  2 
crates/zed/Cargo.toml                                         |  1 
crates/zed/src/main.rs                                        |  1 
35 files changed, 219 insertions(+), 113 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -383,7 +383,7 @@ dependencies = [
  "clock",
  "collections",
  "command_palette_hooks",
- "context_servers",
+ "context_server",
  "ctor",
  "db",
  "editor",
@@ -506,6 +506,20 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "assistant_tools"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "assistant_tool",
+ "chrono",
+ "gpui",
+ "schemars",
+ "serde",
+ "serde_json",
+ "workspace",
+]
+
 [[package]]
 name = "async-attributes"
 version = "1.1.2"
@@ -2613,6 +2627,7 @@ dependencies = [
  "anthropic",
  "anyhow",
  "assistant",
+ "assistant_tool",
  "async-stripe",
  "async-trait",
  "async-tungstenite 0.28.0",
@@ -2631,7 +2646,7 @@ dependencies = [
  "clock",
  "collab_ui",
  "collections",
- "context_servers",
+ "context_server",
  "ctor",
  "dashmap 6.1.0",
  "derive_more",
@@ -2874,12 +2889,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
 
 [[package]]
-name = "context_servers"
+name = "context_server"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "assistant_tool",
  "collections",
  "command_palette_hooks",
+ "context_server_settings",
  "extension",
  "futures 0.3.31",
  "gpui",
@@ -2887,13 +2904,27 @@ dependencies = [
  "parking_lot",
  "postage",
  "project",
- "schemars",
  "serde",
  "serde_json",
  "settings",
  "smol",
+ "ui",
  "url",
  "util",
+ "workspace",
+]
+
+[[package]]
+name = "context_server_settings"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "gpui",
+ "schemars",
+ "serde",
+ "serde_json",
+ "settings",
 ]
 
 [[package]]
@@ -4209,7 +4240,7 @@ dependencies = [
  "async-trait",
  "client",
  "collections",
- "context_servers",
+ "context_server_settings",
  "ctor",
  "env_logger 0.11.5",
  "extension",
@@ -15586,6 +15617,7 @@ dependencies = [
  "assets",
  "assistant",
  "assistant2",
+ "assistant_tools",
  "async-watch",
  "audio",
  "auto_update",

Cargo.toml πŸ”—

@@ -8,6 +8,7 @@ members = [
     "crates/assistant2",
     "crates/assistant_slash_command",
     "crates/assistant_tool",
+    "crates/assistant_tools",
     "crates/audio",
     "crates/auto_update",
     "crates/auto_update_ui",
@@ -22,7 +23,8 @@ members = [
     "crates/collections",
     "crates/command_palette",
     "crates/command_palette_hooks",
-    "crates/context_servers",
+    "crates/context_server",
+    "crates/context_server_settings",
     "crates/copilot",
     "crates/db",
     "crates/diagnostics",
@@ -191,6 +193,7 @@ assistant = { path = "crates/assistant" }
 assistant2 = { path = "crates/assistant2" }
 assistant_slash_command = { path = "crates/assistant_slash_command" }
 assistant_tool = { path = "crates/assistant_tool" }
+assistant_tools = { path = "crates/assistant_tools" }
 audio = { path = "crates/audio" }
 auto_update = { path = "crates/auto_update" }
 auto_update_ui = { path = "crates/auto_update_ui" }
@@ -205,7 +208,8 @@ collab_ui = { path = "crates/collab_ui" }
 collections = { path = "crates/collections" }
 command_palette = { path = "crates/command_palette" }
 command_palette_hooks = { path = "crates/command_palette_hooks" }
-context_servers = { path = "crates/context_servers" }
+context_server = { path = "crates/context_server" }
+context_server_settings = { path = "crates/context_server_settings" }
 copilot = { path = "crates/copilot" }
 db = { path = "crates/db" }
 diagnostics = { path = "crates/diagnostics" }

crates/assistant/Cargo.toml πŸ”—

@@ -33,7 +33,7 @@ client.workspace = true
 clock.workspace = true
 collections.workspace = true
 command_palette_hooks.workspace = true
-context_servers.workspace = true
+context_server.workspace = true
 db.workspace = true
 editor.workspace = true
 feature_flags.workspace = true

crates/assistant/src/assistant.rs πŸ”—

@@ -14,16 +14,12 @@ pub mod slash_command_settings;
 mod slash_command_working_set;
 mod streaming_diff;
 mod terminal_inline_assistant;
-mod tool_working_set;
-mod tools;
 
 use crate::slash_command::project_command::ProjectSlashCommandFeatureFlag;
 pub use crate::slash_command_working_set::{SlashCommandId, SlashCommandWorkingSet};
-pub use crate::tool_working_set::{ToolId, ToolWorkingSet};
 pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
 use assistant_settings::AssistantSettings;
 use assistant_slash_command::SlashCommandRegistry;
-use assistant_tool::ToolRegistry;
 use client::{proto, Client};
 use command_palette_hooks::CommandPaletteFilter;
 pub use context::*;
@@ -246,7 +242,7 @@ pub fn init(
     assistant_slash_command::init(cx);
     assistant_tool::init(cx);
     assistant_panel::init(cx);
-    context_servers::init(cx);
+    context_server::init(cx);
 
     let prompt_builder = prompts::PromptBuilder::new(Some(PromptLoadingParams {
         fs: fs.clone(),
@@ -259,7 +255,6 @@ pub fn init(
     .map(Arc::new)
     .unwrap_or_else(|| Arc::new(prompts::PromptBuilder::new(None).unwrap()));
     register_slash_commands(Some(prompt_builder.clone()), cx);
-    register_tools(cx);
     inline_assistant::init(
         fs.clone(),
         prompt_builder.clone(),
@@ -423,11 +418,6 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
     }
 }
 
-fn register_tools(cx: &mut AppContext) {
-    let tool_registry = ToolRegistry::global(cx);
-    tool_registry.register_tool(tools::now_tool::NowTool);
-}
-
 pub fn humanize_token_count(count: usize) -> String {
     match count {
         0..=999 => count.to_string(),

crates/assistant/src/assistant_panel.rs πŸ”—

@@ -1,6 +1,5 @@
 use crate::slash_command::file_command::codeblock_fence_for_path;
 use crate::slash_command_working_set::SlashCommandWorkingSet;
-use crate::ToolWorkingSet;
 use crate::{
     assistant_settings::{AssistantDockPosition, AssistantSettings},
     humanize_token_count,
@@ -23,6 +22,7 @@ use crate::{
 };
 use anyhow::Result;
 use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
+use assistant_tool::ToolWorkingSet;
 use client::{proto, zed_urls, Client, Status};
 use collections::{hash_map, BTreeSet, HashMap, HashSet};
 use editor::{
@@ -1316,7 +1316,7 @@ impl AssistantPanel {
 
     fn restart_context_servers(
         workspace: &mut Workspace,
-        _action: &context_servers::Restart,
+        _action: &context_server::Restart,
         cx: &mut ViewContext<Workspace>,
     ) {
         let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {

crates/assistant/src/context.rs πŸ”—

@@ -2,7 +2,6 @@
 mod context_tests;
 
 use crate::slash_command_working_set::SlashCommandWorkingSet;
-use crate::ToolWorkingSet;
 use crate::{
     prompts::PromptBuilder,
     slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
@@ -12,6 +11,7 @@ use anyhow::{anyhow, Context as _, Result};
 use assistant_slash_command::{
     SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult,
 };
+use assistant_tool::ToolWorkingSet;
 use client::{self, proto, telemetry::Telemetry};
 use clock::ReplicaId;
 use collections::{HashMap, HashSet};

crates/assistant/src/context/context_tests.rs πŸ”—

@@ -1,6 +1,5 @@
 use super::{AssistantEdit, MessageCacheMetadata};
 use crate::slash_command_working_set::SlashCommandWorkingSet;
-use crate::ToolWorkingSet;
 use crate::{
     assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
     Context, ContextEvent, ContextId, ContextOperation, InvokedSlashCommandId, MessageId,
@@ -11,6 +10,7 @@ use assistant_slash_command::{
     ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
     SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
 };
+use assistant_tool::ToolWorkingSet;
 use collections::{HashMap, HashSet};
 use fs::FakeFs;
 use futures::{

crates/assistant/src/context_store.rs πŸ”—

@@ -1,15 +1,16 @@
 use crate::slash_command::context_server_command;
+use crate::SlashCommandId;
 use crate::{
     prompts::PromptBuilder, slash_command_working_set::SlashCommandWorkingSet, Context,
     ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext, SavedContextMetadata,
 };
-use crate::{tools, SlashCommandId, ToolId, ToolWorkingSet};
 use anyhow::{anyhow, Context as _, Result};
+use assistant_tool::{ToolId, ToolWorkingSet};
 use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
 use clock::ReplicaId;
 use collections::HashMap;
-use context_servers::manager::ContextServerManager;
-use context_servers::ContextServerFactoryRegistry;
+use context_server::manager::ContextServerManager;
+use context_server::{ContextServerFactoryRegistry, ContextServerTool};
 use fs::Fs;
 use futures::StreamExt;
 use fuzzy::StringMatchCandidate;
@@ -808,13 +809,13 @@ impl ContextStore {
     fn handle_context_server_event(
         &mut self,
         context_server_manager: Model<ContextServerManager>,
-        event: &context_servers::manager::Event,
+        event: &context_server::manager::Event,
         cx: &mut ModelContext<Self>,
     ) {
         let slash_command_working_set = self.slash_commands.clone();
         let tool_working_set = self.tools.clone();
         match event {
-            context_servers::manager::Event::ServerStarted { server_id } => {
+            context_server::manager::Event::ServerStarted { server_id } => {
                 if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
                     let context_server_manager = context_server_manager.clone();
                     cx.spawn({
@@ -825,7 +826,7 @@ impl ContextStore {
                                 return;
                             };
 
-                            if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
+                            if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
                                 if let Some(prompts) = protocol.list_prompts().await.log_err() {
                                     let slash_command_ids = prompts
                                         .into_iter()
@@ -853,12 +854,12 @@ impl ContextStore {
                                 }
                             }
 
-                            if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
+                            if protocol.capable(context_server::protocol::ServerCapability::Tools) {
                                 if let Some(tools) = protocol.list_tools().await.log_err() {
                                     let tool_ids = tools.tools.into_iter().map(|tool| {
                                         log::info!("registering context server tool: {:?}", tool.name);
                                         tool_working_set.insert(
-                                            Arc::new(tools::context_server_tool::ContextServerTool::new(
+                                            Arc::new(ContextServerTool::new(
                                                 context_server_manager.clone(),
                                                 server.id(),
                                                 tool,
@@ -880,7 +881,7 @@ impl ContextStore {
                     .detach();
                 }
             }
-            context_servers::manager::Event::ServerStopped { server_id } => {
+            context_server::manager::Event::ServerStopped { server_id } => {
                 if let Some(slash_command_ids) =
                     self.context_server_slash_command_ids.remove(server_id)
                 {

crates/assistant/src/slash_command/context_server_command.rs πŸ”—

@@ -4,7 +4,7 @@ use assistant_slash_command::{
     SlashCommandOutputSection, SlashCommandResult,
 };
 use collections::HashMap;
-use context_servers::{
+use context_server::{
     manager::{ContextServer, ContextServerManager},
     types::Prompt,
 };
@@ -95,9 +95,9 @@ impl SlashCommand for ContextServerSlashCommand {
 
                 let completion_result = protocol
                     .completion(
-                        context_servers::types::CompletionReference::Prompt(
-                            context_servers::types::PromptReference {
-                                r#type: context_servers::types::PromptReferenceType::Prompt,
+                        context_server::types::CompletionReference::Prompt(
+                            context_server::types::PromptReference {
+                                r#type: context_server::types::PromptReferenceType::Prompt,
                                 name: prompt_name,
                             },
                         ),
@@ -152,7 +152,7 @@ impl SlashCommand for ContextServerSlashCommand {
                 if result
                     .messages
                     .iter()
-                    .any(|msg| !matches!(msg.role, context_servers::types::Role::User))
+                    .any(|msg| !matches!(msg.role, context_server::types::Role::User))
                 {
                     return Err(anyhow!(
                         "Prompt contains non-user roles, which is not supported"
@@ -164,7 +164,7 @@ impl SlashCommand for ContextServerSlashCommand {
                     .messages
                     .into_iter()
                     .filter_map(|msg| match msg.content {
-                        context_servers::types::MessageContent::Text { text } => Some(text),
+                        context_server::types::MessageContent::Text { text } => Some(text),
                         _ => None,
                     })
                     .collect::<Vec<String>>()

crates/assistant_tool/src/assistant_tool.rs πŸ”—

@@ -1,4 +1,5 @@
 mod tool_registry;
+mod tool_working_set;
 
 use std::sync::Arc;
 
@@ -6,7 +7,8 @@ use anyhow::Result;
 use gpui::{AppContext, Task, WeakView, WindowContext};
 use workspace::Workspace;
 
-pub use tool_registry::*;
+pub use crate::tool_registry::*;
+pub use crate::tool_working_set::*;
 
 pub fn init(cx: &mut AppContext) {
     ToolRegistry::default_global(cx);

crates/assistant/src/tool_working_set.rs β†’ crates/assistant_tool/src/tool_working_set.rs πŸ”—

@@ -1,8 +1,10 @@
-use assistant_tool::{Tool, ToolRegistry};
+use std::sync::Arc;
+
 use collections::HashMap;
 use gpui::AppContext;
 use parking_lot::Mutex;
-use std::sync::Arc;
+
+use crate::{Tool, ToolRegistry};
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
 pub struct ToolId(usize);

crates/assistant_tools/Cargo.toml πŸ”—

@@ -0,0 +1,22 @@
+[package]
+name = "assistant_tools"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/assistant_tools.rs"
+
+[dependencies]
+anyhow.workspace = true
+assistant_tool.workspace = true
+chrono.workspace = true
+gpui.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+workspace.workspace = true

crates/assistant_tools/src/assistant_tools.rs πŸ”—

@@ -0,0 +1,13 @@
+mod now_tool;
+
+use assistant_tool::ToolRegistry;
+use gpui::AppContext;
+
+use crate::now_tool::NowTool;
+
+pub fn init(cx: &mut AppContext) {
+    assistant_tool::init(cx);
+
+    let registry = ToolRegistry::global(cx);
+    registry.register_tool(NowTool);
+}

crates/collab/Cargo.toml πŸ”—

@@ -79,7 +79,8 @@ uuid.workspace = true
 
 [dev-dependencies]
 assistant = { workspace = true, features = ["test-support"] }
-context_servers.workspace = true
+assistant_tool.workspace = true
+context_server.workspace = true
 async-trait.workspace = true
 audio.workspace = true
 call = { workspace = true, features = ["test-support"] }

crates/collab/src/tests/integration_tests.rs πŸ”—

@@ -6,7 +6,8 @@ use crate::{
     },
 };
 use anyhow::{anyhow, Result};
-use assistant::{ContextStore, PromptBuilder, SlashCommandWorkingSet, ToolWorkingSet};
+use assistant::{ContextStore, PromptBuilder, SlashCommandWorkingSet};
+use assistant_tool::ToolWorkingSet;
 use call::{room, ActiveCall, ParticipantLocation, Room};
 use client::{User, RECEIVE_TIMEOUT};
 use collections::{HashMap, HashSet};
@@ -6486,8 +6487,8 @@ async fn test_context_collaboration_with_reconnect(
         assert_eq!(project.collaborators().len(), 1);
     });
 
-    cx_a.update(context_servers::init);
-    cx_b.update(context_servers::init);
+    cx_a.update(context_server::init);
+    cx_b.update(context_server::init);
     let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
     let context_store_a = cx_a
         .update(|cx| {

crates/context_servers/Cargo.toml β†’ crates/context_server/Cargo.toml πŸ”—

@@ -1,5 +1,5 @@
 [package]
-name = "context_servers"
+name = "context_server"
 version = "0.1.0"
 edition = "2021"
 publish = false
@@ -9,12 +9,14 @@ license = "GPL-3.0-or-later"
 workspace = true
 
 [lib]
-path = "src/context_servers.rs"
+path = "src/context_server.rs"
 
 [dependencies]
 anyhow.workspace = true
+assistant_tool.workspace = true
 collections.workspace = true
 command_palette_hooks.workspace = true
+context_server_settings.workspace = true
 extension.workspace = true
 futures.workspace = true
 gpui.workspace = true
@@ -22,10 +24,11 @@ log.workspace = true
 parking_lot.workspace = true
 postage.workspace = true
 project.workspace = true
-schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true
 smol.workspace = true
+ui.workspace = true
 url = { workspace = true, features = ["serde"] }
 util.workspace = true
+workspace.workspace = true

crates/context_servers/src/context_servers.rs β†’ crates/context_server/src/context_server.rs πŸ”—

@@ -1,4 +1,5 @@
 pub mod client;
+mod context_server_tool;
 mod extension_context_server;
 pub mod manager;
 pub mod protocol;
@@ -6,10 +7,10 @@ mod registry;
 pub mod types;
 
 use command_palette_hooks::CommandPaletteFilter;
+pub use context_server_settings::{ContextServerSettings, ServerCommand, ServerConfig};
 use gpui::{actions, AppContext};
-use settings::Settings;
 
-use crate::manager::ContextServerSettings;
+pub use crate::context_server_tool::ContextServerTool;
 pub use crate::registry::ContextServerFactoryRegistry;
 
 actions!(context_servers, [Restart]);
@@ -18,7 +19,7 @@ actions!(context_servers, [Restart]);
 pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
 
 pub fn init(cx: &mut AppContext) {
-    ContextServerSettings::register(cx);
+    context_server_settings::init(cx);
     ContextServerFactoryRegistry::default_global(cx);
     extension_context_server::init(cx);
 

crates/assistant/src/tools/context_server_tool.rs β†’ crates/context_server/src/context_server_tool.rs πŸ”—

@@ -2,10 +2,11 @@ use std::sync::Arc;
 
 use anyhow::{anyhow, bail};
 use assistant_tool::Tool;
-use context_servers::manager::ContextServerManager;
-use context_servers::types;
 use gpui::{Model, Task};
 
+use crate::manager::ContextServerManager;
+use crate::types;
+
 pub struct ContextServerTool {
     server_manager: Model<ContextServerManager>,
     server_id: Arc<str>,

crates/context_servers/src/extension_context_server.rs β†’ crates/context_server/src/extension_context_server.rs πŸ”—

@@ -3,8 +3,7 @@ use std::sync::Arc;
 use extension::{Extension, ExtensionContextServerProxy, ExtensionHostProxy, ProjectDelegate};
 use gpui::{AppContext, Model};
 
-use crate::manager::ServerCommand;
-use crate::ContextServerFactoryRegistry;
+use crate::{ContextServerFactoryRegistry, ServerCommand};
 
 struct ExtensionProject {
     worktree_ids: Vec<u64>,

crates/context_servers/src/manager.rs β†’ crates/context_server/src/manager.rs πŸ”—

@@ -24,66 +24,16 @@ use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Subscription, Tas
 use log;
 use parking_lot::RwLock;
 use project::Project;
-use schemars::gen::SchemaGenerator;
-use schemars::schema::{InstanceType, Schema, SchemaObject};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources, SettingsStore};
+use settings::{Settings, SettingsStore};
 use util::ResultExt as _;
 
+use crate::{ContextServerSettings, ServerConfig};
+
 use crate::{
     client::{self, Client},
     types, ContextServerFactoryRegistry, CONTEXT_SERVERS_NAMESPACE,
 };
 
-#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
-pub struct ContextServerSettings {
-    /// Settings for context servers used in the Assistant.
-    #[serde(default)]
-    pub context_servers: HashMap<Arc<str>, ServerConfig>,
-}
-
-#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
-pub struct ServerConfig {
-    /// The command to run this context server.
-    ///
-    /// This will override the command set by an extension.
-    pub command: Option<ServerCommand>,
-    /// The settings for this context server.
-    ///
-    /// Consult the documentation for the context server to see what settings
-    /// are supported.
-    #[schemars(schema_with = "server_config_settings_json_schema")]
-    pub settings: Option<serde_json::Value>,
-}
-
-fn server_config_settings_json_schema(_generator: &mut SchemaGenerator) -> Schema {
-    Schema::Object(SchemaObject {
-        instance_type: Some(InstanceType::Object.into()),
-        ..Default::default()
-    })
-}
-
-#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
-pub struct ServerCommand {
-    pub path: String,
-    pub args: Vec<String>,
-    pub env: Option<HashMap<String, String>>,
-}
-
-impl Settings for ContextServerSettings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = Self;
-
-    fn load(
-        sources: SettingsSources<Self::FileContent>,
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        sources.json_merge()
-    }
-}
-
 pub struct ContextServer {
     pub id: Arc<str>,
     pub config: Arc<ServerConfig>,

crates/context_servers/src/registry.rs β†’ crates/context_server/src/registry.rs πŸ”—

@@ -5,7 +5,7 @@ use collections::HashMap;
 use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ReadGlobal, Task};
 use project::Project;
 
-use crate::manager::ServerCommand;
+use crate::ServerCommand;
 
 pub type ContextServerFactory = Arc<
     dyn Fn(Model<Project>, &AsyncAppContext) -> Task<Result<ServerCommand>> + Send + Sync + 'static,

crates/context_server_settings/Cargo.toml πŸ”—

@@ -0,0 +1,21 @@
+[package]
+name = "context_server_settings"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/context_server_settings.rs"
+
+[dependencies]
+anyhow.workspace = true
+collections.workspace = true
+gpui.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+settings.workspace = true

crates/context_server_settings/src/context_server_settings.rs πŸ”—

@@ -0,0 +1,61 @@
+use std::sync::Arc;
+
+use collections::HashMap;
+use gpui::AppContext;
+use schemars::gen::SchemaGenerator;
+use schemars::schema::{InstanceType, Schema, SchemaObject};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::{Settings, SettingsSources};
+
+pub fn init(cx: &mut AppContext) {
+    ContextServerSettings::register(cx);
+}
+
+#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
+pub struct ServerConfig {
+    /// The command to run this context server.
+    ///
+    /// This will override the command set by an extension.
+    pub command: Option<ServerCommand>,
+    /// The settings for this context server.
+    ///
+    /// Consult the documentation for the context server to see what settings
+    /// are supported.
+    #[schemars(schema_with = "server_config_settings_json_schema")]
+    pub settings: Option<serde_json::Value>,
+}
+
+fn server_config_settings_json_schema(_generator: &mut SchemaGenerator) -> Schema {
+    Schema::Object(SchemaObject {
+        instance_type: Some(InstanceType::Object.into()),
+        ..Default::default()
+    })
+}
+
+#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
+pub struct ServerCommand {
+    pub path: String,
+    pub args: Vec<String>,
+    pub env: Option<HashMap<String, String>>,
+}
+
+#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
+pub struct ContextServerSettings {
+    /// Settings for context servers used in the Assistant.
+    #[serde(default)]
+    pub context_servers: HashMap<Arc<str>, ServerConfig>,
+}
+
+impl Settings for ContextServerSettings {
+    const KEY: Option<&'static str> = None;
+
+    type FileContent = Self;
+
+    fn load(
+        sources: SettingsSources<Self::FileContent>,
+        _: &mut gpui::AppContext,
+    ) -> anyhow::Result<Self> {
+        sources.json_merge()
+    }
+}

crates/extension_host/Cargo.toml πŸ”—

@@ -22,7 +22,7 @@ async-tar.workspace = true
 async-trait.workspace = true
 client.workspace = true
 collections.workspace = true
-context_servers.workspace = true
+context_server_settings.workspace = true
 extension.workspace = true
 fs.workspace = true
 futures.workspace = true

crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs πŸ”—

@@ -7,7 +7,7 @@ use anyhow::{anyhow, bail, Context, Result};
 use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
 use async_trait::async_trait;
-use context_servers::manager::ContextServerSettings;
+use context_server_settings::ContextServerSettings;
 use extension::{
     ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
 };

crates/zed/Cargo.toml πŸ”—

@@ -20,6 +20,7 @@ anyhow.workspace = true
 assets.workspace = true
 assistant.workspace = true
 assistant2.workspace = true
+assistant_tools.workspace = true
 async-watch.workspace = true
 audio.workspace = true
 auto_update.workspace = true

crates/zed/src/main.rs πŸ”—

@@ -407,6 +407,7 @@ fn main() {
             cx,
         );
         assistant2::init(cx);
+        assistant_tools::init(cx);
         repl::init(
             app_state.fs.clone(),
             app_state.client.telemetry().clone(),