anomalyco / anomalyco/opencode
fix(provider): defaultModelIDs crashes /provider and /config/providers when a config-declared provider has zero models
@jlongster is already working on this.
Since Aug 22, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
Provider.defaultModelIDs throws TypeError: Cannot read properties of undefined (reading 'id') for any provider whose models map is empty. Zero-model providers are a supported state in this repo (see provider.ts:1465 models: existing?.models ?? {} and the unconditional database[providerID] = parsed at :1554), but the live providers map can carry them and the crash fires on the next /provider or /config/providers HTTP request.
Where
packages/opencode/src/provider/provider.ts:1112— the offending function:
export function defaultModelIDs<T extends { models: Record<string, { id: string }> }>(providers: Record<string, T>) {
return mapValues(providers, (item) => sort(Object.values(item.models))[0].id)
}
sort([]) returns [], so [0] is undefined and .id throws.
packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts:59—ProviderHttpApi.list(/provider) feedsProvider.defaultModelIDs(providers)from the live map.packages/opencode/src/server/routes/instance/httpapi/handlers/config.ts:28—ConfigHttpApi.providers(/config/providers) does the same.
Both call sites feed from provider.list() (provider.ts:1707) which returns state.providers — the same database map that line 1554 keeps populating.
Steps to reproduce
opencode.jsonc with a bare custom provider (common LiteLLM / corporate gateway pattern):
{ "provider": { "my-gateway": { "name": "GW", "options": { "baseURL": "http://localhost:4000" } } } }
GET /config/providers or GET /provider → 500 TypeError from Provider.defaultModelIDs. The model picker gets no providers as a result.
Minimal JS reproduction (verified locally on the current dev tip):
import { mapValues, sortBy } from "remeda"
const defaultModelIDs = (p) => mapValues(p, (i) => sortBy(Object.values(i.models), (m) => m.id)[0].id)
defaultModelIDs({ litellm: { models: {} }, anthropic: { models: { sonnet: { id: "sonnet" } } } })
// → TypeError: undefined is not an object (evaluating '... [0].id')
How a zero-models provider can land in the live map
Two documented paths already produce zero-model providers — neither is filtered out before defaultModelIDs runs:
- Bare config provider (this repro):
models: existing?.models ?? {}(:1465) lands in the database unconditionally via:1554. - Plugin
config()hook injecting a provider that is not in the models.dev catalog (see #25630 thread, 15 affected users; #44081 covers the related hook-ordering path, PR #38836 covers that separately).
Once the provider is in the database, every /provider and /config/providers request explodes.
Expected
defaultModelIDs should omit providers with no models from the default map, not throw. The consumer DefaultModelIDs schema is Record<string, string>; an absent key is semantically correct ("no default") for a provider with no models.
Suggested fix
Guard the lookup at the source so consumers see a clean Record<string, string>:
export function defaultModelIDs<T extends { models: Record<string, { id: string }> }>(providers: Record<string, T>) {
return pickBy(
mapValues(providers, (item) => sort(Object.values(item.models))[0]?.id),
(id): id is string => typeof id === "string",
)
}
pickBy is already imported from remeda in this file (:6).
A regression test would go next to the existing defaultModel* cases in packages/opencode/test/provider/provider.test.ts.
Related (NOT duplicates)
- #25630 / #44081 — plugin
provider.models()hook ordering for config providers; #38836 covers it. Different code path; our fix complements, does not overlap. - #35772 — different
TypeError(reading 'provider', falsy plugin hook returns), fixed by #36102, assigned to Hona. - #41360 — models.opencode.ai fetch failure drops config-declared models. Related symptom (empty models map), different root cause.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.