context servers: Show configuration modal when extension is installed (#29309)
WIP Release Notes: - N/A --------- Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Co-authored-by: Danilo Leal <daniloleal09@gmail.com> Co-authored-by: Marshall Bowers <git@maxdeviant.com> Co-authored-by: Cole Miller <m@cole-miller.net> Co-authored-by: Antonio Scandurra <me@as-cii.com> Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
This commit is contained in:
parent
91074731b0
commit
a6a94f79b5
150
Cargo.lock
generated
150
Cargo.lock
generated
@ -68,6 +68,7 @@ dependencies = [
|
|||||||
"convert_case 0.8.0",
|
"convert_case 0.8.0",
|
||||||
"db",
|
"db",
|
||||||
"editor",
|
"editor",
|
||||||
|
"extension",
|
||||||
"feature_flags",
|
"feature_flags",
|
||||||
"file_icons",
|
"file_icons",
|
||||||
"fs",
|
"fs",
|
||||||
@ -81,6 +82,7 @@ dependencies = [
|
|||||||
"indexmap",
|
"indexmap",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
|
"jsonschema",
|
||||||
"language",
|
"language",
|
||||||
"language_model",
|
"language_model",
|
||||||
"language_model_selector",
|
"language_model_selector",
|
||||||
@ -90,6 +92,7 @@ dependencies = [
|
|||||||
"markdown",
|
"markdown",
|
||||||
"menu",
|
"menu",
|
||||||
"multi_buffer",
|
"multi_buffer",
|
||||||
|
"notifications",
|
||||||
"ordered-float 2.10.1",
|
"ordered-float 2.10.1",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"paths",
|
"paths",
|
||||||
@ -106,6 +109,7 @@ dependencies = [
|
|||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_json_lenient",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
@ -148,7 +152,9 @@ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"const-random",
|
"const-random",
|
||||||
|
"getrandom 0.2.15",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"serde",
|
||||||
"version_check",
|
"version_check",
|
||||||
"zerocopy 0.7.35",
|
"zerocopy 0.7.35",
|
||||||
]
|
]
|
||||||
@ -2186,6 +2192,12 @@ dependencies = [
|
|||||||
"piper",
|
"piper",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "borrow-or-share"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "borsh"
|
name = "borsh"
|
||||||
version = "1.5.7"
|
version = "1.5.7"
|
||||||
@ -2301,6 +2313,12 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytecount"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.22.0"
|
version = "1.22.0"
|
||||||
@ -4779,6 +4797,15 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email_address"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embed-resource"
|
name = "embed-resource"
|
||||||
version = "3.0.2"
|
version = "3.0.2"
|
||||||
@ -5194,6 +5221,7 @@ dependencies = [
|
|||||||
"collections",
|
"collections",
|
||||||
"db",
|
"db",
|
||||||
"editor",
|
"editor",
|
||||||
|
"extension",
|
||||||
"extension_host",
|
"extension_host",
|
||||||
"fs",
|
"fs",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
@ -5426,6 +5454,17 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
|
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fluent-uri"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5"
|
||||||
|
dependencies = [
|
||||||
|
"borrow-or-share",
|
||||||
|
"ref-cast",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -5580,6 +5619,16 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fraction"
|
||||||
|
version = "0.15.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"num",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "freetype-sys"
|
name = "freetype-sys"
|
||||||
version = "0.20.1"
|
version = "0.20.1"
|
||||||
@ -7583,6 +7632,33 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonschema"
|
||||||
|
version = "0.30.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d"
|
||||||
|
dependencies = [
|
||||||
|
"ahash 0.8.11",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bytecount",
|
||||||
|
"email_address",
|
||||||
|
"fancy-regex 0.14.0",
|
||||||
|
"fraction",
|
||||||
|
"idna",
|
||||||
|
"itoa",
|
||||||
|
"num-cmp",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"referencing",
|
||||||
|
"regex",
|
||||||
|
"regex-syntax 0.8.5",
|
||||||
|
"reqwest 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"uuid-simd",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonwebtoken"
|
name = "jsonwebtoken"
|
||||||
version = "9.3.1"
|
version = "9.3.1"
|
||||||
@ -8267,7 +8343,7 @@ dependencies = [
|
|||||||
"prost 0.9.0",
|
"prost 0.9.0",
|
||||||
"prost-build 0.9.0",
|
"prost-build 0.9.0",
|
||||||
"prost-types 0.9.0",
|
"prost-types 0.9.0",
|
||||||
"reqwest 0.12.15",
|
"reqwest 0.12.15 (git+https://github.com/zed-industries/reqwest.git?rev=951c770a32f1998d6e999cef3e59e0013e6c4415)",
|
||||||
"serde",
|
"serde",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
@ -9177,6 +9253,12 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-cmp"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-complex"
|
name = "num-complex"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@ -11770,6 +11852,20 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "referencing"
|
||||||
|
version = "0.30.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e"
|
||||||
|
dependencies = [
|
||||||
|
"ahash 0.8.11",
|
||||||
|
"fluent-uri",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -12039,6 +12135,43 @@ dependencies = [
|
|||||||
"winreg 0.50.0",
|
"winreg 0.50.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.12.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bytes 1.10.1",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body 1.0.1",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper 1.6.0",
|
||||||
|
"hyper-util",
|
||||||
|
"ipnet",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper 1.0.2",
|
||||||
|
"tokio",
|
||||||
|
"tower 0.5.2",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"windows-registry 0.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.15"
|
version = "0.12.15"
|
||||||
@ -12099,7 +12232,7 @@ dependencies = [
|
|||||||
"http_client_tls",
|
"http_client_tls",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest 0.12.15",
|
"reqwest 0.12.15 (git+https://github.com/zed-industries/reqwest.git?rev=951c770a32f1998d6e999cef3e59e0013e6c4415)",
|
||||||
"serde",
|
"serde",
|
||||||
"smol",
|
"smol",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -15950,6 +16083,17 @@ dependencies = [
|
|||||||
"sha1_smol",
|
"sha1_smol",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid-simd"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
|
||||||
|
dependencies = [
|
||||||
|
"outref",
|
||||||
|
"uuid",
|
||||||
|
"vsimd",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "v_frame"
|
name = "v_frame"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
@ -18046,6 +18190,7 @@ dependencies = [
|
|||||||
"hmac",
|
"hmac",
|
||||||
"hyper 0.14.32",
|
"hyper 0.14.32",
|
||||||
"hyper-rustls 0.27.5",
|
"hyper-rustls 0.27.5",
|
||||||
|
"idna",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"inout",
|
"inout",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
@ -18069,6 +18214,7 @@ dependencies = [
|
|||||||
"num-bigint-dig",
|
"num-bigint-dig",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-iter",
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"object",
|
"object",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -461,6 +461,7 @@ indexmap = { version = "2.7.0", features = ["serde"] }
|
|||||||
indoc = "2"
|
indoc = "2"
|
||||||
inventory = "0.3.19"
|
inventory = "0.3.19"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
jsonschema = "0.30.0"
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
|
@ -962,5 +962,13 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "menu::Cancel"
|
"escape": "menu::Cancel"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "ConfigureContextServerModal > Editor",
|
||||||
|
"bindings": {
|
||||||
|
"escape": "menu::Cancel",
|
||||||
|
"enter": "editor::Newline",
|
||||||
|
"ctrl-enter": "menu::Confirm"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1068,5 +1068,14 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"escape": "menu::Cancel"
|
"escape": "menu::Cancel"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "ConfigureContextServerModal > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"escape": "menu::Cancel",
|
||||||
|
"enter": "editor::Newline",
|
||||||
|
"cmd-enter": "menu::Confirm"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -35,6 +35,7 @@ context_server.workspace = true
|
|||||||
convert_case.workspace = true
|
convert_case.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
|
extension.workspace = true
|
||||||
feature_flags.workspace = true
|
feature_flags.workspace = true
|
||||||
file_icons.workspace = true
|
file_icons.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
@ -47,6 +48,7 @@ html_to_markdown.workspace = true
|
|||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
|
jsonschema.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
language_model_selector.workspace = true
|
language_model_selector.workspace = true
|
||||||
@ -56,6 +58,7 @@ lsp.workspace = true
|
|||||||
markdown.workspace = true
|
markdown.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
multi_buffer.workspace = true
|
multi_buffer.workspace = true
|
||||||
|
notifications.workspace = true
|
||||||
ordered-float.workspace = true
|
ordered-float.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
@ -71,6 +74,7 @@ rope.workspace = true
|
|||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
serde_json_lenient.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
@ -6,6 +6,7 @@ mod assistant_panel;
|
|||||||
mod buffer_codegen;
|
mod buffer_codegen;
|
||||||
mod context;
|
mod context;
|
||||||
mod context_picker;
|
mod context_picker;
|
||||||
|
mod context_server_configuration;
|
||||||
mod context_store;
|
mod context_store;
|
||||||
mod context_strip;
|
mod context_strip;
|
||||||
mod history_store;
|
mod history_store;
|
||||||
@ -30,6 +31,7 @@ use command_palette_hooks::CommandPaletteFilter;
|
|||||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, actions, impl_actions};
|
use gpui::{App, actions, impl_actions};
|
||||||
|
use language::LanguageRegistry;
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -107,11 +109,13 @@ pub fn init(
|
|||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
|
language_registry: Arc<LanguageRegistry>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
AssistantSettings::register(cx);
|
AssistantSettings::register(cx);
|
||||||
thread_store::init(cx);
|
thread_store::init(cx);
|
||||||
assistant_panel::init(cx);
|
assistant_panel::init(cx);
|
||||||
|
context_server_configuration::init(language_registry, cx);
|
||||||
|
|
||||||
inline_assistant::init(
|
inline_assistant::init(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
mod add_context_server_modal;
|
mod add_context_server_modal;
|
||||||
|
mod configure_context_server_modal;
|
||||||
mod manage_profiles_modal;
|
mod manage_profiles_modal;
|
||||||
mod tool_picker;
|
mod tool_picker;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::{ContextServer, ContextServerManager, ContextServerStatus};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription,
|
Action, Animation, AnimationExt as _, AnyView, App, Entity, EventEmitter, FocusHandle,
|
||||||
|
Focusable, ScrollHandle, Subscription, pulsating_between,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||||
use settings::{Settings, update_settings_file};
|
use settings::{Settings, update_settings_file};
|
||||||
@ -22,6 +24,7 @@ use util::ResultExt as _;
|
|||||||
use zed_actions::ExtensionCategoryFilter;
|
use zed_actions::ExtensionCategoryFilter;
|
||||||
|
|
||||||
pub(crate) use add_context_server_modal::AddContextServerModal;
|
pub(crate) use add_context_server_modal::AddContextServerModal;
|
||||||
|
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
|
||||||
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
||||||
|
|
||||||
use crate::AddContextServer;
|
use crate::AddContextServer;
|
||||||
@ -256,8 +259,6 @@ 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).all_servers().clone();
|
let context_servers = self.context_server_manager.read(cx).all_servers().clone();
|
||||||
let tools_by_source = self.tools.read(cx).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.";
|
||||||
|
|
||||||
@ -272,136 +273,11 @@ impl AssistantConfiguration {
|
|||||||
.child(Headline::new("Model Context Protocol (MCP) Servers"))
|
.child(Headline::new("Model Context Protocol (MCP) Servers"))
|
||||||
.child(Label::new(SUBHEADING).color(Color::Muted)),
|
.child(Label::new(SUBHEADING).color(Color::Muted)),
|
||||||
)
|
)
|
||||||
.children(context_servers.into_iter().map(|context_server| {
|
.children(
|
||||||
let is_running = context_server.client().is_some();
|
context_servers
|
||||||
let are_tools_expanded = self
|
.into_iter()
|
||||||
.expanded_context_server_tools
|
.map(|context_server| self.render_context_server(context_server, cx)),
|
||||||
.get(&context_server.id())
|
)
|
||||||
.copied()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let tools = tools_by_source
|
|
||||||
.get(&ToolSource::ContextServer {
|
|
||||||
id: context_server.id().into(),
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| &empty);
|
|
||||||
let tool_count = tools.len();
|
|
||||||
|
|
||||||
v_flex()
|
|
||||||
.id(SharedString::from(context_server.id()))
|
|
||||||
.border_1()
|
|
||||||
.rounded_md()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.bg(cx.theme().colors().background.opacity(0.25))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.p_1()
|
|
||||||
.justify_between()
|
|
||||||
.when(are_tools_expanded && tool_count > 1, |element| {
|
|
||||||
element
|
|
||||||
.border_b_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Disclosure::new("tool-list-disclosure", are_tools_expanded)
|
|
||||||
.disabled(tool_count == 0)
|
|
||||||
.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)
|
|
||||||
.size(LabelSize::Small),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Switch::new("context-server-switch", is_running.into())
|
|
||||||
.color(SwitchColor::Accent)
|
|
||||||
.on_click({
|
|
||||||
let context_server_manager =
|
|
||||||
self.context_server_manager.clone();
|
|
||||||
let context_server = context_server.clone();
|
|
||||||
move |state, _window, cx| match state {
|
|
||||||
ToggleState::Unselected
|
|
||||||
| ToggleState::Indeterminate => {
|
|
||||||
context_server_manager.update(cx, |this, cx| {
|
|
||||||
this.stop_server(context_server.clone(), cx)
|
|
||||||
.log_err();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ToggleState::Selected => {
|
|
||||||
cx.spawn({
|
|
||||||
let context_server_manager =
|
|
||||||
context_server_manager.clone();
|
|
||||||
let context_server = context_server.clone();
|
|
||||||
async move |cx| {
|
|
||||||
if let Some(start_server_task) =
|
|
||||||
context_server_manager
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.start_server(
|
|
||||||
context_server,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.log_err()
|
|
||||||
{
|
|
||||||
start_server_task.await.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map(|parent| {
|
|
||||||
if !are_tools_expanded {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.child(v_flex().py_1p5().px_1().gap_1().children(
|
|
||||||
tools.into_iter().enumerate().map(|(ix, tool)| {
|
|
||||||
h_flex()
|
|
||||||
.id(("tool-item", ix))
|
|
||||||
.px_1()
|
|
||||||
.gap_2()
|
|
||||||
.justify_between()
|
|
||||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
|
||||||
.rounded_sm()
|
|
||||||
.child(
|
|
||||||
Label::new(tool.name())
|
|
||||||
.buffer_font(cx)
|
|
||||||
.size(LabelSize::Small),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::Info)
|
|
||||||
.size(IconSize::Small)
|
|
||||||
.color(Color::Ignored),
|
|
||||||
)
|
|
||||||
.tooltip(Tooltip::text(tool.description()))
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
@ -447,6 +323,190 @@ impl AssistantConfiguration {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_context_server(
|
||||||
|
&self,
|
||||||
|
context_server: Arc<ContextServer>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl use<> + IntoElement {
|
||||||
|
let tools_by_source = self.tools.read(cx).tools_by_source(cx);
|
||||||
|
let server_status = self
|
||||||
|
.context_server_manager
|
||||||
|
.read(cx)
|
||||||
|
.status_for_server(&context_server.id());
|
||||||
|
|
||||||
|
let is_running = matches!(server_status, Some(ContextServerStatus::Running));
|
||||||
|
|
||||||
|
let error = if let Some(ContextServerStatus::Error(error)) = server_status.clone() {
|
||||||
|
Some(error)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let are_tools_expanded = self
|
||||||
|
.expanded_context_server_tools
|
||||||
|
.get(&context_server.id())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let tools = tools_by_source
|
||||||
|
.get(&ToolSource::ContextServer {
|
||||||
|
id: context_server.id().into(),
|
||||||
|
})
|
||||||
|
.map_or([].as_slice(), |tools| tools.as_slice());
|
||||||
|
let tool_count = tools.len();
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.id(SharedString::from(context_server.id()))
|
||||||
|
.border_1()
|
||||||
|
.rounded_md()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.bg(cx.theme().colors().background.opacity(0.25))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.p_1()
|
||||||
|
.justify_between()
|
||||||
|
.when(are_tools_expanded && tool_count > 1, |element| {
|
||||||
|
element
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Disclosure::new(
|
||||||
|
"tool-list-disclosure",
|
||||||
|
are_tools_expanded || error.is_some(),
|
||||||
|
)
|
||||||
|
.disabled(tool_count == 0)
|
||||||
|
.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(match server_status {
|
||||||
|
Some(ContextServerStatus::Starting) => {
|
||||||
|
let color = Color::Success.color(cx);
|
||||||
|
Indicator::dot()
|
||||||
|
.color(Color::Success)
|
||||||
|
.with_animation(
|
||||||
|
SharedString::from(format!(
|
||||||
|
"{}-starting",
|
||||||
|
context_server.id(),
|
||||||
|
)),
|
||||||
|
Animation::new(Duration::from_secs(2))
|
||||||
|
.repeat()
|
||||||
|
.with_easing(pulsating_between(0.4, 1.)),
|
||||||
|
move |this, delta| {
|
||||||
|
this.color(color.alpha(delta).into())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
Some(ContextServerStatus::Running) => {
|
||||||
|
Indicator::dot().color(Color::Success).into_any_element()
|
||||||
|
}
|
||||||
|
Some(ContextServerStatus::Error(_)) => {
|
||||||
|
Indicator::dot().color(Color::Error).into_any_element()
|
||||||
|
}
|
||||||
|
None => Indicator::dot().color(Color::Muted).into_any_element(),
|
||||||
|
})
|
||||||
|
.child(Label::new(context_server.id()))
|
||||||
|
.when(is_running, |this| {
|
||||||
|
this.child(
|
||||||
|
Label::new(if tool_count == 1 {
|
||||||
|
SharedString::from("1 tool")
|
||||||
|
} else {
|
||||||
|
SharedString::from(format!("{} tools", tool_count))
|
||||||
|
})
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(LabelSize::Small),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Switch::new("context-server-switch", is_running.into())
|
||||||
|
.color(SwitchColor::Accent)
|
||||||
|
.on_click({
|
||||||
|
let context_server_manager = self.context_server_manager.clone();
|
||||||
|
let context_server = context_server.clone();
|
||||||
|
move |state, _window, cx| match state {
|
||||||
|
ToggleState::Unselected | ToggleState::Indeterminate => {
|
||||||
|
context_server_manager.update(cx, |this, cx| {
|
||||||
|
this.stop_server(context_server.clone(), cx).log_err();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ToggleState::Selected => {
|
||||||
|
cx.spawn({
|
||||||
|
let context_server_manager =
|
||||||
|
context_server_manager.clone();
|
||||||
|
let context_server = context_server.clone();
|
||||||
|
async move |cx| {
|
||||||
|
if let Some(start_server_task) =
|
||||||
|
context_server_manager
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.start_server(context_server, cx)
|
||||||
|
})
|
||||||
|
.log_err()
|
||||||
|
{
|
||||||
|
start_server_task.await.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|parent| {
|
||||||
|
if let Some(error) = error {
|
||||||
|
return parent.child(
|
||||||
|
div().py_1p5().px_2().child(
|
||||||
|
Label::new(error)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.buffer_font(cx)
|
||||||
|
.size(LabelSize::Small),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !are_tools_expanded || tools.is_empty() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.child(v_flex().py_1p5().px_1().gap_1().children(
|
||||||
|
tools.into_iter().enumerate().map(|(ix, tool)| {
|
||||||
|
h_flex()
|
||||||
|
.id(("tool-item", ix))
|
||||||
|
.px_1()
|
||||||
|
.gap_2()
|
||||||
|
.justify_between()
|
||||||
|
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||||
|
.rounded_sm()
|
||||||
|
.child(
|
||||||
|
Label::new(tool.name())
|
||||||
|
.buffer_font(cx)
|
||||||
|
.size(LabelSize::Small),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Info)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Ignored),
|
||||||
|
)
|
||||||
|
.tooltip(Tooltip::text(tool.description()))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AssistantConfiguration {
|
impl Render for AssistantConfiguration {
|
||||||
|
@ -0,0 +1,443 @@
|
|||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use context_server::manager::{ContextServerManager, ContextServerStatus};
|
||||||
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
|
use extension::ContextServerConfiguration;
|
||||||
|
use gpui::{
|
||||||
|
Animation, AnimationExt, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task,
|
||||||
|
TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, percentage,
|
||||||
|
};
|
||||||
|
use language::{Language, LanguageRegistry};
|
||||||
|
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||||
|
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||||
|
use settings::{Settings as _, update_settings_file};
|
||||||
|
use theme::ThemeSettings;
|
||||||
|
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, prelude::*};
|
||||||
|
use util::ResultExt;
|
||||||
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
|
pub(crate) struct ConfigureContextServerModal {
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
context_servers_to_setup: Vec<ConfigureContextServer>,
|
||||||
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfigureContextServer {
|
||||||
|
id: Arc<str>,
|
||||||
|
installation_instructions: Entity<markdown::Markdown>,
|
||||||
|
settings_validator: Option<jsonschema::Validator>,
|
||||||
|
settings_editor: Entity<Editor>,
|
||||||
|
last_error: Option<SharedString>,
|
||||||
|
waiting_for_context_server: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigureContextServerModal {
|
||||||
|
pub fn new(
|
||||||
|
configurations: impl Iterator<Item = (Arc<str>, ContextServerConfiguration)>,
|
||||||
|
jsonc_language: Option<Arc<Language>>,
|
||||||
|
context_server_manager: Entity<ContextServerManager>,
|
||||||
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let context_servers_to_setup = configurations
|
||||||
|
.map(|(id, manifest)| {
|
||||||
|
let jsonc_language = jsonc_language.clone();
|
||||||
|
let settings_validator = jsonschema::validator_for(&manifest.settings_schema)
|
||||||
|
.context("Failed to load JSON schema for context server settings")
|
||||||
|
.log_err();
|
||||||
|
ConfigureContextServer {
|
||||||
|
id: id.clone(),
|
||||||
|
installation_instructions: cx.new(|cx| {
|
||||||
|
Markdown::new(
|
||||||
|
manifest.installation_instructions.clone().into(),
|
||||||
|
Some(language_registry.clone()),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
settings_validator,
|
||||||
|
settings_editor: cx.new(|cx| {
|
||||||
|
let mut editor = Editor::auto_height(16, window, cx);
|
||||||
|
editor.set_text(manifest.default_settings.trim(), window, cx);
|
||||||
|
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
|
||||||
|
buffer.update(cx, |buffer, cx| buffer.set_language(jsonc_language, cx))
|
||||||
|
}
|
||||||
|
editor
|
||||||
|
}),
|
||||||
|
waiting_for_context_server: false,
|
||||||
|
last_error: None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if context_servers_to_setup.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
workspace,
|
||||||
|
context_servers_to_setup,
|
||||||
|
context_server_manager,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigureContextServerModal {
|
||||||
|
pub fn confirm(&mut self, cx: &mut Context<Self>) {
|
||||||
|
if self.context_servers_to_setup.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let configuration = &mut self.context_servers_to_setup[0];
|
||||||
|
if configuration.waiting_for_context_server {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let settings_value = match serde_json_lenient::from_str::<serde_json::Value>(
|
||||||
|
&configuration.settings_editor.read(cx).text(cx),
|
||||||
|
) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => {
|
||||||
|
configuration.last_error = Some(error.to_string().into());
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(validator) = configuration.settings_validator.as_ref() {
|
||||||
|
if let Err(error) = validator.validate(&settings_value) {
|
||||||
|
configuration.last_error = Some(error.to_string().into());
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let id = configuration.id.clone();
|
||||||
|
|
||||||
|
let settings_changed = context_server::ContextServerSettings::get_global(cx)
|
||||||
|
.context_servers
|
||||||
|
.get(&id)
|
||||||
|
.map_or(true, |config| {
|
||||||
|
config.settings.as_ref() != Some(&settings_value)
|
||||||
|
});
|
||||||
|
|
||||||
|
let is_running = self.context_server_manager.read(cx).status_for_server(&id)
|
||||||
|
== Some(ContextServerStatus::Running);
|
||||||
|
|
||||||
|
if !settings_changed && is_running {
|
||||||
|
self.complete_setup(id, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.waiting_for_context_server = true;
|
||||||
|
|
||||||
|
let task = wait_for_context_server(&self.context_server_manager, id.clone(), cx);
|
||||||
|
cx.spawn({
|
||||||
|
let id = id.clone();
|
||||||
|
async move |this, cx| {
|
||||||
|
let result = task.await;
|
||||||
|
this.update(cx, |this, cx| match result {
|
||||||
|
Ok(_) => {
|
||||||
|
this.complete_setup(id, cx);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if let Some(configuration) = this.context_servers_to_setup.get_mut(0) {
|
||||||
|
configuration.last_error = Some(err.into());
|
||||||
|
configuration.waiting_for_context_server = false;
|
||||||
|
} else {
|
||||||
|
this.dismiss(cx);
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
// When we write the settings to the file, the context server will be restarted.
|
||||||
|
update_settings_file::<context_server::ContextServerSettings>(
|
||||||
|
workspace.read(cx).app_state().fs.clone(),
|
||||||
|
cx,
|
||||||
|
{
|
||||||
|
let id = id.clone();
|
||||||
|
|settings, _| {
|
||||||
|
if let Some(server_config) = settings.context_servers.get_mut(&id) {
|
||||||
|
server_config.settings = Some(settings_value);
|
||||||
|
} else {
|
||||||
|
settings.context_servers.insert(
|
||||||
|
id,
|
||||||
|
context_server::ServerConfig {
|
||||||
|
settings: Some(settings_value),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_setup(&mut self, id: Arc<str>, cx: &mut Context<Self>) {
|
||||||
|
self.context_servers_to_setup.remove(0);
|
||||||
|
cx.notify();
|
||||||
|
|
||||||
|
if !self.context_servers_to_setup.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.workspace
|
||||||
|
.update(cx, {
|
||||||
|
|workspace, cx| {
|
||||||
|
let status_toast = StatusToast::new(
|
||||||
|
format!("{} MCP configured successfully", id),
|
||||||
|
cx,
|
||||||
|
|this, _cx| {
|
||||||
|
this.icon(ToastIcon::new(IconName::DatabaseZap).color(Color::Muted))
|
||||||
|
.action("Dismiss", |_, _| {})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
workspace.toggle_status_toast(status_toast, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
|
||||||
|
self.dismiss(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismiss(&self, cx: &mut Context<Self>) {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for_context_server(
|
||||||
|
context_server_manager: &Entity<ContextServerManager>,
|
||||||
|
context_server_id: Arc<str>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Result<(), Arc<str>>> {
|
||||||
|
let (tx, rx) = futures::channel::oneshot::channel();
|
||||||
|
let tx = Arc::new(Mutex::new(Some(tx)));
|
||||||
|
|
||||||
|
let subscription = cx.subscribe(context_server_manager, move |_, event, _cx| match event {
|
||||||
|
context_server::manager::Event::ServerStatusChanged { server_id, status } => match status {
|
||||||
|
Some(ContextServerStatus::Running) => {
|
||||||
|
if server_id == &context_server_id {
|
||||||
|
if let Some(tx) = tx.lock().unwrap().take() {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(ContextServerStatus::Error(error)) => {
|
||||||
|
if server_id == &context_server_id {
|
||||||
|
if let Some(tx) = tx.lock().unwrap().take() {
|
||||||
|
let _ = tx.send(Err(error.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |_cx| {
|
||||||
|
let result = rx.await.unwrap();
|
||||||
|
drop(subscription);
|
||||||
|
result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ConfigureContextServerModal {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let Some(configuration) = self.context_servers_to_setup.first() else {
|
||||||
|
return div().child("No context servers to setup");
|
||||||
|
};
|
||||||
|
|
||||||
|
let focus_handle = self.focus_handle(cx);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.elevation_3(cx)
|
||||||
|
.w(rems(34.))
|
||||||
|
.key_context("ConfigureContextServerModal")
|
||||||
|
.on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx)))
|
||||||
|
.on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.dismiss(cx)))
|
||||||
|
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||||
|
this.focus_handle(cx).focus(window);
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
Modal::new("configure-context-server", None)
|
||||||
|
.header(ModalHeader::new().headline(format!("Configure {}", configuration.id)))
|
||||||
|
.section(
|
||||||
|
Section::new()
|
||||||
|
.child(div().py_2().child(MarkdownElement::new(
|
||||||
|
configuration.installation_instructions.clone(),
|
||||||
|
default_markdown_style(window, cx),
|
||||||
|
)))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.p_2()
|
||||||
|
.rounded_md()
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.gap_1()
|
||||||
|
.child({
|
||||||
|
let settings = ThemeSettings::get_global(cx);
|
||||||
|
let text_style = TextStyle {
|
||||||
|
color: cx.theme().colors().text,
|
||||||
|
font_family: settings.buffer_font.family.clone(),
|
||||||
|
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||||
|
font_size: settings.buffer_font_size(cx).into(),
|
||||||
|
font_weight: settings.buffer_font.weight,
|
||||||
|
line_height: relative(
|
||||||
|
settings.buffer_line_height.value(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
EditorElement::new(
|
||||||
|
&configuration.settings_editor,
|
||||||
|
EditorStyle {
|
||||||
|
background: cx.theme().colors().editor_background,
|
||||||
|
local_player: cx.theme().players().local(),
|
||||||
|
text: text_style,
|
||||||
|
syntax: cx.theme().syntax().clone(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(configuration.last_error.clone(), |this, error| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.px_2()
|
||||||
|
.py_1()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Warning)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Warning),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().w_full().child(
|
||||||
|
Label::new(error)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.when(configuration.waiting_for_context_server, |this| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::ArrowCircle)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Info)
|
||||||
|
.with_animation(
|
||||||
|
"arrow-circle",
|
||||||
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
|
|icon, delta| {
|
||||||
|
icon.transform(Transformation::rotate(
|
||||||
|
percentage(delta),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("Waiting for Context Server")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.footer(
|
||||||
|
ModalFooter::new().end_slot(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Button::new("cancel", "Cancel")
|
||||||
|
.key_binding(
|
||||||
|
KeyBinding::for_action_in(
|
||||||
|
&menu::Cancel,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
|
this.dismiss(cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("configure-server", "Configure MCP")
|
||||||
|
.disabled(configuration.waiting_for_context_server)
|
||||||
|
.key_binding(
|
||||||
|
KeyBinding::for_action_in(
|
||||||
|
&menu::Confirm,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|kb| kb.size(rems_from_px(12.))),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
|
this.confirm(cx)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||||
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
|
let colors = cx.theme().colors();
|
||||||
|
let mut text_style = window.text_style();
|
||||||
|
text_style.refine(&TextStyleRefinement {
|
||||||
|
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||||
|
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||||
|
font_features: Some(theme_settings.ui_font.features.clone()),
|
||||||
|
font_size: Some(TextSize::XSmall.rems(cx).into()),
|
||||||
|
color: Some(colors.text_muted),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
MarkdownStyle {
|
||||||
|
base_text_style: text_style.clone(),
|
||||||
|
selection_background_color: cx.theme().players().local().selection,
|
||||||
|
link: TextStyleRefinement {
|
||||||
|
background_color: Some(colors.editor_foreground.opacity(0.025)),
|
||||||
|
underline: Some(UnderlineStyle {
|
||||||
|
color: Some(colors.text_accent.opacity(0.5)),
|
||||||
|
thickness: px(1.),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModalView for ConfigureContextServerModal {}
|
||||||
|
impl EventEmitter<DismissEvent> for ConfigureContextServerModal {}
|
||||||
|
impl Focusable for ConfigureContextServerModal {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
if let Some(current) = self.context_servers_to_setup.first() {
|
||||||
|
current.settings_editor.read(cx).focus_handle(cx)
|
||||||
|
} else {
|
||||||
|
cx.focus_handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
crates/agent/src/context_server_configuration.rs
Normal file
120
crates/agent/src/context_server_configuration.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use context_server::ContextServerDescriptorRegistry;
|
||||||
|
use extension::ExtensionManifest;
|
||||||
|
use language::LanguageRegistry;
|
||||||
|
use ui::prelude::*;
|
||||||
|
use util::ResultExt;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::{AssistantPanel, assistant_configuration::ConfigureContextServerModal};
|
||||||
|
|
||||||
|
pub(crate) fn init(language_registry: Arc<LanguageRegistry>, cx: &mut App) {
|
||||||
|
cx.observe_new(move |_: &mut Workspace, window, cx| {
|
||||||
|
let Some(window) = window else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
|
||||||
|
cx.subscribe_in(extension_events, window, {
|
||||||
|
let language_registry = language_registry.clone();
|
||||||
|
move |workspace, _, event, window, cx| match event {
|
||||||
|
extension::Event::ExtensionInstalled(manifest) => {
|
||||||
|
show_configure_mcp_modal(
|
||||||
|
language_registry.clone(),
|
||||||
|
manifest,
|
||||||
|
workspace,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
extension::Event::ConfigureExtensionRequested(manifest) => {
|
||||||
|
if !manifest.context_servers.is_empty() {
|
||||||
|
show_configure_mcp_modal(
|
||||||
|
language_registry.clone(),
|
||||||
|
manifest,
|
||||||
|
workspace,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
} else {
|
||||||
|
log::info!(
|
||||||
|
"No extension events global found. Skipping context server configuration wizard"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_configure_mcp_modal(
|
||||||
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
manifest: &Arc<ExtensionManifest>,
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<'_, Workspace>,
|
||||||
|
) {
|
||||||
|
let Some(context_server_manager) = workspace.panel::<AssistantPanel>(cx).map(|panel| {
|
||||||
|
panel
|
||||||
|
.read(cx)
|
||||||
|
.thread_store()
|
||||||
|
.read(cx)
|
||||||
|
.context_server_manager()
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let registry = ContextServerDescriptorRegistry::global(cx).read(cx);
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
let configuration_tasks = manifest
|
||||||
|
.context_servers
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.filter_map({
|
||||||
|
|key| {
|
||||||
|
let descriptor = registry.context_server_descriptor(&key)?;
|
||||||
|
Some(cx.spawn({
|
||||||
|
let project = project.clone();
|
||||||
|
async move |_, cx| {
|
||||||
|
descriptor
|
||||||
|
.configuration(project, &cx)
|
||||||
|
.await
|
||||||
|
.context("Failed to resolve context server configuration")
|
||||||
|
.log_err()
|
||||||
|
.flatten()
|
||||||
|
.map(|config| (key, config))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let jsonc_language = language_registry.language_for_name("jsonc");
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let descriptors = futures::future::join_all(configuration_tasks).await;
|
||||||
|
let jsonc_language = jsonc_language.await.ok();
|
||||||
|
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
let modal = ConfigureContextServerModal::new(
|
||||||
|
descriptors.into_iter().flatten(),
|
||||||
|
jsonc_language,
|
||||||
|
context_server_manager,
|
||||||
|
language_registry,
|
||||||
|
cx.entity().downgrade(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
if let Some(modal) = modal {
|
||||||
|
this.toggle_modal(window, cx, |_, _| modal);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
@ -9,8 +9,8 @@ use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};
|
|||||||
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
|
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::{ContextServerManager, ContextServerStatus};
|
||||||
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
use context_server::{ContextServerDescriptorRegistry, ContextServerTool};
|
||||||
use futures::channel::{mpsc, oneshot};
|
use futures::channel::{mpsc, oneshot};
|
||||||
use futures::future::{self, BoxFuture, Shared};
|
use futures::future::{self, BoxFuture, Shared};
|
||||||
use futures::{FutureExt as _, StreamExt as _};
|
use futures::{FutureExt as _, StreamExt as _};
|
||||||
@ -108,7 +108,7 @@ impl ThreadStore {
|
|||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> (Self, oneshot::Receiver<()>) {
|
) -> (Self, oneshot::Receiver<()>) {
|
||||||
let context_server_factory_registry = ContextServerFactoryRegistry::default_global(cx);
|
let context_server_factory_registry = ContextServerDescriptorRegistry::default_global(cx);
|
||||||
let context_server_manager = cx.new(|cx| {
|
let context_server_manager = cx.new(|cx| {
|
||||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||||
});
|
});
|
||||||
@ -555,62 +555,68 @@ impl ThreadStore {
|
|||||||
) {
|
) {
|
||||||
let tool_working_set = self.tools.clone();
|
let tool_working_set = self.tools.clone();
|
||||||
match event {
|
match event {
|
||||||
context_server::manager::Event::ServerStarted { server_id } => {
|
context_server::manager::Event::ServerStatusChanged { server_id, status } => {
|
||||||
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
|
match status {
|
||||||
let context_server_manager = context_server_manager.clone();
|
Some(ContextServerStatus::Running) => {
|
||||||
cx.spawn({
|
if let Some(server) = context_server_manager.read(cx).get_server(server_id)
|
||||||
let server = server.clone();
|
{
|
||||||
let server_id = server_id.clone();
|
let context_server_manager = context_server_manager.clone();
|
||||||
async move |this, cx| {
|
cx.spawn({
|
||||||
let Some(protocol) = server.client() else {
|
let server = server.clone();
|
||||||
return;
|
let server_id = server_id.clone();
|
||||||
};
|
async move |this, cx| {
|
||||||
|
let Some(protocol) = server.client() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
||||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||||
let tool_ids = tool_working_set
|
let tool_ids = tool_working_set
|
||||||
.update(cx, |tool_working_set, _| {
|
.update(cx, |tool_working_set, _| {
|
||||||
tools
|
tools
|
||||||
.tools
|
.tools
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tool| {
|
.map(|tool| {
|
||||||
log::info!(
|
log::info!(
|
||||||
"registering context server tool: {:?}",
|
"registering context server tool: {:?}",
|
||||||
tool.name
|
tool.name
|
||||||
);
|
);
|
||||||
tool_working_set.insert(Arc::new(
|
tool_working_set.insert(Arc::new(
|
||||||
ContextServerTool::new(
|
ContextServerTool::new(
|
||||||
context_server_manager.clone(),
|
context_server_manager.clone(),
|
||||||
server.id(),
|
server.id(),
|
||||||
tool,
|
tool,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.log_err();
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
if let Some(tool_ids) = tool_ids {
|
if let Some(tool_ids) = tool_ids {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.context_server_tool_ids
|
this.context_server_tool_ids
|
||||||
.insert(server_id, tool_ids);
|
.insert(server_id, tool_ids);
|
||||||
this.load_default_profile(cx);
|
this.load_default_profile(cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.detach();
|
None => {
|
||||||
}
|
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
||||||
}
|
tool_working_set.update(cx, |tool_working_set, _| {
|
||||||
context_server::manager::Event::ServerStopped { server_id } => {
|
tool_working_set.remove(&tool_ids);
|
||||||
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
});
|
||||||
tool_working_set.update(cx, |tool_working_set, _| {
|
self.load_default_profile(cx);
|
||||||
tool_working_set.remove(&tool_ids);
|
}
|
||||||
});
|
}
|
||||||
self.load_default_profile(cx);
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
|||||||
use client::{Client, TypedEnvelope, proto, telemetry::Telemetry};
|
use client::{Client, TypedEnvelope, proto, telemetry::Telemetry};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::ContextServerFactoryRegistry;
|
use context_server::ContextServerDescriptorRegistry;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::{ContextServerManager, ContextServerStatus};
|
||||||
use fs::{Fs, RemoveOptions};
|
use fs::{Fs, RemoveOptions};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
@ -99,7 +99,7 @@ impl ContextStore {
|
|||||||
|
|
||||||
let this = cx.new(|cx: &mut Context<Self>| {
|
let this = cx.new(|cx: &mut Context<Self>| {
|
||||||
let context_server_factory_registry =
|
let context_server_factory_registry =
|
||||||
ContextServerFactoryRegistry::default_global(cx);
|
ContextServerDescriptorRegistry::default_global(cx);
|
||||||
let context_server_manager = cx.new(|cx| {
|
let context_server_manager = cx.new(|cx| {
|
||||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||||
});
|
});
|
||||||
@ -831,54 +831,60 @@ impl ContextStore {
|
|||||||
) {
|
) {
|
||||||
let slash_command_working_set = self.slash_commands.clone();
|
let slash_command_working_set = self.slash_commands.clone();
|
||||||
match event {
|
match event {
|
||||||
context_server::manager::Event::ServerStarted { server_id } => {
|
context_server::manager::Event::ServerStatusChanged { server_id, status } => {
|
||||||
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
|
match status {
|
||||||
let context_server_manager = context_server_manager.clone();
|
Some(ContextServerStatus::Running) => {
|
||||||
cx.spawn({
|
if let Some(server) = context_server_manager.read(cx).get_server(server_id)
|
||||||
let server = server.clone();
|
{
|
||||||
let server_id = server_id.clone();
|
let context_server_manager = context_server_manager.clone();
|
||||||
async move |this, cx| {
|
cx.spawn({
|
||||||
let Some(protocol) = server.client() else {
|
let server = server.clone();
|
||||||
return;
|
let server_id = server_id.clone();
|
||||||
};
|
async move |this, cx| {
|
||||||
|
let Some(protocol) = server.client() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
|
if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
|
||||||
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
if let Some(prompts) = protocol.list_prompts().await.log_err() {
|
||||||
let slash_command_ids = prompts
|
let slash_command_ids = prompts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(assistant_slash_commands::acceptable_prompt)
|
.filter(assistant_slash_commands::acceptable_prompt)
|
||||||
.map(|prompt| {
|
.map(|prompt| {
|
||||||
log::info!(
|
log::info!(
|
||||||
"registering context server command: {:?}",
|
"registering context server command: {:?}",
|
||||||
prompt.name
|
prompt.name
|
||||||
);
|
);
|
||||||
slash_command_working_set.insert(Arc::new(
|
slash_command_working_set.insert(Arc::new(
|
||||||
assistant_slash_commands::ContextServerSlashCommand::new(
|
assistant_slash_commands::ContextServerSlashCommand::new(
|
||||||
context_server_manager.clone(),
|
context_server_manager.clone(),
|
||||||
&server,
|
&server,
|
||||||
prompt,
|
prompt,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
this.update( cx, |this, _cx| {
|
this.update( cx, |this, _cx| {
|
||||||
this.context_server_slash_command_ids
|
this.context_server_slash_command_ids
|
||||||
.insert(server_id.clone(), slash_command_ids);
|
.insert(server_id.clone(), slash_command_ids);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.detach();
|
None => {
|
||||||
}
|
if let Some(slash_command_ids) =
|
||||||
}
|
self.context_server_slash_command_ids.remove(server_id)
|
||||||
context_server::manager::Event::ServerStopped { server_id } => {
|
{
|
||||||
if let Some(slash_command_ids) =
|
slash_command_working_set.remove(&slash_command_ids);
|
||||||
self.context_server_slash_command_ids.remove(server_id)
|
}
|
||||||
{
|
}
|
||||||
slash_command_working_set.remove(&slash_command_ids);
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,3 +34,7 @@ smol.workspace = true
|
|||||||
url = { workspace = true, features = ["serde"] }
|
url = { workspace = true, features = ["serde"] }
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
project = { workspace = true, features = ["test-support"] }
|
||||||
|
@ -140,7 +140,7 @@ impl Client {
|
|||||||
/// This function initializes a new Client by spawning a child process for the context server,
|
/// This function initializes a new Client by spawning a child process for the context server,
|
||||||
/// setting up communication channels, and initializing handlers for input/output operations.
|
/// setting up communication channels, and initializing handlers for input/output operations.
|
||||||
/// It takes a server ID, binary information, and an async app context as input.
|
/// It takes a server ID, binary information, and an async app context as input.
|
||||||
pub fn new(
|
pub fn stdio(
|
||||||
server_id: ContextServerId,
|
server_id: ContextServerId,
|
||||||
binary: ModelContextServerBinary,
|
binary: ModelContextServerBinary,
|
||||||
cx: AsyncApp,
|
cx: AsyncApp,
|
||||||
@ -158,7 +158,16 @@ impl Client {
|
|||||||
.unwrap_or_else(String::new);
|
.unwrap_or_else(String::new);
|
||||||
|
|
||||||
let transport = Arc::new(StdioTransport::new(binary, &cx)?);
|
let transport = Arc::new(StdioTransport::new(binary, &cx)?);
|
||||||
|
Self::new(server_id, server_name.into(), transport, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Client instance for a context server.
|
||||||
|
pub fn new(
|
||||||
|
server_id: ContextServerId,
|
||||||
|
server_name: Arc<str>,
|
||||||
|
transport: Arc<dyn Transport>,
|
||||||
|
cx: AsyncApp,
|
||||||
|
) -> Result<Self> {
|
||||||
let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
|
let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
|
||||||
let (output_done_tx, output_done_rx) = barrier::channel();
|
let (output_done_tx, output_done_rx) = barrier::channel();
|
||||||
|
|
||||||
@ -167,7 +176,7 @@ impl Client {
|
|||||||
let response_handlers =
|
let response_handlers =
|
||||||
Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
|
Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
|
||||||
|
|
||||||
let stdout_input_task = cx.spawn({
|
let receive_input_task = cx.spawn({
|
||||||
let notification_handlers = notification_handlers.clone();
|
let notification_handlers = notification_handlers.clone();
|
||||||
let response_handlers = response_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
let transport = transport.clone();
|
let transport = transport.clone();
|
||||||
@ -177,13 +186,13 @@ impl Client {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let stderr_input_task = cx.spawn({
|
let receive_err_task = cx.spawn({
|
||||||
let transport = transport.clone();
|
let transport = transport.clone();
|
||||||
async move |_| Self::handle_stderr(transport).log_err().await
|
async move |_| Self::handle_err(transport).log_err().await
|
||||||
});
|
});
|
||||||
let input_task = cx.spawn(async move |_| {
|
let input_task = cx.spawn(async move |_| {
|
||||||
let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
|
let (input, err) = futures::join!(receive_input_task, receive_err_task);
|
||||||
stdout.or(stderr)
|
input.or(err)
|
||||||
});
|
});
|
||||||
|
|
||||||
let output_task = cx.background_spawn({
|
let output_task = cx.background_spawn({
|
||||||
@ -201,7 +210,7 @@ impl Client {
|
|||||||
server_id,
|
server_id,
|
||||||
notification_handlers,
|
notification_handlers,
|
||||||
response_handlers,
|
response_handlers,
|
||||||
name: server_name.into(),
|
name: server_name,
|
||||||
next_id: Default::default(),
|
next_id: Default::default(),
|
||||||
outbound_tx,
|
outbound_tx,
|
||||||
executor: cx.background_executor().clone(),
|
executor: cx.background_executor().clone(),
|
||||||
@ -247,7 +256,7 @@ impl Client {
|
|||||||
|
|
||||||
/// Handles the stderr output from the context server.
|
/// Handles the stderr output from the context server.
|
||||||
/// Continuously reads and logs any error messages from the server.
|
/// Continuously reads and logs any error messages from the server.
|
||||||
async fn handle_stderr(transport: Arc<dyn Transport>) -> anyhow::Result<()> {
|
async fn handle_err(transport: Arc<dyn Transport>) -> anyhow::Result<()> {
|
||||||
while let Some(err) = transport.receive_err().next().await {
|
while let Some(err) = transport.receive_err().next().await {
|
||||||
log::warn!("context server stderr: {}", err.trim());
|
log::warn!("context server stderr: {}", err.trim());
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ pub use context_server_settings::{ContextServerSettings, ServerCommand, ServerCo
|
|||||||
use gpui::{App, actions};
|
use gpui::{App, actions};
|
||||||
|
|
||||||
pub use crate::context_server_tool::ContextServerTool;
|
pub use crate::context_server_tool::ContextServerTool;
|
||||||
pub use crate::registry::ContextServerFactoryRegistry;
|
pub use crate::registry::ContextServerDescriptorRegistry;
|
||||||
|
|
||||||
actions!(context_servers, [Restart]);
|
actions!(context_servers, [Restart]);
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
|
|||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
context_server_settings::init(cx);
|
context_server_settings::init(cx);
|
||||||
ContextServerFactoryRegistry::default_global(cx);
|
ContextServerDescriptorRegistry::default_global(cx);
|
||||||
extension_context_server::init(cx);
|
extension_context_server::init(cx);
|
||||||
|
|
||||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
|
@ -1,9 +1,21 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use extension::{Extension, ExtensionContextServerProxy, ExtensionHostProxy, ProjectDelegate};
|
use anyhow::Result;
|
||||||
use gpui::{App, Entity};
|
use extension::{
|
||||||
|
ContextServerConfiguration, Extension, ExtensionContextServerProxy, ExtensionHostProxy,
|
||||||
|
ProjectDelegate,
|
||||||
|
};
|
||||||
|
use gpui::{App, AsyncApp, Entity, Task};
|
||||||
|
use project::Project;
|
||||||
|
|
||||||
use crate::{ContextServerFactoryRegistry, ServerCommand};
|
use crate::{ContextServerDescriptorRegistry, ServerCommand, registry};
|
||||||
|
|
||||||
|
pub fn init(cx: &mut App) {
|
||||||
|
let proxy = ExtensionHostProxy::default_global(cx);
|
||||||
|
proxy.register_context_server_proxy(ContextServerDescriptorRegistryProxy {
|
||||||
|
context_server_factory_registry: ContextServerDescriptorRegistry::global(cx),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
struct ExtensionProject {
|
struct ExtensionProject {
|
||||||
worktree_ids: Vec<u64>,
|
worktree_ids: Vec<u64>,
|
||||||
@ -15,60 +27,78 @@ impl ProjectDelegate for ExtensionProject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
struct ContextServerDescriptor {
|
||||||
let proxy = ExtensionHostProxy::default_global(cx);
|
id: Arc<str>,
|
||||||
proxy.register_context_server_proxy(ContextServerFactoryRegistryProxy {
|
extension: Arc<dyn Extension>,
|
||||||
context_server_factory_registry: ContextServerFactoryRegistry::global(cx),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContextServerFactoryRegistryProxy {
|
fn extension_project(project: Entity<Project>, cx: &mut AsyncApp) -> Result<Arc<ExtensionProject>> {
|
||||||
context_server_factory_registry: Entity<ContextServerFactoryRegistry>,
|
project.update(cx, |project, cx| {
|
||||||
|
Arc::new(ExtensionProject {
|
||||||
|
worktree_ids: project
|
||||||
|
.visible_worktrees(cx)
|
||||||
|
.map(|worktree| worktree.read(cx).id().to_proto())
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy {
|
impl registry::ContextServerDescriptor for ContextServerDescriptor {
|
||||||
|
fn command(&self, project: Entity<Project>, cx: &AsyncApp) -> Task<Result<ServerCommand>> {
|
||||||
|
let id = self.id.clone();
|
||||||
|
let extension = self.extension.clone();
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let extension_project = extension_project(project, cx)?;
|
||||||
|
let mut command = extension
|
||||||
|
.context_server_command(id.clone(), extension_project.clone())
|
||||||
|
.await?;
|
||||||
|
command.command = extension
|
||||||
|
.path_from_extension(command.command.as_ref())
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
log::info!("loaded command for context server {id}: {command:?}");
|
||||||
|
|
||||||
|
Ok(ServerCommand {
|
||||||
|
path: command.command,
|
||||||
|
args: command.args,
|
||||||
|
env: Some(command.env.into_iter().collect()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configuration(
|
||||||
|
&self,
|
||||||
|
project: Entity<Project>,
|
||||||
|
cx: &AsyncApp,
|
||||||
|
) -> Task<Result<Option<ContextServerConfiguration>>> {
|
||||||
|
let id = self.id.clone();
|
||||||
|
let extension = self.extension.clone();
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let extension_project = extension_project(project, cx)?;
|
||||||
|
let configuration = extension
|
||||||
|
.context_server_configuration(id.clone(), extension_project)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
log::debug!("loaded configuration for context server {id}: {configuration:?}");
|
||||||
|
|
||||||
|
Ok(configuration)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContextServerDescriptorRegistryProxy {
|
||||||
|
context_server_factory_registry: Entity<ContextServerDescriptorRegistry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionContextServerProxy for ContextServerDescriptorRegistryProxy {
|
||||||
fn register_context_server(&self, extension: Arc<dyn Extension>, id: Arc<str>, cx: &mut App) {
|
fn register_context_server(&self, extension: Arc<dyn Extension>, id: Arc<str>, cx: &mut App) {
|
||||||
self.context_server_factory_registry
|
self.context_server_factory_registry
|
||||||
.update(cx, |registry, _| {
|
.update(cx, |registry, _| {
|
||||||
registry.register_server_factory(
|
registry.register_context_server_descriptor(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
Arc::new({
|
Arc::new(ContextServerDescriptor { id, extension })
|
||||||
move |project, cx| {
|
as Arc<dyn registry::ContextServerDescriptor>,
|
||||||
log::info!(
|
|
||||||
"loading command for context server {id} from extension {}",
|
|
||||||
extension.manifest().id
|
|
||||||
);
|
|
||||||
|
|
||||||
let id = id.clone();
|
|
||||||
let extension = extension.clone();
|
|
||||||
cx.spawn(async move |cx| {
|
|
||||||
let extension_project = project.update(cx, |project, cx| {
|
|
||||||
Arc::new(ExtensionProject {
|
|
||||||
worktree_ids: project
|
|
||||||
.visible_worktrees(cx)
|
|
||||||
.map(|worktree| worktree.read(cx).id().to_proto())
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut command = extension
|
|
||||||
.context_server_command(id.clone(), extension_project)
|
|
||||||
.await?;
|
|
||||||
command.command = extension
|
|
||||||
.path_from_extension(command.command.as_ref())
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
log::info!("loaded command for context server {id}: {command:?}");
|
|
||||||
|
|
||||||
Ok(ServerCommand {
|
|
||||||
path: command.command,
|
|
||||||
args: command.args,
|
|
||||||
env: Some(command.env.into_iter().collect()),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -27,18 +27,27 @@ use project::Project;
|
|||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
|
use crate::transport::Transport;
|
||||||
use crate::{ContextServerSettings, ServerConfig};
|
use crate::{ContextServerSettings, ServerConfig};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CONTEXT_SERVERS_NAMESPACE, ContextServerFactoryRegistry,
|
CONTEXT_SERVERS_NAMESPACE, ContextServerDescriptorRegistry,
|
||||||
client::{self, Client},
|
client::{self, Client},
|
||||||
types,
|
types,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ContextServerStatus {
|
||||||
|
Starting,
|
||||||
|
Running,
|
||||||
|
Error(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ContextServer {
|
pub struct ContextServer {
|
||||||
pub id: Arc<str>,
|
pub id: Arc<str>,
|
||||||
pub config: Arc<ServerConfig>,
|
pub config: Arc<ServerConfig>,
|
||||||
pub client: RwLock<Option<Arc<crate::protocol::InitializedContextServerProtocol>>>,
|
pub client: RwLock<Option<Arc<crate::protocol::InitializedContextServerProtocol>>>,
|
||||||
|
transport: Option<Arc<dyn Transport>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextServer {
|
impl ContextServer {
|
||||||
@ -47,9 +56,20 @@ impl ContextServer {
|
|||||||
id,
|
id,
|
||||||
config,
|
config,
|
||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
|
transport: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn test(id: Arc<str>, transport: Arc<dyn crate::transport::Transport>) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
id,
|
||||||
|
client: RwLock::new(None),
|
||||||
|
config: Arc::new(ServerConfig::default()),
|
||||||
|
transport: Some(transport),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> Arc<str> {
|
pub fn id(&self) -> Arc<str> {
|
||||||
self.id.clone()
|
self.id.clone()
|
||||||
}
|
}
|
||||||
@ -63,20 +83,32 @@ impl ContextServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(self: Arc<Self>, cx: &AsyncApp) -> Result<()> {
|
pub async fn start(self: Arc<Self>, cx: &AsyncApp) -> Result<()> {
|
||||||
log::info!("starting context server {}", self.id);
|
let client = if let Some(transport) = self.transport.clone() {
|
||||||
let Some(command) = &self.config.command else {
|
Client::new(
|
||||||
bail!("no command specified for server {}", self.id);
|
client::ContextServerId(self.id.clone()),
|
||||||
|
self.id(),
|
||||||
|
transport,
|
||||||
|
cx.clone(),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
let Some(command) = &self.config.command else {
|
||||||
|
bail!("no command specified for server {}", self.id);
|
||||||
|
};
|
||||||
|
Client::stdio(
|
||||||
|
client::ContextServerId(self.id.clone()),
|
||||||
|
client::ModelContextServerBinary {
|
||||||
|
executable: Path::new(&command.path).to_path_buf(),
|
||||||
|
args: command.args.clone(),
|
||||||
|
env: command.env.clone(),
|
||||||
|
},
|
||||||
|
cx.clone(),
|
||||||
|
)?
|
||||||
};
|
};
|
||||||
let client = Client::new(
|
self.initialize(client).await
|
||||||
client::ContextServerId(self.id.clone()),
|
}
|
||||||
client::ModelContextServerBinary {
|
|
||||||
executable: Path::new(&command.path).to_path_buf(),
|
|
||||||
args: command.args.clone(),
|
|
||||||
env: command.env.clone(),
|
|
||||||
},
|
|
||||||
cx.clone(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
|
async fn initialize(&self, client: Client) -> Result<()> {
|
||||||
|
log::info!("starting context server {}", self.id);
|
||||||
let protocol = crate::protocol::ModelContextProtocol::new(client);
|
let protocol = crate::protocol::ModelContextProtocol::new(client);
|
||||||
let client_info = types::Implementation {
|
let client_info = types::Implementation {
|
||||||
name: "Zed".to_string(),
|
name: "Zed".to_string(),
|
||||||
@ -105,23 +137,26 @@ impl ContextServer {
|
|||||||
|
|
||||||
pub struct ContextServerManager {
|
pub struct ContextServerManager {
|
||||||
servers: HashMap<Arc<str>, Arc<ContextServer>>,
|
servers: HashMap<Arc<str>, Arc<ContextServer>>,
|
||||||
|
server_status: HashMap<Arc<str>, ContextServerStatus>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
registry: Entity<ContextServerFactoryRegistry>,
|
registry: Entity<ContextServerDescriptorRegistry>,
|
||||||
update_servers_task: Option<Task<Result<()>>>,
|
update_servers_task: Option<Task<Result<()>>>,
|
||||||
needs_server_update: bool,
|
needs_server_update: bool,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ServerStarted { server_id: Arc<str> },
|
ServerStatusChanged {
|
||||||
ServerStopped { server_id: Arc<str> },
|
server_id: Arc<str>,
|
||||||
|
status: Option<ContextServerStatus>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for ContextServerManager {}
|
impl EventEmitter<Event> for ContextServerManager {}
|
||||||
|
|
||||||
impl ContextServerManager {
|
impl ContextServerManager {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
registry: Entity<ContextServerFactoryRegistry>,
|
registry: Entity<ContextServerDescriptorRegistry>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -138,6 +173,7 @@ impl ContextServerManager {
|
|||||||
registry,
|
registry,
|
||||||
needs_server_update: false,
|
needs_server_update: false,
|
||||||
servers: HashMap::default(),
|
servers: HashMap::default(),
|
||||||
|
server_status: HashMap::default(),
|
||||||
update_servers_task: None,
|
update_servers_task: None,
|
||||||
};
|
};
|
||||||
this.available_context_servers_changed(cx);
|
this.available_context_servers_changed(cx);
|
||||||
@ -153,7 +189,9 @@ impl ContextServerManager {
|
|||||||
this.needs_server_update = false;
|
this.needs_server_update = false;
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Self::maintain_servers(this.clone(), cx).await?;
|
if let Err(err) = Self::maintain_servers(this.clone(), cx).await {
|
||||||
|
log::error!("Error maintaining context servers: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
let has_any_context_servers = !this.running_servers().is_empty();
|
let has_any_context_servers = !this.running_servers().is_empty();
|
||||||
@ -181,52 +219,37 @@ impl ContextServerManager {
|
|||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn status_for_server(&self, id: &str) -> Option<ContextServerStatus> {
|
||||||
|
self.server_status.get(id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_server(
|
pub fn start_server(
|
||||||
&self,
|
&self,
|
||||||
server: Arc<ContextServer>,
|
server: Arc<ContextServer>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<anyhow::Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| Self::run_server(this, server, cx).await)
|
||||||
let id = server.id.clone();
|
|
||||||
server.start(&cx).await?;
|
|
||||||
this.update(cx, |_, cx| cx.emit(Event::ServerStarted { server_id: id }))?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_server(
|
pub fn stop_server(
|
||||||
&self,
|
&mut self,
|
||||||
server: Arc<ContextServer>,
|
server: Arc<ContextServer>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> anyhow::Result<()> {
|
) -> Result<()> {
|
||||||
server.stop()?;
|
server.stop().log_err();
|
||||||
cx.emit(Event::ServerStopped {
|
self.update_server_status(server.id().clone(), None, cx);
|
||||||
server_id: server.id(),
|
|
||||||
});
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restart_server(
|
pub fn restart_server(&mut self, id: &Arc<str>, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||||
&mut self,
|
|
||||||
id: &Arc<str>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Task<anyhow::Result<()>> {
|
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
if let Some(server) = this.update(cx, |this, _cx| this.servers.remove(&id))? {
|
if let Some(server) = this.update(cx, |this, _cx| this.servers.remove(&id))? {
|
||||||
server.stop()?;
|
|
||||||
let config = server.config();
|
let config = server.config();
|
||||||
|
|
||||||
|
this.update(cx, |this, cx| this.stop_server(server, cx))??;
|
||||||
let new_server = Arc::new(ContextServer::new(id.clone(), config));
|
let new_server = Arc::new(ContextServer::new(id.clone(), config));
|
||||||
new_server.clone().start(&cx).await?;
|
Self::run_server(this, new_server, cx).await?;
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.servers.insert(id.clone(), new_server);
|
|
||||||
cx.emit(Event::ServerStopped {
|
|
||||||
server_id: id.clone(),
|
|
||||||
});
|
|
||||||
cx.emit(Event::ServerStarted {
|
|
||||||
server_id: id.clone(),
|
|
||||||
});
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -263,12 +286,14 @@ impl ContextServerManager {
|
|||||||
(this.registry.clone(), this.project.clone())
|
(this.registry.clone(), this.project.clone())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for (id, factory) in
|
for (id, descriptor) in
|
||||||
registry.read_with(cx, |registry, _| registry.context_server_factories())?
|
registry.read_with(cx, |registry, _| registry.context_server_descriptors())?
|
||||||
{
|
{
|
||||||
let config = desired_servers.entry(id).or_default();
|
let config = desired_servers.entry(id).or_default();
|
||||||
if config.command.is_none() {
|
if config.command.is_none() {
|
||||||
if let Some(extension_command) = factory(project.clone(), &cx).await.log_err() {
|
if let Some(extension_command) =
|
||||||
|
descriptor.command(project.clone(), &cx).await.log_err()
|
||||||
|
{
|
||||||
config.command = Some(extension_command);
|
config.command = Some(extension_command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,28 +315,270 @@ impl ContextServerManager {
|
|||||||
for (id, config) in desired_servers {
|
for (id, config) in desired_servers {
|
||||||
let existing_config = this.servers.get(&id).map(|server| server.config());
|
let existing_config = this.servers.get(&id).map(|server| server.config());
|
||||||
if existing_config.as_deref() != Some(&config) {
|
if existing_config.as_deref() != Some(&config) {
|
||||||
let config = Arc::new(config);
|
let server = Arc::new(ContextServer::new(id.clone(), Arc::new(config)));
|
||||||
let server = Arc::new(ContextServer::new(id.clone(), config));
|
|
||||||
servers_to_start.insert(id.clone(), server.clone());
|
servers_to_start.insert(id.clone(), server.clone());
|
||||||
let old_server = this.servers.insert(id.clone(), server);
|
if let Some(old_server) = this.servers.remove(&id) {
|
||||||
if let Some(old_server) = old_server {
|
|
||||||
servers_to_stop.insert(id, old_server);
|
servers_to_stop.insert(id, old_server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for (id, server) in servers_to_stop {
|
for (_, server) in servers_to_stop {
|
||||||
server.stop().log_err();
|
this.update(cx, |this, cx| this.stop_server(server, cx).ok())?;
|
||||||
this.update(cx, |_, cx| cx.emit(Event::ServerStopped { server_id: id }))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id, server) in servers_to_start {
|
for (_, server) in servers_to_start {
|
||||||
if server.start(&cx).await.log_err().is_some() {
|
Self::run_server(this.clone(), server, cx).await.ok();
|
||||||
this.update(cx, |_, cx| cx.emit(Event::ServerStarted { server_id: id }))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn run_server(
|
||||||
|
this: WeakEntity<Self>,
|
||||||
|
server: Arc<ContextServer>,
|
||||||
|
cx: &mut AsyncApp,
|
||||||
|
) -> Result<()> {
|
||||||
|
let id = server.id();
|
||||||
|
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.update_server_status(id.clone(), Some(ContextServerStatus::Starting), cx);
|
||||||
|
this.servers.insert(id.clone(), server.clone());
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match server.start(&cx).await {
|
||||||
|
Ok(_) => {
|
||||||
|
log::debug!("`{}` context server started", id);
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.update_server_status(id.clone(), Some(ContextServerStatus::Running), cx)
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("`{}` context server failed to start\n{}", id, err);
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.update_server_status(
|
||||||
|
id.clone(),
|
||||||
|
Some(ContextServerStatus::Error(err.to_string().into())),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_server_status(
|
||||||
|
&mut self,
|
||||||
|
id: Arc<str>,
|
||||||
|
status: Option<ContextServerStatus>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(status) = status.clone() {
|
||||||
|
self.server_status.insert(id.clone(), status);
|
||||||
|
} else {
|
||||||
|
self.server_status.remove(&id);
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.emit(Event::ServerStatusChanged {
|
||||||
|
server_id: id,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::types::{
|
||||||
|
Implementation, InitializeResponse, ProtocolVersion, RequestType, ServerCapabilities,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use futures::{Stream, StreamExt as _, lock::Mutex};
|
||||||
|
use gpui::{AppContext as _, TestAppContext};
|
||||||
|
use project::FakeFs;
|
||||||
|
use serde_json::json;
|
||||||
|
use util::path;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_context_server_status(cx: &mut TestAppContext) {
|
||||||
|
init_test_settings(cx);
|
||||||
|
let project = create_test_project(cx, json!({"code.rs": ""})).await;
|
||||||
|
|
||||||
|
let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
|
||||||
|
let manager = cx.new(|cx| ContextServerManager::new(registry.clone(), project, cx));
|
||||||
|
|
||||||
|
let server_1_id: Arc<str> = "mcp-1".into();
|
||||||
|
let server_2_id: Arc<str> = "mcp-2".into();
|
||||||
|
|
||||||
|
let transport_1 = Arc::new(FakeTransport::new(
|
||||||
|
|_, request_type, _| match request_type {
|
||||||
|
Some(RequestType::Initialize) => {
|
||||||
|
Some(create_initialize_response("mcp-1".to_string()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let transport_2 = Arc::new(FakeTransport::new(
|
||||||
|
|_, request_type, _| match request_type {
|
||||||
|
Some(RequestType::Initialize) => {
|
||||||
|
Some(create_initialize_response("mcp-2".to_string()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let server_1 = ContextServer::test(server_1_id.clone(), transport_1.clone());
|
||||||
|
let server_2 = ContextServer::test(server_2_id.clone(), transport_2.clone());
|
||||||
|
|
||||||
|
manager
|
||||||
|
.update(cx, |manager, cx| manager.start_server(server_1, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
assert_eq!(
|
||||||
|
manager.read(cx).status_for_server(&server_1_id),
|
||||||
|
Some(ContextServerStatus::Running)
|
||||||
|
);
|
||||||
|
assert_eq!(manager.read(cx).status_for_server(&server_2_id), None);
|
||||||
|
});
|
||||||
|
|
||||||
|
manager
|
||||||
|
.update(cx, |manager, cx| manager.start_server(server_2.clone(), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
assert_eq!(
|
||||||
|
manager.read(cx).status_for_server(&server_1_id),
|
||||||
|
Some(ContextServerStatus::Running)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
manager.read(cx).status_for_server(&server_2_id),
|
||||||
|
Some(ContextServerStatus::Running)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
manager
|
||||||
|
.update(cx, |manager, cx| manager.stop_server(server_2, cx))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
assert_eq!(
|
||||||
|
manager.read(cx).status_for_server(&server_1_id),
|
||||||
|
Some(ContextServerStatus::Running)
|
||||||
|
);
|
||||||
|
assert_eq!(manager.read(cx).status_for_server(&server_2_id), None);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_test_project(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
files: serde_json::Value,
|
||||||
|
) -> Entity<Project> {
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(path!("/test"), files).await;
|
||||||
|
Project::test(fs, [path!("/test").as_ref()], cx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_test_settings(cx: &mut TestAppContext) {
|
||||||
|
cx.update(|cx| {
|
||||||
|
let settings_store = SettingsStore::test(cx);
|
||||||
|
cx.set_global(settings_store);
|
||||||
|
Project::init_settings(cx);
|
||||||
|
ContextServerSettings::register(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_initialize_response(server_name: String) -> serde_json::Value {
|
||||||
|
serde_json::to_value(&InitializeResponse {
|
||||||
|
protocol_version: ProtocolVersion(types::LATEST_PROTOCOL_VERSION.to_string()),
|
||||||
|
server_info: Implementation {
|
||||||
|
name: server_name,
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
},
|
||||||
|
capabilities: ServerCapabilities::default(),
|
||||||
|
meta: None,
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FakeTransport {
|
||||||
|
on_request: Arc<
|
||||||
|
dyn Fn(u64, Option<RequestType>, serde_json::Value) -> Option<serde_json::Value>
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
>,
|
||||||
|
tx: futures::channel::mpsc::UnboundedSender<String>,
|
||||||
|
rx: Arc<Mutex<futures::channel::mpsc::UnboundedReceiver<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeTransport {
|
||||||
|
fn new(
|
||||||
|
on_request: impl Fn(
|
||||||
|
u64,
|
||||||
|
Option<RequestType>,
|
||||||
|
serde_json::Value,
|
||||||
|
) -> Option<serde_json::Value>
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
) -> Self {
|
||||||
|
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||||
|
Self {
|
||||||
|
on_request: Arc::new(on_request),
|
||||||
|
tx,
|
||||||
|
rx: Arc::new(Mutex::new(rx)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Transport for FakeTransport {
|
||||||
|
async fn send(&self, message: String) -> Result<()> {
|
||||||
|
if let Ok(msg) = serde_json::from_str::<serde_json::Value>(&message) {
|
||||||
|
let id = msg.get("id").and_then(|id| id.as_u64()).unwrap_or(0);
|
||||||
|
|
||||||
|
if let Some(method) = msg.get("method") {
|
||||||
|
let request_type = method
|
||||||
|
.as_str()
|
||||||
|
.and_then(|method| types::RequestType::try_from(method).ok());
|
||||||
|
if let Some(payload) = (self.on_request.as_ref())(id, request_type, msg) {
|
||||||
|
let response = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": id,
|
||||||
|
"result": payload
|
||||||
|
});
|
||||||
|
|
||||||
|
self.tx
|
||||||
|
.unbounded_send(response.to_string())
|
||||||
|
.map_err(|e| anyhow::anyhow!("Failed to send message: {}", e))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(&self) -> Pin<Box<dyn Stream<Item = String> + Send>> {
|
||||||
|
let rx = self.rx.clone();
|
||||||
|
Box::pin(futures::stream::unfold(rx, |rx| async move {
|
||||||
|
let mut rx_guard = rx.lock().await;
|
||||||
|
if let Some(message) = rx_guard.next().await {
|
||||||
|
drop(rx_guard);
|
||||||
|
Some((message, rx))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_err(&self) -> Pin<Box<dyn Stream<Item = String> + Send>> {
|
||||||
|
Box::pin(futures::stream::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,47 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use extension::ContextServerConfiguration;
|
||||||
use gpui::{App, AppContext as _, AsyncApp, Entity, Global, ReadGlobal, Task};
|
use gpui::{App, AppContext as _, AsyncApp, Entity, Global, ReadGlobal, Task};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
|
||||||
use crate::ServerCommand;
|
use crate::ServerCommand;
|
||||||
|
|
||||||
pub type ContextServerFactory =
|
pub trait ContextServerDescriptor {
|
||||||
Arc<dyn Fn(Entity<Project>, &AsyncApp) -> Task<Result<ServerCommand>> + Send + Sync + 'static>;
|
fn command(&self, project: Entity<Project>, cx: &AsyncApp) -> Task<Result<ServerCommand>>;
|
||||||
|
fn configuration(
|
||||||
struct GlobalContextServerFactoryRegistry(Entity<ContextServerFactoryRegistry>);
|
&self,
|
||||||
|
project: Entity<Project>,
|
||||||
impl Global for GlobalContextServerFactoryRegistry {}
|
cx: &AsyncApp,
|
||||||
|
) -> Task<Result<Option<ContextServerConfiguration>>>;
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ContextServerFactoryRegistry {
|
|
||||||
context_servers: HashMap<Arc<str>, ContextServerFactory>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextServerFactoryRegistry {
|
struct GlobalContextServerDescriptorRegistry(Entity<ContextServerDescriptorRegistry>);
|
||||||
/// Returns the global [`ContextServerFactoryRegistry`].
|
|
||||||
|
impl Global for GlobalContextServerDescriptorRegistry {}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ContextServerDescriptorRegistry {
|
||||||
|
context_servers: HashMap<Arc<str>, Arc<dyn ContextServerDescriptor>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextServerDescriptorRegistry {
|
||||||
|
/// Returns the global [`ContextServerDescriptorRegistry`].
|
||||||
pub fn global(cx: &App) -> Entity<Self> {
|
pub fn global(cx: &App) -> Entity<Self> {
|
||||||
GlobalContextServerFactoryRegistry::global(cx).0.clone()
|
GlobalContextServerDescriptorRegistry::global(cx).0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the global [`ContextServerFactoryRegistry`].
|
/// Returns the global [`ContextServerDescriptorRegistry`].
|
||||||
///
|
///
|
||||||
/// Inserts a default [`ContextServerFactoryRegistry`] if one does not yet exist.
|
/// Inserts a default [`ContextServerDescriptorRegistry`] if one does not yet exist.
|
||||||
pub fn default_global(cx: &mut App) -> Entity<Self> {
|
pub fn default_global(cx: &mut App) -> Entity<Self> {
|
||||||
if !cx.has_global::<GlobalContextServerFactoryRegistry>() {
|
if !cx.has_global::<GlobalContextServerDescriptorRegistry>() {
|
||||||
let registry = cx.new(|_| Self::new());
|
let registry = cx.new(|_| Self::new());
|
||||||
cx.set_global(GlobalContextServerFactoryRegistry(registry));
|
cx.set_global(GlobalContextServerDescriptorRegistry(registry));
|
||||||
}
|
}
|
||||||
cx.global::<GlobalContextServerFactoryRegistry>().0.clone()
|
cx.global::<GlobalContextServerDescriptorRegistry>()
|
||||||
|
.0
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -42,20 +51,28 @@ impl ContextServerFactoryRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_server_factories(&self) -> Vec<(Arc<str>, ContextServerFactory)> {
|
pub fn context_server_descriptors(&self) -> Vec<(Arc<str>, Arc<dyn ContextServerDescriptor>)> {
|
||||||
self.context_servers
|
self.context_servers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, factory)| (id.clone(), factory.clone()))
|
.map(|(id, factory)| (id.clone(), factory.clone()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers the provided [`ContextServerFactory`].
|
pub fn context_server_descriptor(&self, id: &str) -> Option<Arc<dyn ContextServerDescriptor>> {
|
||||||
pub fn register_server_factory(&mut self, id: Arc<str>, factory: ContextServerFactory) {
|
self.context_servers.get(id).cloned()
|
||||||
self.context_servers.insert(id, factory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unregisters the [`ContextServerFactory`] for the server with the given ID.
|
/// Registers the provided [`ContextServerDescriptor`].
|
||||||
pub fn unregister_server_factory_by_id(&mut self, server_id: &str) {
|
pub fn register_context_server_descriptor(
|
||||||
|
&mut self,
|
||||||
|
id: Arc<str>,
|
||||||
|
descriptor: Arc<dyn ContextServerDescriptor>,
|
||||||
|
) {
|
||||||
|
self.context_servers.insert(id, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregisters the [`ContextServerDescriptor`] for the server with the given ID.
|
||||||
|
pub fn unregister_context_server_descriptor_by_id(&mut self, server_id: &str) {
|
||||||
self.context_servers.remove(server_id);
|
self.context_servers.remove(server_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,30 @@ impl RequestType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for RequestType {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||||
|
match s {
|
||||||
|
"initialize" => Ok(RequestType::Initialize),
|
||||||
|
"tools/call" => Ok(RequestType::CallTool),
|
||||||
|
"resources/unsubscribe" => Ok(RequestType::ResourcesUnsubscribe),
|
||||||
|
"resources/subscribe" => Ok(RequestType::ResourcesSubscribe),
|
||||||
|
"resources/read" => Ok(RequestType::ResourcesRead),
|
||||||
|
"resources/list" => Ok(RequestType::ResourcesList),
|
||||||
|
"logging/setLevel" => Ok(RequestType::LoggingSetLevel),
|
||||||
|
"prompts/get" => Ok(RequestType::PromptsGet),
|
||||||
|
"prompts/list" => Ok(RequestType::PromptsList),
|
||||||
|
"completion/complete" => Ok(RequestType::CompletionComplete),
|
||||||
|
"ping" => Ok(RequestType::Ping),
|
||||||
|
"tools/list" => Ok(RequestType::ListTools),
|
||||||
|
"resources/templates/list" => Ok(RequestType::ListResourceTemplates),
|
||||||
|
"roots/list" => Ok(RequestType::ListRoots),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ProtocolVersion(pub String);
|
pub struct ProtocolVersion(pub String);
|
||||||
@ -154,7 +178,7 @@ pub struct CompletionArgument {
|
|||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct InitializeResponse {
|
pub struct InitializeResponse {
|
||||||
pub protocol_version: ProtocolVersion,
|
pub protocol_version: ProtocolVersion,
|
||||||
@ -343,7 +367,7 @@ pub struct ClientCapabilities {
|
|||||||
pub roots: Option<RootsCapabilities>,
|
pub roots: Option<RootsCapabilities>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ServerCapabilities {
|
pub struct ServerCapabilities {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
@ -424,7 +424,13 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
|
|||||||
prompt_store::init(cx);
|
prompt_store::init(cx);
|
||||||
let stdout_is_a_pty = false;
|
let stdout_is_a_pty = false;
|
||||||
let prompt_builder = PromptBuilder::load(fs.clone(), stdout_is_a_pty, cx);
|
let prompt_builder = PromptBuilder::load(fs.clone(), stdout_is_a_pty, cx);
|
||||||
agent::init(fs.clone(), client.clone(), prompt_builder.clone(), cx);
|
agent::init(
|
||||||
|
fs.clone(),
|
||||||
|
client.clone(),
|
||||||
|
prompt_builder.clone(),
|
||||||
|
languages.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
assistant_tools::init(client.http_client(), cx);
|
assistant_tools::init(client.http_client(), cx);
|
||||||
|
|
||||||
SettingsStore::update_global(cx, |store, cx| {
|
SettingsStore::update_global(cx, |store, cx| {
|
||||||
|
@ -121,6 +121,12 @@ pub trait Extension: Send + Sync + 'static {
|
|||||||
project: Arc<dyn ProjectDelegate>,
|
project: Arc<dyn ProjectDelegate>,
|
||||||
) -> Result<Command>;
|
) -> Result<Command>;
|
||||||
|
|
||||||
|
async fn context_server_configuration(
|
||||||
|
&self,
|
||||||
|
context_server_id: Arc<str>,
|
||||||
|
project: Arc<dyn ProjectDelegate>,
|
||||||
|
) -> Result<Option<ContextServerConfiguration>>;
|
||||||
|
|
||||||
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
|
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
|
||||||
|
|
||||||
async fn index_docs(
|
async fn index_docs(
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global};
|
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global};
|
||||||
|
|
||||||
|
use crate::ExtensionManifest;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
let extension_events = cx.new(ExtensionEvents::new);
|
let extension_events = cx.new(ExtensionEvents::new);
|
||||||
cx.set_global(GlobalExtensionEvents(extension_events));
|
cx.set_global(GlobalExtensionEvents(extension_events));
|
||||||
@ -31,7 +35,9 @@ impl ExtensionEvents {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
ExtensionInstalled(Arc<ExtensionManifest>),
|
||||||
ExtensionsInstalledChanged,
|
ExtensionsInstalledChanged,
|
||||||
|
ConfigureExtensionRequested(Arc<ExtensionManifest>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for ExtensionEvents {}
|
impl EventEmitter<Event> for ExtensionEvents {}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
mod context_server;
|
||||||
mod lsp;
|
mod lsp;
|
||||||
mod slash_command;
|
mod slash_command;
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
pub use context_server::*;
|
||||||
pub use lsp::*;
|
pub use lsp::*;
|
||||||
pub use slash_command::*;
|
pub use slash_command::*;
|
||||||
|
|
||||||
|
10
crates/extension/src/types/context_server.rs
Normal file
10
crates/extension/src/types/context_server.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/// Configuration for a context server.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ContextServerConfiguration {
|
||||||
|
/// Installation instructions for the user.
|
||||||
|
pub installation_instructions: String,
|
||||||
|
/// Default settings for the context server.
|
||||||
|
pub default_settings: String,
|
||||||
|
/// JSON schema describing server settings.
|
||||||
|
pub settings_schema: serde_json::Value,
|
||||||
|
}
|
@ -18,6 +18,7 @@ pub use wit::{
|
|||||||
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
||||||
KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree, download_file,
|
KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree, download_file,
|
||||||
make_file_executable,
|
make_file_executable,
|
||||||
|
zed::extension::context_server::ContextServerConfiguration,
|
||||||
zed::extension::github::{
|
zed::extension::github::{
|
||||||
GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
|
GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
|
||||||
latest_github_release,
|
latest_github_release,
|
||||||
@ -159,6 +160,15 @@ pub trait Extension: Send + Sync {
|
|||||||
Err("`context_server_command` not implemented".to_string())
|
Err("`context_server_command` not implemented".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the configuration options for the specified context server.
|
||||||
|
fn context_server_configuration(
|
||||||
|
&mut self,
|
||||||
|
_context_server_id: &ContextServerId,
|
||||||
|
_project: &Project,
|
||||||
|
) -> Result<Option<ContextServerConfiguration>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a list of package names as suggestions to be included in the
|
/// Returns a list of package names as suggestions to be included in the
|
||||||
/// search results of the `/docs` slash command.
|
/// search results of the `/docs` slash command.
|
||||||
///
|
///
|
||||||
@ -342,6 +352,14 @@ impl wit::Guest for Component {
|
|||||||
extension().context_server_command(&context_server_id, project)
|
extension().context_server_command(&context_server_id, project)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn context_server_configuration(
|
||||||
|
context_server_id: String,
|
||||||
|
project: &Project,
|
||||||
|
) -> Result<Option<ContextServerConfiguration>, String> {
|
||||||
|
let context_server_id = ContextServerId(context_server_id);
|
||||||
|
extension().context_server_configuration(&context_server_id, project)
|
||||||
|
}
|
||||||
|
|
||||||
fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
|
fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
|
||||||
extension().suggest_docs_packages(provider)
|
extension().suggest_docs_packages(provider)
|
||||||
}
|
}
|
||||||
|
11
crates/extension_api/wit/since_v0.5.0/context-server.wit
Normal file
11
crates/extension_api/wit/since_v0.5.0/context-server.wit
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
interface context-server {
|
||||||
|
///
|
||||||
|
record context-server-configuration {
|
||||||
|
///
|
||||||
|
installation-instructions: string,
|
||||||
|
///
|
||||||
|
settings-schema: string,
|
||||||
|
///
|
||||||
|
default-settings: string,
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package zed:extension;
|
package zed:extension;
|
||||||
|
|
||||||
world extension {
|
world extension {
|
||||||
|
import context-server;
|
||||||
import github;
|
import github;
|
||||||
import http-client;
|
import http-client;
|
||||||
import platform;
|
import platform;
|
||||||
@ -8,6 +9,7 @@ world extension {
|
|||||||
import nodejs;
|
import nodejs;
|
||||||
|
|
||||||
use common.{env-vars, range};
|
use common.{env-vars, range};
|
||||||
|
use context-server.{context-server-configuration};
|
||||||
use lsp.{completion, symbol};
|
use lsp.{completion, symbol};
|
||||||
use process.{command};
|
use process.{command};
|
||||||
use slash-command.{slash-command, slash-command-argument-completion, slash-command-output};
|
use slash-command.{slash-command, slash-command-argument-completion, slash-command-output};
|
||||||
@ -139,6 +141,9 @@ world extension {
|
|||||||
/// Returns the command used to start up a context server.
|
/// Returns the command used to start up a context server.
|
||||||
export context-server-command: func(context-server-id: string, project: borrow<project>) -> result<command, string>;
|
export context-server-command: func(context-server-id: string, project: borrow<project>) -> result<command, string>;
|
||||||
|
|
||||||
|
/// Returns the configuration for a context server.
|
||||||
|
export context-server-configuration: func(context-server-id: string, project: borrow<project>) -> result<option<context-server-configuration>, string>;
|
||||||
|
|
||||||
/// Returns a list of packages as suggestions to be included in the `/docs`
|
/// Returns a list of packages as suggestions to be included in the `/docs`
|
||||||
/// search results.
|
/// search results.
|
||||||
///
|
///
|
||||||
|
@ -431,6 +431,13 @@ impl ExtensionStore {
|
|||||||
.filter_map(|extension| extension.dev.then_some(&extension.manifest))
|
.filter_map(|extension| extension.dev.then_some(&extension.manifest))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extension_manifest_for_id(&self, extension_id: &str) -> Option<&Arc<ExtensionManifest>> {
|
||||||
|
self.extension_index
|
||||||
|
.extensions
|
||||||
|
.get(extension_id)
|
||||||
|
.map(|extension| &extension.manifest)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the names of themes provided by extensions.
|
/// Returns the names of themes provided by extensions.
|
||||||
pub fn extension_themes<'a>(
|
pub fn extension_themes<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
@ -744,8 +751,18 @@ impl ExtensionStore {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let ExtensionOperation::Install = operation {
|
if let ExtensionOperation::Install = operation {
|
||||||
this.update( cx, |_, cx| {
|
this.update( cx, |this, cx| {
|
||||||
cx.emit(Event::ExtensionInstalled(extension_id));
|
cx.emit(Event::ExtensionInstalled(extension_id.clone()));
|
||||||
|
if let Some(events) = ExtensionEvents::try_global(cx) {
|
||||||
|
if let Some(manifest) = this.extension_manifest_for_id(&extension_id) {
|
||||||
|
events.update(cx, |this, cx| {
|
||||||
|
this.emit(
|
||||||
|
extension::Event::ExtensionInstalled(manifest.clone()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@ -935,6 +952,17 @@ impl ExtensionStore {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.update(cx, |this, cx| this.reload(None, cx))?.await;
|
this.update(cx, |this, cx| this.reload(None, cx))?.await;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
cx.emit(Event::ExtensionInstalled(extension_id.clone()));
|
||||||
|
if let Some(events) = ExtensionEvents::try_global(cx) {
|
||||||
|
if let Some(manifest) = this.extension_manifest_for_id(&extension_id) {
|
||||||
|
events.update(cx, |this, cx| {
|
||||||
|
this.emit(extension::Event::ExtensionInstalled(manifest.clone()), cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,9 @@ use crate::ExtensionManifest;
|
|||||||
use anyhow::{Context as _, Result, anyhow, bail};
|
use anyhow::{Context as _, Result, anyhow, bail};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use extension::{
|
use extension::{
|
||||||
CodeLabel, Command, Completion, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate,
|
CodeLabel, Command, Completion, ContextServerConfiguration, ExtensionHostProxy,
|
||||||
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
|
KeyValueStoreDelegate, ProjectDelegate, SlashCommand, SlashCommandArgumentCompletion,
|
||||||
|
SlashCommandOutput, Symbol, WorktreeDelegate,
|
||||||
};
|
};
|
||||||
use fs::{Fs, normalize_path};
|
use fs::{Fs, normalize_path};
|
||||||
use futures::future::LocalBoxFuture;
|
use futures::future::LocalBoxFuture;
|
||||||
@ -306,6 +307,33 @@ impl extension::Extension for WasmExtension {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn context_server_configuration(
|
||||||
|
&self,
|
||||||
|
context_server_id: Arc<str>,
|
||||||
|
project: Arc<dyn ProjectDelegate>,
|
||||||
|
) -> Result<Option<ContextServerConfiguration>> {
|
||||||
|
self.call(|extension, store| {
|
||||||
|
async move {
|
||||||
|
let project_resource = store.data_mut().table().push(project)?;
|
||||||
|
let Some(configuration) = extension
|
||||||
|
.call_context_server_configuration(
|
||||||
|
store,
|
||||||
|
context_server_id.clone(),
|
||||||
|
project_resource,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.map_err(|err| anyhow!("{err}"))?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(configuration.try_into()?))
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> {
|
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> {
|
||||||
self.call(|extension, store| {
|
self.call(|extension, store| {
|
||||||
async move {
|
async move {
|
||||||
|
@ -25,6 +25,7 @@ use wasmtime::{
|
|||||||
pub use latest::CodeLabelSpanLiteral;
|
pub use latest::CodeLabelSpanLiteral;
|
||||||
pub use latest::{
|
pub use latest::{
|
||||||
CodeLabel, CodeLabelSpan, Command, ExtensionProject, Range, SlashCommand,
|
CodeLabel, CodeLabelSpan, Command, ExtensionProject, Range, SlashCommand,
|
||||||
|
zed::extension::context_server::ContextServerConfiguration,
|
||||||
zed::extension::lsp::{
|
zed::extension::lsp::{
|
||||||
Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
|
Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
|
||||||
},
|
},
|
||||||
@ -726,6 +727,29 @@ impl Extension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn call_context_server_configuration(
|
||||||
|
&self,
|
||||||
|
store: &mut Store<WasmState>,
|
||||||
|
context_server_id: Arc<str>,
|
||||||
|
project: Resource<ExtensionProject>,
|
||||||
|
) -> Result<Result<Option<ContextServerConfiguration>, String>> {
|
||||||
|
match self {
|
||||||
|
Extension::V0_5_0(ext) => {
|
||||||
|
ext.call_context_server_configuration(store, &context_server_id, project)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Extension::V0_0_1(_)
|
||||||
|
| Extension::V0_0_4(_)
|
||||||
|
| Extension::V0_0_6(_)
|
||||||
|
| Extension::V0_1_0(_)
|
||||||
|
| Extension::V0_2_0(_)
|
||||||
|
| Extension::V0_3_0(_)
|
||||||
|
| Extension::V0_4_0(_) => Err(anyhow!(
|
||||||
|
"`context_server_configuration` not available prior to v0.5.0"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn call_suggest_docs_packages(
|
pub async fn call_suggest_docs_packages(
|
||||||
&self,
|
&self,
|
||||||
store: &mut Store<WasmState>,
|
store: &mut Store<WasmState>,
|
||||||
|
@ -247,6 +247,21 @@ impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
|
||||||
|
let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
|
||||||
|
.context("Failed to parse settings_schema")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
installation_instructions: value.installation_instructions,
|
||||||
|
default_settings: value.default_settings,
|
||||||
|
settings_schema,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HostKeyValueStore for WasmState {
|
impl HostKeyValueStore for WasmState {
|
||||||
async fn insert(
|
async fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -610,6 +625,9 @@ impl process::Host for WasmState {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl slash_command::Host for WasmState {}
|
impl slash_command::Host for WasmState {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl context_server::Host for WasmState {}
|
||||||
|
|
||||||
impl ExtensionImports for WasmState {
|
impl ExtensionImports for WasmState {
|
||||||
async fn get_settings(
|
async fn get_settings(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -17,6 +17,7 @@ client.workspace = true
|
|||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
|
extension.workspace = true
|
||||||
extension_host.workspace = true
|
extension_host.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
|
@ -246,6 +246,12 @@ fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ExtensionCardButtons {
|
||||||
|
install_or_uninstall: Button,
|
||||||
|
upgrade: Option<Button>,
|
||||||
|
configure: Option<Button>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ExtensionsPage {
|
pub struct ExtensionsPage {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
list: UniformListScrollHandle,
|
list: UniformListScrollHandle,
|
||||||
@ -522,6 +528,8 @@ impl ExtensionsPage {
|
|||||||
|
|
||||||
let repository_url = extension.repository.clone();
|
let repository_url = extension.repository.clone();
|
||||||
|
|
||||||
|
let can_configure = !extension.context_servers.is_empty();
|
||||||
|
|
||||||
ExtensionCard::new()
|
ExtensionCard::new()
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@ -568,7 +576,36 @@ impl ExtensionsPage {
|
|||||||
})
|
})
|
||||||
.color(Color::Accent)
|
.color(Color::Accent)
|
||||||
.disabled(matches!(status, ExtensionStatus::Removing)),
|
.disabled(matches!(status, ExtensionStatus::Removing)),
|
||||||
),
|
)
|
||||||
|
.when(can_configure, |this| {
|
||||||
|
this.child(
|
||||||
|
Button::new(
|
||||||
|
SharedString::from(format!("configure-{}", extension.id)),
|
||||||
|
"Configure",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
.on_click({
|
||||||
|
let manifest = Arc::new(extension.clone());
|
||||||
|
move |_, _, cx| {
|
||||||
|
if let Some(events) =
|
||||||
|
extension::ExtensionEvents::try_global(cx)
|
||||||
|
{
|
||||||
|
events.update(cx, |this, cx| {
|
||||||
|
this.emit(
|
||||||
|
extension::Event::ConfigureExtensionRequested(
|
||||||
|
manifest.clone(),
|
||||||
|
),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.color(Color::Accent)
|
||||||
|
.disabled(matches!(status, ExtensionStatus::Installing)),
|
||||||
|
)
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@ -629,8 +666,7 @@ impl ExtensionsPage {
|
|||||||
let has_dev_extension = Self::dev_extension_exists(&extension.id, cx);
|
let has_dev_extension = Self::dev_extension_exists(&extension.id, cx);
|
||||||
|
|
||||||
let extension_id = extension.id.clone();
|
let extension_id = extension.id.clone();
|
||||||
let (install_or_uninstall_button, upgrade_button) =
|
let buttons = self.buttons_for_entry(extension, &status, has_dev_extension, cx);
|
||||||
self.buttons_for_entry(extension, &status, has_dev_extension, cx);
|
|
||||||
let version = extension.manifest.version.clone();
|
let version = extension.manifest.version.clone();
|
||||||
let repository_url = extension.manifest.repository.clone();
|
let repository_url = extension.manifest.repository.clone();
|
||||||
let authors = extension.manifest.authors.clone();
|
let authors = extension.manifest.authors.clone();
|
||||||
@ -695,8 +731,9 @@ impl ExtensionsPage {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.children(upgrade_button)
|
.children(buttons.upgrade)
|
||||||
.child(install_or_uninstall_button),
|
.children(buttons.configure)
|
||||||
|
.child(buttons.install_or_uninstall),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@ -861,22 +898,35 @@ impl ExtensionsPage {
|
|||||||
status: &ExtensionStatus,
|
status: &ExtensionStatus,
|
||||||
has_dev_extension: bool,
|
has_dev_extension: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> (Button, Option<Button>) {
|
) -> ExtensionCardButtons {
|
||||||
let is_compatible =
|
let is_compatible =
|
||||||
extension_host::is_version_compatible(ReleaseChannel::global(cx), extension);
|
extension_host::is_version_compatible(ReleaseChannel::global(cx), extension);
|
||||||
|
|
||||||
if has_dev_extension {
|
if has_dev_extension {
|
||||||
// If we have a dev extension for the given extension, just treat it as uninstalled.
|
// If we have a dev extension for the given extension, just treat it as uninstalled.
|
||||||
// The button here is a placeholder, as it won't be interactable anyways.
|
// The button here is a placeholder, as it won't be interactable anyways.
|
||||||
return (
|
return ExtensionCardButtons {
|
||||||
Button::new(SharedString::from(extension.id.clone()), "Install"),
|
install_or_uninstall: Button::new(
|
||||||
None,
|
SharedString::from(extension.id.clone()),
|
||||||
);
|
"Install",
|
||||||
|
),
|
||||||
|
configure: None,
|
||||||
|
upgrade: None,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_configurable = extension
|
||||||
|
.manifest
|
||||||
|
.provides
|
||||||
|
.contains(&ExtensionProvides::ContextServers);
|
||||||
|
|
||||||
match status.clone() {
|
match status.clone() {
|
||||||
ExtensionStatus::NotInstalled => (
|
ExtensionStatus::NotInstalled => ExtensionCardButtons {
|
||||||
Button::new(SharedString::from(extension.id.clone()), "Install").on_click({
|
install_or_uninstall: Button::new(
|
||||||
|
SharedString::from(extension.id.clone()),
|
||||||
|
"Install",
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
let extension_id = extension.id.clone();
|
let extension_id = extension.id.clone();
|
||||||
move |_, _, cx| {
|
move |_, _, cx| {
|
||||||
telemetry::event!("Extension Installed");
|
telemetry::event!("Extension Installed");
|
||||||
@ -885,20 +935,41 @@ impl ExtensionsPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
None,
|
configure: None,
|
||||||
),
|
upgrade: None,
|
||||||
ExtensionStatus::Installing => (
|
},
|
||||||
Button::new(SharedString::from(extension.id.clone()), "Install").disabled(true),
|
ExtensionStatus::Installing => ExtensionCardButtons {
|
||||||
None,
|
install_or_uninstall: Button::new(
|
||||||
),
|
SharedString::from(extension.id.clone()),
|
||||||
ExtensionStatus::Upgrading => (
|
"Install",
|
||||||
Button::new(SharedString::from(extension.id.clone()), "Uninstall").disabled(true),
|
)
|
||||||
Some(
|
.disabled(true),
|
||||||
|
configure: None,
|
||||||
|
upgrade: None,
|
||||||
|
},
|
||||||
|
ExtensionStatus::Upgrading => ExtensionCardButtons {
|
||||||
|
install_or_uninstall: Button::new(
|
||||||
|
SharedString::from(extension.id.clone()),
|
||||||
|
"Uninstall",
|
||||||
|
)
|
||||||
|
.disabled(true),
|
||||||
|
configure: is_configurable.then(|| {
|
||||||
|
Button::new(
|
||||||
|
SharedString::from(format!("configure-{}", extension.id.clone())),
|
||||||
|
"Configure",
|
||||||
|
)
|
||||||
|
.disabled(true)
|
||||||
|
}),
|
||||||
|
upgrade: Some(
|
||||||
Button::new(SharedString::from(extension.id.clone()), "Upgrade").disabled(true),
|
Button::new(SharedString::from(extension.id.clone()), "Upgrade").disabled(true),
|
||||||
),
|
),
|
||||||
),
|
},
|
||||||
ExtensionStatus::Installed(installed_version) => (
|
ExtensionStatus::Installed(installed_version) => ExtensionCardButtons {
|
||||||
Button::new(SharedString::from(extension.id.clone()), "Uninstall").on_click({
|
install_or_uninstall: Button::new(
|
||||||
|
SharedString::from(extension.id.clone()),
|
||||||
|
"Uninstall",
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
let extension_id = extension.id.clone();
|
let extension_id = extension.id.clone();
|
||||||
move |_, _, cx| {
|
move |_, _, cx| {
|
||||||
telemetry::event!("Extension Uninstalled", extension_id);
|
telemetry::event!("Extension Uninstalled", extension_id);
|
||||||
@ -907,7 +978,32 @@ impl ExtensionsPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
if installed_version == extension.manifest.version {
|
configure: is_configurable.then(|| {
|
||||||
|
Button::new(
|
||||||
|
SharedString::from(format!("configure-{}", extension.id.clone())),
|
||||||
|
"Configure",
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
let extension_id = extension.id.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
if let Some(manifest) = ExtensionStore::global(cx)
|
||||||
|
.read(cx)
|
||||||
|
.extension_manifest_for_id(&extension_id)
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
if let Some(events) = extension::ExtensionEvents::try_global(cx) {
|
||||||
|
events.update(cx, |this, cx| {
|
||||||
|
this.emit(
|
||||||
|
extension::Event::ConfigureExtensionRequested(manifest),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
upgrade: if installed_version == extension.manifest.version {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(
|
Some(
|
||||||
@ -944,11 +1040,22 @@ impl ExtensionsPage {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
ExtensionStatus::Removing => (
|
ExtensionStatus::Removing => ExtensionCardButtons {
|
||||||
Button::new(SharedString::from(extension.id.clone()), "Uninstall").disabled(true),
|
install_or_uninstall: Button::new(
|
||||||
None,
|
SharedString::from(extension.id.clone()),
|
||||||
),
|
"Uninstall",
|
||||||
|
)
|
||||||
|
.disabled(true),
|
||||||
|
configure: is_configurable.then(|| {
|
||||||
|
Button::new(
|
||||||
|
SharedString::from(format!("configure-{}", extension.id.clone())),
|
||||||
|
"Configure",
|
||||||
|
)
|
||||||
|
.disabled(true)
|
||||||
|
}),
|
||||||
|
upgrade: None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3790,13 +3790,11 @@ impl LspStore {
|
|||||||
evt: &extension::Event,
|
evt: &extension::Event,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
#[expect(
|
match evt {
|
||||||
irrefutable_let_patterns,
|
extension::Event::ExtensionInstalled(_)
|
||||||
reason = "Make sure to handle new event types in extension properly"
|
| extension::Event::ConfigureExtensionRequested(_) => return,
|
||||||
)]
|
extension::Event::ExtensionsInstalledChanged => {}
|
||||||
let extension::Event::ExtensionsInstalledChanged = evt else {
|
}
|
||||||
return;
|
|
||||||
};
|
|
||||||
if self.as_local().is_none() {
|
if self.as_local().is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -513,6 +513,7 @@ fn main() {
|
|||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
app_state.client.clone(),
|
app_state.client.clone(),
|
||||||
prompt_builder.clone(),
|
prompt_builder.clone(),
|
||||||
|
app_state.languages.clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
assistant_tools::init(app_state.client.http_client(), cx);
|
assistant_tools::init(app_state.client.http_client(), cx);
|
||||||
|
@ -15,6 +15,7 @@ publish = false
|
|||||||
|
|
||||||
### BEGIN HAKARI SECTION
|
### BEGIN HAKARI SECTION
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ahash = { version = "0.8", features = ["serde"] }
|
||||||
aho-corasick = { version = "1" }
|
aho-corasick = { version = "1" }
|
||||||
anstream = { version = "0.6" }
|
anstream = { version = "0.6" }
|
||||||
arrayvec = { version = "0.7", features = ["serde"] }
|
arrayvec = { version = "0.7", features = ["serde"] }
|
||||||
@ -65,6 +66,7 @@ hashbrown-3575ec1268b04181 = { package = "hashbrown", version = "0.15", features
|
|||||||
hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features = ["raw"] }
|
hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features = ["raw"] }
|
||||||
hmac = { version = "0.12", default-features = false, features = ["reset"] }
|
hmac = { version = "0.12", default-features = false, features = ["reset"] }
|
||||||
hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
|
hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
|
||||||
|
idna = { version = "1" }
|
||||||
indexmap = { version = "2", features = ["serde"] }
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
|
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
|
||||||
libc = { version = "0.2", features = ["extra_traits"] }
|
libc = { version = "0.2", features = ["extra_traits"] }
|
||||||
@ -78,6 +80,8 @@ miniz_oxide = { version = "0.8", features = ["simd"] }
|
|||||||
nom = { version = "7" }
|
nom = { version = "7" }
|
||||||
num-bigint = { version = "0.4" }
|
num-bigint = { version = "0.4" }
|
||||||
num-integer = { version = "0.1", features = ["i128"] }
|
num-integer = { version = "0.1", features = ["i128"] }
|
||||||
|
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
||||||
|
num-rational = { version = "0.4", features = ["num-bigint-std"] }
|
||||||
num-traits = { version = "0.2", features = ["i128", "libm"] }
|
num-traits = { version = "0.2", features = ["i128", "libm"] }
|
||||||
once_cell = { version = "1" }
|
once_cell = { version = "1" }
|
||||||
percent-encoding = { version = "2" }
|
percent-encoding = { version = "2" }
|
||||||
@ -125,6 +129,7 @@ wasmtime-cranelift = { version = "29", default-features = false, features = ["co
|
|||||||
wasmtime-environ = { version = "29", default-features = false, features = ["compile", "component-model", "demangle", "gc-drc"] }
|
wasmtime-environ = { version = "29", default-features = false, features = ["compile", "component-model", "demangle", "gc-drc"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
ahash = { version = "0.8", features = ["serde"] }
|
||||||
aho-corasick = { version = "1" }
|
aho-corasick = { version = "1" }
|
||||||
anstream = { version = "0.6" }
|
anstream = { version = "0.6" }
|
||||||
arrayvec = { version = "0.7", features = ["serde"] }
|
arrayvec = { version = "0.7", features = ["serde"] }
|
||||||
@ -177,6 +182,7 @@ hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features
|
|||||||
heck = { version = "0.4", features = ["unicode"] }
|
heck = { version = "0.4", features = ["unicode"] }
|
||||||
hmac = { version = "0.12", default-features = false, features = ["reset"] }
|
hmac = { version = "0.12", default-features = false, features = ["reset"] }
|
||||||
hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
|
hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
|
||||||
|
idna = { version = "1" }
|
||||||
indexmap = { version = "2", features = ["serde"] }
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" }
|
itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" }
|
||||||
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
|
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
|
||||||
@ -191,6 +197,8 @@ miniz_oxide = { version = "0.8", features = ["simd"] }
|
|||||||
nom = { version = "7" }
|
nom = { version = "7" }
|
||||||
num-bigint = { version = "0.4" }
|
num-bigint = { version = "0.4" }
|
||||||
num-integer = { version = "0.1", features = ["i128"] }
|
num-integer = { version = "0.1", features = ["i128"] }
|
||||||
|
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
||||||
|
num-rational = { version = "0.4", features = ["num-bigint-std"] }
|
||||||
num-traits = { version = "0.2", features = ["i128", "libm"] }
|
num-traits = { version = "0.2", features = ["i128", "libm"] }
|
||||||
once_cell = { version = "1" }
|
once_cell = { version = "1" }
|
||||||
percent-encoding = { version = "2" }
|
percent-encoding = { version = "2" }
|
||||||
@ -352,7 +360,7 @@ tower = { version = "0.5", default-features = false, features = ["timeout", "uti
|
|||||||
|
|
||||||
[target.x86_64-unknown-linux-gnu.dependencies]
|
[target.x86_64-unknown-linux-gnu.dependencies]
|
||||||
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
||||||
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng", "std"] }
|
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] }
|
||||||
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
||||||
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
||||||
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
||||||
@ -372,7 +380,6 @@ mio = { version = "1", features = ["net", "os-ext"] }
|
|||||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
|
||||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||||
quote = { version = "1" }
|
quote = { version = "1" }
|
||||||
@ -395,7 +402,7 @@ zvariant = { version = "5", default-features = false, features = ["enumflags2",
|
|||||||
|
|
||||||
[target.x86_64-unknown-linux-gnu.build-dependencies]
|
[target.x86_64-unknown-linux-gnu.build-dependencies]
|
||||||
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
||||||
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng", "std"] }
|
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] }
|
||||||
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
||||||
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
||||||
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
||||||
@ -415,7 +422,6 @@ mio = { version = "1", features = ["net", "os-ext"] }
|
|||||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
|
||||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||||
@ -436,7 +442,7 @@ zvariant = { version = "5", default-features = false, features = ["enumflags2",
|
|||||||
|
|
||||||
[target.aarch64-unknown-linux-gnu.dependencies]
|
[target.aarch64-unknown-linux-gnu.dependencies]
|
||||||
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
||||||
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng", "std"] }
|
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] }
|
||||||
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
||||||
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
||||||
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
||||||
@ -456,7 +462,6 @@ mio = { version = "1", features = ["net", "os-ext"] }
|
|||||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
|
||||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||||
quote = { version = "1" }
|
quote = { version = "1" }
|
||||||
@ -479,7 +484,7 @@ zvariant = { version = "5", default-features = false, features = ["enumflags2",
|
|||||||
|
|
||||||
[target.aarch64-unknown-linux-gnu.build-dependencies]
|
[target.aarch64-unknown-linux-gnu.build-dependencies]
|
||||||
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
||||||
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng", "std"] }
|
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] }
|
||||||
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
||||||
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
||||||
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
||||||
@ -499,7 +504,6 @@ mio = { version = "1", features = ["net", "os-ext"] }
|
|||||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
|
||||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||||
@ -569,7 +573,7 @@ windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", feat
|
|||||||
|
|
||||||
[target.x86_64-unknown-linux-musl.dependencies]
|
[target.x86_64-unknown-linux-musl.dependencies]
|
||||||
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
||||||
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng", "std"] }
|
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] }
|
||||||
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
||||||
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
||||||
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
||||||
@ -589,7 +593,6 @@ mio = { version = "1", features = ["net", "os-ext"] }
|
|||||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
|
||||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||||
quote = { version = "1" }
|
quote = { version = "1" }
|
||||||
@ -612,7 +615,7 @@ zvariant = { version = "5", default-features = false, features = ["enumflags2",
|
|||||||
|
|
||||||
[target.x86_64-unknown-linux-musl.build-dependencies]
|
[target.x86_64-unknown-linux-musl.build-dependencies]
|
||||||
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
||||||
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng", "std"] }
|
ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] }
|
||||||
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
bytemuck = { version = "1", default-features = false, features = ["min_const_generics"] }
|
||||||
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
cipher = { version = "0.4", default-features = false, features = ["block-padding", "rand_core", "zeroize"] }
|
||||||
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
crypto-common = { version = "0.1", default-features = false, features = ["rand_core", "std"] }
|
||||||
@ -632,7 +635,6 @@ mio = { version = "1", features = ["net", "os-ext"] }
|
|||||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||||
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
num-bigint-dig = { version = "0.8", features = ["i128", "prime", "zeroize"] }
|
||||||
num-iter = { version = "0.1", default-features = false, features = ["i128", "std"] }
|
|
||||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user