Skip to content

Provider config overrides npm and baseURL asymmetrically, producing impossible endpoints #28

Description

@filipeforattini

What

A user provider block can override options.baseURL but not npm. The catalog's npm wins while the user's baseURL wins, so the two get combined into a request that cannot succeed anywhere.

Reproduction, from a real session

Config in ~/.config/redcode/opencode.jsonc:

"minimax": {
  "npm": "@ai-sdk/openai-compatible",
  "options": { "baseURL": "https://api.minimax.chat/v1" }
}

models.dev catalog for the same provider:

npm: @ai-sdk/anthropic
api: https://api.minimax.io/anthropic/v1

What actually went out, from the durable error record (metadata.url):

https://api.minimax.chat/v1/messages   →  404 page not found

/v1/messages is the Anthropic path, so the SDK came from the catalog; the host came from the user config. Neither source describes that combination.

Probed each candidate directly — 401 means the route exists and only auth was missing, 404 means it does not exist:

401  https://api.minimax.io/anthropic/v1/messages       ← catalog pair, valid
401  https://api.minimax.chat/v1/chat/completions       ← user pair, valid
404  https://api.minimax.chat/v1/messages               ← what Redcode requested

Both coherent pairs work. Only the mixture fails.

Why it matters

The failure is silent and misattributed. Nothing reports that the npm override was dropped, so the user reasonably concludes their endpoint is wrong — it is not. Both halves of their intent were individually reasonable.

Possible directions, not a decision

  • Honour npm from user config the way options already is, or
  • Reject the config with a clear error when npm is supplied and cannot be applied, or
  • Validate coherence between the resolved npm and baseURL and fail loudly at resolution rather than at request time.

Whichever way, the asymmetry should stop being silent.

Activity

  1. filipeforattini commented on Aug 16, 2026

    @filipeforattini
    Author

    Corrected root cause (supersedes the analysis in the issue body)

    The asymmetry is real and the diagnosis in the title is right, but it is not in the v2 config/Catalog code. It is in the legacy provider stack, packages/opencode/src/provider/provider.ts, which is what run and normal prompting actually execute. I confirmed by instrumentation that SessionRunnerModel.fromCatalogModel (the v2 Catalog consumer) is never called during opencode run.

    The mechanism

    packages/opencode/src/provider/provider.ts:1425-1448:

    for (const [providerID, provider] of configProviders) {
      const existing = database[providerID]
      const parsed: Info = {
        ...
        options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),   // provider-wide
        models: existing?.models ?? {},                                        // catalog models kept as-is
      }
    
      for (const [modelID, model] of Object.entries(provider.models ?? {})) {  // npm read ONLY here
        const apiNpm = model.provider?.npm ?? provider.npm ?? existingModel?.api.npm ?? modelsDev[providerID]?.npm ?? ...
    • provider.options — which carries baseURL — is merged into the provider and therefore applies to every model.
    • provider.npm is only reachable inside the provider.models loop. With no models declared in config, that loop body never runs, so npm is dead code and the catalog's package survives on every model.

    resolveSDK then prefers options.baseURL over model.api.url (provider.ts:1698-1719) and picks the SDK factory by BUNDLED_PROVIDERS[model.api.npm] (provider.ts:106-134, 1770-1778). Catalog package + user host = the impossible endpoint; @ai-sdk/anthropic appends /messages.

    Reproduction, on the real config

    Instrumented resolveSDK, MINIMAX_API_KEY set to a dummy value.

    A — the config as filed (npm + options.baseURL, no models):

    DBG model=MiniMax-M3 npm=@ai-sdk/anthropic catalogUrl=https://api.minimax.io/anthropic/v1 baseURL=https://api.minimax.chat/v1
    Error: 404 Page not found
    

    npm dropped, baseURL applied → https://api.minimax.chat/v1/messages, matching metadata.url in the durable record.

    B — byte-identical except for one added line, "models": { "MiniMax-M3": {} }:

    DBG model=MiniMax-M3 npm=@ai-sdk/openai-compatible catalogUrl=https://api.minimax.io/anthropic/v1 baseURL=https://api.minimax.chat/v1
    Error: invalid api key (2049)
    

    npm now honoured, coherent pair, endpoint reached — the error is auth (expected, the key was a dummy), not 404.

    So the override is not merely ignored: whether npm applies at all depends on whether the same block also declares models, which nothing documents or reports.

    Workaround for anyone hitting this now

    Declare the models you use under the provider block; npm then applies:

    "minimax": {
      "npm": "@ai-sdk/openai-compatible",
      "options": { "baseURL": "https://api.minimax.chat/v1" },
      "models": { "MiniMax-M3": {} }
    }

    Notes on scope

    • The earlier hypothesis that the config was edited after the failure is wrong — the file mtime predates the failure, and config A above reproduces the 404 from the file as filed.
    • PR fix(core): reject v2 providers npm that conflicts with the resolved package #30 fixes a genuinely analogous asymmetry in the v2 providers schema, but that path is not executed by run, so it does not fix this issue. It has been retitled and no longer claims to close this.
    • The provider.npm-only-inside-the-models-loop behaviour is the fix site. Note that "override the host, keep the catalog's SDK" is a legitimate use (self-hosted proxies), so it cannot simply become an error — the fix is to make provider.npm apply provider-wide the way provider.options already does.
  2. added a commit that references this issue on Aug 16, 2026
    232b6f5
  3. added 2 commits that reference this issue on Aug 17, 2026
    e027128
    0f7b685
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions