Skip to content

Commit f2e4ed7

Browse files
committed
Refactor providers config for better extends in the future
1 parent ead11e1 commit f2e4ed7

File tree

15 files changed

+86
-82
lines changed

15 files changed

+86
-82
lines changed

CHANGELOG.md

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

55
- Update copilot models
66
- Drop uneeded `ollama useTools` and `ollama think` configs.
7+
- Refactor configs for config providers unification.
8+
- `<provider>ApiKey` and `<providerApiUrl>` now live in `:providers "<provider>" :key`.
79

810
## 0.30.0
911

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ To use ECA, you need to configure at least one model with your API key. See the
7070

7171
```json
7272
{
73-
"openaiApiKey": "your-openai-api-key-here",
74-
"anthropicApiKey": "your-anthropic-api-key-here"
73+
"providers": {
74+
"openai": {"key": "your-openai-api-key-here"},
75+
"anthropic": {"key": "your-anthropic-api-key-here"}
76+
}
7577
}
7678
```
7779

docs/configuration.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,10 @@ There are 3 possible ways to configure rules following this order of priority:
153153

154154
```typescript
155155
interface Config {
156-
openaiApiKey?: string;
157-
openaiApiUrl?: string;
158-
anthropicApiKey?: string;
159-
anthropicApiUrl?: string;
160-
ollamaApiUrl: string;
156+
providers: {[key: string]: {
157+
url?: string;
158+
key?: string; // when provider supports api key.
159+
}};
161160
rules: [{path: string;}];
162161
commands: [{path: string;}];
163162
systemPromptTemplateFile?: string;
@@ -210,11 +209,14 @@ There are 3 possible ways to configure rules following this order of priority:
210209

211210
```javascript
212211
{
213-
"openaiApiKey" : null,
214-
"openaiApiUrl" : null,
215-
"anthropicApiKey" : null,
216-
"anthropicApiUrl" : null,
217-
"ollamaApiUrl": "http://localhost:11434"
212+
"providers": {
213+
"openai": {"key": null,
214+
"url": "https://api.openai.com"},
215+
"anthropic": {"key": null,
216+
"url": "https://api.anthropic.com"},
217+
"github-copilot": {"url": "https://api.githubcopilot.com"},
218+
"ollama": {"url": "http://localhost:11434"}
219+
},
218220
"rules" : [],
219221
"commands" : [],
220222
"nativeTools": {"filesystem": {"enabled": true},

docs/features.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,10 @@ Eca supports commands that usually are triggered via shash (`/`) in the chat, co
7979
The built-in commands are:
8080

8181
`/init`: Create/update the AGENT.md file with details about the workspace for best LLM output quality.
82+
`/login`: Log into a provider. Ex: `/login github-copilot`
8283
`/costs`: Show costs about current session.
8384
`/resume`: Resume a chat from previous session of this workspace folder.
85+
`/config`: Show ECA config for troubleshooting.
8486
`/doctor`: Show information about ECA, useful for troubleshooting.
8587
`/repo-map-show`: Show the current repoMap context of the session.
8688
`/prompt-show`: Show the final prompt sent to LLM with all contexts and ECA details.

docs/models.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ Create a configuration file at `.eca/config.json` in your project root or at `~/
2727

2828
```javascript
2929
{
30-
"openaiApiKey": "your-openai-api-key-here",
31-
"anthropicApiKey": "your-anthropic-api-key-here"
30+
"providers": {
31+
"openai": {"key": "your-openai-api-key-here"},
32+
"anthropic": {"key": "your-anthropic-api-key-here"}
33+
}
3234
}
3335
```
3436

@@ -42,7 +44,9 @@ You can add new models or override existing ones in your configuration:
4244

4345
```javascript
4446
{
45-
"openaiApiKey": "your-openai-api-key-here",
47+
"providers": {
48+
"openai": {"key": "your-openai-api-key-here"}
49+
},
4650
"models": {
4751
"gpt-5": {},
4852
"claude-3-5-sonnet-20241022": {}
@@ -56,7 +60,9 @@ You can customize model parameters like temperature, reasoning effort, etc.:
5660

5761
```javascript
5862
{
59-
"openaiApiKey": "your-openai-api-key-here",
63+
"providers": {
64+
"openai": {"key": "your-openai-api-key-here"}
65+
},
6066
"models": {
6167
"gpt-5": {
6268
"extraPayload": {

integration-test/integration/chat/ollama_test.clj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
(is (match?
1717
{:models (m/embeds ["ollama/qwen3"])}
1818
(eca/request! (fixture/initialize-request {:initializationOptions (merge fixture/default-init-options
19-
{:ollamaApiUrl (str fixture/base-llm-mock-url "/ollama")})
19+
{:providers {"ollama" {:url (str fixture/base-llm-mock-url "/ollama")}}})
2020
:capabilities {:codeAssistant {:chat {}}}}))))
2121
(eca/notify! (fixture/initialized-notification))
2222
(let [chat-id* (atom nil)]
@@ -109,7 +109,7 @@
109109
(is (match?
110110
{:models (m/embeds ["ollama/qwen3"])}
111111
(eca/request! (fixture/initialize-request {:initializationOptions (merge fixture/default-init-options
112-
{:ollamaApiUrl (str fixture/base-llm-mock-url "/ollama")})
112+
{:providers {"ollama" {:url (str fixture/base-llm-mock-url "/ollama")}}})
113113
:capabilities {:codeAssistant {:chat {}}}}))))
114114
(eca/notify! (fixture/initialized-notification))
115115
(let [chat-id* (atom nil)]
@@ -185,7 +185,7 @@
185185
(is (match?
186186
{:models (m/embeds ["ollama/qwen3"])}
187187
(eca/request! (fixture/initialize-request {:initializationOptions (merge fixture/default-init-options
188-
{:ollamaApiUrl (str fixture/base-llm-mock-url "/ollama")})
188+
{:providers {"ollama" {:url (str fixture/base-llm-mock-url "/ollama")}}})
189189
:capabilities {:codeAssistant {:chat {}}}}))))
190190
(eca/notify! (fixture/initialized-notification))
191191
(let [chat-id* (atom nil)]

integration-test/integration/fixture.clj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
(str "http://localhost:" llm-mock.server/port))
99

1010
(def default-init-options {:pureConfig true
11-
:openaiApiUrl (str base-llm-mock-url "/openai")
12-
:openaiApiKey "foo"
13-
:anthropicApiUrl (str base-llm-mock-url "/anthropic")
14-
:anthropicApiKey "foo"
15-
:githubCopilotApiUrl (str base-llm-mock-url "/github-copilot")
16-
:githubCopilotApiKey "foo"})
11+
:providers {"openai" {:url (str base-llm-mock-url "/openai")
12+
:key "foo-key"}
13+
"anthropic" {:url (str base-llm-mock-url "/anthropic")
14+
:key "foo-key"}
15+
"github-copilot" {:url (str base-llm-mock-url "/github-copilot")
16+
:key "foo-key"}}})
1717

1818
(defn initialize-request
1919
([]

src/eca/config.clj

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
3. local config-file: searching from a local `.eca/config.json` file.
77
4. `initializatonOptions` sent in `initialize` request."
88
(:require
9+
[camel-snake-kebab.core :as csk]
910
[cheshire.core :as json]
1011
[cheshire.factory :as json.factory]
1112
[clojure.core.memoize :as memoize]
@@ -18,12 +19,12 @@
1819
(set! *warn-on-reflection* true)
1920

2021
(def initial-config
21-
{:openaiApiKey nil
22-
:anthropicApiKey nil
23-
:openaiApiUrl nil
24-
:anthropicApiUrl nil
25-
:githubCopilotApiUrl nil
26-
:ollamaApiUrl nil
22+
{:providers {"openai" {:key nil
23+
:url "https://api.openai.com"}
24+
"anthropic" {:key nil
25+
:url "https://api.anthropic.com"}
26+
"github-copilot" {:url "https://api.githubcopilot.com"}
27+
"ollama" {:url "http://localhost:11434"}}
2728
:rules []
2829
:commands []
2930
:nativeTools {:filesystem {:enabled true}
@@ -114,11 +115,16 @@
114115

115116
(def ollama-model-prefix "ollama/")
116117

118+
(defn ^:private normalize-fields [config]
119+
(-> config
120+
(update-in [:providers] update-keys #(csk/->kebab-case (string/replace-first (str %) ":" "")))))
121+
117122
(defn all [db]
118123
(let [initialization-config @initialization-config*
119124
pure-config? (:pureConfig initialization-config)]
120-
(deep-merge initial-config
121-
initialization-config
122-
(when-not pure-config? (config-from-envvar))
123-
(when-not pure-config? (config-from-global-file))
124-
(when-not pure-config? (config-from-local-file (:workspace-folders db))))))
125+
(normalize-fields
126+
(deep-merge initial-config
127+
initialization-config
128+
(when-not pure-config? (config-from-envvar))
129+
(when-not pure-config? (config-from-global-file))
130+
(when-not pure-config? (config-from-local-file (:workspace-folders db)))))))

src/eca/features/commands.clj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require
33
[babashka.fs :as fs]
44
[clojure.java.io :as io]
5+
[clojure.pprint :as pprint]
56
[clojure.string :as string]
67
[eca.config :as config]
78
[eca.features.index :as f.index]
@@ -88,6 +89,10 @@
8889
:type :native
8990
:description "Resume the chats from this session workspaces."
9091
:arguments []}
92+
{:name "config"
93+
:type :native
94+
:description "Show ECA config for troubleshooting."
95+
:arguments []}
9196
{:name "doctor"
9297
:type :native
9398
:description "Check ECA details for troubleshooting."
@@ -173,6 +178,8 @@
173178
(str "Total cost: $" (shared/tokens->cost total-input-tokens total-input-cache-creation-tokens total-input-cache-read-tokens total-output-tokens full-model db)))]
174179
{:type :chat-messages
175180
:chats {chat-id [{:role "system" :content [{:type :text :text text}]}]}})
181+
"config" {:type :chat-messages
182+
:chats {chat-id [{:role "system" :content [{:type :text :text (with-out-str (pprint/pprint config))}]}]}}
176183
"doctor" {:type :chat-messages
177184
:chats {chat-id [{:role "system" :content [{:type :text :text (doctor-msg db config)}]}]}}
178185
"repo-map-show" {:type :chat-messages

src/eca/llm_api.clj

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
[clojure.string :as string]
55
[eca.config :as config]
66
[eca.llm-providers.anthropic :as llm-providers.anthropic]
7-
[eca.llm-providers.copilot :as llm-providers.copilot]
87
[eca.llm-providers.ollama :as llm-providers.ollama]
98
[eca.llm-providers.openai :as llm-providers.openai]
109
[eca.llm-providers.openai-chat :as llm-providers.openai-chat]
@@ -32,36 +31,20 @@
3231
(string/join "\n" (subvec lines start end)))
3332
content))))
3433

35-
(defn ^:private anthropic-api-key [config]
36-
(or (:anthropicApiKey config)
37-
(config/get-env "ANTHROPIC_API_KEY")))
34+
(defn ^:private provider-api-key [provider config]
35+
(or (get-in config [:providers (name provider) :key])
36+
(config/get-env (str (-> provider
37+
(string/replace "-" "_")
38+
string/upper-case) "_API_KEY"))))
3839

39-
(defn ^:private anthropic-api-url [config]
40-
(or (:anthropicApiUrl config)
41-
(config/get-env "ANTHROPIC_API_URL")
42-
llm-providers.anthropic/base-url))
43-
44-
(defn ^:private openai-api-key [config]
45-
(or (:openaiApiKey config)
46-
(config/get-env "OPENAI_API_KEY")))
47-
48-
(defn ^:private openai-api-url [config]
49-
(or (:openaiApiUrl config)
50-
(config/get-env "OPENAI_API_URL")
51-
llm-providers.openai/base-url))
52-
53-
(defn ^:private github-copilot-api-url [config]
54-
(or (:githubCopilotApiUrl config)
55-
(config/get-env "GITHUB_COPILOT_API_URL")
56-
llm-providers.copilot/base-api-url))
57-
58-
(defn ^:private ollama-api-url [config]
59-
(or (:ollamaApiUrl config)
60-
(config/get-env "OLLAMA_API_URL")
61-
llm-providers.ollama/base-url))
40+
(defn ^:private provider-api-url [provider config]
41+
(or (get-in config [:providers (name provider) :url])
42+
(config/get-env (str (-> provider
43+
(string/replace "-" "_")
44+
string/upper-case) "_API_URL"))))
6245

6346
(defn extra-models [config]
64-
(let [ollama-api-url (ollama-api-url config)]
47+
(let [ollama-api-url (provider-api-url "ollama" config)]
6548
(mapv
6649
(fn [{:keys [model] :as ollama-model}]
6750
(let [capabilities (llm-providers.ollama/model-capabilities {:api-url ollama-api-url :model model})]
@@ -86,9 +69,9 @@
8669
model))
8770
(:models db)))]
8871
[:custom-provider-default-model custom-provider-default-model])
89-
(when (anthropic-api-key config)
72+
(when (provider-api-key "anthropic" config)
9073
[:api-key-found "anthropic/claude-sonnet-4-20250514"])
91-
(when (openai-api-key config)
74+
(when (provider-api-key "openai" config)
9275
[:api-key-found "openai/gpt-5"])
9376
(when (get-in db [:auth "github-copilot" :api-key])
9477
[:api-key-found "github-copilot/gpt-4.1"])
@@ -133,6 +116,8 @@
133116
(map #(str (name k) "/" %) (:models v)))
134117
custom-providers))
135118
extra-payload (get-in config [:models (keyword model) :extraPayload])
119+
provider-api-key (provider-api-key provider config)
120+
provider-api-url (provider-api-url provider config)
136121
callbacks {:on-message-received on-message-received-wrapper
137122
:on-error on-error-wrapper
138123
:on-prepare-tool-call on-prepare-tool-call-wrapper
@@ -152,8 +137,8 @@
152137
:tools tools
153138
:web-search web-search
154139
:extra-payload extra-payload
155-
:api-url (openai-api-url config)
156-
:api-key (openai-api-key config)}
140+
:api-url provider-api-url
141+
:api-key provider-api-key}
157142
callbacks)
158143

159144
(= "anthropic" provider)
@@ -167,8 +152,8 @@
167152
:tools tools
168153
:web-search web-search
169154
:extra-payload extra-payload
170-
:api-url (anthropic-api-url config)
171-
:api-key (anthropic-api-key config)}
155+
:api-url provider-api-url
156+
:api-key provider-api-key}
172157
callbacks)
173158

174159
(= "github-copilot" provider)
@@ -181,7 +166,7 @@
181166
:past-messages past-messages
182167
:tools tools
183168
:extra-payload extra-payload
184-
:api-url (github-copilot-api-url config)
169+
:api-url provider-api-url
185170
:api-key (:api-token provider-auth)
186171
:extra-headers {"openai-intent" "conversation-panel"
187172
"x-request-id" (str (random-uuid))
@@ -192,7 +177,7 @@
192177

193178
(= "ollama" provider)
194179
(llm-providers.ollama/completion!
195-
{:api-url (ollama-api-url config)
180+
{:api-url provider-api-url
196181
:reason? (:reason? model-config)
197182
:model model
198183
:instructions instructions

0 commit comments

Comments
 (0)