From e7579d3779fa921b1d77df2bce81c8e2a3005119 Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:01:16 +0800 Subject: [PATCH] fix: Provider.models is a Record keyed by modelID, not an array Per the OpenCode server docs (GET /config/providers), each Provider's 'models' is { [modelID: string]: Model } (a Record), so model IDs can be referenced as strings (matching the 'default' map of providerID -> modelID). Our code treated it as Model[], which: 1. crashed with 'p.models is not iterable' on real responses, and 2. after the previous Array.isArray guard, skipped EVERY provider (a Record is never an array), so the inline picker showed nothing. Update types.ts (OpenCodeProvider.models -> Record, add OpenCodeModel, add optional 'default' map to the response) and rewrite flattenProviders to iterate Object.keys(p.models). Update test fixture to the Record shape. Co-Authored-By: Claude Fable 5 --- src/__tests__/opencode_cell_actions.spec.ts | 9 +++++++-- src/components/opencode_inline_prompt.ts | 11 +++++------ src/types.ts | 19 +++++++++++++++++-- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/__tests__/opencode_cell_actions.spec.ts b/src/__tests__/opencode_cell_actions.spec.ts index 1eca44e..886b18d 100644 --- a/src/__tests__/opencode_cell_actions.spec.ts +++ b/src/__tests__/opencode_cell_actions.spec.ts @@ -184,8 +184,13 @@ describe('OpenCodeCellActions', () => { it('renders a model selector with the first option selected when providers are cached', () => { setOpenCodeProviders({ providers: [ - { id: 'anthropic', models: [{ id: 'claude-sonnet-4-20250514' }] }, - { id: 'openai', models: [{ id: 'gpt-5' }] } + { + id: 'anthropic', + models: { + 'claude-sonnet-4-20250514': { id: 'claude-sonnet-4-20250514' } + } + }, + { id: 'openai', models: { 'gpt-5': { id: 'gpt-5' } } } ] }); const cell = makeFakeCell('x = 1'); diff --git a/src/components/opencode_inline_prompt.ts b/src/components/opencode_inline_prompt.ts index 4e4aed7..fd4b6f0 100644 --- a/src/components/opencode_inline_prompt.ts +++ b/src/components/opencode_inline_prompt.ts @@ -114,14 +114,13 @@ function flattenProviders( } const out: { providerId: string; modelId: string }[] = []; for (const p of providers.providers) { - // Some OpenCode Serve responses include providers without a `models` - // field (or with a non-array value). Guard to avoid the "p.models is - // not iterable" crash and simply skip such providers in the picker. - if (!Array.isArray(p.models)) { + // Per the OpenCode server docs, Provider.models is a Record keyed by + // modelID ({ [modelID: string]: Model }), NOT an array. + if (!p.models || typeof p.models !== "object") { continue; } - for (const m of p.models) { - out.push({ providerId: p.id, modelId: m.id }); + for (const modelId of Object.keys(p.models)) { + out.push({ providerId: p.id, modelId }); } } return out; diff --git a/src/types.ts b/src/types.ts index 8002ed5..e1b8b02 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,12 +47,27 @@ export interface OpenCodeFailure { export type OpenCodeResponse = OpenCodeSuccess | OpenCodeFailure; -/** Response from GET /opencode-bridge/providers. */ +/** Response from GET /opencode-bridge/providers (proxies OpenCode /config/providers). + * + * Per the OpenCode server docs, `/config/providers` returns + * `{ providers: Provider[], default: { [providerID]: modelID } }`. Each + * `Provider.models` is a Record keyed by modelID (NOT an array), so we + * can reference a model by its string ID (also matching `default[providerID]`). + */ +export interface OpenCodeModel { + id: string; + name?: string; + [k: string]: unknown; +} + export interface OpenCodeProvider { id: string; - models: { id: string; [k: string]: unknown }[]; + name?: string; + source?: string; + models: { [modelID: string]: OpenCodeModel }; } export interface OpenCodeProvidersResponse { providers: OpenCodeProvider[]; + default?: { [providerID: string]: string }; }