Makelore 2.0 initial clean snapshot
This commit is contained in:
450
tests/unit/opencode-provider-config.test.ts
Normal file
450
tests/unit/opencode-provider-config.test.ts
Normal file
@@ -0,0 +1,450 @@
|
||||
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> = {}): 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({});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user