Files
openchamber/packages/web/server/lib/small-model/runtime-providers.test.js
T

119 lines
4.2 KiB
JavaScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
ZEN_ANONYMOUS_API_KEY,
configureOpenCodeRuntimeProviders,
getRuntimeProvider,
getRuntimeProviderSnapshot,
resetOpenCodeRuntimeProviders,
} from './runtime-providers.js';
const providerPayload = (overrides = {}) => ({
all: [
{
id: 'llmapi',
source: 'config',
options: { apiKey: 'plugin-key', baseURL: 'https://api.llmapi.ai/v1/' },
models: {
'claude-opus-4-8': { api: { id: 'claude-opus-4-8', url: '', npm: '@ai-sdk/anthropic' } },
'gpt-5.6-luna': { api: { id: 'gpt-5.6-luna', url: 'https://api.llmapi.ai/v1', npm: '@ai-sdk/openai' } },
},
},
{
id: 'opencode',
source: 'custom',
options: { apiKey: ZEN_ANONYMOUS_API_KEY },
models: { 'free-model': { api: { id: 'free-model', url: 'https://opencode.ai/zen/v1', npm: '@ai-sdk/openai-compatible' } } },
},
{
id: 'zai-coding-plan',
source: 'api',
key: 'auth-json-key',
options: {},
models: { 'glm-5': { api: { id: 'glm-5', url: 'https://api.z.ai/api/coding/paas/v4', npm: '@ai-sdk/openai-compatible' } } },
},
],
connected: ['llmapi', 'opencode', 'zai-coding-plan'],
...overrides,
});
describe('OpenCode runtime provider snapshot', () => {
let fetchMock;
beforeEach(() => {
fetchMock = vi.fn(async () => new Response(JSON.stringify(providerPayload()), {
status: 200,
headers: { 'content-type': 'application/json' },
}));
vi.stubGlobal('fetch', fetchMock);
configureOpenCodeRuntimeProviders({
buildOpenCodeUrl: (pathname) => `http://127.0.0.1:4096${pathname}`,
getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic test' }),
});
});
afterEach(() => {
configureOpenCodeRuntimeProviders(null);
resetOpenCodeRuntimeProviders();
vi.unstubAllGlobals();
});
it('reports the credential and endpoint a plugin registered at runtime', async () => {
const provider = await getRuntimeProvider('llmapi');
expect(provider).toMatchObject({ apiKey: 'plugin-key', baseURL: 'https://api.llmapi.ai/v1' });
expect(provider.models.get('gpt-5.6-luna')).toEqual({
api: { url: 'https://api.llmapi.ai/v1', npm: '@ai-sdk/openai' },
});
expect(fetchMock.mock.calls[0][0]).toBe('http://127.0.0.1:4096/provider');
expect(fetchMock.mock.calls[0][1].headers).toMatchObject({ Authorization: 'Basic test' });
});
it('refuses the zen sentinel as a credential', async () => {
const provider = await getRuntimeProvider('opencode');
expect(provider.apiKey).toBeNull();
expect(provider.anonymousZen).toBe(true);
// The endpoint is still reported; only the credential is withheld.
expect(provider.baseURL).toBe('https://opencode.ai/zen/v1');
});
it('falls back to the model endpoint when the provider carries no baseURL', async () => {
expect((await getRuntimeProvider('zai-coding-plan')).baseURL).toBe('https://api.z.ai/api/coding/paas/v4');
});
it('serves one snapshot to concurrent callers instead of refetching', async () => {
await Promise.all([getRuntimeProvider('llmapi'), getRuntimeProvider('opencode'), getRuntimeProvider('zai-coding-plan')]);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it('answers "unknown" rather than "no providers" when OpenCode is unreachable', async () => {
resetOpenCodeRuntimeProviders();
fetchMock.mockRejectedValue(new Error('connection refused'));
expect(await getRuntimeProviderSnapshot()).toBeNull();
});
it('keeps the previous snapshot when a later refresh fails', async () => {
await getRuntimeProviderSnapshot();
fetchMock.mockRejectedValue(new Error('connection refused'));
// Past the snapshot TTL, so the next read genuinely attempts a refresh.
vi.useFakeTimers();
vi.setSystemTime(Date.now() + 60_000);
const refreshed = await getRuntimeProviderSnapshot();
vi.useRealTimers();
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(refreshed.providers.has('llmapi')).toBe(true);
});
it('stays on file-based resolution until it is configured', async () => {
configureOpenCodeRuntimeProviders(null);
expect(await getRuntimeProvider('llmapi')).toBeNull();
expect(fetchMock).not.toHaveBeenCalled();
});
});