anomalyco / anomalyco/opencode

fix(provider): defaultModelIDs crashes /provider and /config/providers when a config-declared provider has zero models

Open
#44,088 1 comment 0 reactions 1 assignee View on GitHub

@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:59ProviderHttpApi.list (/provider) feeds Provider.defaultModelIDs(providers) from the live map.
  • packages/opencode/src/server/routes/instance/httpapi/handlers/config.ts:28ConfigHttpApi.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:

  1. Bare config provider (this repro): models: existing?.models ?? {} (:1465) lands in the database unconditionally via :1554.
  2. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.