Skip to content

Commit e95e3e2

Browse files
committed
Add LSP diagnostics support. Related to #56
1 parent 66c6b9b commit e95e3e2

File tree

13 files changed

+271
-21
lines changed

13 files changed

+271
-21
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Add editor tools to retrieve information like diagnostics. #56
6+
57
## 0.28.0
68

79
- Change api for custom providers to support `openai-responses` instead of just `openai`, still supporting `openai` only.

docs/configuration.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,11 @@ There are 3 possible ways to configure rules following this order of priority:
161161
rules: [{path: string;}];
162162
commands: [{path: string;}];
163163
systemPromptTemplateFile?: string;
164-
nativeTools: {
164+
nativeTools?: {
165165
filesystem: {enabled: boolean};
166166
shell: {enabled: boolean,
167167
excludeCommands: string[]};
168+
editor: {enabled: boolean,};
168169
};
169170
disabledTools: string[],
170171
toolCall?: {
@@ -222,7 +223,8 @@ There are 3 possible ways to configure rules following this order of priority:
222223
"commands" : [],
223224
"nativeTools": {"filesystem": {"enabled": true},
224225
"shell": {"enabled": true,
225-
"excludeCommands": []}},
226+
"excludeCommands": []},
227+
"editor": {"enabled": true}},
226228
"disabledTools": [],
227229
"toolCall": {
228230
"manualApproval": null,

docs/features.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ Provides access to run shell commands, useful to run build tools, tests, and oth
4242

4343
- `eca_shell_command`: run shell command. Supports configs to exclude commands via `:nativeTools :shell :excludeCommands`.
4444

45+
#### Editor
46+
47+
Provides access to get information from editor workspaces.
48+
49+
- `eca_editor_diagnostics`: Ask client about the diagnostics (like LSP diagnostics).
50+
4551
### Contexts
4652

4753
![](./images/features/contexts.png)

docs/protocol.md

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,19 @@ interface WorkspaceFolder {
127127
interface ClientCapabilities {
128128
codeAssistant?: {
129129
chat?: boolean;
130-
doc?: boolean;
131-
edit?: boolean;
132-
fix?: boolean;
130+
131+
/**
132+
* Whether client supports provide editor informations to server like
133+
* diagnostics, cursor information and others.
134+
*/
135+
editor?: {
136+
/**
137+
* Whether client supports provide editor diagnostics
138+
* information to server (Ex: LSP diagnostics) via `editor/getDiagnostics`
139+
* server request.
140+
*/
141+
diagnostics?: boolean;
142+
}
133143
}
134144
}
135145

@@ -943,6 +953,78 @@ _Response:_
943953
interface ChatDeleteResponse {}
944954
```
945955

956+
## Editor diagnostics (↪️)
957+
958+
A server request to retrieve LSP or any other kind of diagnostics if available from current workspaces.
959+
Useful for server to provide to LLM information about errors/warnings about current code.
960+
961+
_Request:_
962+
963+
* method: `editor/getDiagnostics`
964+
* params: `EditorGetDiagnosticsParams` defined as follows:
965+
966+
```typescript
967+
interface EditorGetDiagnosticsParams {
968+
/**
969+
* Optional uri to get diagnostics, if nil return whole workspaces diagnostics.
970+
*/
971+
uri?: string;
972+
}
973+
```
974+
975+
_Response:_
976+
977+
```typescript
978+
interface EditorGetDiagnosticsResponse {
979+
/**
980+
* The list of diagnostics.
981+
*/
982+
diagnostics: EditorDiagnostic[];
983+
}
984+
985+
interface EditorDiagnostic {
986+
/**
987+
* The diagnostic file uri.
988+
*/
989+
uri: string;
990+
991+
/**
992+
* The diagnostic severity.
993+
*/
994+
severity: 'error' | 'warning' | 'info' | 'hint';
995+
996+
/**
997+
* The diagnostic source. Ex: 'clojure-lsp'
998+
*/
999+
source: string;
1000+
1001+
/**
1002+
* The diagnostic range (1-based).
1003+
*/
1004+
range: {
1005+
start: {
1006+
line: number;
1007+
character: number;
1008+
};
1009+
1010+
end: {
1011+
line: number;
1012+
character: number;
1013+
};
1014+
};
1015+
1016+
/**
1017+
* The diagnostic code. Ex: 'wrong-args'
1018+
*/
1019+
code?: string;
1020+
1021+
/**
1022+
* The diagnostic message. Ex: 'Wrong number of args for function X'
1023+
*/
1024+
message: string;
1025+
}
1026+
```
1027+
9461028
### Completion (↩️)
9471029

9481030
Soon

src/eca/config.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
:commands []
2828
:nativeTools {:filesystem {:enabled true}
2929
:shell {:enabled true
30-
:excludeCommands []}}
30+
:excludeCommands []}
31+
:editor {:enabled true}}
3132
:disabledTools []
3233
:mcpTimeoutSeconds 60
3334
:mcpServers {}

src/eca/features/chat.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
(defn ^:private prompt-messages!
104104
[user-messages
105105
clear-history-after-finished?
106-
{:keys [db* config chat-id contexts behavior model instructions] :as chat-ctx}]
106+
{:keys [db* config chat-id contexts behavior model instructions messenger] :as chat-ctx}]
107107
(when (seq contexts)
108108
(send-content! chat-ctx :system {:type :progress
109109
:state :running
@@ -210,7 +210,7 @@
210210
;; Execute each tool call concurrently
211211
(future
212212
(if @approved?*
213-
(let [result (f.tools/call-tool! name arguments @db* config)]
213+
(let [result (f.tools/call-tool! name arguments @db* config messenger)]
214214
(assert-chat-not-stopped! chat-ctx)
215215
(add-to-history! {:role "tool_call" :content (assoc tool-call
216216
:details details

src/eca/features/tools.clj

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[babashka.fs :as fs]
66
[clojure.string :as string]
77
[eca.diff :as diff]
8+
[eca.features.tools.editor :as f.tools.editor]
89
[eca.features.tools.filesystem :as f.tools.filesystem]
910
[eca.features.tools.mcp :as f.mcp]
1011
[eca.features.tools.shell :as f.tools.shell]
@@ -31,7 +32,9 @@
3132
(when (get-in config [:nativeTools :filesystem :enabled])
3233
f.tools.filesystem/definitions)
3334
(when (get-in config [:nativeTools :shell :enabled])
34-
f.tools.shell/definitions))))
35+
f.tools.shell/definitions)
36+
(when (get-in config [:nativeTools :editor :enabled])
37+
f.tools.editor/definitions))))
3538

3639
(defn ^:private native-tools [db config]
3740
(vals (native-definitions db config)))
@@ -53,12 +56,14 @@
5356
(mapv #(assoc % :origin :native) (native-tools db config))
5457
(mapv #(assoc % :origin :mcp) (f.mcp/all-tools db))))))
5558

56-
(defn call-tool! [^String name ^Map arguments db config]
59+
(defn call-tool! [^String name ^Map arguments db config messenger]
5760
(logger/info logger-tag (format "Calling tool '%s' with args '%s'" name arguments))
5861
(let [arguments (update-keys arguments clojure.core/name)]
5962
(try
6063
(let [result (if-let [native-tool-handler (get-in (native-definitions db config) [name :handler])]
61-
(native-tool-handler arguments {:db db :config config})
64+
(native-tool-handler arguments {:db db
65+
:config config
66+
:messenger messenger})
6267
(f.mcp/call-tool! name arguments db))]
6368
(logger/debug logger-tag "Tool call result: " result)
6469
result)
@@ -114,10 +119,10 @@
114119

115120
(defn manual-approval? [name config]
116121
(boolean
117-
(let [manual-approval? (get-in config [:toolCall :manualApproval] nil)]
118-
(if (coll? manual-approval?)
119-
(some #(= name (str %)) manual-approval?)
120-
manual-approval?))))
122+
(let [manual-approval? (get-in config [:toolCall :manualApproval] nil)]
123+
(if (coll? manual-approval?)
124+
(some #(= name (str %)) manual-approval?)
125+
manual-approval?))))
121126

122127
(defn tool-call-summary [all-tools name args]
123128
(when-let [summary-fn (:summary-fn (first (filter #(= name (:name %))

src/eca/features/tools/editor.clj

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
(ns eca.features.tools.editor
2+
(:require
3+
[babashka.fs :as fs]
4+
[clojure.string :as string]
5+
[eca.features.tools.util :as tools.util]
6+
[eca.logger :as logger]
7+
[eca.messenger :as messenger]
8+
[eca.shared :as shared]))
9+
10+
(defn ^:private diagnostics [arguments {:keys [messenger]}]
11+
(or (tools.util/invalid-arguments arguments [["path" #(or (nil? %)
12+
(string/blank? %)
13+
(not (fs/directory? %))) "Path needs to be a file, not a directory."]])
14+
(let [uri (some-> (get arguments "path") not-empty shared/filename->uri)]
15+
(try
16+
(let [diags (:diagnostics @(messenger/editor-diagnostics messenger uri))]
17+
(if (seq diags)
18+
{:error false
19+
:contents [{:type :text
20+
:text (reduce
21+
(fn [s {:keys [uri range severity code message]}]
22+
(str s (format "%s:%s:%s: %s: %s%s"
23+
(shared/uri->filename uri)
24+
(-> range :start :line)
25+
(-> range :start :character)
26+
severity
27+
(if code (format "[%s] " code) "")
28+
message)))
29+
""
30+
diags)}]}
31+
{:error false
32+
:contents [{:type :text
33+
:text "No diagnostics found"}]}))
34+
(catch Exception e
35+
(logger/error (format "Error getting editor diagnostics for arguments %s: %s" arguments e))
36+
{:error true
37+
:contents [{:type :text
38+
:text "Error getting editor diagnostics"}]})))))
39+
40+
(def definitions
41+
{"eca_editor_diagnostics"
42+
{:description (str "Return editor diagnostics/findings (Ex: LSP diagnostics) for workspaces. "
43+
"Only provide the path if you want to get diagnostics for a specific file.")
44+
:parameters {:type "object"
45+
:properties {"path" {:type "string"
46+
:description "Optional absolute path to a file to return diagnostics only for that file."}}
47+
:required []}
48+
:handler #'diagnostics
49+
:enabled-fn (fn [{:keys [db]}] (-> db :client-capabilities :code-assistant :editor :diagnostics))
50+
:summary-fn (constantly "Checking diagnostics")}})

src/eca/messenger.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
(defprotocol IMessenger
88
(chat-content-received [this data])
99
(tool-server-updated [this params])
10-
(showMessage [this msg]))
10+
(showMessage [this msg])
11+
(editor-diagnostics [this uri]))

src/eca/server.clj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[eca.logger :as logger]
88
[eca.messenger :as messenger]
99
[eca.nrepl :as nrepl]
10-
[eca.shared :as shared]
10+
[eca.shared :as shared :refer [assoc-some]]
1111
[lsp4clj.io-server :as io-server]
1212
[lsp4clj.liveness-probe :as liveness-probe]
1313
[lsp4clj.server :as lsp.server]))
@@ -104,7 +104,10 @@
104104
(lsp.server/send-notification server "tool/serverUpdated" params)))
105105
(showMessage [_this msg]
106106
(lsp.server/discarding-stdout
107-
(lsp.server/send-notification server "$/showMessage" msg))))
107+
(lsp.server/send-notification server "$/showMessage" msg)))
108+
(editor-diagnostics [_this uri]
109+
(lsp.server/discarding-stdout
110+
(lsp.server/send-request server "editor/getDiagnostics" (assoc-some {} :uri uri)))))
108111

109112
(defn start-server! [server]
110113
(let [db* (atom db/initial-db)

0 commit comments

Comments
 (0)