import { describe, expect, it, vi } from 'vitest'; import type { ProviderAccount } from '@electron/shared/providers/types'; import { buildOpencodeRuntimeConfig, summarizeOpencodeRuntimeConfig, } from '@electron/opencode/provider-config'; function createAccount(overrides: Partial = {}): ProviderAccount { return { id: 'openai', vendorId: 'openai', label: 'OpenAI', authMode: 'api_key', model: 'gpt-5.4', enabled: true, isDefault: false, createdAt: '2026-05-12T00:00:00.000Z', updatedAt: '2026-05-12T00:00:00.000Z', ...overrides, }; } describe('buildOpencodeRuntimeConfig', () => { it('creates env-backed opencode config for the default built-in provider', async () => { const resolveApiKey = vi.fn().mockResolvedValue('sk-openai'); const result = await buildOpencodeRuntimeConfig({ accounts: [createAccount()], defaultAccountId: 'openai', resolveApiKey, }); expect(result.config).toMatchObject({ '$schema': 'https://opencode.ai/config.json', enabled_providers: ['openai'], permission: { external_directory: 'ask', }, provider: { openai: { options: { apiKey: '{env:NIANCODE_OPENCODE_OPENAI_API_KEY}', }, }, }, model: 'openai/gpt-5.4', }); expect(result.config.provider.openai).not.toHaveProperty('npm'); expect(result.env).toEqual({ NIANCODE_OPENCODE_OPENAI_API_KEY: 'sk-openai', }); }); it('includes configured MCP servers in the runtime config and sanitized summary', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [createAccount()], defaultAccountId: 'openai', resolveApiKey: vi.fn().mockResolvedValue('sk-openai'), mcpServers: { playwright: { type: 'local', command: ['node', 'node_modules/@playwright/mcp/cli.js'], enabled: true, env: { ELECTRON_RUN_AS_NODE: '1', }, }, }, }); expect(result.config.mcp).toEqual({ playwright: { type: 'local', command: ['node', 'node_modules/@playwright/mcp/cli.js'], enabled: true, env: { ELECTRON_RUN_AS_NODE: '1', }, }, }); expect(summarizeOpencodeRuntimeConfig(result)).toMatchObject({ mcpServerIds: ['playwright'], mcpServerCount: 1, mcpServers: [ { id: 'playwright', type: 'local', enabled: true, command: ['node', 'node_modules/@playwright/mcp/cli.js'], envKeys: ['ELECTRON_RUN_AS_NODE'], }, ], }); expect(JSON.stringify(summarizeOpencodeRuntimeConfig(result))).not.toContain('sk-openai'); }); it('creates a custom OpenAI-compatible provider entry with base URL and model metadata', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'custom-kimi', vendorId: 'custom', label: 'Kimi Proxy', baseUrl: 'https://api.moonshot.cn/v1/', apiProtocol: 'openai-completions', headers: { 'X-Works-Square-AI-Token': '{env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}', }, model: 'kimi-k2.6', }), ], defaultAccountId: 'custom-kimi', resolveApiKey: vi.fn().mockResolvedValue('sk-kimi'), }); expect(result.config).toMatchObject({ provider: { 'custom-kimi': { npm: '@ai-sdk/openai-compatible', name: 'Kimi Proxy', options: { apiKey: '{env:NIANCODE_OPENCODE_CUSTOM_KIMI_API_KEY}', baseURL: 'https://api.moonshot.cn/v1', headers: { 'X-Works-Square-AI-Token': '{env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}', }, }, models: { 'kimi-k2.6': { name: 'kimi-k2.6', }, }, }, }, model: 'custom-kimi/kimi-k2.6', }); expect(result.env).toEqual({ NIANCODE_OPENCODE_CUSTOM_KIMI_API_KEY: 'sk-kimi', }); }); it('expands fallback models as additional runtime models for one imported provider', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'niancode-user-models', vendorId: 'custom', label: 'Makelore Models', baseUrl: 'https://one-api.example.com/v1', apiProtocol: 'openai-completions', model: 'gpt-4.1-mini', fallbackModels: ['gpt-4o-mini', 'qwen3-coder', 'gpt-4o-mini'], }), ], defaultAccountId: 'niancode-user-models', resolveApiKey: vi.fn().mockResolvedValue('sk-user-token'), }); expect(result.config.provider['niancode-user-models'].models).toEqual({ 'gpt-4.1-mini': { name: 'gpt-4.1-mini' }, 'gpt-4o-mini': { name: 'gpt-4o-mini' }, 'qwen3-coder': { name: 'qwen3-coder' }, }); expect(result.config.model).toBe('niancode-user-models/gpt-4.1-mini'); }); it('adds verified limits only to exact imported Qwen Plus profiles', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'niancode-user-models', vendorId: 'custom', label: '娉ュ湡 Models', baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1', apiProtocol: 'openai-completions', model: 'qwen3.6-plus', fallbackModels: [ 'qwen3.6-plus-2026-04-02', 'qwen3.7-plus', 'qwen3.7-plus-2026-05-26', 'qwen-vl-max', 'qwen-omni-turbo', 'qwen3.8-plus', 'qwen3.7-plus-latest', 'qwen3.7-plus-2026-06-15', 'deepseek-chat', ], }), ], defaultAccountId: 'niancode-user-models', resolveApiKey: vi.fn().mockResolvedValue('local-host-api-token'), }); const models = result.config.provider['niancode-user-models'].models; expect(models).toMatchObject({ 'qwen3.6-plus': { modalities: { input: ['text', 'image'], output: ['text'] }, limit: { context: 1_000_000, output: 65_536 }, }, 'qwen3.6-plus-2026-04-02': { modalities: { input: ['text', 'image'], output: ['text'] }, limit: { context: 1_000_000, output: 65_536 }, }, 'qwen3.7-plus': { modalities: { input: ['text', 'image'], output: ['text'] }, limit: { context: 1_000_000, output: 65_536 }, }, 'qwen3.7-plus-2026-05-26': { modalities: { input: ['text', 'image'], output: ['text'] }, limit: { context: 1_000_000, output: 65_536 }, }, 'qwen-vl-max': { modalities: { input: ['text', 'image'], output: ['text'] }, }, 'qwen-omni-turbo': { modalities: { input: ['text', 'image'], output: ['text'] }, }, }); for (const modelId of ['qwen-vl-max', 'qwen-omni-turbo']) { expect(models?.[modelId]).not.toHaveProperty('limit'); } for (const modelId of [ 'qwen3.8-plus', 'qwen3.7-plus-latest', 'qwen3.7-plus-2026-06-15', 'deepseek-chat', ]) { expect(models?.[modelId]).not.toHaveProperty('modalities'); expect(models?.[modelId]).not.toHaveProperty('limit'); } expect(summarizeOpencodeRuntimeConfig(result).providers[0]).toMatchObject({ imageInputModelIds: [ 'qwen3.6-plus', 'qwen3.6-plus-2026-04-02', 'qwen3.7-plus', 'qwen3.7-plus-2026-05-26', 'qwen-vl-max', 'qwen-omni-turbo', ], modelLimits: { 'qwen3.6-plus': { context: 1_000_000, output: 65_536 }, 'qwen3.6-plus-2026-04-02': { context: 1_000_000, output: 65_536 }, 'qwen3.7-plus': { context: 1_000_000, output: 65_536 }, 'qwen3.7-plus-2026-05-26': { context: 1_000_000, output: 65_536 }, }, }); expect(JSON.stringify(summarizeOpencodeRuntimeConfig(result))).not.toContain('local-host-api-token'); }); it('uses only the imported Works Square model provider when it is available', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'openai', vendorId: 'openai', label: 'OpenAI', model: 'gpt-5.4', }), createAccount({ id: 'niancode-user-models', vendorId: 'custom', label: 'Makelore Models', baseUrl: 'https://one-api.example.com/v1', apiProtocol: 'openai-completions', model: 'gpt-4.1-mini', fallbackModels: ['gpt-4o-mini'], }), ], defaultAccountId: 'openai', resolveApiKey: vi.fn(async (account: ProviderAccount) => ( account.id === 'niancode-user-models' ? 'sk-user-token' : 'sk-openai' )), }); expect(Object.keys(result.config.provider)).toEqual(['niancode-user-models']); expect(result.config.enabled_providers).toEqual(['niancode-user-models']); expect(result.config.provider).not.toHaveProperty('openai'); expect(result.config.provider['niancode-user-models']).toMatchObject({ npm: '@ai-sdk/openai-compatible', name: 'Makelore Models', options: { apiKey: '{env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}', baseURL: 'https://one-api.example.com/v1', }, models: { 'gpt-4.1-mini': { name: 'gpt-4.1-mini' }, 'gpt-4o-mini': { name: 'gpt-4o-mini' }, }, }); expect(result.config.model).toBe('niancode-user-models/gpt-4.1-mini'); expect(result.env).toEqual({ NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY: 'sk-user-token', }); expect(summarizeOpencodeRuntimeConfig(result)).toMatchObject({ model: 'niancode-user-models/gpt-4.1-mini', enabledProviderIds: ['niancode-user-models'], providers: [ { id: 'niancode-user-models', npm: '@ai-sdk/openai-compatible', baseURL: 'https://one-api.example.com/v1', modelIds: ['gpt-4.1-mini', 'gpt-4o-mini'], hasApiKey: true, apiKeyEnv: 'NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY', }, ], }); expect(JSON.stringify(summarizeOpencodeRuntimeConfig(result))).not.toContain('sk-user-token'); }); it('normalizes an existing root Works Square AI gateway URL before passing it to opencode', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'niancode-user-models', vendorId: 'custom', label: 'Makelore Models', baseUrl: 'https://token.nianxx.cn', apiProtocol: 'openai-completions', model: 'deepseek-chat', headers: { 'X-Works-Square-AI-Token': '{env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}', }, metadata: { worksSquareCredentialMode: 'works_square_ai_gateway', }, }), ], defaultAccountId: 'niancode-user-models', resolveApiKey: vi.fn().mockResolvedValue('sk-user-token'), }); expect(result.config.provider['niancode-user-models'].options).toMatchObject({ baseURL: 'https://token.nianxx.cn/v1', headers: { Authorization: 'Bearer {env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}', 'X-Works-Square-AI-Token': '{env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}', }, }); }); it('routes imported Works Square models through the local AI proxy without gateway-token headers', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'niancode-user-models', vendorId: 'custom', label: 'Makelore Models', baseUrl: 'http://127.0.0.1:13210/api/ai-proxy/v1', apiProtocol: 'openai-completions', model: 'deepseek-chat', fallbackModels: ['gpt-4.1-mini'], metadata: { worksSquareCredentialMode: 'works_square_ai_gateway_proxy', worksSquareOneApiBaseUrl: 'https://open-api.example.com/v1', }, }), ], defaultAccountId: 'niancode-user-models', resolveApiKey: vi.fn().mockResolvedValue('local-host-api-token'), }); expect(result.config.provider['niancode-user-models'].options).toEqual({ apiKey: '{env:NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY}', baseURL: 'http://127.0.0.1:13210/api/ai-proxy/v1', }); expect(JSON.stringify(result.config)).not.toContain('X-Works-Square-AI-Token'); expect(JSON.stringify(result.config)).not.toContain('https://open-api.example.com'); expect(result.env).toEqual({ NIANCODE_OPENCODE_NIANCODE_USER_MODELS_API_KEY: 'local-host-api-token', }); }); it('strips DeepSeek routing prefixes from imported Works Square runtime models', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'niancode-user-models', vendorId: 'custom', label: 'Makelore Models', baseUrl: 'https://one-api.example.com/v1', apiProtocol: 'openai-completions', model: 'deepseek/deepseek-v4-pro', fallbackModels: ['deepseek/deepseek-chat'], }), ], defaultAccountId: 'niancode-user-models', resolveApiKey: vi.fn().mockResolvedValue('sk-user-token'), }); expect(result.config.provider['niancode-user-models'].models).toEqual({ 'deepseek-v4-pro': { name: 'deepseek-v4-pro' }, 'deepseek-chat': { name: 'deepseek-chat' }, }); expect(result.config.model).toBe('niancode-user-models/deepseek-v4-pro'); }); it('does not fall back to other providers when the imported Works Square account exists', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'openai', vendorId: 'openai', label: 'OpenAI', model: 'gpt-5.4', }), createAccount({ id: 'niancode-user-models', vendorId: 'custom', label: 'Makelore Models', baseUrl: 'https://one-api.example.com/v1', apiProtocol: 'openai-completions', model: 'gpt-4.1-mini', enabled: false, }), ], defaultAccountId: 'openai', resolveApiKey: vi.fn().mockResolvedValue('sk-openai'), }); expect(result.config.provider).toEqual({}); expect(result.config.enabled_providers).toEqual([]); expect(result.config.model).toBeUndefined(); expect(result.env).toEqual({}); }); it('skips disabled accounts and accounts without usable credentials', async () => { const result = await buildOpencodeRuntimeConfig({ accounts: [ createAccount({ id: 'disabled-openai', enabled: false }), createAccount({ id: 'missing-key-openai' }), ], defaultAccountId: 'missing-key-openai', resolveApiKey: vi.fn().mockResolvedValue(null), }); expect(result.config).toEqual({ '$schema': 'https://opencode.ai/config.json', enabled_providers: [], permission: { external_directory: 'ask', }, provider: {}, }); expect(result.env).toEqual({}); }); });