agent: Rework context server settings (#32793)
This changes the way context servers are organised. We now store a `source` which indicates if the MCP server is configured manually or managed by an extension. Release Notes: - N/A --------- Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
parent
c35f22dde0
commit
d7db4d4e0a
10 changed files with 639 additions and 197 deletions
|
@ -586,7 +586,7 @@ impl AgentConfiguration {
|
||||||
if let Some(server) =
|
if let Some(server) =
|
||||||
this.get_server(&context_server_id)
|
this.get_server(&context_server_id)
|
||||||
{
|
{
|
||||||
this.start_server(server, cx).log_err();
|
this.start_server(server, cx);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use context_server::ContextServerCommand;
|
use context_server::ContextServerCommand;
|
||||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
||||||
use project::project_settings::{ContextServerConfiguration, ProjectSettings};
|
use project::project_settings::{ContextServerSettings, ProjectSettings};
|
||||||
use serde_json::json;
|
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||||
use ui_input::SingleLineInput;
|
use ui_input::SingleLineInput;
|
||||||
|
@ -81,13 +80,12 @@ impl AddContextServerModal {
|
||||||
update_settings_file::<ProjectSettings>(fs.clone(), cx, |settings, _| {
|
update_settings_file::<ProjectSettings>(fs.clone(), cx, |settings, _| {
|
||||||
settings.context_servers.insert(
|
settings.context_servers.insert(
|
||||||
name.into(),
|
name.into(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Custom {
|
||||||
command: Some(ContextServerCommand {
|
command: ContextServerCommand {
|
||||||
path,
|
path,
|
||||||
args,
|
args,
|
||||||
env: None,
|
env: None,
|
||||||
}),
|
},
|
||||||
settings: Some(json!({})),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle};
|
||||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||||
use project::{
|
use project::{
|
||||||
context_server_store::{ContextServerStatus, ContextServerStore},
|
context_server_store::{ContextServerStatus, ContextServerStore},
|
||||||
project_settings::{ContextServerConfiguration, ProjectSettings},
|
project_settings::{ContextServerSettings, ProjectSettings},
|
||||||
};
|
};
|
||||||
use settings::{Settings as _, update_settings_file};
|
use settings::{Settings as _, update_settings_file};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
|
@ -175,8 +175,9 @@ impl ConfigureContextServerModal {
|
||||||
let settings_changed = ProjectSettings::get_global(cx)
|
let settings_changed = ProjectSettings::get_global(cx)
|
||||||
.context_servers
|
.context_servers
|
||||||
.get(&id.0)
|
.get(&id.0)
|
||||||
.map_or(true, |config| {
|
.map_or(true, |settings| match settings {
|
||||||
config.settings.as_ref() != Some(&settings_value)
|
ContextServerSettings::Custom { .. } => false,
|
||||||
|
ContextServerSettings::Extension { settings } => settings != &settings_value,
|
||||||
});
|
});
|
||||||
|
|
||||||
let is_running = self.context_server_store.read(cx).status_for_server(&id)
|
let is_running = self.context_server_store.read(cx).status_for_server(&id)
|
||||||
|
@ -221,17 +222,12 @@ impl ConfigureContextServerModal {
|
||||||
update_settings_file::<ProjectSettings>(workspace.read(cx).app_state().fs.clone(), cx, {
|
update_settings_file::<ProjectSettings>(workspace.read(cx).app_state().fs.clone(), cx, {
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
|settings, _| {
|
|settings, _| {
|
||||||
if let Some(server_config) = settings.context_servers.get_mut(&id.0) {
|
settings.context_servers.insert(
|
||||||
server_config.settings = Some(settings_value);
|
id.0,
|
||||||
} else {
|
ContextServerSettings::Extension {
|
||||||
settings.context_servers.insert(
|
settings: settings_value,
|
||||||
id.0,
|
},
|
||||||
ContextServerConfiguration {
|
);
|
||||||
settings: Some(settings_value),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -938,24 +938,33 @@ impl ExtensionImports for WasmState {
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
"context_servers" => {
|
"context_servers" => {
|
||||||
let configuration = key
|
let settings = key
|
||||||
.and_then(|key| {
|
.and_then(|key| {
|
||||||
ProjectSettings::get(location, cx)
|
ProjectSettings::get(location, cx)
|
||||||
.context_servers
|
.context_servers
|
||||||
.get(key.as_str())
|
.get(key.as_str())
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.context("Failed to get context server configuration")?;
|
||||||
Ok(serde_json::to_string(&settings::ContextServerSettings {
|
|
||||||
command: configuration.command.map(|command| {
|
match settings {
|
||||||
settings::CommandSettings {
|
project::project_settings::ContextServerSettings::Custom {
|
||||||
|
command,
|
||||||
|
} => Ok(serde_json::to_string(&settings::ContextServerSettings {
|
||||||
|
command: Some(settings::CommandSettings {
|
||||||
path: Some(command.path),
|
path: Some(command.path),
|
||||||
arguments: Some(command.args),
|
arguments: Some(command.args),
|
||||||
env: command.env.map(|env| env.into_iter().collect()),
|
env: command.env.map(|env| env.into_iter().collect()),
|
||||||
}
|
}),
|
||||||
}),
|
settings: None,
|
||||||
settings: configuration.settings,
|
})?),
|
||||||
})?)
|
project::project_settings::ContextServerSettings::Extension {
|
||||||
|
settings,
|
||||||
|
} => Ok(serde_json::to_string(&settings::ContextServerSettings {
|
||||||
|
command: None,
|
||||||
|
settings: Some(settings),
|
||||||
|
})?),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
bail!("Unknown settings category: {}", category);
|
bail!("Unknown settings category: {}", category);
|
||||||
|
|
|
@ -75,3 +75,9 @@ pub(crate) mod m_2025_05_29 {
|
||||||
|
|
||||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) mod m_2025_06_16 {
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||||
|
}
|
||||||
|
|
152
crates/migrator/src/migrations/m_2025_06_16/settings.rs
Normal file
152
crates/migrator/src/migrations/m_2025_06_16/settings.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use tree_sitter::{Query, QueryMatch};
|
||||||
|
|
||||||
|
use crate::MigrationPatterns;
|
||||||
|
|
||||||
|
pub const SETTINGS_PATTERNS: MigrationPatterns = &[
|
||||||
|
(
|
||||||
|
SETTINGS_CUSTOM_CONTEXT_SERVER_PATTERN,
|
||||||
|
migrate_custom_context_server_settings,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SETTINGS_EXTENSION_CONTEXT_SERVER_PATTERN,
|
||||||
|
migrate_extension_context_server_settings,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SETTINGS_EMPTY_CONTEXT_SERVER_PATTERN,
|
||||||
|
migrate_empty_context_server_settings,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const SETTINGS_CUSTOM_CONTEXT_SERVER_PATTERN: &str = r#"(document
|
||||||
|
(object
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @context-servers)
|
||||||
|
value: (object
|
||||||
|
(pair
|
||||||
|
key: (string)
|
||||||
|
value: (object
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @previous-key)
|
||||||
|
value: (object)
|
||||||
|
)*
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @key)
|
||||||
|
value: (object)
|
||||||
|
)
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @next-key)
|
||||||
|
value: (object)
|
||||||
|
)*
|
||||||
|
) @server-settings
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(#eq? @context-servers "context_servers")
|
||||||
|
(#eq? @key "command")
|
||||||
|
(#not-eq? @previous-key "source")
|
||||||
|
(#not-eq? @next-key "source")
|
||||||
|
)"#;
|
||||||
|
|
||||||
|
fn migrate_custom_context_server_settings(
|
||||||
|
_contents: &str,
|
||||||
|
mat: &QueryMatch,
|
||||||
|
query: &Query,
|
||||||
|
) -> Option<(Range<usize>, String)> {
|
||||||
|
let server_settings_index = query.capture_index_for_name("server-settings")?;
|
||||||
|
let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
|
||||||
|
// Move forward 1 to get inside the object
|
||||||
|
let start = server_settings.start_byte() + 1;
|
||||||
|
|
||||||
|
Some((
|
||||||
|
start..start,
|
||||||
|
r#"
|
||||||
|
"source": "custom","#
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const SETTINGS_EXTENSION_CONTEXT_SERVER_PATTERN: &str = r#"(document
|
||||||
|
(object
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @context-servers)
|
||||||
|
value: (object
|
||||||
|
(pair
|
||||||
|
key: (string)
|
||||||
|
value: (object
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @previous-key)
|
||||||
|
value: (object)
|
||||||
|
)*
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @key)
|
||||||
|
value: (object)
|
||||||
|
)
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @next-key)
|
||||||
|
value: (object)
|
||||||
|
)*
|
||||||
|
) @server-settings
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(#eq? @context-servers "context_servers")
|
||||||
|
(#eq? @key "settings")
|
||||||
|
(#not-match? @previous-key "^command|source$")
|
||||||
|
(#not-match? @next-key "^command|source$")
|
||||||
|
)"#;
|
||||||
|
|
||||||
|
fn migrate_extension_context_server_settings(
|
||||||
|
_contents: &str,
|
||||||
|
mat: &QueryMatch,
|
||||||
|
query: &Query,
|
||||||
|
) -> Option<(Range<usize>, String)> {
|
||||||
|
let server_settings_index = query.capture_index_for_name("server-settings")?;
|
||||||
|
let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
|
||||||
|
// Move forward 1 to get inside the object
|
||||||
|
let start = server_settings.start_byte() + 1;
|
||||||
|
|
||||||
|
Some((
|
||||||
|
start..start,
|
||||||
|
r#"
|
||||||
|
"source": "extension","#
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const SETTINGS_EMPTY_CONTEXT_SERVER_PATTERN: &str = r#"(document
|
||||||
|
(object
|
||||||
|
(pair
|
||||||
|
key: (string (string_content) @context-servers)
|
||||||
|
value: (object
|
||||||
|
(pair
|
||||||
|
key: (string)
|
||||||
|
value: (object) @server-settings
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(#eq? @context-servers "context_servers")
|
||||||
|
(#eq? @server-settings "{}")
|
||||||
|
)"#;
|
||||||
|
|
||||||
|
fn migrate_empty_context_server_settings(
|
||||||
|
_contents: &str,
|
||||||
|
mat: &QueryMatch,
|
||||||
|
query: &Query,
|
||||||
|
) -> Option<(Range<usize>, String)> {
|
||||||
|
let server_settings_index = query.capture_index_for_name("server-settings")?;
|
||||||
|
let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
|
||||||
|
|
||||||
|
Some((
|
||||||
|
server_settings.byte_range(),
|
||||||
|
r#"{
|
||||||
|
"source": "extension",
|
||||||
|
"settings": {}
|
||||||
|
}"#
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
|
@ -148,6 +148,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||||
migrations::m_2025_05_29::SETTINGS_PATTERNS,
|
migrations::m_2025_05_29::SETTINGS_PATTERNS,
|
||||||
&SETTINGS_QUERY_2025_05_29,
|
&SETTINGS_QUERY_2025_05_29,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
migrations::m_2025_06_16::SETTINGS_PATTERNS,
|
||||||
|
&SETTINGS_QUERY_2025_06_16,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
run_migrations(text, migrations)
|
run_migrations(text, migrations)
|
||||||
}
|
}
|
||||||
|
@ -246,6 +250,10 @@ define_query!(
|
||||||
SETTINGS_QUERY_2025_05_29,
|
SETTINGS_QUERY_2025_05_29,
|
||||||
migrations::m_2025_05_29::SETTINGS_PATTERNS
|
migrations::m_2025_05_29::SETTINGS_PATTERNS
|
||||||
);
|
);
|
||||||
|
define_query!(
|
||||||
|
SETTINGS_QUERY_2025_06_16,
|
||||||
|
migrations::m_2025_06_16::SETTINGS_PATTERNS
|
||||||
|
);
|
||||||
|
|
||||||
// custom query
|
// custom query
|
||||||
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||||
|
@ -854,4 +862,152 @@ mod tests {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mcp_settings_migration() {
|
||||||
|
assert_migrate_settings(
|
||||||
|
r#"{
|
||||||
|
"context_servers": {
|
||||||
|
"empty_server": {},
|
||||||
|
"extension_server": {
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"custom_server": {
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invalid_server": {
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty_server2": {},
|
||||||
|
"extension_server2": {
|
||||||
|
"foo": "bar",
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
},
|
||||||
|
"bar": "foo"
|
||||||
|
},
|
||||||
|
"custom_server2": {
|
||||||
|
"foo": "bar",
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bar": "foo"
|
||||||
|
},
|
||||||
|
"invalid_server2": {
|
||||||
|
"foo": "bar",
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bar": "foo",
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
Some(
|
||||||
|
r#"{
|
||||||
|
"context_servers": {
|
||||||
|
"empty_server": {
|
||||||
|
"source": "extension",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"extension_server": {
|
||||||
|
"source": "extension",
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"custom_server": {
|
||||||
|
"source": "custom",
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invalid_server": {
|
||||||
|
"source": "custom",
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"empty_server2": {
|
||||||
|
"source": "extension",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"extension_server2": {
|
||||||
|
"source": "extension",
|
||||||
|
"foo": "bar",
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
},
|
||||||
|
"bar": "foo"
|
||||||
|
},
|
||||||
|
"custom_server2": {
|
||||||
|
"source": "custom",
|
||||||
|
"foo": "bar",
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bar": "foo"
|
||||||
|
},
|
||||||
|
"invalid_server2": {
|
||||||
|
"source": "custom",
|
||||||
|
"foo": "bar",
|
||||||
|
"command": {
|
||||||
|
"path": "foo",
|
||||||
|
"args": ["bar"],
|
||||||
|
"env": {
|
||||||
|
"FOO": "BAR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bar": "foo",
|
||||||
|
"settings": {
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,15 @@ use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use context_server::{ContextServer, ContextServerId};
|
use context_server::{ContextServer, ContextServerCommand, ContextServerId};
|
||||||
|
use futures::{FutureExt as _, future::join_all};
|
||||||
use gpui::{App, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity, actions};
|
use gpui::{App, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity, actions};
|
||||||
use registry::ContextServerDescriptorRegistry;
|
use registry::ContextServerDescriptorRegistry;
|
||||||
use settings::{Settings as _, SettingsStore};
|
use settings::{Settings as _, SettingsStore};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
project_settings::{ContextServerConfiguration, ProjectSettings},
|
project_settings::{ContextServerSettings, ProjectSettings},
|
||||||
worktree_store::WorktreeStore,
|
worktree_store::WorktreeStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -81,6 +82,50 @@ impl ContextServerState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum ContextServerConfiguration {
|
||||||
|
Custom {
|
||||||
|
command: ContextServerCommand,
|
||||||
|
},
|
||||||
|
Extension {
|
||||||
|
command: ContextServerCommand,
|
||||||
|
settings: serde_json::Value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextServerConfiguration {
|
||||||
|
pub fn command(&self) -> &ContextServerCommand {
|
||||||
|
match self {
|
||||||
|
ContextServerConfiguration::Custom { command } => command,
|
||||||
|
ContextServerConfiguration::Extension { command, .. } => command,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_settings(
|
||||||
|
settings: ContextServerSettings,
|
||||||
|
id: ContextServerId,
|
||||||
|
registry: Entity<ContextServerDescriptorRegistry>,
|
||||||
|
worktree_store: Entity<WorktreeStore>,
|
||||||
|
cx: &AsyncApp,
|
||||||
|
) -> Option<Self> {
|
||||||
|
match settings {
|
||||||
|
ContextServerSettings::Custom { command } => {
|
||||||
|
Some(ContextServerConfiguration::Custom { command })
|
||||||
|
}
|
||||||
|
ContextServerSettings::Extension { settings } => {
|
||||||
|
let descriptor = cx
|
||||||
|
.update(|cx| registry.read(cx).context_server_descriptor(&id.0))
|
||||||
|
.ok()
|
||||||
|
.flatten()?;
|
||||||
|
|
||||||
|
let command = descriptor.command(worktree_store, cx).await.log_err()?;
|
||||||
|
|
||||||
|
Some(ContextServerConfiguration::Extension { command, settings })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type ContextServerFactory =
|
pub type ContextServerFactory =
|
||||||
Box<dyn Fn(ContextServerId, Arc<ContextServerConfiguration>) -> Arc<ContextServer>>;
|
Box<dyn Fn(ContextServerId, Arc<ContextServerConfiguration>) -> Arc<ContextServer>>;
|
||||||
|
|
||||||
|
@ -207,29 +252,37 @@ impl ContextServerStore {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_server(
|
pub fn start_server(&mut self, server: Arc<ContextServer>, cx: &mut Context<Self>) {
|
||||||
&mut self,
|
cx.spawn(async move |this, cx| {
|
||||||
server: Arc<ContextServer>,
|
let this = this.upgrade().context("Context server store dropped")?;
|
||||||
cx: &mut Context<Self>,
|
let settings = this
|
||||||
) -> Result<()> {
|
.update(cx, |this, cx| {
|
||||||
let location = self
|
this.context_server_settings(cx)
|
||||||
.worktree_store
|
.get(&server.id().0)
|
||||||
.read(cx)
|
.cloned()
|
||||||
.visible_worktrees(cx)
|
})
|
||||||
.next()
|
.ok()
|
||||||
.map(|worktree| settings::SettingsLocation {
|
.flatten()
|
||||||
worktree_id: worktree.read(cx).id(),
|
.context("Failed to get context server settings")?;
|
||||||
path: Path::new(""),
|
|
||||||
});
|
|
||||||
let settings = ProjectSettings::get(location, cx);
|
|
||||||
let configuration = settings
|
|
||||||
.context_servers
|
|
||||||
.get(&server.id().0)
|
|
||||||
.context("Failed to load context server configuration from settings")?
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
self.run_server(server, Arc::new(configuration), cx);
|
let (registry, worktree_store) = this.update(cx, |this, _| {
|
||||||
Ok(())
|
(this.registry.clone(), this.worktree_store.clone())
|
||||||
|
})?;
|
||||||
|
let configuration = ContextServerConfiguration::from_settings(
|
||||||
|
settings,
|
||||||
|
server.id(),
|
||||||
|
registry,
|
||||||
|
worktree_store,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to create context server configuration")?;
|
||||||
|
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.run_server(server, Arc::new(configuration), cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_server(&mut self, id: &ContextServerId, cx: &mut Context<Self>) -> Result<()> {
|
pub fn stop_server(&mut self, id: &ContextServerId, cx: &mut Context<Self>) -> Result<()> {
|
||||||
|
@ -349,11 +402,6 @@ impl ContextServerStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_configuration_valid(&self, configuration: &ContextServerConfiguration) -> bool {
|
|
||||||
// Command must be some when we are running in stdio mode.
|
|
||||||
self.context_server_factory.as_ref().is_some() || configuration.command.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_context_server(
|
fn create_context_server(
|
||||||
&self,
|
&self,
|
||||||
id: ContextServerId,
|
id: ContextServerId,
|
||||||
|
@ -362,14 +410,29 @@ impl ContextServerStore {
|
||||||
if let Some(factory) = self.context_server_factory.as_ref() {
|
if let Some(factory) = self.context_server_factory.as_ref() {
|
||||||
Ok(factory(id, configuration))
|
Ok(factory(id, configuration))
|
||||||
} else {
|
} else {
|
||||||
let command = configuration
|
Ok(Arc::new(ContextServer::stdio(
|
||||||
.command
|
id,
|
||||||
.clone()
|
configuration.command().clone(),
|
||||||
.context("Missing command to run context server")?;
|
)))
|
||||||
Ok(Arc::new(ContextServer::stdio(id, command)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn context_server_settings<'a>(
|
||||||
|
&'a self,
|
||||||
|
cx: &'a App,
|
||||||
|
) -> &'a HashMap<Arc<str>, ContextServerSettings> {
|
||||||
|
let location = self
|
||||||
|
.worktree_store
|
||||||
|
.read(cx)
|
||||||
|
.visible_worktrees(cx)
|
||||||
|
.next()
|
||||||
|
.map(|worktree| settings::SettingsLocation {
|
||||||
|
worktree_id: worktree.read(cx).id(),
|
||||||
|
path: Path::new(""),
|
||||||
|
});
|
||||||
|
&ProjectSettings::get(location, cx).context_servers
|
||||||
|
}
|
||||||
|
|
||||||
fn update_server_state(
|
fn update_server_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: ContextServerId,
|
id: ContextServerId,
|
||||||
|
@ -407,43 +470,39 @@ impl ContextServerStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn maintain_servers(this: WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
|
async fn maintain_servers(this: WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
|
||||||
let mut desired_servers = HashMap::default();
|
let (mut configured_servers, registry, worktree_store) = this.update(cx, |this, cx| {
|
||||||
|
(
|
||||||
let (registry, worktree_store) = this.update(cx, |this, cx| {
|
this.context_server_settings(cx).clone(),
|
||||||
let location = this
|
this.registry.clone(),
|
||||||
.worktree_store
|
this.worktree_store.clone(),
|
||||||
.read(cx)
|
)
|
||||||
.visible_worktrees(cx)
|
|
||||||
.next()
|
|
||||||
.map(|worktree| settings::SettingsLocation {
|
|
||||||
worktree_id: worktree.read(cx).id(),
|
|
||||||
path: Path::new(""),
|
|
||||||
});
|
|
||||||
let settings = ProjectSettings::get(location, cx);
|
|
||||||
desired_servers = settings.context_servers.clone();
|
|
||||||
|
|
||||||
(this.registry.clone(), this.worktree_store.clone())
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for (id, descriptor) in
|
for (id, _) in
|
||||||
registry.read_with(cx, |registry, _| registry.context_server_descriptors())?
|
registry.read_with(cx, |registry, _| registry.context_server_descriptors())?
|
||||||
{
|
{
|
||||||
let config = desired_servers.entry(id.clone()).or_default();
|
configured_servers
|
||||||
if config.command.is_none() {
|
.entry(id)
|
||||||
if let Some(extension_command) = descriptor
|
.or_insert(ContextServerSettings::Extension {
|
||||||
.command(worktree_store.clone(), &cx)
|
settings: serde_json::json!({}),
|
||||||
.await
|
});
|
||||||
.log_err()
|
|
||||||
{
|
|
||||||
config.command = Some(extension_command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(cx, |this, _| {
|
let configured_servers = join_all(configured_servers.into_iter().map(|(id, settings)| {
|
||||||
// Filter out configurations without commands, the user uninstalled an extension.
|
let id = ContextServerId(id);
|
||||||
desired_servers.retain(|_, configuration| this.is_configuration_valid(configuration));
|
ContextServerConfiguration::from_settings(
|
||||||
})?;
|
settings,
|
||||||
|
id.clone(),
|
||||||
|
registry.clone(),
|
||||||
|
worktree_store.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.map(|config| (id, config))
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(id, config)| config.map(|config| (id, config)))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let mut servers_to_start = Vec::new();
|
let mut servers_to_start = Vec::new();
|
||||||
let mut servers_to_remove = HashSet::default();
|
let mut servers_to_remove = HashSet::default();
|
||||||
|
@ -452,16 +511,13 @@ impl ContextServerStore {
|
||||||
this.update(cx, |this, _cx| {
|
this.update(cx, |this, _cx| {
|
||||||
for server_id in this.servers.keys() {
|
for server_id in this.servers.keys() {
|
||||||
// All servers that are not in desired_servers should be removed from the store.
|
// All servers that are not in desired_servers should be removed from the store.
|
||||||
// E.g. this can happen if the user removed a server from the configuration,
|
// This can happen if the user removed a server from the context server settings.
|
||||||
// or the user uninstalled an extension.
|
if !configured_servers.contains_key(&server_id) {
|
||||||
if !desired_servers.contains_key(&server_id.0) {
|
|
||||||
servers_to_remove.insert(server_id.clone());
|
servers_to_remove.insert(server_id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id, config) in desired_servers {
|
for (id, config) in configured_servers {
|
||||||
let id = ContextServerId(id.clone());
|
|
||||||
|
|
||||||
let existing_config = this.servers.get(&id).map(|state| state.configuration());
|
let existing_config = this.servers.get(&id).map(|state| state.configuration());
|
||||||
if existing_config.as_deref() != Some(&config) {
|
if existing_config.as_deref() != Some(&config) {
|
||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
|
@ -478,27 +534,28 @@ impl ContextServerStore {
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for id in servers_to_stop {
|
this.update(cx, |this, cx| {
|
||||||
this.update(cx, |this, cx| this.stop_server(&id, cx).ok())?;
|
for id in servers_to_stop {
|
||||||
}
|
this.stop_server(&id, cx)?;
|
||||||
|
}
|
||||||
for id in servers_to_remove {
|
for id in servers_to_remove {
|
||||||
this.update(cx, |this, cx| this.remove_server(&id, cx).ok())?;
|
this.remove_server(&id, cx)?;
|
||||||
}
|
}
|
||||||
|
for (server, config) in servers_to_start {
|
||||||
for (server, config) in servers_to_start {
|
this.run_server(server, config, cx);
|
||||||
this.update(cx, |this, cx| this.run_server(server, config, cx))
|
}
|
||||||
.log_err();
|
anyhow::Ok(())
|
||||||
}
|
})?
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{FakeFs, Project, project_settings::ProjectSettings};
|
use crate::{
|
||||||
|
FakeFs, Project, context_server_store::registry::ContextServerDescriptor,
|
||||||
|
project_settings::ProjectSettings,
|
||||||
|
};
|
||||||
use context_server::test::create_fake_transport;
|
use context_server::test::create_fake_transport;
|
||||||
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
|
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -514,8 +571,8 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": ""}),
|
json!({"code.rs": ""}),
|
||||||
vec![
|
vec![
|
||||||
(SERVER_1_ID.into(), ContextServerConfiguration::default()),
|
(SERVER_1_ID.into(), dummy_server_settings()),
|
||||||
(SERVER_2_ID.into(), ContextServerConfiguration::default()),
|
(SERVER_2_ID.into(), dummy_server_settings()),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -537,9 +594,7 @@ mod tests {
|
||||||
Arc::new(create_fake_transport(SERVER_2_ID, cx.executor())),
|
Arc::new(create_fake_transport(SERVER_2_ID, cx.executor())),
|
||||||
));
|
));
|
||||||
|
|
||||||
store
|
store.update(cx, |store, cx| store.start_server(server_1, cx));
|
||||||
.update(cx, |store, cx| store.start_server(server_1, cx))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
@ -551,9 +606,7 @@ mod tests {
|
||||||
assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
|
assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
|
||||||
});
|
});
|
||||||
|
|
||||||
store
|
store.update(cx, |store, cx| store.start_server(server_2.clone(), cx));
|
||||||
.update(cx, |store, cx| store.start_server(server_2.clone(), cx))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
@ -593,8 +646,8 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": ""}),
|
json!({"code.rs": ""}),
|
||||||
vec![
|
vec![
|
||||||
(SERVER_1_ID.into(), ContextServerConfiguration::default()),
|
(SERVER_1_ID.into(), dummy_server_settings()),
|
||||||
(SERVER_2_ID.into(), ContextServerConfiguration::default()),
|
(SERVER_2_ID.into(), dummy_server_settings()),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -628,15 +681,11 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
store
|
store.update(cx, |store, cx| store.start_server(server_1, cx));
|
||||||
.update(cx, |store, cx| store.start_server(server_1, cx))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
store
|
store.update(cx, |store, cx| store.start_server(server_2.clone(), cx));
|
||||||
.update(cx, |store, cx| store.start_server(server_2.clone(), cx))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
@ -652,7 +701,7 @@ mod tests {
|
||||||
let (_fs, project) = setup_context_server_test(
|
let (_fs, project) = setup_context_server_test(
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": ""}),
|
json!({"code.rs": ""}),
|
||||||
vec![(SERVER_1_ID.into(), ContextServerConfiguration::default())],
|
vec![(SERVER_1_ID.into(), dummy_server_settings())],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -684,21 +733,11 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
store
|
store.update(cx, |store, cx| {
|
||||||
.update(cx, |store, cx| {
|
store.start_server(server_with_same_id_1.clone(), cx)
|
||||||
store.start_server(server_with_same_id_1.clone(), cx)
|
});
|
||||||
})
|
store.update(cx, |store, cx| {
|
||||||
.unwrap();
|
store.start_server(server_with_same_id_2.clone(), cx)
|
||||||
store
|
|
||||||
.update(cx, |store, cx| {
|
|
||||||
store.start_server(server_with_same_id_2.clone(), cx)
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
cx.update(|cx| {
|
|
||||||
assert_eq!(
|
|
||||||
store.read(cx).status_for_server(&server_id),
|
|
||||||
Some(ContextServerStatus::Starting)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
@ -719,23 +758,28 @@ mod tests {
|
||||||
let server_1_id = ContextServerId(SERVER_1_ID.into());
|
let server_1_id = ContextServerId(SERVER_1_ID.into());
|
||||||
let server_2_id = ContextServerId(SERVER_2_ID.into());
|
let server_2_id = ContextServerId(SERVER_2_ID.into());
|
||||||
|
|
||||||
|
let fake_descriptor_1 = Arc::new(FakeContextServerDescriptor::new(SERVER_1_ID));
|
||||||
|
|
||||||
let (_fs, project) = setup_context_server_test(
|
let (_fs, project) = setup_context_server_test(
|
||||||
cx,
|
cx,
|
||||||
json!({"code.rs": ""}),
|
json!({"code.rs": ""}),
|
||||||
vec![(
|
vec![(
|
||||||
SERVER_1_ID.into(),
|
SERVER_1_ID.into(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Extension {
|
||||||
command: None,
|
settings: json!({
|
||||||
settings: Some(json!({
|
|
||||||
"somevalue": true
|
"somevalue": true
|
||||||
})),
|
}),
|
||||||
},
|
},
|
||||||
)],
|
)],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let executor = cx.executor();
|
let executor = cx.executor();
|
||||||
let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
|
let registry = cx.new(|_| {
|
||||||
|
let mut registry = ContextServerDescriptorRegistry::new();
|
||||||
|
registry.register_context_server_descriptor(SERVER_1_ID.into(), fake_descriptor_1);
|
||||||
|
registry
|
||||||
|
});
|
||||||
let store = cx.new(|cx| {
|
let store = cx.new(|cx| {
|
||||||
ContextServerStore::test_maintain_server_loop(
|
ContextServerStore::test_maintain_server_loop(
|
||||||
Box::new(move |id, _| {
|
Box::new(move |id, _| {
|
||||||
|
@ -777,11 +821,10 @@ mod tests {
|
||||||
set_context_server_configuration(
|
set_context_server_configuration(
|
||||||
vec![(
|
vec![(
|
||||||
server_1_id.0.clone(),
|
server_1_id.0.clone(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Extension {
|
||||||
command: None,
|
settings: json!({
|
||||||
settings: Some(json!({
|
|
||||||
"somevalue": false
|
"somevalue": false
|
||||||
})),
|
}),
|
||||||
},
|
},
|
||||||
)],
|
)],
|
||||||
cx,
|
cx,
|
||||||
|
@ -796,11 +839,10 @@ mod tests {
|
||||||
set_context_server_configuration(
|
set_context_server_configuration(
|
||||||
vec![(
|
vec![(
|
||||||
server_1_id.0.clone(),
|
server_1_id.0.clone(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Extension {
|
||||||
command: None,
|
settings: json!({
|
||||||
settings: Some(json!({
|
|
||||||
"somevalue": false
|
"somevalue": false
|
||||||
})),
|
}),
|
||||||
},
|
},
|
||||||
)],
|
)],
|
||||||
cx,
|
cx,
|
||||||
|
@ -823,20 +865,58 @@ mod tests {
|
||||||
vec![
|
vec![
|
||||||
(
|
(
|
||||||
server_1_id.0.clone(),
|
server_1_id.0.clone(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Extension {
|
||||||
command: None,
|
settings: json!({
|
||||||
settings: Some(json!({
|
|
||||||
"somevalue": false
|
"somevalue": false
|
||||||
})),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
server_2_id.0.clone(),
|
server_2_id.0.clone(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Custom {
|
||||||
command: None,
|
command: ContextServerCommand {
|
||||||
settings: Some(json!({
|
path: "somebinary".to_string(),
|
||||||
"somevalue": true
|
args: vec!["arg".to_string()],
|
||||||
})),
|
env: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that mcp-2 is restarted once the args have changed
|
||||||
|
{
|
||||||
|
let _server_events = assert_server_events(
|
||||||
|
&store,
|
||||||
|
vec![
|
||||||
|
(server_2_id.clone(), ContextServerStatus::Stopped),
|
||||||
|
(server_2_id.clone(), ContextServerStatus::Starting),
|
||||||
|
(server_2_id.clone(), ContextServerStatus::Running),
|
||||||
|
],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
set_context_server_configuration(
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
server_1_id.0.clone(),
|
||||||
|
ContextServerSettings::Extension {
|
||||||
|
settings: json!({
|
||||||
|
"somevalue": false
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
server_2_id.0.clone(),
|
||||||
|
ContextServerSettings::Custom {
|
||||||
|
command: ContextServerCommand {
|
||||||
|
path: "somebinary".to_string(),
|
||||||
|
args: vec!["anotherArg".to_string()],
|
||||||
|
env: None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -856,11 +936,10 @@ mod tests {
|
||||||
set_context_server_configuration(
|
set_context_server_configuration(
|
||||||
vec![(
|
vec![(
|
||||||
server_1_id.0.clone(),
|
server_1_id.0.clone(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Extension {
|
||||||
command: None,
|
settings: json!({
|
||||||
settings: Some(json!({
|
|
||||||
"somevalue": false
|
"somevalue": false
|
||||||
})),
|
}),
|
||||||
},
|
},
|
||||||
)],
|
)],
|
||||||
cx,
|
cx,
|
||||||
|
@ -875,7 +954,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_context_server_configuration(
|
fn set_context_server_configuration(
|
||||||
context_servers: Vec<(Arc<str>, ContextServerConfiguration)>,
|
context_servers: Vec<(Arc<str>, ContextServerSettings)>,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) {
|
) {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
|
@ -909,6 +988,16 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dummy_server_settings() -> ContextServerSettings {
|
||||||
|
ContextServerSettings::Custom {
|
||||||
|
command: ContextServerCommand {
|
||||||
|
path: "somebinary".to_string(),
|
||||||
|
args: vec!["arg".to_string()],
|
||||||
|
env: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_server_events(
|
fn assert_server_events(
|
||||||
store: &Entity<ContextServerStore>,
|
store: &Entity<ContextServerStore>,
|
||||||
expected_events: Vec<(ContextServerId, ContextServerStatus)>,
|
expected_events: Vec<(ContextServerId, ContextServerStatus)>,
|
||||||
|
@ -953,7 +1042,7 @@ mod tests {
|
||||||
async fn setup_context_server_test(
|
async fn setup_context_server_test(
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
files: serde_json::Value,
|
files: serde_json::Value,
|
||||||
context_server_configurations: Vec<(Arc<str>, ContextServerConfiguration)>,
|
context_server_configurations: Vec<(Arc<str>, ContextServerSettings)>,
|
||||||
) -> (Arc<FakeFs>, Entity<Project>) {
|
) -> (Arc<FakeFs>, Entity<Project>) {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
|
@ -972,4 +1061,36 @@ mod tests {
|
||||||
|
|
||||||
(fs, project)
|
(fs, project)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FakeContextServerDescriptor {
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeContextServerDescriptor {
|
||||||
|
fn new(path: impl Into<String>) -> Self {
|
||||||
|
Self { path: path.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextServerDescriptor for FakeContextServerDescriptor {
|
||||||
|
fn command(
|
||||||
|
&self,
|
||||||
|
_worktree_store: Entity<WorktreeStore>,
|
||||||
|
_cx: &AsyncApp,
|
||||||
|
) -> Task<Result<ContextServerCommand>> {
|
||||||
|
Task::ready(Ok(ContextServerCommand {
|
||||||
|
path: self.path.clone(),
|
||||||
|
args: vec!["arg1".to_string(), "arg2".to_string()],
|
||||||
|
env: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configuration(
|
||||||
|
&self,
|
||||||
|
_worktree_store: Entity<WorktreeStore>,
|
||||||
|
_cx: &AsyncApp,
|
||||||
|
) -> Task<Result<Option<::extension::ContextServerConfiguration>>> {
|
||||||
|
Task::ready(Ok(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub struct ProjectSettings {
|
||||||
|
|
||||||
/// Settings for context servers used for AI-related features.
|
/// Settings for context servers used for AI-related features.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub context_servers: HashMap<Arc<str>, ContextServerConfiguration>,
|
pub context_servers: HashMap<Arc<str>, ContextServerSettings>,
|
||||||
|
|
||||||
/// Configuration for Diagnostics-related features.
|
/// Configuration for Diagnostics-related features.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -84,17 +84,22 @@ pub struct DapSettings {
|
||||||
pub binary: Option<String>,
|
pub binary: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||||
pub struct ContextServerConfiguration {
|
#[serde(tag = "source", rename_all = "snake_case")]
|
||||||
/// The command to run this context server.
|
pub enum ContextServerSettings {
|
||||||
///
|
Custom {
|
||||||
/// This will override the command set by an extension.
|
/// The command to run this context server.
|
||||||
pub command: Option<ContextServerCommand>,
|
///
|
||||||
/// The settings for this context server.
|
/// This will override the command set by an extension.
|
||||||
///
|
command: ContextServerCommand,
|
||||||
/// Consult the documentation for the context server to see what settings
|
},
|
||||||
/// are supported.
|
Extension {
|
||||||
pub settings: Option<serde_json::Value>,
|
/// The settings for this context server specified by the extension.
|
||||||
|
///
|
||||||
|
/// Consult the documentation for the context server to see what settings
|
||||||
|
/// are supported.
|
||||||
|
settings: serde_json::Value,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
|
@ -472,13 +477,12 @@ impl Settings for ProjectSettings {
|
||||||
.extend(mcp.iter().filter_map(|(k, v)| {
|
.extend(mcp.iter().filter_map(|(k, v)| {
|
||||||
Some((
|
Some((
|
||||||
k.clone().into(),
|
k.clone().into(),
|
||||||
ContextServerConfiguration {
|
ContextServerSettings::Custom {
|
||||||
command: Some(
|
command: serde_json::from_value::<VsCodeContextServerCommand>(
|
||||||
serde_json::from_value::<VsCodeContextServerCommand>(v.clone())
|
v.clone(),
|
||||||
.ok()?
|
)
|
||||||
.into(),
|
.ok()?
|
||||||
),
|
.into(),
|
||||||
settings: None,
|
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -41,12 +41,12 @@ You can connect them by adding their commands directly to your `settings.json`,
|
||||||
{
|
{
|
||||||
"context_servers": {
|
"context_servers": {
|
||||||
"some-context-server": {
|
"some-context-server": {
|
||||||
|
"source": "custom",
|
||||||
"command": {
|
"command": {
|
||||||
"path": "some-command",
|
"path": "some-command",
|
||||||
"args": ["arg-1", "arg-2"],
|
"args": ["arg-1", "arg-2"],
|
||||||
"env": {}
|
"env": {}
|
||||||
},
|
}
|
||||||
"settings": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue