Skip to content

Commit 9b76536

Browse files
committed
Return only authenticated models
1 parent 3a39d5f commit 9b76536

File tree

14 files changed

+177
-130
lines changed

14 files changed

+177
-130
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- Support `chat/selectedBehaviorChanged` client notification.
6+
- Update models according with supported models given its auth or key/url configuration.
67

78
## 0.42.0
89

docs/models.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Models
22

3-
All providers and models are configured under `providers` config.
3+
All providers and models are configured under `providers` config or via `/login` when supported.
44

55
Models capabilities and configurations are retrieved from [models.dev](https://models.dev) API.
66

7+
ECA will return to clients the models configured, either via config or login.
8+
79
## Built-in providers and capabilities
810

911
| model | tools (MCP) | reasoning / thinking | prompt caching | web_search |

integration-test/entrypoint.clj

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66

77
(def namespaces
88
'[integration.initialize-test
9-
;integration.chat.openai-test
10-
;integration.chat.anthropic-test
11-
;integration.chat.github-copilot-test
12-
;integration.chat.ollama-test
13-
;integration.chat.custom-provider-test
14-
])
9+
integration.chat.openai-test
10+
integration.chat.anthropic-test
11+
integration.chat.github-copilot-test
12+
integration.chat.ollama-test
13+
integration.chat.custom-provider-test])
1514

1615
(defn timeout [timeout-ms callback]
1716
(let [fut (future (callback))

integration-test/integration/fixture.clj

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@
77
(def base-llm-mock-url
88
(str "http://localhost:" llm-mock.server/port))
99

10+
(def default-providers
11+
{"openai" {:url (str base-llm-mock-url "/openai")
12+
:key "foo-key"
13+
:keyEnv "FOO"}
14+
"anthropic" {:url (str base-llm-mock-url "/anthropic")
15+
:key "foo-key"
16+
:keyEnv "FOO"}
17+
"github-copilot" {:url (str base-llm-mock-url "/github-copilot")
18+
:key "foo-key"
19+
:keyEnv "FOO"}})
20+
1021
(def default-init-options {:pureConfig true
1122
:toolCall {:approval {:byDefault "allow"}}
12-
:providers {"openai" {:url (str base-llm-mock-url "/openai")
13-
:key "foo-key"
14-
:keyEnv "FOO"}
15-
"anthropic" {:url (str base-llm-mock-url "/anthropic")
16-
:key "foo-key"
17-
:keyEnv "FOO"}
18-
"github-copilot" {:url (str base-llm-mock-url "/github-copilot")
19-
:key "foo-key"
20-
:keyEnv "FOO"}}})
23+
:providers default-providers})
2124

2225
(defn initialize-request
2326
([]

integration-test/integration/initialize_test.clj

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -67,35 +67,48 @@
6767

6868
(deftest initialize-with-custom-providers
6969
(eca/start-process!)
70-
(testing "initialize request with custom providers"
71-
(is (match?
72-
{:models ["anthropic/claude-3-5-haiku-20241022"
73-
"anthropic/claude-opus-4-1-20250805"
74-
"anthropic/claude-opus-4-20250514"
75-
"anthropic/claude-sonnet-4-20250514"
76-
"github-copilot/claude-sonnet-4"
77-
"github-copilot/gemini-2.5-pro"
78-
"github-copilot/gpt-4.1"
79-
"github-copilot/gpt-5"
80-
"github-copilot/gpt-5-mini"
81-
"my-custom/bar2"
82-
"my-custom/foo1"
83-
"openai/gpt-4.1"
84-
"openai/gpt-5"
85-
"openai/gpt-5-mini"
86-
"openai/gpt-5-nano"
87-
"openai/o3"
88-
"openai/o4-mini"]
89-
:chatDefaultModel "myCustom/bar-2"
90-
:chatBehaviors ["agent" "plan"]
91-
:chatDefaultBehavior "agent"
92-
:chatWelcomeMessage "Welcome to ECA!\n\nType '/' for commands\n\n"}
93-
(eca/request! (fixture/initialize-request
94-
{:initializationOptions (merge fixture/default-init-options
95-
{:defaultModel "myCustom/bar-2"
96-
:providers
97-
{"myCustom" {:api "openai"
98-
:urlEnv "MY_CUSTOM_API_URL"
99-
:keyEnv "MY_CUSTOM_API_KEY"
100-
:models {"foo1" {}
101-
"bar2" {}}}}})}))))))
70+
(let [models ["anthropic/claude-3-5-haiku-20241022"
71+
"anthropic/claude-opus-4-1-20250805"
72+
"anthropic/claude-opus-4-20250514"
73+
"anthropic/claude-sonnet-4-20250514"
74+
"github-copilot/claude-sonnet-4"
75+
"github-copilot/gemini-2.5-pro"
76+
"github-copilot/gpt-4.1"
77+
"github-copilot/gpt-5"
78+
"github-copilot/gpt-5-mini"
79+
"my-custom/bar2"
80+
"my-custom/foo1"
81+
"openai/gpt-4.1"
82+
"openai/gpt-5"
83+
"openai/gpt-5-mini"
84+
"openai/gpt-5-nano"
85+
"openai/o3"
86+
"openai/o4-mini"]]
87+
(testing "initialize request with custom providers"
88+
(is (match?
89+
{:models models
90+
:chatDefaultModel "my-custom/bar-2"
91+
:chatBehaviors ["agent" "plan"]
92+
:chatDefaultBehavior "agent"
93+
:chatWelcomeMessage "Welcome to ECA!\n\nType '/' for commands\n\n"}
94+
(eca/request! (fixture/initialize-request
95+
{:initializationOptions (merge fixture/default-init-options
96+
{:defaultModel "my-custom/bar-2"
97+
:providers
98+
(merge fixture/default-providers
99+
{"my-custom" {:api "openai-chat"
100+
:url "MY_URL"
101+
:key "MY_KEY"
102+
:models {"foo1" {}
103+
"bar2" {}}}})})})))))
104+
(testing "initialized notification"
105+
(eca/notify! (fixture/initialized-notification)))
106+
107+
(testing "config updated"
108+
(is (match?
109+
{:chat {:models models
110+
:defaultModel "my-custom/bar-2"
111+
:behaviors ["agent" "plan"]
112+
:defaultBehavior "agent"
113+
:welcomeMessage "Welcome to ECA!\n\nType '/' for commands\n\n"}}
114+
(eca/client-awaits-server-notification :config/updated))))))

src/eca/db.clj

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,14 @@
104104
(when (= version (:version cache))
105105
cache)))
106106

