Repository navigation
tui: /models picker hides provider names for config-declared providers #50322
Description
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
-
Define one or more custom OpenAI-compatible providers in
~/.config/opencode/opencode.jsoncunderproviders, each carrying credentials inline viasettings.apiKey(no/connectaccount 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": {} } } } } -
Verify the models load correctly:
opencode modelslists every configured model across all providers, andopencode api get /api/providerreturns them with"activation":"enabled". -
Verify no account connections exist:
opencode auth listprintsNo authenticated integrations, andopencode api get /api/integrationshows"connections":[]for every integration, including the custom provider IDs (BaiLian,idealab,omniroute,tokenplan). -
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 integrationaction (/connect) makesconnections.length > 0true, after which all provider names render as section headers — including the config-declared ones. - An environment-credential integration also counts: with
DASHSCOPE_API_KEYset,/api/integrationreportsalibabawithconnections: [{"type":"env","name":"DASHSCOPE_API_KEY"}]and the same gate flips. - Non-interactive
opencode auth login <provider>is not usable for this: thekeymethod has noformfields (so--answer key=valueis rejected withUnknown 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
ijrunner commentedon Sep 22, 2026
+1 — hitting this with a larger config-declared setup: 5 custom OpenAI-compatible providers in
~/.config/opencode/opencode.jsonc(inlinesettings.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-astraeach 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.
372046933 commentedon Oct 5, 2026
flben233 commentedon Oct 21, 2026
I agree. It's hard to distinguish the same models from different providers.