import assert from 'node:assert/strict'; import test from 'node:test'; import { createLlmProviderService, CUSTOM_PROVIDER_ID, MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID, buildExecutorLaunchPlan, decryptSecret, encryptSecret, LLM_PROVIDER_CATALOG, LOCAL_LLM_FALLBACK, maskApiKey, normalizeApiUrl, resolveApiBaseUrl, parseModelList, resolveChatCompletionsUrl, testLocalLlmConnection, testRelayConnection, syncProfileToGoosed, } from './llm-providers.mjs'; test('encryptSecret round-trips with derived key', () => { const encrypted = encryptSecret('sk-test-key-12345678', 'unit-test-secret'); const plain = decryptSecret(encrypted, 'unit-test-secret'); assert.equal(plain, 'sk-test-key-12345678'); }); test('parseModelList accepts comma and newline separated values', () => { assert.deepEqual(parseModelList('qwen2.5:3b, llama3.2:1b\nmistral'), [ 'qwen2.5:3b', 'llama3.2:1b', 'mistral', ]); }); test('normalizeApiUrl adds http scheme', () => { assert.equal( normalizeApiUrl('127.0.0.1:18300/relay/buyer/v1/chat/completions'), 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions', ); }); test('maskApiKey hides middle segment', () => { const masked = maskApiKey('sk-1234567890abcdef'); assert.ok(masked.startsWith('sk-1')); assert.ok(masked.endsWith('cdef')); assert.ok(masked.includes('*')); }); test('createKey selects first profile and syncs to goosed', async () => { const rows = []; let selectedId = null; const syncCalls = []; const pool = { async query(sql, params = []) { if (sql.includes('COUNT(*) AS total FROM h5_llm_provider_keys')) { return [[{ total: rows.length }]]; } if (sql.includes('UPDATE h5_llm_provider_keys SET is_selected = 0')) { for (const row of rows) row.is_selected = 0; return [[]]; } if (sql.includes('INSERT INTO h5_llm_provider_keys')) { const encrypted = { ciphertext: params[9], iv: params[10], tag: params[11], }; const row = { id: params[0], provider_id: params[1], provider_kind: params[2], api_url: params[3], base_path: params[4], models_json: params[5], goosed_provider_id: null, engine: params[6], relay_provider: params[7], name: params[8], api_key_ciphertext: encrypted.ciphertext, api_key_iv: encrypted.iv, api_key_tag: encrypted.tag, default_model: params[12], status: 'active', is_selected: params[13], created_at: params[14], updated_at: params[15], }; rows.push(row); if (row.is_selected) selectedId = row.id; return [[]]; } if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) { return [[rows.find((row) => row.id === params[0]) ?? null].filter(Boolean)]; } if (sql.includes('ORDER BY is_selected DESC')) { return [rows]; } if (sql.includes('SELECT id FROM h5_llm_provider_keys WHERE name = ?')) { return [[rows.find((row) => row.name === params[0])].filter(Boolean)]; } if (sql.includes('SET goosed_provider_id = ?')) { const target = rows.find((row) => row.id === params[3]); if (target) { target.goosed_provider_id = params[0]; target.provider_id = params[1]; } return [[]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const mockFetch = async (url, init) => { syncCalls.push({ url: String(url), body: init?.body ? JSON.parse(init.body) : null }); if (String(url).includes('/config/custom-providers') && init?.method === 'POST') { return { ok: true, json: async () => ({ provider_name: 'relay_ollama' }) }; } return { ok: true, text: async () => '' }; }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.createKey({ providerId: 'custom_deepseek', name: 'DeepSeek 主账号', apiKey: 'sk-deepseek-test', defaultModel: 'deepseek-v4-pro', }); assert.equal(result.ok, true); assert.equal(result.key.isSelected, true); assert.equal(rows.length, 1); assert.equal(selectedId, rows[0].id); assert.equal(syncCalls.filter((item) => item.body?.key === 'DEEPSEEK_API_KEY').length, 1); }); test('create custom relay profile registers goosed custom provider', async () => { const rows = []; const mockFetch = async (url, init) => { if (String(url).includes('/config/custom-providers') && init?.method === 'POST') { const body = JSON.parse(init.body); assert.equal(body.api_url, 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions'); assert.deepEqual(body.models, ['qwen2.5:3b', 'llama3.2:1b']); assert.equal(body.headers['X-Provider'], 'ollama'); return { ok: true, json: async () => ({ provider_name: 'relay_buyer' }) }; } return { ok: true, text: async () => '' }; }; const pool = { async query(sql, params = []) { if (sql.includes('COUNT(*) AS total FROM h5_llm_provider_keys')) return [[{ total: 0 }]]; if (sql.includes('UPDATE h5_llm_provider_keys SET is_selected = 0')) return [[]]; if (sql.includes('INSERT INTO h5_llm_provider_keys')) { rows.push({ id: params[0], provider_id: params[1], provider_kind: params[2], api_url: params[3], base_path: params[4], models_json: params[5], goosed_provider_id: null, engine: params[6], relay_provider: params[7], name: params[8], api_key_ciphertext: params[9], api_key_iv: params[10], api_key_tag: params[11], default_model: params[12], status: 'active', is_selected: params[13], created_at: params[14], updated_at: params[15], }); return [[]]; } if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) { return [[rows.find((row) => row.id === params[0]) ?? null].filter(Boolean)]; } if (sql.includes('SELECT id FROM h5_llm_provider_keys WHERE name = ?')) return [[]]; if (sql.includes('SET goosed_provider_id = ?')) { rows[0].goosed_provider_id = params[0]; rows[0].provider_id = params[1]; return [[]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.createKey({ providerId: CUSTOM_PROVIDER_ID, name: 'Relay Ollama', apiKey: 'UqyHPKSSEZq0-oPnl8sru-7hZcJ2anPUL1yAVk866Vo', apiUrl: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions', models: ['qwen2.5:3b', 'llama3.2:1b'], defaultModel: 'qwen2.5:3b', relayProvider: 'ollama', }); assert.equal(result.ok, true); assert.equal(result.key.providerKind, 'custom'); assert.equal(rows[0].provider_kind, 'custom'); }); test('deleteKey treats a missing TKMind custom provider as already deleted', async () => { const row = { id: 'key-relay', provider_id: CUSTOM_PROVIDER_ID, provider_kind: 'custom', goosed_provider_id: 'custom_relay_buyer_ollama', name: 'Relay Buyer Ollama', status: 'active', is_selected: 0, }; let deleted = false; const pool = { async query(sql, params = []) { if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) { return [[params[0] === row.id ? row : null].filter(Boolean)]; } if (sql.includes('DELETE FROM h5_llm_provider_keys WHERE id = ?')) { deleted = params[0] === row.id; return [[]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const mockFetch = async (_url, init = {}) => { assert.equal(init.method, 'DELETE'); return { ok: false, status: 500, text: async () => JSON.stringify({ message: 'Provider not found: custom_relay_buyer_ollama', }), }; }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.deleteKey(row.id); assert.equal(result.ok, true); assert.equal(deleted, true); }); test('selectKey rejects disabled profile', async () => { const row = { id: 'key-1', provider_id: 'custom_deepseek', provider_kind: 'builtin', name: 'disabled', api_key_ciphertext: encryptSecret('sk-x', 'unit-test-secret').ciphertext, api_key_iv: encryptSecret('sk-x', 'unit-test-secret').iv, api_key_tag: encryptSecret('sk-x', 'unit-test-secret').tag, default_model: 'deepseek-v4-pro', status: 'disabled', is_selected: 0, created_at: 1, updated_at: 1, }; const pool = { async query(sql, params = []) { if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) { return [[row]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', }); const result = await service.selectKey('key-1'); assert.equal(result.ok, false); assert.match(result.message, /禁用/); }); test('catalog includes custom provider template', () => { const custom = LLM_PROVIDER_CATALOG.find((item) => item.id === CUSTOM_PROVIDER_ID); assert.ok(custom); assert.equal(custom.kind, 'custom'); }); test('DeepSeek catalog uses the currently supported V4 model names', () => { const deepseek = LLM_PROVIDER_CATALOG.find((item) => item.id === 'custom_deepseek'); assert.ok(deepseek); assert.equal(deepseek.defaultModel, 'deepseek-v4-pro'); assert.deepEqual(deepseek.models, ['deepseek-v4-pro', 'deepseek-v4-flash']); }); test('listExecutorBindings returns all executor placeholders', async () => { const pool = { async query(sql) { if (sql.includes('ORDER BY is_selected DESC')) return [[]]; if (sql.includes('FROM h5_llm_executor_bindings')) return [[]]; throw new Error(`Unexpected SQL: ${sql}`); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', }); const bindings = await service.listExecutorBindings(); assert.deepEqual( bindings.map((binding) => binding.executor), ['goose', 'aider', 'openhands'], ); assert.equal(bindings[0].enabled, false); }); test('resolveChatCompletionsUrl keeps full completions path', () => { assert.equal( resolveChatCompletionsUrl('http://127.0.0.1:18300/relay/buyer/v1/chat/completions'), 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions', ); }); test('resolveApiBaseUrl strips chat completions suffix', () => { assert.equal( resolveApiBaseUrl('http://127.0.0.1:18300/relay/buyer/v1/chat/completions'), 'http://127.0.0.1:18300/relay/buyer/v1', ); }); test('testKey checks builtin DeepSeek through OpenAI compatible endpoint', async () => { const encrypted = encryptSecret('sk-deepseek-test', 'unit-test-secret'); const row = { id: 'key-deepseek', provider_id: 'custom_deepseek', provider_kind: 'builtin', name: 'DeepSeek', api_key_ciphertext: encrypted.ciphertext, api_key_iv: encrypted.iv, api_key_tag: encrypted.tag, default_model: 'deepseek-v4-pro', status: 'active', is_selected: 1, created_at: 1, updated_at: 1, }; const pool = { async query(sql, params = []) { if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) { assert.equal(params[0], 'key-deepseek'); return [[row]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const calls = []; const mockFetch = async (url, init) => { calls.push({ url: String(url), init, body: JSON.parse(init.body) }); return { ok: true, text: async () => JSON.stringify({ choices: [{ message: { content: 'ok' } }] }), }; }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.testKey('key-deepseek', 'deepseek-v4-pro'); assert.equal(result.ok, true); assert.equal(calls[0].url, 'https://api.deepseek.com/v1/chat/completions'); assert.equal(calls[0].init.headers.Authorization, 'Bearer sk-deepseek-test'); assert.equal(calls[0].body.model, 'deepseek-v4-pro'); }); test('createChatCompletion uses selected OpenAI-compatible provider', async () => { const encrypted = encryptSecret('sk-direct-test', 'unit-test-secret'); const row = { id: 'key-direct', provider_id: CUSTOM_PROVIDER_ID, provider_kind: 'custom', api_url: 'http://127.0.0.1:19999/v1', base_path: null, models_json: JSON.stringify(['direct-model']), goosed_provider_id: null, engine: 'openai', relay_provider: 'deepseek', name: 'Direct Provider', api_key_ciphertext: encrypted.ciphertext, api_key_iv: encrypted.iv, api_key_tag: encrypted.tag, default_model: 'direct-model', status: 'active', is_selected: 1, created_at: 1, updated_at: 1, }; const pool = { async query(sql, params = []) { if (sql.includes('WHERE is_selected = 1')) return [[row]]; throw new Error(`Unexpected SQL: ${sql} ${JSON.stringify(params)}`); }, }; const calls = []; const mockFetch = async (url, init) => { calls.push({ url: String(url), init, body: JSON.parse(init.body) }); return { ok: true, text: async () => JSON.stringify({ choices: [{ message: { content: 'direct reply' } }], usage: { prompt_tokens: 11, completion_tokens: 7 }, }), }; }; const service = createLlmProviderService(pool, { encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.createChatCompletion({ messages: [{ role: 'user', content: 'hi' }], }); assert.equal(result.ok, true); assert.equal(result.reply, 'direct reply'); assert.equal(result.model, 'direct-model'); assert.deepEqual(result.usage, { inputTokens: 11, outputTokens: 7, raw: { prompt_tokens: 11, completion_tokens: 7 }, }); assert.equal(calls[0].url, 'http://127.0.0.1:19999/v1/chat/completions'); assert.equal(calls[0].init.headers.Authorization, 'Bearer sk-direct-test'); assert.equal(calls[0].body.provider, 'deepseek'); assert.equal(calls[0].body.stream, false); }); test('createChatCompletion can use an explicit provider key for router calls', async () => { const encryptedSelected = encryptSecret('sk-selected-test', 'unit-test-secret'); const encryptedRouter = encryptSecret('sk-router-test', 'unit-test-secret'); const selectedRow = { id: 'key-selected', provider_id: CUSTOM_PROVIDER_ID, provider_kind: 'custom', api_url: 'http://127.0.0.1:19998/v1', base_path: null, models_json: JSON.stringify(['selected-model']), goosed_provider_id: null, engine: 'openai', relay_provider: null, name: 'Selected Provider', api_key_ciphertext: encryptedSelected.ciphertext, api_key_iv: encryptedSelected.iv, api_key_tag: encryptedSelected.tag, default_model: 'selected-model', status: 'active', is_selected: 1, created_at: 1, updated_at: 1, }; const routerRow = { ...selectedRow, id: 'key-router', api_url: 'http://127.0.0.1:19997/v1', models_json: JSON.stringify(['router-model']), name: 'Router Provider', api_key_ciphertext: encryptedRouter.ciphertext, api_key_iv: encryptedRouter.iv, api_key_tag: encryptedRouter.tag, default_model: 'router-model', is_selected: 0, }; const rows = [selectedRow, routerRow]; const pool = { async query(sql, params = []) { if (sql.includes('WHERE id = ?')) { return [[rows.find((row) => row.id === params[0])].filter(Boolean)]; } if (sql.includes('WHERE is_selected = 1')) return [[selectedRow]]; throw new Error(`Unexpected SQL: ${sql} ${JSON.stringify(params)}`); }, }; const calls = []; const mockFetch = async (url, init) => { calls.push({ url: String(url), init, body: JSON.parse(init.body) }); return { ok: true, text: async () => JSON.stringify({ choices: [{ message: { content: 'router reply' } }], }), }; }; const service = createLlmProviderService(pool, { encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.createChatCompletion({ providerKeyId: 'key-router', model: 'router-model', messages: [{ role: 'user', content: 'route this' }], }); assert.equal(result.ok, true); assert.equal(result.providerKeyId, 'key-router'); assert.equal(result.model, 'router-model'); assert.equal(result.reply, 'router reply'); assert.equal(calls[0].url, 'http://127.0.0.1:19997/v1/chat/completions'); assert.equal(calls[0].init.headers.Authorization, 'Bearer sk-router-test'); }); test('getExecutorRuntimeConfig resolves aider env from binding', async () => { const encrypted = encryptSecret('sk-deepseek-test', 'unit-test-secret'); const keyRow = { id: 'key-deepseek', provider_id: 'custom_deepseek', provider_kind: 'builtin', name: 'DeepSeek 主账号', api_key_ciphertext: encrypted.ciphertext, api_key_iv: encrypted.iv, api_key_tag: encrypted.tag, default_model: 'deepseek-v4-pro', status: 'active', is_selected: 1, created_at: 1, updated_at: 1, }; const bindingRow = { id: 'binding-aider', executor: 'aider', purpose: 'default', provider_key_id: 'key-deepseek', model: 'deepseek-v4-flash', enabled: 1, created_at: 1, updated_at: 1, }; const pool = { async query(sql, params = []) { if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[bindingRow]]; if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) { assert.equal(params[0], 'key-deepseek'); return [[keyRow]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', }); const runtime = await service.getExecutorRuntimeConfig('aider'); assert.equal(runtime.ok, true); assert.equal(runtime.model, 'deepseek-v4-flash'); assert.equal(runtime.env.AIDER_MODEL, 'deepseek/deepseek-v4-flash'); assert.equal(runtime.env.DEEPSEEK_API_KEY, '[hidden]'); }); test('buildExecutorLaunchPlan creates openhands headless command', () => { const plan = buildExecutorLaunchPlan({ ok: true, executor: 'openhands', executorLabel: 'OpenHands', purpose: 'default', providerName: 'DeepSeek 主账号', model: 'deepseek-v4-flash', env: { LLM_MODEL: 'deepseek-v4-flash', LLM_API_KEY: '[hidden]' }, }, { mode: 'headless', cwd: '/repo/memind', instruction: 'add search', }); assert.equal(plan.ok, true); assert.equal(plan.command, 'openhands'); assert.deepEqual(plan.args.slice(0, 3), ['--headless', '--json', '--override-with-envs']); assert.equal(plan.cwd, '/repo/memind'); assert.ok(plan.args.includes('--task')); }); test('buildExecutorLaunchPlan creates openhands serve command', () => { const plan = buildExecutorLaunchPlan({ ok: true, executor: 'openhands', executorLabel: 'OpenHands', purpose: 'default', providerName: 'DeepSeek 主账号', model: 'deepseek-v4-flash', env: { LLM_MODEL: 'deepseek-v4-flash', LLM_API_KEY: '[hidden]' }, }, { mode: 'serve', cwd: '/repo/memind', }); assert.equal(plan.ok, true); assert.equal(plan.command, 'openhands'); assert.deepEqual(plan.args, ['serve', '--mount-cwd']); }); test('buildExecutorLaunchPlan adds aider one-shot message when provided', () => { const plan = buildExecutorLaunchPlan({ ok: true, executor: 'aider', executorLabel: 'Aider', purpose: 'default', providerName: 'DeepSeek 主账号', model: 'deepseek-v4-pro', env: { AIDER_MODEL: 'deepseek/deepseek-v4-pro', OPENAI_API_KEY: '[hidden]', TKMIND_EXECUTOR_PROVIDER: 'custom_deepseek', }, }, { cwd: '/repo/memind', instruction: 'fix login button', }); assert.equal(plan.ok, true); assert.equal(plan.command, 'aider'); assert.deepEqual(plan.args.slice(0, 2), ['--model', 'deepseek/deepseek-v4-pro']); assert.ok(plan.args.includes('--message')); assert.ok(plan.args.includes('fix login button')); }); test('buildExecutorLaunchPlan rejects openhands headless without instruction', () => { const plan = buildExecutorLaunchPlan({ ok: true, executor: 'openhands', executorLabel: 'OpenHands', purpose: 'default', providerName: 'DeepSeek 主账号', model: 'deepseek-v4-flash', env: { LLM_MODEL: 'deepseek-v4-flash', LLM_API_KEY: '[hidden]' }, }, { mode: 'headless', cwd: '/repo/memind', }); assert.equal(plan.ok, false); assert.match(plan.message ?? '', /instruction/); }); test('launchExecutor rejects goose launch path', async () => { const pool = { async query() { throw new Error('should not query for goose launch'); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', }); const result = await service.launchExecutor('goose'); assert.equal(result.ok, false); assert.match(result.message ?? '', /Goose/); }); test('launchExecutor rejects openhands headless without instruction', async () => { const pool = { async query() { throw new Error('should not query for validation failure'); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', }); const result = await service.launchExecutor('openhands', { mode: 'headless' }); assert.equal(result.ok, false); assert.match(result.message ?? '', /instruction/); }); test('launchExecutor returns friendly error when command is missing', async () => { const encryptedKey = encryptSecret('sk-x', 'unit-test-secret'); const keyRow = { id: 'key-1', provider_id: 'custom_deepseek', provider_kind: 'builtin', name: 'DeepSeek', api_key_ciphertext: encryptedKey.ciphertext, api_key_iv: encryptedKey.iv, api_key_tag: encryptedKey.tag, default_model: 'deepseek-v4-pro', status: 'active', is_selected: 1, created_at: 1, updated_at: 1, }; const bindingRow = { id: 'binding-1', executor: 'aider', purpose: 'default', provider_key_id: 'key-1', model: 'deepseek-v4-pro', enabled: 1, created_at: 1, updated_at: 1, }; const pool = { async query(sql) { if (sql.includes('SELECT * FROM h5_llm_executor_bindings WHERE executor = ? AND purpose = ? LIMIT 1')) { return [[bindingRow]]; } if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]]; if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) { return [[keyRow]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', }); const oldAiderBin = process.env.AIDER_BIN; process.env.AIDER_BIN = '/definitely/missing/aider'; try { const result = await service.launchExecutor('aider', { instruction: 'hello' }); assert.equal(result.ok, false); assert.match(result.message ?? '', /PATH|未安装/); } finally { if (oldAiderBin == null) delete process.env.AIDER_BIN; else process.env.AIDER_BIN = oldAiderBin; } }); test('testRelayConnection surfaces 401 as token error', async () => { const mockFetch = async () => ({ ok: false, status: 401, text: async () => '{}', }); const result = await testRelayConnection( { apiUrl: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions', apiKey: 'bad-token', model: 'qwen2.5:3b', relayProvider: 'ollama', }, mockFetch, ); assert.equal(result.ok, false); assert.match(result.message ?? '', /401/); assert.match(result.message ?? '', /Token/); }); test('testRelayConnection parses OpenAI-style response', async () => { const mockFetch = async () => ({ ok: true, text: async () => JSON.stringify({ choices: [{ message: { content: 'Hi there' } }], }), }); const result = await testRelayConnection( { apiUrl: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions', apiKey: 'token', model: 'qwen2.5:3b', relayProvider: 'ollama', }, mockFetch, ); assert.equal(result.ok, true); assert.equal(result.reply, 'Hi there'); }); test('syncProfileToGoosed writes provider, model and secret keys for builtin', async () => { const writes = []; const mockFetch = async (_url, init) => { writes.push(JSON.parse(init.body)); return { ok: true, text: async () => '' }; }; await syncProfileToGoosed( 'https://127.0.0.1:18006', 'secret', { providerKind: 'builtin', providerId: 'custom_deepseek', defaultModel: 'deepseek-v4-pro', apiKey: 'sk-test', }, mockFetch, ); assert.deepEqual( writes.map((item) => item.key), ['DEEPSEEK_API_KEY', 'GOOSE_PROVIDER', 'GOOSE_MODEL', 'TKMIND_PROVIDER', 'TKMIND_MODEL'], ); }); test('syncProfileToGoosed routes explicitly protected production DeepSeek through the compatibility proxy', async () => { const previousProfile = process.env.MEMIND_RUNTIME_PROFILE; const previousDisableThinking = process.env.MEMIND_DEEPSEEK_DISABLE_THINKING; const previousProxyBase = process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL; const previousGateway = process.env.MEMIND_GOOSED_HOST_GATEWAY; process.env.MEMIND_RUNTIME_PROFILE = 'production'; process.env.MEMIND_DEEPSEEK_DISABLE_THINKING = '1'; delete process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL; process.env.MEMIND_GOOSED_HOST_GATEWAY = 'host.lima.internal'; try { const calls = []; const mockFetch = async (url, init = {}) => { const href = String(url); const body = init.body ? JSON.parse(init.body) : null; calls.push({ href, method: init.method ?? 'GET', body }); if ( init.method === 'PUT' && href.includes(`/config/custom-providers/${MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID}`) ) { return { ok: false, status: 500, text: async () => JSON.stringify({ message: `Provider not found: ${MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID}`, }), }; } if (init.method === 'POST' && href.endsWith('/config/custom-providers')) { return { ok: true, json: async () => ({ provider_name: MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID }), text: async () => '', }; } return { ok: true, status: 200, text: async () => '' }; }; const providerId = await syncProfileToGoosed( 'https://127.0.0.1:18006', 'secret', { providerKind: 'builtin', providerId: 'custom_deepseek', defaultModel: 'deepseek-v4-pro', apiKey: 'sk-test', }, mockFetch, ); assert.equal(providerId, MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID); const createCall = calls.find( (call) => call.method === 'POST' && call.href.endsWith('/config/custom-providers'), ); assert.equal(createCall?.body?.api_url, 'http://host.lima.internal:18036/v1'); assert.equal(createCall?.body?.preserves_thinking, false); const providerWrite = calls.find( (call) => call.href.includes('/config/upsert') && call.body?.key === 'GOOSE_PROVIDER', ); assert.equal(providerWrite?.body?.value, MEMIND_DEEPSEEK_NO_THINK_PROVIDER_ID); } finally { if (previousProfile == null) delete process.env.MEMIND_RUNTIME_PROFILE; else process.env.MEMIND_RUNTIME_PROFILE = previousProfile; if (previousDisableThinking == null) delete process.env.MEMIND_DEEPSEEK_DISABLE_THINKING; else process.env.MEMIND_DEEPSEEK_DISABLE_THINKING = previousDisableThinking; if (previousProxyBase == null) delete process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL; else process.env.MEMIND_DEEPSEEK_NO_THINK_BASE_URL = previousProxyBase; if (previousGateway == null) delete process.env.MEMIND_GOOSED_HOST_GATEWAY; else process.env.MEMIND_GOOSED_HOST_GATEWAY = previousGateway; } }); test('syncProfileToGoosed routes local Moonshot profiles through schema compat proxy', async () => { const previousProfile = process.env.MEMIND_RUNTIME_PROFILE; const previousCompat = process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT; const previousBase = process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL; const previousGateway = process.env.MEMIND_GOOSED_HOST_GATEWAY; process.env.MEMIND_RUNTIME_PROFILE = 'split-service'; delete process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT; delete process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL; process.env.MEMIND_GOOSED_HOST_GATEWAY = 'host.lima.internal'; try { const calls = []; const mockFetch = async (url, init = {}) => { const href = String(url); const body = init.body ? JSON.parse(init.body) : null; calls.push({ href, method: init.method ?? 'GET', body }); if ( init.method === 'PUT' && href.includes('/config/custom-providers/custom_kimi') ) { return { ok: true, status: 200, text: async () => '' }; } return { ok: true, status: 200, text: async () => '' }; }; const providerId = await syncProfileToGoosed( 'https://127.0.0.1:18006', 'secret', { providerKind: 'custom', providerId: 'custom_kimi', goosedProviderId: 'custom_kimi', name: 'kimi', apiUrl: 'https://api.moonshot.cn/v1', apiKey: 'sk-test', models: ['kimi-k2.6', 'kimi-k3'], defaultModel: 'kimi-k2.6', engine: 'openai', }, mockFetch, ); assert.equal(providerId, 'custom_kimi'); const updateCall = calls.find( (call) => call.method === 'PUT' && call.href.includes('/config/custom-providers/custom_kimi'), ); assert.equal( updateCall?.body?.api_url, 'http://host.lima.internal:18036/moonshot/v1', ); assert.equal(updateCall?.body?.preserves_thinking, false); } finally { if (previousProfile == null) delete process.env.MEMIND_RUNTIME_PROFILE; else process.env.MEMIND_RUNTIME_PROFILE = previousProfile; if (previousCompat == null) delete process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT; else process.env.MEMIND_MOONSHOT_TOOL_SCHEMA_COMPAT = previousCompat; if (previousBase == null) delete process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL; else process.env.MEMIND_MOONSHOT_COMPAT_BASE_URL = previousBase; if (previousGateway == null) delete process.env.MEMIND_GOOSED_HOST_GATEWAY; else process.env.MEMIND_GOOSED_HOST_GATEWAY = previousGateway; } }); test('syncSelectedToGoosed writes selected provider to every configured target', async () => { const encrypted = encryptSecret('sk-test', 'unit-test-secret'); const row = { id: 'key-deepseek', provider_id: 'custom_deepseek', provider_kind: 'builtin', name: 'DeepSeek', api_url: null, base_path: null, models_json: JSON.stringify(['deepseek-v4-pro']), goosed_provider_id: null, engine: 'openai', relay_provider: null, api_key_ciphertext: encrypted.ciphertext, api_key_iv: encrypted.iv, api_key_tag: encrypted.tag, default_model: 'deepseek-v4-pro', status: 'active', is_selected: 1, created_at: 1, updated_at: 1, }; const pool = { async query(sql) { if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]]; if (sql.includes('is_selected = 1')) return [[row]]; throw new Error(`Unexpected SQL: ${sql}`); }, }; const calls = []; const mockFetch = async (url, init) => { calls.push({ url: String(url), body: JSON.parse(init.body) }); if (String(url).includes('/chat/completions')) { return { ok: true, json: async () => ({ choices: [{ message: { content: 'ok' } }] }), text: async () => JSON.stringify({ choices: [{ message: { content: 'ok' } }] }), }; } return { ok: true, text: async () => '' }; }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiTargets: ['https://127.0.0.1:18006', 'https://127.0.0.1:18007'], apiSecret: 'secret', encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.syncSelectedToGoosed(); assert.equal(result.ok, true); assert.deepEqual(result.targets, ['https://127.0.0.1:18006', 'https://127.0.0.1:18007']); assert.deepEqual( calls .filter((call) => call.url.includes('/config/upsert') && call.body.key === 'GOOSE_PROVIDER') .map((call) => new URL(call.url).port), ['18006', '18007'], ); }); test('syncProfileToGoosed tolerates missing /config/upsert endpoint', async () => { const mockFetch = async (_url, init) => { if (String(_url).includes('/config/custom-providers')) { return { ok: true, json: async () => ({ provider_name: 'custom_local_ollama_7b' }), }; } if (String(_url).includes('/config/upsert')) { return { ok: false, status: 404, text: async () => '' }; } return { ok: true, text: async () => '' }; }; const providerId = await syncProfileToGoosed( 'https://127.0.0.1:18006', 'secret', { providerKind: 'custom', providerId: CUSTOM_PROVIDER_ID, name: 'Local Ollama 7B', apiUrl: LOCAL_LLM_FALLBACK.apiUrl, apiKey: LOCAL_LLM_FALLBACK.apiKey, models: [LOCAL_LLM_FALLBACK.model], defaultModel: LOCAL_LLM_FALLBACK.model, engine: 'openai', }, mockFetch, ); assert.equal(providerId, 'custom_local_ollama_7b'); }); test('LOCAL_LLM_FALLBACK defaults to local ollama 7b', () => { assert.equal(LOCAL_LLM_FALLBACK.model, 'qwen2.5:3b'); assert.match(LOCAL_LLM_FALLBACK.apiUrl, /11434/); assert.equal(LOCAL_LLM_FALLBACK.enabled, true); }); test('syncSelectedToGoosed does not auto-fallback to local when selected relay is down', async () => { const encrypted = encryptSecret('token', 'unit-test-secret'); const row = { id: 'key-relay', provider_id: '__custom__', provider_kind: 'custom', name: 'Relay Buyer Ollama', api_url: 'http://127.0.0.1:18300/relay/buyer/v1/chat/completions', base_path: null, models_json: JSON.stringify(['qwen2.5:3b']), goosed_provider_id: null, engine: 'openai', relay_provider: 'ollama', api_key_ciphertext: encrypted.ciphertext, api_key_iv: encrypted.iv, api_key_tag: encrypted.tag, default_model: 'qwen2.5:3b', status: 'active', is_selected: 1, created_at: 1, updated_at: 1, }; const pool = { async query(sql) { if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]]; if (sql.includes('is_selected = 1')) return [[row]]; throw new Error(`Unexpected SQL: ${sql}`); }, }; const mockFetch = async () => { throw Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' }); }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', apiFetchImpl: mockFetch, }); const result = await service.syncSelectedToGoosed(); assert.equal(result.ok, false); assert.equal(result.synced, false); assert.ok(result.message); }); test('ensureBootstrapRelay keeps existing selected provider', async () => { const rows = [ { id: 'key-deepseek', provider_id: 'custom_deepseek', provider_kind: 'builtin', name: 'DeepSeek', status: 'active', is_selected: 1, updated_at: 1, }, { id: 'key-relay', provider_id: 'custom_relay_buyer_ollama_27', provider_kind: 'custom', name: 'Relay Buyer Ollama', status: 'active', is_selected: 0, updated_at: 1, }, ]; const pool = { async query(sql, params = []) { if (sql.includes('SELECT id FROM h5_llm_provider_keys WHERE name = ?')) { return [[rows.find((row) => row.name === params[0])].filter(Boolean)]; } if (sql.includes('UPDATE h5_llm_provider_keys SET is_selected')) { throw new Error('bootstrap relay should not change the selected provider'); } throw new Error(`Unexpected SQL: ${sql}`); }, }; const service = createLlmProviderService(pool, { apiTarget: 'https://127.0.0.1:18006', apiSecret: 'secret', encryptionKey: 'unit-test-secret', }); const result = await service.ensureBootstrapRelay(); assert.equal(result.ok, true); assert.equal(result.created, false); assert.equal(rows.find((row) => row.id === 'key-deepseek').is_selected, 1); assert.equal(rows.find((row) => row.id === 'key-relay').is_selected, 0); });