1use async_trait::async_trait;
2use harness_core::{PermissionPolicy, ToolError};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::watch;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub enum LspOperation {
11 Hover,
12 Definition,
13 References,
14 DocumentSymbol,
15 WorkspaceSymbol,
16 Implementation,
17}
18
19impl LspOperation {
20 pub fn as_str(&self) -> &'static str {
21 match self {
22 Self::Hover => "hover",
23 Self::Definition => "definition",
24 Self::References => "references",
25 Self::DocumentSymbol => "documentSymbol",
26 Self::WorkspaceSymbol => "workspaceSymbol",
27 Self::Implementation => "implementation",
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
33pub struct Position1 {
34 pub line: u32,
35 pub character: u32,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct LspServerProfile {
40 pub language: String,
41 pub extensions: Vec<String>,
42 pub command: Vec<String>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub root_patterns: Option<Vec<String>>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub initialization_options: Option<serde_json::Value>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct LspManifest {
51 pub servers: HashMap<String, LspServerProfile>,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum ServerState {
57 Starting,
58 Ready,
59 Crashed,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ServerHandle {
64 pub language: String,
65 pub root: String,
66 pub state: ServerState,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct LspLocation {
71 pub path: String,
72 pub line: u32,
73 pub character: u32,
74 pub preview: String,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct LspSymbolInfo {
79 pub name: String,
80 pub kind: String,
81 pub path: String,
82 pub line: u32,
83 pub character: u32,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub container_name: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub children: Option<Vec<LspSymbolInfo>>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct LspHoverResult {
92 pub contents: String,
93 pub is_markdown: bool,
94}
95
96pub type CancelSignal = watch::Receiver<bool>;
99
100#[async_trait]
101pub trait LspClient: Send + Sync {
102 async fn ensure_server(
103 &self,
104 language: &str,
105 root: &str,
106 profile: &LspServerProfile,
107 ) -> Result<ServerHandle, String>;
108
109 async fn hover(
110 &self,
111 handle: &ServerHandle,
112 path: &str,
113 pos: Position1,
114 cancel: CancelSignal,
115 ) -> Result<Option<LspHoverResult>, String>;
116
117 async fn definition(
118 &self,
119 handle: &ServerHandle,
120 path: &str,
121 pos: Position1,
122 cancel: CancelSignal,
123 ) -> Result<Vec<LspLocation>, String>;
124
125 async fn references(
126 &self,
127 handle: &ServerHandle,
128 path: &str,
129 pos: Position1,
130 cancel: CancelSignal,
131 ) -> Result<Vec<LspLocation>, String>;
132
133 async fn document_symbol(
134 &self,
135 handle: &ServerHandle,
136 path: &str,
137 cancel: CancelSignal,
138 ) -> Result<Vec<LspSymbolInfo>, String>;
139
140 async fn workspace_symbol(
141 &self,
142 handle: &ServerHandle,
143 query: &str,
144 cancel: CancelSignal,
145 ) -> Result<Vec<LspSymbolInfo>, String>;
146
147 async fn implementation(
148 &self,
149 handle: &ServerHandle,
150 path: &str,
151 pos: Position1,
152 cancel: CancelSignal,
153 ) -> Result<Vec<LspLocation>, String>;
154
155 async fn close_session(&self);
156}
157
158#[derive(Clone)]
159pub struct LspPermissionPolicy {
160 pub inner: PermissionPolicy,
161 pub unsafe_allow_lsp_without_hook: bool,
162}
163
164impl LspPermissionPolicy {
165 pub fn new(inner: PermissionPolicy) -> Self {
166 Self {
167 inner,
168 unsafe_allow_lsp_without_hook: false,
169 }
170 }
171
172 pub fn with_unsafe_bypass(mut self, v: bool) -> Self {
173 self.unsafe_allow_lsp_without_hook = v;
174 self
175 }
176}
177
178impl std::fmt::Debug for LspPermissionPolicy {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 f.debug_struct("LspPermissionPolicy")
181 .field(
182 "unsafe_allow_lsp_without_hook",
183 &self.unsafe_allow_lsp_without_hook,
184 )
185 .field("inner", &self.inner)
186 .finish()
187 }
188}
189
190#[derive(Clone)]
191pub struct LspSessionConfig {
192 pub cwd: String,
193 pub permissions: LspPermissionPolicy,
194 pub client: Arc<dyn LspClient>,
195 pub manifest: Option<LspManifest>,
196 pub manifest_path: Option<String>,
197 pub default_head_limit: Option<usize>,
198 pub default_timeout_ms: Option<u64>,
199 pub session_backstop_ms: Option<u64>,
200 pub server_startup_max_wait_ms: Option<u64>,
201 pub max_hover_markdown_bytes: Option<usize>,
202 pub max_preview_line_length: Option<usize>,
203 pub retry_counter: Arc<tokio::sync::Mutex<HashMap<String, u64>>>,
206}
207
208impl LspSessionConfig {
209 pub fn new(
210 cwd: impl Into<String>,
211 permissions: LspPermissionPolicy,
212 client: Arc<dyn LspClient>,
213 ) -> Self {
214 Self {
215 cwd: cwd.into(),
216 permissions,
217 client,
218 manifest: None,
219 manifest_path: None,
220 default_head_limit: None,
221 default_timeout_ms: None,
222 session_backstop_ms: None,
223 server_startup_max_wait_ms: None,
224 max_hover_markdown_bytes: None,
225 max_preview_line_length: None,
226 retry_counter: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
227 }
228 }
229}
230
231impl std::fmt::Debug for LspSessionConfig {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 f.debug_struct("LspSessionConfig")
234 .field("cwd", &self.cwd)
235 .field("permissions", &self.permissions)
236 .finish()
237 }
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct LspHoverOk {
244 pub output: String,
245 pub path: String,
246 pub line: u32,
247 pub character: u32,
248 pub contents: String,
249 pub is_markdown: bool,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct LspDefinitionOk {
254 pub output: String,
255 pub path: String,
256 pub line: u32,
257 pub character: u32,
258 pub locations: Vec<LspLocation>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct LspReferencesOk {
263 pub output: String,
264 pub path: String,
265 pub line: u32,
266 pub character: u32,
267 pub locations: Vec<LspLocation>,
268 pub total: usize,
269 pub truncated: bool,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct LspDocumentSymbolOk {
274 pub output: String,
275 pub path: String,
276 pub symbols: Vec<LspSymbolInfo>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct LspWorkspaceSymbolOk {
281 pub output: String,
282 pub query: String,
283 pub symbols: Vec<LspSymbolInfo>,
284 pub total: usize,
285 pub truncated: bool,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct LspImplementationOk {
290 pub output: String,
291 pub path: String,
292 pub line: u32,
293 pub character: u32,
294 pub locations: Vec<LspLocation>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct LspNoResults {
299 pub output: String,
300 pub operation: LspOperation,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct LspServerStarting {
305 pub output: String,
306 pub language: String,
307 pub retry_ms: u64,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct LspError {
312 pub error: ToolError,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(tag = "kind", rename_all = "snake_case")]
317pub enum LspResult {
318 #[serde(rename = "hover")]
319 Hover(LspHoverOk),
320 #[serde(rename = "definition")]
321 Definition(LspDefinitionOk),
322 #[serde(rename = "references")]
323 References(LspReferencesOk),
324 #[serde(rename = "documentSymbol")]
325 DocumentSymbol(LspDocumentSymbolOk),
326 #[serde(rename = "workspaceSymbol")]
327 WorkspaceSymbol(LspWorkspaceSymbolOk),
328 #[serde(rename = "implementation")]
329 Implementation(LspImplementationOk),
330 #[serde(rename = "no_results")]
331 NoResults(LspNoResults),
332 #[serde(rename = "server_starting")]
333 ServerStarting(LspServerStarting),
334 #[serde(rename = "error")]
335 Error(LspError),
336}
337
338impl From<LspError> for LspResult {
339 fn from(e: LspError) -> Self {
340 LspResult::Error(e)
341 }
342}