119 lines
4.2 KiB
JavaScript
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();
|
|
});
|
|
});
|