Skip to content

Commit bb939c6

Browse files
authored
Merge pull request #524 from editor-code-assistant/discover-github-copilot-model-capabilities
Discover GitHub Copilot model capabilities
2 parents 1407bf5 + b707537 commit bb939c6

14 files changed

Lines changed: 541 additions & 119 deletions

File tree

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+
- Discover GitHub Copilot model APIs and reasoning variants from its authenticated `/models` endpoint.
6+
57
## 0.145.1
68

79
- Notify in chat when instructions change mid-chat (prompt, contexts, rules, skills, tools), invalidating the prompt cache.

docs/config/variants.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Variants are named payload presets for a model, allowing you to quickly switch b
1010

1111
## Built-in Variants
1212

13-
ECA ships with built-in variants for some known models via the `variantsByModel` config which has a good default.
13+
ECA ships with built-in variants for some known models via the `variantsByModel` config which has a good default. For authenticated GitHub Copilot, ECA uses only the model's advertised reasoning capabilities from Copilot's `/models` endpoint and does not guess variants when that metadata is unavailable.
1414

1515
!!! note
1616

integration-test/integration/eca.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ If `*http-proxy*` is set, passes it as the `HTTP_PROXY` environment variable."
3636
(println :--eca.integration.start-server/starting :cmd cmd-full :log-path log-path :http-proxy *http-proxy*)
3737
(p/process cmd-full
3838
(cond-> {:err log-path
39+
:extra-env {"XDG_CACHE_HOME" (str (fs/path *eca-out-dir* "cache"))}
3940
:exit-fn (fn [{:keys [cmd exit]}]
4041
(when (not= exit 0)
4142
(println :--eca.integration.start-server/exited :cmd cmd :exit-status exit)
@@ -44,7 +45,7 @@ If `*http-proxy*` is set, passes it as the `HTTP_PROXY` environment variable."
4445
(catch Exception _e))
4546
(System/exit exit)))}
4647
*http-proxy*
47-
(assoc :extra-env {"HTTP_PROXY" *http-proxy*})))))
48+
(assoc-in [:extra-env "HTTP_PROXY"] *http-proxy*)))))
4849