107-
(defn load-db-from-cache! [db*]
108-
(when-let [global-cache (read-global-cache)]
109-
(swap! db* shared/deep-merge global-cache))
110-
(when-let [global-by-workspace-cache (read-global-by-workspaces-cache (:workspace-folders @db*))]
111-
(swap! db* shared/deep-merge global-by-workspace-cache)))
107+
(defn load-db-from-cache! [db* config]
108+
(when-not (:pureConfig config)
109+
(when-let [global-cache (read-global-cache)]
110+
(logger/info logger-tag "Loading from global-cache caches...")
111+
(swap! db* shared/deep-merge global-cache))
112+
(when-let [global-by-workspace-cache (read-global-by-workspaces-cache (:workspace-folders @db*))]
113+
(logger/info logger-tag "Loading from workspace-cache caches...")
114+
(swap! db* shared/deep-merge global-by-workspace-cache))))
112115

113116
(defn ^:private normalize-db-for-workspace-write [db]
114117
(-> (select-keys db [:chats])

src/eca/features/login.clj

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
(:require
33
[clojure.string :as string]
44
[eca.db :as db]
5-
[eca.messenger :as messenger]))
5+
[eca.messenger :as messenger]
6+
[eca.models :as models]))
67

78
(defmulti login-step (fn [ctx] [(:provider ctx) (:step ctx)]))
89

@@ -14,14 +15,16 @@
1415
:provider provider
1516
:db* db*}))
1617

