ai_agent/utils/plugins/
lsp_plugin_integration.rs1#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::path::Path;
6
7use serde::{Deserialize, Serialize};
8
9use super::plugin_directories::get_plugin_data_dir;
10use super::plugin_options_storage::{
11 get_plugin_storage_id, load_plugin_options, substitute_plugin_variables,
12 substitute_user_config_variables,
13};
14use crate::plugin::types::{LoadedPlugin, PluginError};
15
16#[derive(Serialize, Deserialize, Debug, Clone)]
18pub struct LspServerConfig {
19 pub command: String,
20 pub args: Option<Vec<String>>,
21 pub env: Option<HashMap<String, String>>,
22 pub workspace_folder: Option<String>,
23 #[serde(rename = "extensionToLanguage")]
24 pub extension_to_language: Option<HashMap<String, String>>,
25}
26
27#[derive(Debug, Clone)]
29pub struct ScopedLspServerConfig {
30 pub config: LspServerConfig,
31 pub scope: String,
32 pub source: String,
33}
34
35pub async fn load_plugin_lsp_servers(
37 plugin: &LoadedPlugin,
38 errors: &mut Vec<PluginError>,
39) -> Result<Option<HashMap<String, LspServerConfig>>, String> {
40 let mut servers: HashMap<String, LspServerConfig> = HashMap::new();
41
42 let lsp_json_path = Path::new(&plugin.path).join(".lsp.json");
44 if lsp_json_path.exists() {
45 match tokio::fs::read_to_string(&lsp_json_path).await {
46 Ok(content) => {
47 match serde_json::from_str::<HashMap<String, LspServerConfig>>(&content) {
48 Ok(parsed) => {
49 servers.extend(parsed);
50 }
51 Err(e) => {
52 log::error!(
53 "LSP config validation failed for .lsp.json in plugin {}: {}",
54 plugin.name,
55 e
56 );
57 }
58 }
59 }
60 Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
61 log::error!("Failed to read .lsp.json in plugin {}: {}", plugin.name, e);
62 }
63 _ => {}
64 }
65 }
66
67 if servers.is_empty() {
68 Ok(None)
69 } else {
70 Ok(Some(servers))
71 }
72}
73
74pub fn resolve_plugin_lsp_environment(
76 config: &LspServerConfig,
77 plugin: &LoadedPlugin,
78 user_config: Option<&HashMap<String, serde_json::Value>>,
79) -> LspServerConfig {
80 let resolve_value = |value: &str| -> String {
81 let mut resolved = substitute_plugin_variables(value, &plugin.path, &plugin.source);
82
83 if let Some(uc) = user_config {
84 resolved = substitute_user_config_variables(&resolved, uc);
85 }
86
87 expand_env_vars_in_string(&resolved)
88 };
89
90 let mut resolved = config.clone();
91
92 resolved.command = resolve_value(&config.command);
93
94 if let Some(ref args) = config.args {
95 resolved.args = Some(args.iter().map(|a| resolve_value(a)).collect());
96 }
97
98 let mut resolved_env: HashMap<String, String> = HashMap::new();
99 resolved_env.insert("CLAUDE_PLUGIN_ROOT".to_string(), plugin.path.clone());
100 resolved_env.insert(
101 "CLAUDE_PLUGIN_DATA".to_string(),
102 get_plugin_data_dir(&plugin.source),
103 );
104
105 if let Some(ref env) = config.env {
106 for (key, value) in env {
107 if key != "CLAUDE_PLUGIN_ROOT" && key != "CLAUDE_PLUGIN_DATA" {
108 resolved_env.insert(key.clone(), resolve_value(value));
109 }
110 }
111 }
112 resolved.env = Some(resolved_env);
113
114 if let Some(ref wf) = config.workspace_folder {
115 resolved.workspace_folder = Some(resolve_value(wf));
116 }
117
118 resolved
119}
120
121fn expand_env_vars_in_string(value: &str) -> String {
122 let mut result = value.to_string();
123 let mut start = 0;
124 while let Some(pos) = result[start..].find("${") {
125 let abs_pos = start + pos;
126 if let Some(end) = result[abs_pos + 2..].find('}') {
127 let var_name = &result[abs_pos + 2..abs_pos + 2 + end];
128 if let Ok(env_value) = std::env::var(var_name) {
129 result.replace_range(abs_pos..abs_pos + 2 + end + 1, &env_value);
130 start = abs_pos + env_value.len();
131 continue;
132 }
133 }
134 start = abs_pos + 2;
135 }
136 result
137}
138
139pub fn add_plugin_scope_to_lsp_servers(
141 servers: HashMap<String, LspServerConfig>,
142 plugin_name: &str,
143) -> HashMap<String, ScopedLspServerConfig> {
144 servers
145 .into_iter()
146 .map(|(name, config)| {
147 let scoped_name = format!("plugin:{}:{}", plugin_name, name);
148 (
149 scoped_name,
150 ScopedLspServerConfig {
151 config,
152 scope: "dynamic".to_string(),
153 source: plugin_name.to_string(),
154 },
155 )
156 })
157 .collect()
158}
159
160pub async fn extract_lsp_servers_from_plugins(
162 plugins: &[LoadedPlugin],
163 errors: &mut Vec<PluginError>,
164) -> HashMap<String, ScopedLspServerConfig> {
165 let mut all_servers = HashMap::new();
166
167 for plugin in plugins {
168 if !plugin.enabled.unwrap_or(true) {
169 continue;
170 }
171
172 if let Ok(Some(servers)) = load_plugin_lsp_servers(plugin, errors).await {
173 let scoped_servers = add_plugin_scope_to_lsp_servers(servers, &plugin.name);
174 log::debug!(
175 "Loaded {} LSP servers from plugin {}",
176 scoped_servers.len(),
177 plugin.name
178 );
179 all_servers.extend(scoped_servers);
180 }
181 }
182
183 all_servers
184}