fix: resolve the qwen model catalog from the gateway, not qwen-code - #313
Merged
Conversation
The model picker rendered empty for the qwen backend against a gateway
serving a dozen models.
qwen-code's `getAvailableModels()` is not a catalog fetch. It returns
`modelRegistry.getModelsForAuthType(currentAuthType)` — an in-memory
registry seeded once at construction from the hardcoded QWEN_OAUTH_MODELS
(a single entry, `coder-model`) for qwen-oauth, and from the user's
`modelProviders` setting for every other authType. Under
`authType: "openai"` with no `modelProviders` declared it returns `[]`,
verified against the live SDK. qwen-code never asks the gateway what it
hosts, so there was nothing for codeoid to cache — `_cacheModels`
correctly ignores empty reports.
Ask the gateway directly instead. On the openai path it is an
OpenAI-compatible endpoint, so `GET /models` is authoritative and stays
current as the provider adds models — no hand-maintained list on any box.
The qwen-code registry is unioned in rather than replaced: it carries real
display labels, and qwen-oauth has a dynamic base URL and no key to
present, so HTTP is not an option there. A failed fetch falls back to the
registry; the session still runs, only the picker is poorer.
Everything the endpoint reports is surfaced. The response carries only
{id, object, created, owned_by} with no modality field, so filtering out
image/audio entries would mean pattern-matching ids — the same
hardcoded-list problem this removes.
Two supporting fixes:
- normalizeModelCatalog read `name` for the display label, but the SDK
emits `label` (its handler projects the registry entry down to
{id, label, capabilities, contextWindowSize}). Every model rendered as
its raw id. `name`/`modelId`/`availableModels` stay as accepted aliases.
- _cacheModels was first-non-empty-wins per daemon lifetime. Providers
report on each query-loop build, so a model added to a gateway now
appears on the next session instead of after a daemon restart. Empty
reports are still ignored so a failed fetch cannot clobber a good
catalog, and an unchanged report skips the SQLite write.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rsharath
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The model picker rendered empty for the qwen backend, against a gateway serving a dozen models.
The cause is not codeoid's caching. qwen-code's
getAvailableModels()is not a catalog fetch — it returnsmodelRegistry.getModelsForAuthType(currentAuthType), an in-memory registry seeded once at construction from:QWEN_OAUTH_MODELS(a single entry,coder-model) forqwen-oauthmodelProviderssetting for every other authTypeUnder
authType: "openai"with nomodelProvidersdeclared it returns[]. Verified against the live SDK:qwen-code never contacts the gateway, so there was nothing for codeoid to cache —
_cacheModelscorrectly ignores empty reports. DeclaringmodelProvidersin~/.qwen/settings.jsonfixes it, but that is a hand-maintained list that goes stale and has to be repeated on every box.Change
QwenProviderasks the gateway itself. On theopenaipath it is an OpenAI-compatible endpoint, soGET /modelsis authoritative and stays current as the provider adds models.The qwen-code registry is unioned in rather than replaced: it carries real display labels, and
qwen-oauthhas a dynamic base URL and no key to present, so HTTP is not an option there. A failed fetch falls back to the registry and logs once — the session still runs, only the picker is poorer.Everything the endpoint reports is surfaced. The response carries only
{id, object, created, owned_by}— no modality field — so filtering out image/audio entries would mean pattern-matching ids, which is the same hardcoded-list problem this removes.Two supporting fixes:
normalizeModelCatalogread the wrong field. It looked forname, but the SDK emitslabel(its handler projects the registry entry down to{id, label, capabilities, contextWindowSize}, also droppingdescription). Every model rendered as its raw id.name/modelId/availableModelsare kept as accepted aliases so a future rename doesn't silently empty the picker again._cacheModelswas first-non-empty-wins per daemon lifetime. Providers report on each query-loop build, so a model added to a gateway now appears on the next session instead of after a daemon restart. Empty reports are still ignored so a failed fetch cannot clobber a good catalog, and an unchanged report skips the SQLite write.Verification
Against the real Bailian token-plan gateway, with
modelProvidersremoved from~/.qwen/settings.json:bun test— 2411 pass, 19 skip, 0 fail (161 files)bun run typecheck— cleanbun run lint— cleanNew coverage: the real SDK 0.1.8 response shape,
fetchOpenAiModelCatalog(list shape, trailing-slash base URLs, non-2xx, junk payloads),unionCatalogs(dedupe, label preference, registry-only models, ordering), and both_cacheModelscache semantics.🤖 Generated with Claude Code