1use async_trait::async_trait;
8use serde_json::{Value, json};
9
10use super::{AgentTool, AgentToolResult, LspAction, ToolContext, ToolError};
11
12pub struct LspTool;
17
18#[async_trait]
19impl AgentTool for LspTool {
20 fn name(&self) -> &str {
21 "lsp"
22 }
23
24 fn label(&self) -> &str {
25 "LSP"
26 }
27
28 fn essential(&self) -> bool {
29 false
30 }
31
32 fn description(&self) -> &str {
33 "Language Server Protocol operations: diagnostics, definition, references, \
34 hover, rename, symbols. Requires LSP enabled in settings."
35 }
36
37 fn parameters_schema(&self) -> Value {
38 json!({
39 "type": "object",
40 "properties": {
41 "action": {
42 "type": "string",
43 "enum": [
44 "diagnostics", "definition", "references", "hover",
45 "rename", "symbols", "status",
46 "code_actions", "type_definition", "implementation",
47 "file_rename"
48 ]
49 },
50 "file": {"type": "string", "description": "File path"},
51 "old_path": {"type": "string", "description": "Existing path for file_rename"},
52 "new_path": {"type": "string", "description": "Target path for file_rename"},
53 "line": {"type": "integer", "description": "1-based line number"},
54 "symbol": {"type": "string", "description": "Symbol name (optional, for disambiguation)"},
55 "new_name": {"type": "string", "description": "New name for rename"},
56 "apply": {"type": "boolean", "description": "Apply changes (vs preview)"},
57 "query": {"type": "string", "description": "Search query for symbols"}
58 },
59 "required": ["action"]
60 })
61 }
62
63 async fn execute(
64 &self,
65 _tool_call_id: &str,
66 params: Value,
67 _signal: Option<tokio::sync::oneshot::Receiver<()>>,
68 ctx: &ToolContext,
69 ) -> Result<AgentToolResult, ToolError> {
70 let provider = ctx
71 .lsp
72 .as_ref()
73 .ok_or("LSP not configured. Enable LSP in settings or install language servers.")?;
74
75 let action = parse_lsp_action(¶ms)?;
76
77 let result = provider.execute_action(&action).await?;
78
79 Ok(AgentToolResult::success(result))
80 }
81}
82
83fn parse_lsp_action(params: &Value) -> Result<LspAction, ToolError> {
85 let action = params
86 .get("action")
87 .and_then(|v| v.as_str())
88 .ok_or("Missing required parameter: action")?;
89
90 let file = params
91 .get("file")
92 .and_then(|v| v.as_str())
93 .unwrap_or("")
94 .to_string();
95
96 let line = params
97 .get("line")
98 .and_then(|v| v.as_u64())
99 .map(|n| n as u32)
100 .unwrap_or(1);
101
102 let symbol = params
103 .get("symbol")
104 .and_then(|v| v.as_str())
105 .map(String::from);
106
107 match action {
108 "diagnostics" => Ok(LspAction::Diagnostics { file }),
109 "definition" => Ok(LspAction::Definition { file, line, symbol }),
110 "references" => Ok(LspAction::References { file, line, symbol }),
111 "hover" => Ok(LspAction::Hover { file, line, symbol }),
112 "rename" => {
113 let new_name = params
114 .get("new_name")
115 .and_then(|v| v.as_str())
116 .ok_or("rename requires 'new_name'")?
117 .to_string();
118 let sym = symbol.clone().unwrap_or_default();
119 let apply = params
120 .get("apply")
121 .and_then(|v| v.as_bool())
122 .unwrap_or(false);
123 Ok(LspAction::Rename {
124 file,
125 line,
126 symbol: sym,
127 new_name,
128 apply,
129 })
130 }
131 "symbols" => {
132 let query = params
133 .get("query")
134 .and_then(|v| v.as_str())
135 .map(String::from);
136 Ok(LspAction::Symbols { file, query })
137 }
138 "status" => Ok(LspAction::Status),
139 "code_actions" => Ok(LspAction::CodeActions { file, line, symbol }),
140 "type_definition" => Ok(LspAction::TypeDefinition { file, line, symbol }),
141 "implementation" => Ok(LspAction::Implementation { file, line, symbol }),
142 "file_rename" => {
143 let old_path = params
144 .get("old_path")
145 .and_then(|v| v.as_str())
146 .ok_or("file_rename requires 'old_path'")?
147 .to_string();
148 let new_path = params
149 .get("new_path")
150 .and_then(|v| v.as_str())
151 .ok_or("file_rename requires 'new_path'")?
152 .to_string();
153 let apply = params
154 .get("apply")
155 .and_then(|v| v.as_bool())
156 .unwrap_or(false);
157 Ok(LspAction::FileRename {
158 old_path,
159 new_path,
160 apply,
161 })
162 }
163 other => Err(format!("Unknown LSP action: '{}'", other)),
164 }
165}