17-
(defn continue [{:keys [message chat-id request-id]} db* messenger]
18+
(defn continue [{:keys [message chat-id request-id]} db* messenger config]
1819
(let [provider (get-in @db* [:chats chat-id :login-provider])
1920
step (get-in @db* [:auth provider :step])
2021
input (string/trim message)
2122
ctx {:chat-id chat-id
2223
:step step
2324
:input input
2425
:db* db*
26+
:config config
27+
:messenger messenger
2528
:provider provider
2629
:send-msg! (fn [msg]
2730
(messenger/chat-content-received
@@ -56,3 +59,12 @@
5659
:step :login/renew-token
5760
:db* db*})
5861
(db/update-global-cache! @db*))
62+
63+
(defn login-done! [{:keys [db* config messenger]}]
64+
(models/sync-models! db*
65+
config
66+
(fn [new-models]
67+
(messenger/config-updated
68+
messenger
69+
{:chat
70+
{:models (sort (keys new-models))}}))))

src/eca/handlers.clj

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,18 @@
11
(ns eca.handlers
22
(:require
3-
[clojure.string :as string]
43
[eca.config :as config]
54
[eca.db :as db]
65
[eca.features.chat :as f.chat]
76
[eca.features.login :as f.login]
87
[eca.features.tools :as f.tools]
98
[eca.features.tools.mcp :as f.mcp]
10-
[eca.llm-api :as llm-api]
119
[eca.logger :as logger]
1210
[eca.messenger :as messenger]
1311
[eca.models :as models]
1412
[eca.shared :as shared]))
1513

1614
(set! *warn-on-reflection* true)
1715

18-
(defn ^:private sync-models! [db* config on-models-updated]
19-
(let [all-models (models/all)
20-
all-models (reduce
21-
(fn [p [provider provider-config]]
22-
(merge p
23-
(reduce
24-
(fn [m [model _model-config]]
25-
(let [full-model (str provider "/" model)
26-
model-capabilities (merge
27-
(or (get all-models full-model)
28-
;; we guess the capabilities from
29-
;; the first model with same name
30-
(when-let [found-full-model (first (filter #(= (shared/normalize-model-name model)
31-
(shared/normalize-model-name (second (string/split % #"/" 2))))
32-
(keys all-models)))]
33-
(get all-models found-full-model))
34-
{:tools true
35-
:reason? true
36-
:web-search true}))]
37-
(assoc m full-model model-capabilities)))
38-
{}
39-
(:models provider-config))))
40-
{}
41-
(:providers config))
42-
all-models (if-let [local-models (seq (llm-api/local-models config))]
43-
(let [models (reduce
44-
(fn [models {:keys [model] :as ollama-model}]
45-
(assoc models
46-
(str config/ollama-model-prefix model)
47-
(select-keys ollama-model [:tools :reason?])))
48-
{}
49-
local-models)]
50-
(merge all-models models))
51-
all-models)]
52-
(swap! db* assoc :models all-models)
53-
(on-models-updated)))
54-
5516
(defn initialize [{:keys [db*]} params]
5617
(logger/logging-task
5718
:eca/initialize
@@ -63,12 +24,12 @@
6324
:workspace-folders (:workspace-folders params)
6425
:client-capabilities (:capabilities params))
6526
(when-not (:pureConfig config)
66-
(db/load-db-from-cache! db*))
27+
(db/load-db-from-cache! db* config))
6728

6829
;; Deprecated
6930
;; For backward compatibility,
7031
;; we now return chat config via `config/updated` notification.
71-
(sync-models! db* config (fn []))
32+
(models/sync-models! db* config (fn [_]))
7233
(let [db @db*]
7334
{:models (sort (keys (:models db)))
7435
:chat-default-model (f.chat/default-model db config)
@@ -83,11 +44,11 @@
8344
(let [new-providers-hash (hash (:providers config))]
8445
(when (not= (:providers-config-hash @db*) new-providers-hash)
8546
(swap! db* assoc :providers-config-hash new-providers-hash)
86-
(sync-models! db* config (fn []
47+
(models/sync-models! db* config (fn [models]
8748
(let [db @db*]
8849
(config/notify-fields-changed-only!
8950
{:chat
90-
{:models (sort (keys (:models db)))
51+
{:models (sort (keys models))
9152
:default-model (f.chat/default-model db config)
9253
:behaviors (:chat-behaviors db)
9354
:default-behavior (or (:defaultBehavior (:chat config)) ;;legacy
@@ -122,7 +83,7 @@
12283
(logger/logging-task
12384
:eca/chat-prompt
12485
(case (get-in @db* [:chats (:chat-id params) :status])
125-
:login (f.login/continue params db* messenger)
86+
:login (f.login/continue params db* messenger config)
12687
(f.chat/prompt params db* messenger config))))
12788

12889
(defn chat-query-context [{:keys [db* config]} params]

src/eca/llm_api.clj

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
[eca.llm-providers.ollama :as llm-providers.ollama]
99
[eca.llm-providers.openai :as llm-providers.openai]
1010
[eca.llm-providers.openai-chat :as llm-providers.openai-chat]
11+
[eca.llm-util :as llm-util]
1112
[eca.logger :as logger]))
1213

1314
(set! *warn-on-reflection* true)
@@ -32,25 +33,6 @@
3233
(string/join "\n" (subvec lines start end)))
3334
content))))
3435

35-
(defn ^:private provider-api-key [provider provider-auth config]
36-
(or (get-in config [:providers (name provider) :key])
37-
(:api-key provider-auth)
38-
(some-> (get-in config [:providers (name provider) :keyEnv]) config/get-env)))
39-
40-
(defn ^:private provider-api-url [provider config]
41-
(or (get-in config [:providers (name provider) :url])
42-
(some-> (get-in config [:providers (name provider) :urlEnv]) config/get-env)))
43-
44-
(defn local-models [config]
45-
(let [ollama-api-url (provider-api-url "ollama" config)]
46-
(mapv
47-
(fn [{:keys [model] :as ollama-model}]
48-
(let [capabilities (llm-providers.ollama/model-capabilities {:api-url ollama-api-url :model model})]
49-
(assoc ollama-model
50-
:tools (boolean (some #(= % "tools") capabilities))
51-
:reason? (boolean (some #(= % "thinking") capabilities)))))
52-
(llm-providers.ollama/list-models {:api-url ollama-api-url}))))
53-
5436
(defn default-model
5537
"Returns the default LLM model checking this waterfall:
5638
- defaultModel set
@@ -63,9 +45,9 @@
6345
(let [[decision model]
6446
(or (when-let [config-default-model (:defaultModel config)]
6547
[:config-default-model config-default-model])
66-
(when (provider-api-key "anthropic" (get-in db [:auth "anthropic"]) config)
48+
(when (llm-util/provider-api-key "anthropic" (get-in db [:auth "anthropic"]) config)
6749
[:api-key-found "anthropic/claude-sonnet-4-20250514"])
68-
(when (provider-api-key "openai" (get-in db [:auth "openai"]) config)
50+
(when (llm-util/provider-api-key "openai" (get-in db [:auth "openai"]) config)
6951
[:api-key-found "openai/gpt-5"])
7052
(when (get-in db [:auth "github-copilot" :api-key])
7153
[:api-key-found "github-copilot/gpt-4.1"])
@@ -109,8 +91,8 @@
10991
provider-config (get-in config [:providers provider])
11092
model-config (get-in provider-config [:models model])
11193
extra-payload (:extraPayload model-config)
112-
api-key (provider-api-key provider provider-auth config)
113-
api-url (provider-api-url provider config)
94+
api-key (llm-util/provider-api-key provider provider-auth config)
95+
api-url (llm-util/provider-api-url provider config)
11496
provider-auth-type (:type provider-auth)
11597
callbacks {:on-message-received on-message-received-wrapper
11698
:on-error on-error-wrapper

0 commit comments

Comments
 (0)