Skip to content

Commit 30f7b41

Browse files
committed
Add Anthropic integration
1 parent b100784 commit 30f7b41

File tree

9 files changed

+122
-35
lines changed

9 files changed

+122
-35
lines changed

CHANGELOG.md

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

33
## Unreleased
44

5+
- First alpha release
6+

build.clj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
[clojure.string :as string]
66
[clojure.tools.build.api :as b]))
77

8-
(def lib 'com.github.editor-code-assistant/eca)
9-
(def current-version (string/trim (slurp (io/resource "ECA_VERSION"))))
108
(def class-dir "target/classes")
119
(def basis {:project "deps.edn"})
1210
(def file "target/eca.jar")

docs/protocol.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ interface ChatPromptParams {
250250
type ChatModel =
251251
| 'o4-mini'
252252
| 'gpt-4.1'
253+
| 'claude-sonnet-4-0'
254+
| 'claude-opus-4-0'
255+
| 'claude-3-5-haiku-latest'
253256
| "auto";
254257

255258
type ChatContext = FileContext | DirectoryContext | WebContext | CodeContext;

src/eca/config.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
(def initial-config
1616
{:openai-api-key nil
17+
:anthropic-api-key nil
1718
:index {:ignore-files [".gitignore"]}})
1819

1920
(defn ^:private safe-read-json-string [raw-string]

src/eca/db.clj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
:client-capabilities {}
77
:chats []
88
:chat-behavior :agent
9-
:models ["o4-mini" "gpt-4.1"]
9+
:models ["o4-mini"
10+
"gpt-4.1"
11+
"claude-sonnet-4-0"
12+
"claude-opus-4-0"
13+
"claude-3-5-haiku-latest"]
1014
:default-model "o4-mini"})
1115

1216
(defonce db* (atom initial-db))

src/eca/features/chat.clj

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,23 @@
2323
nil))
2424
context))
2525

26-
(defn ^:private behavior->prompt-input [behavior]
27-
(case (keyword behavior)
28-
:agent "Help suggesting what needs to be changed if requested, offering help to make itself."
29-
:ask "Only answer questions and doubts."
30-
:manual "Help suggesting what needs to be changed."
31-
""))
32-
33-
(defn ^:private build-prompt [message behavior refined-context]
34-
(format (str "You are an expert AI coding tool called ECA (Editor Code Assistant). Structure your answer in markdown *WITHOUT* using markdown code block.\n"
35-
"Your behavior is to '%s'.\n"
36-
"The user is asking: '%s'\n"
37-
"Context: %s")
38-
(behavior->prompt-input behavior)
39-
message
40-
(reduce (fn [msg {:keys [type path content-map]}]
41-
(str
42-
msg
43-
(case type
44-
:file (str path ":\n" content-map)
45-
"")
46-
"\n")) "" refined-context)))
26+
(defn ^:private build-context [behavior refined-context]
27+
{:role (str "You are an expert AI coding tool called ECA (Editor Code Assistant).\n"
28+
"Structure your answer in markdown *WITHOUT* using markdown code block.")
29+
:behavior (format "Your behavior is to '%s'."
30+
(case (keyword behavior)
31+
:agent "Help suggesting what needs to be changed if requested, offering help to make itself."
32+
:ask "Only answer questions and doubts."
33+
:manual "Help suggesting what needs to be changed."
34+
""))
35+
:context (format "<context>\n%s\n</context>"
36+
(reduce (fn [msg {:keys [type path content-map]}]
37+
(str
38+
msg
39+
(case type
40+
:file (str path ":\n" content-map)
41+
"")
42+
"\n")) "" refined-context))})
4743