4950
(defn start-process! []
5051
(let [server (start-server *eca-binary-path*)

src/eca/config.clj

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,10 @@
218218
:readFile {:maxLines 2000}
219219
:shellCommand {:summaryMaxLength 35}
220220
:outputTruncation {:lines 2000 :sizeKb 50}}
221-
:variantsByModel {".*sonnet[-._]4[-._]6|opus[-._]4[-._][56]" {:variants anthropic-variants}
222-
".*opus[-._]4[-._][78]|.*sonnet[-._]5|.*fable[-._]5|.*mythos[-._]5" {:variants anthropic-v2-variants}
221+
:variantsByModel {".*sonnet[-._]4[-._]6|opus[-._]4[-._][56]" {:variants anthropic-variants
222+
:api ["anthropic" "bedrock"]}
223+
".*opus[-._]4[-._][78]|.*sonnet[-._]5|.*fable[-._]5|.*mythos[-._]5" {:variants anthropic-v2-variants
224+
:api ["anthropic" "bedrock"]}
223225
".*gpt[-._]5(?:[-._](?:2|4|5)(?!\\d)|[-._]3[-._]codex)" {:variants openai-variants
224226
:excludeProviders ["github-copilot"]}
225227
".*gpt[-._]5[-._]6(?!\\d)" {:variants openai-gpt-5-6-variants
@@ -282,32 +284,31 @@
282284
false)))
283285

284286
(defn effective-model-variants
285-
"Returns effective variants for a model by merging built-in variants (from
286-
:variantsByModel regex matching on the model key) with user-defined variants.
287-
User-defined variants override built-in ones on name clash.
288-
A variant set to {} is removed from the result, allowing users to disable
289-
built-in variants."
290-
[config provider model-name user-variants]
291-
(let [provider-api (get-in config [:providers provider :api])
292-
api-match? (fn [api config-val]
293-
(cond (sequential? config-val) (some #{api} config-val)
294-
config-val (= api config-val)
295-
:else true))
296-
builtin (when model-name
297-
(some (fn [[pattern-str {:keys [variants excludeProviders api]}]]
298-
(when (and (regex-matches? pattern-str model-name)
299-
(not (some #{provider} excludeProviders))
300-
(api-match? provider-api api))
301-
variants))
302-
(:variantsByModel config)))
303-
merged (cond
304-
(and builtin user-variants) (merge builtin user-variants)
305-
builtin builtin
306-
:else user-variants)]
307-
(when merged
308-
(let [filtered (into {} (remove (fn [[_ v]] (= {} v))) merged)]
309-
(when (seq filtered)
310-
filtered)))))
287+
"Returns effective variants for a model. Built-in regex variants are the
288+
fallback, discovered provider variants override them, and user variants have
289+
final priority. A variant set to {} is removed from the result."
290+
([config provider model-name user-variants]
291+
(effective-model-variants config provider model-name nil user-variants))
292+
([config provider model-name model-capabilities user-variants]
293+
(let [provider-api (or (some-> (:api model-capabilities) name)
294+
(get-in config [:providers provider :api]))
295+
api-match? (fn [api config-val]
296+
(cond (sequential? config-val) (some #{api} config-val)
297+
config-val (= api config-val)
298+
:else true))
299+
builtin (when model-name
300+
(some (fn [[pattern-str {:keys [variants excludeProviders api]}]]
301+
(when (and (regex-matches? pattern-str model-name)
302+
(not (some #{provider} excludeProviders))
303+
(api-match? provider-api api))
304+
variants))
305+
(:variantsByModel config)))
306+
merged (merge (or (not-empty (:variants model-capabilities)) builtin)
307+
user-variants)]
308+
(when (seq merged)
309+
(let [filtered (into {} (remove (fn [[_ v]] (= {} v))) merged)]
310+
(when (seq filtered)
311+
filtered))))))
311312

312313
(defn selectable-variant-names
313314
"Returns sorted variant names suitable for UI display, excluding internal-only
@@ -826,11 +827,12 @@
826827
config)
827828
agent-config (get-in config [:agent default-agent-name])
828829
[provider model-name] (shared/full-model->provider+model full-model)
830+
model-capabilities (get-in @db* [:models full-model])
829831
user-variants (when (and provider model-name)
830832
(get-in config [:providers provider :models model-name :variants]))
831833
variants (when (and provider model-name)
832834
(selectable-variant-names
833-
(effective-model-variants config provider model-name user-variants)))
835+
(effective-model-variants config provider model-name model-capabilities user-variants)))
834836
agent-variant (:variant agent-config)
835837
valid? (fn [v] (and v variants (some #{v} variants)))
836838
select-variant (cond

src/eca/features/chat.clj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,8 @@
631631
opaque ids the new provider would reject and triggers a chat-visible
632632
notice via `:on-history-sanitized`. This used to throw outright (#209)."
633633
[db chat-id provider model config]
634-
(let [current-api (:api (llm-api/provider->api-handler provider model config))
634+
(let [model-capabilities (get-in db [:models (str provider "/" model)])
635+
current-api (:api (llm-api/provider->api-handler provider model model-capabilities config))
635636
last-api (get-in db [:chats chat-id :last-api])]
636637
(when (and last-api current-api (not= last-api current-api))
637638
(logger/info logger-tag
@@ -837,7 +838,7 @@
837838
;; eca.llm-api so a later swap to a different model can drop entries
838839
;; whose opaque ids (Anthropic signatures, OpenAI rs_*/encrypted_content,
839840
;; toolu_*/call_* tool ids) the new provider would reject. #209
840-
current-api (:api (llm-api/provider->api-handler provider model config))
841+
current-api (:api (llm-api/provider->api-handler provider model model-capabilities config))
841842
add-to-history! (fn [{:keys [role content] :as msg}]
842843
(let [with-ts (update msg :created-at #(or % (System/currentTimeMillis)))
843844
tagged (if (and current-api
@@ -990,7 +991,7 @@
990991
(doseq [message user-messages]
991992
(add-to-history!
992993
(assoc message :content-id (:user-content-id chat-ctx))))
993-
(swap! db* assoc-in [:chats chat-id :last-api] (:api (llm-api/provider->api-handler provider model config)))
994+
(swap! db* assoc-in [:chats chat-id :last-api] (:api (llm-api/provider->api-handler provider model model-capabilities config)))
994995
(lifecycle/send-content! chat-ctx :system {:type :progress
995996
:state :running
996997
:text "Generating"}))

src/eca/features/tools/agent.clj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,15 @@
111111

112112
(defn ^:private model-variant-names
113113
"Returns sorted variant names for a specific full model string (e.g. \"anthropic/claude-sonnet-4-6\")."
114-
[config ^String full-model]
114+
[config db ^String full-model]
115115
(when full-model
116116
(let [idx (.indexOf full-model "/")]
117117
(when (pos? idx)
118118
(let [provider (subs full-model 0 idx)
119119
model (subs full-model (inc idx))
120+
model-capabilities (get-in db [:models full-model])
120121
user-variants (get-in config [:providers provider :models model :variants])
121-
variants (config/effective-model-variants config provider model user-variants)]
122+
variants (config/effective-model-variants config provider model model-capabilities user-variants)]
122123
(config/selectable-variant-names variants))))))
123124

124125
(defn ^:private spawn-agent
@@ -177,7 +178,7 @@
177178
;; configured variants accept any variant (the LLM API will reject if invalid).
178179
user-variant (get arguments "variant")
179180
_ (when user-variant
180-
(let [valid-variants (model-variant-names config subagent-model)]
181+
(let [valid-variants (model-variant-names config db subagent-model)]
181182
(when (and (seq valid-variants)
182183
(not (some #{user-variant} valid-variants)))
183184
(throw (ex-info (format "Variant '%s' is not available for model '%s'. Available variants: %s"

src/eca/handlers.clj

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,16 @@
2222
(set! *warn-on-reflection* true)
2323

2424
(defn ^:private model-variants
25-
"Returns sorted variant names for a full model (e.g. \"anthropic/claude-sonnet-4-5\")
26-
by resolving effective variants: built-in (from :variantsByModel regex match)
27-
merged with user-defined [:providers provider :models model :variants]."
28-
^clojure.lang.IPersistentVector [config ^String full-model]
25+
"Returns sorted built-in, discovered, and user-defined variant names for a full model."
26+
^clojure.lang.IPersistentVector [config db ^String full-model]
2927
(when full-model
3028
(let [idx (.indexOf full-model "/")]
3129
(when (pos? idx)
3230
(let [provider (subs full-model 0 idx)
3331
model (subs full-model (inc idx))
32+
model-capabilities (get-in db [:models full-model])
3433
user-variants (get-in config [:providers provider :models model :variants])
35-
variants (config/effective-model-variants config provider model user-variants)]
34+
variants (config/effective-model-variants config provider model model-capabilities user-variants)]
3635
(config/selectable-variant-names variants))))))
3736

3837
(defn ^:private select-variant
@@ -93,7 +92,7 @@
9392
(:defaultAgent fresh-config))
9493
fresh-config)
9594
default-agent-config (get-in fresh-config [:agent default-agent-name])
96-
variants (model-variants fresh-config default-model)]
95+
variants (model-variants fresh-config db default-model)]
9796
(config/notify-fields-changed-only!
9897
{:chat
9998
;; Advertise the default trust only when enabled, so clients
@@ -416,7 +415,7 @@
416415
([agent-config config messenger db* chat-id]
417416
(when-let [model (or (:defaultModel agent-config)
418417
(:defaultModel config))]
419-
(let [variants (model-variants config model)
418+
(let [variants (model-variants config @db* model)
420419
agent-variant (select-variant agent-config variants)
421420
;; CAS: only mutate the chat record if it still exists at swap
422421
;; time, avoiding TOCTOU resurrection when chat/delete races us.
@@ -482,7 +481,7 @@
482481
(or (:defaultAgent (:chat config))
483482
(:defaultAgent config))
484483
config)
485-
variants (model-variants config model)
484+
variants (model-variants config @db* model)
486485
;; CAS: only mutate the chat record if it still exists at swap
487486
;; time, avoiding TOCTOU resurrection when chat/delete races us.
488487
[old-db _new-db] (when chat-id

0 commit comments

Comments
 (0)