import { describe, expect, it } from 'vitest'; import { buildConfiguredModelOptions, buildRuntimeProviderOptions, formatModelRefLabel, getConfiguredModelCapabilityLabel, resolveRuntimeProviderKey, } from '../../src/lib/model-options'; import type { ProviderAccount, ProviderVendorInfo, ProviderWithKeyInfo } from '../../src/lib/providers'; import type { OpencodeRuntimeConfigSummary } from '../../src/types/opencode'; const now = '2026-04-28T00:00:00.000Z'; function account(overrides: Partial): ProviderAccount { return { id: 'custom-alpha1234', vendorId: 'custom', label: 'Alpha', authMode: 'api_key', model: 'model-alpha', enabled: true, isDefault: false, createdAt: now, updatedAt: now, ...overrides, } as ProviderAccount; } function status(id: string, hasKey = true): ProviderWithKeyInfo { return { id, type: 'custom', name: id, enabled: true, createdAt: now, updatedAt: now, hasKey, keyMasked: hasKey ? 'sk-***' : null, } as ProviderWithKeyInfo; } function vendor(id: ProviderVendorInfo['id'], name = id): ProviderVendorInfo { return { id, name, icon: '', placeholder: '', requiresApiKey: true, category: id === 'custom' ? 'custom' : 'official', supportedAuthModes: ['api_key'], defaultAuthMode: 'api_key', supportsMultipleAccounts: true, } as ProviderVendorInfo; } describe('model option helpers', () => { it('formats model refs using only the text after the provider prefix', () => { expect(formatModelRefLabel('openrouter/openai/gpt-5.4')).toBe('openai/gpt-5.4'); expect(formatModelRefLabel('custom-alpha1234/model-alpha')).toBe('model-alpha'); }); it('builds one configured custom model option per account', () => { const options = buildConfiguredModelOptions( [ account({ id: 'alpha1234', model: 'model-alpha', updatedAt: '2026-04-03T00:00:00.000Z' }), account({ id: 'beta5678', label: 'Beta', model: 'provider/model-beta', updatedAt: '2026-04-02T00:00:00.000Z' }), ], [status('alpha1234'), status('beta5678')], 'alpha1234', ); expect(options).toEqual([ { modelRef: 'alpha1234/model-alpha', label: 'model-alpha', runtimeProviderKey: 'alpha1234', accountId: 'alpha1234', capability: 'text', }, { modelRef: 'beta5678/provider/model-beta', label: 'provider/model-beta', runtimeProviderKey: 'beta5678', accountId: 'beta5678', capability: 'text', }, ]); }); it('expands configured fallback models into selectable options for the same runtime provider', () => { const options = buildConfiguredModelOptions( [ account({ id: 'niancode-user-models', label: 'Makelore Models', model: 'gpt-4.1-mini', fallbackModels: ['gpt-4o-mini', 'qwen3-coder', 'gpt-4o-mini'], }), ], [status('niancode-user-models')], 'niancode-user-models', ); expect(options).toEqual([ { modelRef: 'niancode-user-models/gpt-4.1-mini', label: 'gpt-4.1-mini', runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', capability: 'text', }, { modelRef: 'niancode-user-models/gpt-4o-mini', label: 'gpt-4o-mini', runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', capability: 'text', }, { modelRef: 'niancode-user-models/qwen3-coder', label: 'qwen3-coder', runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', capability: 'text', }, ]); }); it('marks known imported image-input models as multimodal options', () => { const options = buildConfiguredModelOptions( [ account({ id: 'niancode-user-models', label: 'Makelore Models', model: 'qwen3.6-plus', fallbackModels: ['deepseek-chat'], }), ], [status('niancode-user-models')], 'niancode-user-models', ); expect(options).toMatchObject([ { label: 'qwen3.6-plus', capability: 'multimodal' }, { label: 'deepseek-chat', capability: 'text' }, ]); }); it('falls back to known model capability when runtime summaries omit image metadata', () => { const [option] = buildConfiguredModelOptions( [ account({ id: 'niancode-user-models', label: 'Makelore Models', model: 'qwen3.6-plus', }), ], [status('niancode-user-models')], 'niancode-user-models', ); const runtimeSummary: OpencodeRuntimeConfigSummary = { model: 'niancode-user-models/qwen3.6-plus', smallModel: null, providerIds: ['niancode-user-models'], providerCount: 1, providers: [ { id: 'niancode-user-models', name: 'Makelore Models', modelIds: ['qwen3.6-plus'], hasApiKey: true, headerNames: [], }, ], }; expect(getConfiguredModelCapabilityLabel(option, runtimeSummary)).toBe('多模态'); }); it('exposes only imported Works Square models when that account is available', () => { const options = buildConfiguredModelOptions( [ account({ id: 'openai', vendorId: 'openai', label: 'OpenAI', model: 'gpt-5.4', }), account({ id: 'niancode-user-models', label: 'Makelore Models', model: 'gpt-4.1-mini', fallbackModels: ['gpt-4o-mini'], }), ], [status('openai'), status('niancode-user-models')], 'openai', ); expect(options).toEqual([ { modelRef: 'niancode-user-models/gpt-4.1-mini', label: 'gpt-4.1-mini', runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', capability: 'text', }, { modelRef: 'niancode-user-models/gpt-4o-mini', label: 'gpt-4o-mini', runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', capability: 'text', }, ]); }); it('exposes only the imported Works Square runtime provider when that account is available', () => { const options = buildRuntimeProviderOptions( [ account({ id: 'openai', vendorId: 'openai', label: 'OpenAI', model: 'gpt-5.4', }), account({ id: 'niancode-user-models', label: 'Makelore Models', model: 'gpt-4.1-mini', }), ], [status('openai'), status('niancode-user-models')], [vendor('openai', 'OpenAI'), vendor('custom', 'Custom')], 'openai', ); expect(options).toEqual([ { runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', label: 'Makelore Models (Custom)', modelIdPlaceholder: undefined, configuredModelId: 'gpt-4.1-mini', }, ]); }); it('does not expose fallback configured models when the imported Works Square account exists', () => { const options = buildConfiguredModelOptions( [ account({ id: 'openai', vendorId: 'openai', label: 'OpenAI', model: 'gpt-5.4', }), account({ id: 'niancode-user-models', label: 'Makelore Models', model: 'gpt-4.1-mini', enabled: false, }), ], [status('openai'), status('niancode-user-models')], 'openai', ); expect(options).toEqual([]); }); it('keeps prefixed account models intact and skips accounts without credentials', () => { const runtimeKey = resolveRuntimeProviderKey(account({ id: 'gamma9012' })); const options = buildConfiguredModelOptions( [ account({ id: 'gamma9012', model: `${runtimeKey}/model-gamma` }), account({ id: 'delta3456', label: 'Delta', model: 'model-delta' }), ], [status('gamma9012'), status('delta3456', false)], null, ); expect(options).toHaveLength(1); expect(options[0].modelRef).toBe('gamma9012/model-gamma'); expect(options[0].label).toBe('model-gamma'); }); it('strips DeepSeek routing prefixes from imported Works Square models', () => { const options = buildConfiguredModelOptions( [ account({ id: 'niancode-user-models', label: 'Makelore Models', model: 'deepseek/deepseek-v4-pro', fallbackModels: ['deepseek/deepseek-chat'], }), ], [status('niancode-user-models')], 'niancode-user-models', ); expect(options).toEqual([ { modelRef: 'niancode-user-models/deepseek-v4-pro', label: 'deepseek-v4-pro', runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', capability: 'text', }, { modelRef: 'niancode-user-models/deepseek-chat', label: 'deepseek-chat', runtimeProviderKey: 'niancode-user-models', accountId: 'niancode-user-models', capability: 'text', }, ]); }); it('treats malformed provider snapshots as empty options', () => { expect( buildConfiguredModelOptions( {} as ProviderAccount[], {} as ProviderWithKeyInfo[], null, ), ).toEqual([]); }); });