4844
(defn prompt
4945
[{:keys [message model behavior contexts chat-id request-id]}
@@ -71,7 +67,8 @@
7167
:role :system
7268
:content {:type :temporary-text
7369
:text "Parsing given context..."}}))
74-
(let [refined-context (raw-context->refined contexts)]
70+
(let [refined-contexts (raw-context->refined contexts)
71+
context (build-context (or behavior (:chat-behavior @db*)) refined-contexts)]
7572
(messenger/chat-content-received
7673
messenger
7774
{:chat-id chat-id
@@ -81,7 +78,8 @@
8178
:content {:type :temporary-text
8279
:text "Generating..."}})
8380
(llm-api/complete! {:model (or model (:default-model @db*))
84-
:message (build-prompt message (or behavior (:chat-behavior @db*)) refined-context)
81+
:user-prompt message
82+
:context context
8583
:config config
8684
:on-message-received (fn [{:keys [message finish-reason]}]
8785
(messenger/chat-content-received

src/eca/llm_api.clj

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
(ns eca.llm-api
22
(:require
3+
[eca.llm-providers.anthropic :as llm-providers.anthropic]
34
[eca.llm-providers.openai :as llm-providers.openai]))
45

56
(set! *warn-on-reflection* true)
@@ -8,13 +9,25 @@
89
;; TODO ask LLM for the most relevant parts of the path
910
(slurp path))
1011

11-
(defn complete! [{:keys [model message config on-message-received on-error]}]
12+
(defn complete! [{:keys [model context user-prompt config on-message-received on-error]}]
1213
(case model
1314
("o4-mini"
14-
"gpt-4.1") (llm-providers.openai/completion!
15-
{:model model
16-
:messages [{:role "user" :content message}]
17-
:api-key (:openai-api-key config)}
18-
{:on-message-received on-message-received
19-
:on-error on-error})
15+
"gpt-4.1")
16+
(llm-providers.openai/completion!
17+
{:model model
18+
:context context
19+
:user-prompt user-prompt
20+
:api-key (:openai-api-key config)}
21+
{:on-message-received on-message-received
22+
:on-error on-error})
23+
("claude-sonnet-4-0"
24+
"claude-opus-4-0"
25+
"claude-3-5-haiku-latest")
26+
(llm-providers.anthropic/completion!
27+
{:model model
28+
:context context
29+
:user-prompt user-prompt
30+
:api-key (:anthropic-api-key config)}
31+
{:on-message-received on-message-received
32+
:on-error on-error})
2033
(on-error {:msg (str "ECA Unsupported model: " model)})))
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
(ns eca.llm-providers.anthropic
2+
(:require
3+
[cheshire.core :as json]
4+
[clojure.java.io :as io]
5+
[clojure.string :as str]
6+
[eca.logger :as logger]
7+
[hato.client :as http]))
8+
9+
(def ^:private logger-tag "[ANTHROPIC]")
10+
11+
(def ^:private url "https://api.anthropic.com/v1/messages")
12+
13+
(defn ^:private raw-data->messages [data]
14+
(let [{:keys [type delta]} (json/parse-string data true)]
15+
(case type
16+
"content_block_delta" (case (:type delta)
17+
"text_delta" {:message (:text delta)}
18+
(logger/warn "Unkown response delta type" (:type delta)))
19+
"message_stop" {:finish-reason type}
20+
nil)))
21+
22+
(defn ^:private context->system [{:keys [role behavior context]}]
23+
(format "%s\n%s\n%s\n"
24+
role behavior context))
25+
26+
(defn completion! [{:keys [model user-prompt temperature context max-tokens api-key]
27+
:or {max-tokens 1024
28+
temperature 1.0}}
29+
{:keys [on-message-received on-error]}]
30+
(let [body {:model model
31+
:messages [{:role "user" :content user-prompt}]
32+
:max_tokens max-tokens
33+
:temperature temperature
34+
;; TODO support :thinking
35+
:stream true
36+
:system (context->system context)}
37+
api-key (or api-key
38+
(System/getenv "ANTHROPIC_API_KEY"))]
39+
(http/post
40+
url
41+
{:headers {"x-api-key" api-key
42+
"anthropic-version" "2023-06-01"
43+
"Content-Type" "application/json"}
44+
:body (json/generate-string body)
45+
:throw-exceptions? false
46+
:async? true
47+
:as :stream}
48+
(fn [{:keys [status body]}]
49+
(try
50+
(with-open [rdr (io/reader body)]
51+
(doseq [line (line-seq rdr)]
52+
(if (not= 200 status)
53+
(let [msg line]
54+
(logger/warn logger-tag "Unexpected response status" status "." msg)
55+
(on-error {:message (str "Anthropic response status: " status)}))
56+
(when (str/starts-with? line "data: ")
57+
(let [data (subs line 6)]
58+
(when-let [message (raw-data->messages data)]
59+
(on-message-received message)))))))
60+
(catch Exception e
61+
(on-error {:exception e}))))
62+
(fn [e]
63+
(on-error {:exception e})))))

src/eca/llm_providers/openai.clj

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818
finish_reason (assoc :finish-reason finish_reason)))
1919
choices)))
2020

21-
(defn completion! [{:keys [model messages temperature api-key]
21+
(defn ^:private ->message [{:keys [role behavior context]} user-prompt]
22+
(format "%s\n%s\n%s\nThe user is asking: '%s'"
23+
role behavior context user-prompt))
24+
25+
(defn completion! [{:keys [model user-prompt context temperature api-key]
2226
:or {temperature 1.0}}
2327
{:keys [on-message-received on-error]}]
24-
(let [body {:model model
28+
(let [messages [{:role "user" :content (->message context user-prompt)}]
29+
body {:model model
2530
:messages messages
2631
:temperature temperature
2732
:stream true}

0 commit comments

Comments
 (0)