feat: dual select (provider + model) with default[provider] model default
The inline prompt now renders two <select> boxes:
- Provider: lists every provider from the cached /config/providers
response. Default = first provider.
- Model: lists that provider's model keys (Object.keys of the Record).
Default = default[providerID] (from the same response) when that
modelID is present in the provider's models; otherwise the first
model. Changing the provider select rebuilds the model select with
the new provider's models and its own default[provider].
On submit the two select values are read separately and passed as
providerId / modelId to the OpenCodeRequest (no more '|' delimiter).
The /opencode-bridge/edit body shape is unchanged (providerId, modelId).
Also fix the index.ts startup console log (p.models is a Record, use
Object.values), and drop the unused _cell field on the prompt widget
(now an unused param).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e7579d3779
commit
04f90ab5a0
@@ -181,17 +181,23 @@ describe('OpenCodeCellActions', () => {
|
||||
expect(model.stateChanged.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders a model selector with the first option selected when providers are cached', () => {
|
||||
it('renders provider and model selects; model default uses default[provider] when it matches', () => {
|
||||
setOpenCodeProviders({
|
||||
providers: [
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
models: {
|
||||
'claude-sonnet-4-20250514': { id: 'claude-sonnet-4-20250514' }
|
||||
'claude-sonnet-4-20250514': { id: 'claude-sonnet-4-20250514' },
|
||||
'claude-haiku-4-5': { id: 'claude-haiku-4-5' }
|
||||
}
|
||||
},
|
||||
{ id: 'openai', models: { 'gpt-5': { id: 'gpt-5' } } }
|
||||
]
|
||||
],
|
||||
default: {
|
||||
anthropic: 'claude-sonnet-4-20250514',
|
||||
openai: 'gpt-5'
|
||||
}
|
||||
});
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const actions = new OpenCodeCellActions(cell);
|
||||
@@ -201,15 +207,103 @@ describe('OpenCodeCellActions', () => {
|
||||
const btn = actions.node.querySelector('button') as HTMLButtonElement;
|
||||
btn.click();
|
||||
|
||||
const select = cell.node.querySelector(
|
||||
const prompt = cell.node.querySelector(
|
||||
'.opencode-inline-prompt'
|
||||
) as HTMLElement;
|
||||
const providerSel = prompt.querySelector(
|
||||
'.opencode-provider-select'
|
||||
) as HTMLSelectElement;
|
||||
const modelSel = prompt.querySelector(
|
||||
'.opencode-model-select'
|
||||
) as HTMLSelectElement;
|
||||
expect(providerSel).not.toBeNull();
|
||||
expect(modelSel).not.toBeNull();
|
||||
|
||||
// Provider: first provider selected by default.
|
||||
expect(providerSel.options.length).toBe(2);
|
||||
expect(providerSel.options[0].value).toBe('anthropic');
|
||||
expect(providerSel.options[0].textContent).toContain('anthropic');
|
||||
expect(providerSel.options[0].textContent).toContain('Anthropic');
|
||||
expect(providerSel.value).toBe('anthropic');
|
||||
|
||||
// Model: options are that provider's model keys, and the default
|
||||
// matches default['anthropic'] = 'claude-sonnet-4-20250514'.
|
||||
expect(modelSel.options.length).toBe(2);
|
||||
expect(modelSel.options[0].value).toBe('claude-sonnet-4-20250514');
|
||||
expect(modelSel.options[1].value).toBe('claude-haiku-4-5');
|
||||
expect(modelSel.value).toBe('claude-sonnet-4-20250514');
|
||||
|
||||
setOpenCodeProviders(null);
|
||||
});
|
||||
|
||||
it('falls back to the first model when default[provider] is not in models', () => {
|
||||
setOpenCodeProviders({
|
||||
providers: [
|
||||
{ id: 'bailian', models: { 'kimi/kimi-k2.7-code': { id: 'kimi/kimi-k2.7-code' } } }
|
||||
],
|
||||
// default['bailian'] references a modelID not present in models.
|
||||
default: { bailian: 'qwen3.7-plus' }
|
||||
});
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const actions = new OpenCodeCellActions(cell);
|
||||
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
|
||||
(actions as any).onAfterAttach({} as any);
|
||||
|
||||
const btn = actions.node.querySelector('button') as HTMLButtonElement;
|
||||
btn.click();
|
||||
|
||||
const modelSel = cell.node.querySelector(
|
||||
'.opencode-inline-prompt .opencode-model-select'
|
||||
) as HTMLSelectElement;
|
||||
expect(select).not.toBeNull();
|
||||
expect(select.options.length).toBe(2);
|
||||
expect(select.options[0].textContent).toBe('anthropic / claude-sonnet-4-20250514');
|
||||
expect(select.options[1].textContent).toBe('openai / gpt-5');
|
||||
// v3-final: default = first option (no settings).
|
||||
expect(select.value).toBe('anthropic|claude-sonnet-4-20250514');
|
||||
expect(modelSel.value).toBe('kimi/kimi-k2.7-code');
|
||||
setOpenCodeProviders(null);
|
||||
});
|
||||
|
||||
it('rebuilds the model select when the provider select changes', () => {
|
||||
setOpenCodeProviders({
|
||||
providers: [
|
||||
{
|
||||
id: 'anthropic',
|
||||
models: { 'claude-sonnet-4-20250514': { id: 'claude-sonnet-4-20250514' } }
|
||||
},
|
||||
{
|
||||
id: 'openai',
|
||||
models: { 'gpt-5': { id: 'gpt-5' } }
|
||||
}
|
||||
],
|
||||
default: { openai: 'gpt-5' }
|
||||
});
|
||||
const cell = makeFakeCell('x = 1');
|
||||
const actions = new OpenCodeCellActions(cell);
|
||||
Object.defineProperty(actions, 'parent', { value: cell, configurable: true });
|
||||
(actions as any).onAfterAttach({} as any);
|
||||
|
||||
const btn = actions.node.querySelector('button') as HTMLButtonElement;
|
||||
btn.click();
|
||||
|
||||
const prompt = cell.node.querySelector(
|
||||
'.opencode-inline-prompt'
|
||||
) as HTMLElement;
|
||||
const providerSel = prompt.querySelector(
|
||||
'.opencode-provider-select'
|
||||
) as HTMLSelectElement;
|
||||
const modelSel = prompt.querySelector(
|
||||
'.opencode-model-select'
|
||||
) as HTMLSelectElement;
|
||||
|
||||
// Initial: anthropic, its only model.
|
||||
expect(providerSel.value).toBe('anthropic');
|
||||
expect(modelSel.options.length).toBe(1);
|
||||
expect(modelSel.value).toBe('claude-sonnet-4-20250514');
|
||||
|
||||
// Switch to openai: model select rebuilds with openai's model and
|
||||
// default['openai'] = 'gpt-5' is in openai's models so it matches.
|
||||
providerSel.value = 'openai';
|
||||
providerSel.dispatchEvent(new Event('change'));
|
||||
expect(modelSel.options.length).toBe(1);
|
||||
expect(modelSel.options[0].value).toBe('gpt-5');
|
||||
expect(modelSel.value).toBe('gpt-5');
|
||||
|
||||
setOpenCodeProviders(null);
|
||||
});
|
||||
|
||||
@@ -223,6 +317,9 @@ describe('OpenCodeCellActions', () => {
|
||||
const btn = actions.node.querySelector('button') as HTMLButtonElement;
|
||||
btn.click();
|
||||
|
||||
expect(
|
||||
cell.node.querySelector('.opencode-inline-prompt .opencode-provider-select')
|
||||
).toBeNull();
|
||||
expect(
|
||||
cell.node.querySelector('.opencode-inline-prompt .opencode-model-select')
|
||||
).toBeNull();
|
||||
|
||||
Reference in New Issue
Block a user