Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Improve `editor_diagnostics` tool summary to show the target filename (e.g. `Checking diagnostics: foo.clj`) or `Checking all diagnostics` when no path is provided.
- Bugfix: preserve the chat's selected variant when changing model on an existing chat (regression from per-chat scoping in v0.133.1). `chat/selectedModelChanged` now keeps the chat's persisted `:variant` if it is still supported by the new model, before falling back to the chat's agent variant. Resume flows (`chat/open`, `/resume`) align the same way.
- Native ECA tools now auto-resolve bare tool names like `write_file` to their canonical `eca__...` form, avoiding repeated failed retries when an LLM omits the native server prefix.
- Support Claude console subscription auth: dispatch `x-api-key` or `Authorization: Bearer` header based on configurable auth type.

## 0.133.2

Expand Down
2 changes: 1 addition & 1 deletion src/eca/llm_util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
(or (when-let [key (not-empty (get-in config [:providers (name provider) :key]))]
[:auth/token key])
(when-let [key (:api-key provider-auth)]
[:auth/oauth key])
[(get provider-auth :type :auth/oauth) key])
(when-let [key (config/get-env (str (csk/->SCREAMING_SNAKE_CASE (name provider)) "_API_KEY"))]
[:auth/token key])
;; legacy
Expand Down
24 changes: 14 additions & 10 deletions src/eca/models.clj
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@
nil))

(defn ^:private models-endpoint-headers
[api-key]
(client/merge-llm-headers
(assoc-some
{"Content-Type" "application/json"}
"Authorization" (when api-key (str "Bearer " api-key)))))
[auth-type api-type api-key]
(let [oauth? (= :auth/oauth auth-type)
anthropic? (= "anthropic" api-type)]
(client/merge-llm-headers
(assoc-some
{"Content-Type" "application/json"}
"x-api-key" (when (and api-key anthropic? (not oauth?)) api-key)
"Authorization" (when (and api-key (or oauth? (not anthropic?))) (str "Bearer " api-key))))))

(defn ^:private fetch-models-dev-data []
(let [{:keys [status body]} (http/get models-dev-api-url
Expand Down Expand Up @@ -235,11 +238,11 @@
(defn ^:private fetch-provider-native-models
"Fetches models from provider's native /models endpoint.
Returns a map of model-id -> {} on success, nil on failure."
[{:keys [api-url api-key api-type provider]}]
[{:keys [api-url auth-type api-key api-type provider]}]
(when-let [models-path (provider-models-endpoint-path api-type)]
(let [url (shared/join-api-url api-url models-path)
rid (llm-util/gen-rid)
headers (models-endpoint-headers api-key)]
headers (models-endpoint-headers auth-type api-type api-key)]
(try
(logger/debug logger-tag (format "[%s] Provider '%s': Fetching models from %s" rid provider url))
(let [{:keys [status body]} (http/get url
Expand Down Expand Up @@ -275,14 +278,15 @@
(when (contains? native-models-endpoint-providers (:api provider-config))
(let [api-url (or (get-in db [:auth provider :api-url])
(llm-util/provider-api-url provider config))
[_ api-key] (llm-util/provider-api-key provider
(get-in db [:auth provider])
config)
[auth-type api-key] (llm-util/provider-api-key provider
(get-in db [:auth provider])
config)
api-type (:api provider-config)]
(when api-url
(when-let [models (fetch-provider-native-models
{:provider provider
:api-url api-url
:auth-type auth-type
:api-key api-key
:api-type api-type})]
(logger/info logger-tag
Expand Down
33 changes: 33 additions & 0 deletions test/eca/models_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,39 @@
(is (= "https://api.openai.com/v1/models" (first @request*)))
(is (= "Bearer sk-test" (get-in @request* [1 :headers "Authorization"]))))))

(deftest fetch-provider-models-anthropic-token-uses-x-api-key-test
(let [request* (atom nil)
models-dev-data {"anthropic" {"api" "https://api.anthropic.com"
"models" {"from-models-dev" {"id" "from-models-dev"}}}}]
(with-redefs [http/get (fn [url opts]
(reset! request* [url opts])
{:status 200
:body {:data [{:id "claude-3"}]}})]
(#'models/fetch-provider-models-with-priority
{:providers {"anthropic" {:api "anthropic"
:url "https://api.anthropic.com"
:key "sk-ant-test"}}}
{}
models-dev-data)
(is (= "sk-ant-test" (get-in @request* [1 :headers "x-api-key"])))
(is (nil? (get-in @request* [1 :headers "Authorization"]))))))

(deftest fetch-provider-models-anthropic-oauth-uses-bearer-test
(let [request* (atom nil)
models-dev-data {"anthropic" {"api" "https://api.anthropic.com"
"models" {"from-models-dev" {"id" "from-models-dev"}}}}]
(with-redefs [http/get (fn [url opts]
(reset! request* [url opts])
{:status 200
:body {:data [{:id "claude-3"}]}})]
(#'models/fetch-provider-models-with-priority
{:providers {"anthropic" {:api "anthropic"
:url "https://api.anthropic.com"}}}
{:auth {"anthropic" {:api-key "oauth-token" :type :auth/oauth}}}
models-dev-data)
(is (= "Bearer oauth-token" (get-in @request* [1 :headers "Authorization"])))
(is (nil? (get-in @request* [1 :headers "x-api-key"]))))))

(deftest fetch-provider-models-with-priority-fallback-test
(let [models-dev-data {"native-provider" {"api" "https://api.openai.com"
"models" {"from-models-dev" {"id" "from-models-dev"}}}}]
Expand Down
Loading