Skip to content

tui: /models picker hides provider names for config-declared providers #50322

Open

Description

@MarJoSong

Summary

The /models TUI picker shows no provider name for models whose provider is declared in opencode.jsonc with an inline settings.apiKey. Provider labels only appear for the built-in opencode zero-cost models (the Free footer). Models coming from different custom providers with identical IDs are therefore indistinguishable in the picker.

Environment

  • opencode version: opencode v2.0.11
  • OS: Darwin MacBookAir.local 25.5.0 Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:19 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T8122 arm64
  • Terminal: TERM_PROGRAM=Apple_Terminal, TERM=xterm-256color, COLORTERM=truecolor
  • Shell: /bin/zsh
  • Install/channel: latest (local=false, v2.0.11)
  • Active plugins: file:///Users/songyuhua/.loongsuite-pilot/plugins/opencode/plugin.mjs (as configured)
  • opencode service status: http://127.0.0.1:49374 (running)
  • opencode api get /api/info: {"version":"2.0.11","pid":...,"urls":["http://127.0.0.1:49374"]}

Reproduction

  1. Define one or more custom OpenAI-compatible providers in ~/.config/opencode/opencode.jsonc under providers, each carrying credentials inline via settings.apiKey (no /connect account saved). Redacted shape:

    {
      "$schema": "https://opencode.ai/config.json",
      "providers": {
        "BaiLian": {
          "name": "Bailian",
          "package": "@opencode/ai/providers/openai-compatible",
          "settings": {
            "apiKey": "<redacted>",
            "baseURL": "https://dashscope.aliyuncs.com/compatible-mode/v1"
          },
          "models": {
            "deepseek-v4.1-flash": { "name": "deepseek-v4.1-flash" }
          }
        },
        "tokenplan": {
          "name": "TokenPlan",
          "package": "@opencode/ai/providers/openai-compatible",
          "settings": {
            "apiKey": "<redacted>",
            "baseURL": "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"
          },
          "models": {
            "deepseek-v4.1-flash": { "name": "deepseek-v4.1-flash" }
          }
        },
        "omniroute": { "name": "OmniRoute", "package": "@opencode/ai/providers/openai-compatible", "models": { "gpt-5.6-sol": {} } }
      }
    }
  2. Verify the models load correctly: opencode models lists every configured model across all providers, and opencode api get /api/provider returns them with "activation":"enabled".

  3. Verify no account connections exist: opencode auth list prints No authenticated integrations, and opencode api get /api/integration shows "connections":[] for every integration, including the custom provider IDs (BaiLian, idealab, omniroute, tokenplan).

  4. Open the TUI and run /models.

Expected Behavior

Each model row (or its section header) shows the owning provider name, so that the same model ID offered by multiple providers can be told apart — e.g. deepseek-v4.1-flash exists in both BaiLian and tokenplan, and kimi-k3 in both BaiLian and idealab.

Actual Behavior

The picker renders a flat list with no provider names anywhere. The only label shown is the Free footer on the built-in opencode zero-cost models. Nothing identifies BaiLian vs tokenplan vs omniroute vs idealab.

Additional Context

Suspected cause

In the v2.0.11 TUI bundle, the model dialog gates provider display on whether any integration connection exists, not on whether a provider is configured:

function zi(i){ return i.some((a) => a.connections.length > 0) }
function mn(){ let i = vt(); return A(() => zi(i.location.integration.list() ?? [])) }
// ...
function no(i){
  let I = mn(); // "connected"
  // ...
  category: I() ? (ne?.name ?? X.providerID) : void 0,
  footer:   Ki(X) ? "Free" : void 0,
}

So with zero connections every category is undefined and no provider grouping/labels are rendered; the only surviving badge is Free for opencode models whose cost is all zero. Credentials supplied via settings.apiKey in config do not create an integration connection, so this configuration never satisfies the gate.

Regression note

V1's dialog-model.tsx used:

export function useConnected() {
  const sync = useSync()
  return createMemo(() =>
    sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
  )
}

That check correctly accounted for config-declared providers. The V2 gate switched to integration connections, which config-only credentials never populate. This looks like a regression for config-declared providers.

Workaround (confirmed to change the gate)

  • Connecting any integration via the dialog's Connect an integration action (/connect) makes connections.length > 0 true, after which all provider names render as section headers — including the config-declared ones.
  • An environment-credential integration also counts: with DASHSCOPE_API_KEY set, /api/integration reports alibaba with connections: [{"type":"env","name":"DASHSCOPE_API_KEY"}] and the same gate flips.
  • Non-interactive opencode auth login <provider> is not usable for this: the key method has no form fields (so --answer key=value is rejected with Unknown form field) and the API-key prompt requires a TTY (API key input requires an interactive terminal).

Related

Adjacent to the known model-picker issues around the recent/favorites filter (e.g. #6169), but distinct: here the models are present, only the provider label is suppressed.

Reproduces consistently.

Activity

  1. ijrunner commented on Sep 22, 2026

    @ijrunner

    +1 — hitting this with a larger config-declared setup: 5 custom OpenAI-compatible providers in ~/.config/opencode/opencode.jsonc (inline settings.apiKey) with ~60 models total.
    Because provider labels are hidden for config-declared providers, /models (Ctrl+X M) is one flat list — and several model IDs exist under multiple providers (e.g. claude-fable-5, kimi-k3, deepseek-v4-flash, gpt-6-astra each appear under 2+ providers), so they are literally indistinguishable in the picker.
    V1's picker grouped models by provider with visible suffixes; the V2 rewrite dropped that, so for multi-provider configs I have to cross-reference the config file by hand to know which row I'm picking. Current workaround is just F2 (recently-used models).
    Environment: opencode v2.0.12, Linux x64.

  2. 372046933 commented on Oct 5, 2026

    @372046933
    Image Luckily, the order is stable. User defined provider shows last in the list
  3. flben233 commented on Oct 21, 2026

    @flben233

    I agree. It's hard to distinguish the same models from different providers.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions