Files

198 lines
7.1 KiB
JavaScript
Raw Permalink Normal View History

import { describe, it, expect } from 'bun:test';
import { resolveSmallModel, parseModelRef, isUsableAuthEntry } from './resolve.js';
const catalog = {
google: {
id: 'google',
models: {
'gemini-2.5-flash': { id: 'gemini-2.5-flash', family: 'gemini-flash', release_date: '2025-06-01' },
'gemini-2.0-flash': { id: 'gemini-2.0-flash', family: 'gemini-flash', release_date: '2024-12-01' },
'gemini-2.5-pro': { id: 'gemini-2.5-pro', family: 'gemini-pro', release_date: '2025-06-01' },
},
},
anthropic: {
id: 'anthropic',
models: {
'claude-haiku-4-5': { id: 'claude-haiku-4-5', family: 'claude-haiku', release_date: '2025-10-01' },
'claude-sonnet-4-5': { id: 'claude-sonnet-4-5', family: 'claude-sonnet', release_date: '2025-09-01' },
},
},
};
describe('parseModelRef', () => {
it('splits provider/model on the first slash', () => {
expect(parseModelRef('anthropic/claude-haiku-4-5')).toEqual({
providerID: 'anthropic',
modelID: 'claude-haiku-4-5',
});
});
it('keeps slashes inside the model id', () => {
expect(parseModelRef('openrouter/google/gemini-2.5-flash')).toEqual({
providerID: 'openrouter',
modelID: 'google/gemini-2.5-flash',
});
});
it('rejects values without a provider or model part', () => {
expect(parseModelRef('anthropic/')).toBeNull();
expect(parseModelRef('/model')).toBeNull();
expect(parseModelRef('plain')).toBeNull();
expect(parseModelRef(undefined)).toBeNull();
});
});
describe('isUsableAuthEntry', () => {
it('accepts api keys, oauth tokens, and wellknown tokens', () => {
expect(isUsableAuthEntry({ type: 'api', key: 'sk-x' })).toBe(true);
expect(isUsableAuthEntry({ type: 'oauth', access: 'a', refresh: 'r', expires: 0 })).toBe(true);
expect(isUsableAuthEntry({ type: 'wellknown', key: 'k', token: 't' })).toBe(true);
});
it('rejects empty or malformed entries', () => {
expect(isUsableAuthEntry({ type: 'api', key: '' })).toBe(false);
expect(isUsableAuthEntry({ type: 'oauth' })).toBe(false);
expect(isUsableAuthEntry(null)).toBe(false);
});
});
describe('resolveSmallModel', () => {
it('gives the OpenChamber settings override top priority', () => {
const result = resolveSmallModel({
auth: { anthropic: { type: 'api', key: 'sk-x' } },
catalog,
settingsSmallModel: 'anthropic/claude-haiku-4-5',
configSmallModel: 'openai/gpt-4o-mini',
preferredProviderID: 'anthropic',
});
expect(result).toEqual({ providerID: 'anthropic', modelID: 'claude-haiku-4-5', source: 'settings' });
});
it('prefers the configured small_model', () => {
const result = resolveSmallModel({
auth: { anthropic: { type: 'api', key: 'sk-x' } },
catalog,
configSmallModel: 'openai/gpt-4o-mini',
});
expect(result).toEqual({ providerID: 'openai', modelID: 'gpt-4o-mini', source: 'config' });
});
it('scans authenticated providers by family priority, newest first', () => {
const result = resolveSmallModel({
auth: {
google: { type: 'api', key: 'g-key' },
anthropic: { type: 'api', key: 'sk-x' },
},
catalog,
configSmallModel: null,
});
expect(result).toEqual({ providerID: 'google', modelID: 'gemini-2.5-flash', source: 'family-scan' });
});
it('skips providers without a usable credential', () => {
const result = resolveSmallModel({
auth: {
google: { type: 'api', key: '' },
anthropic: { type: 'api', key: 'sk-x' },
},
catalog,
configSmallModel: null,
});
expect(result).toEqual({ providerID: 'anthropic', modelID: 'claude-haiku-4-5', source: 'family-scan' });
});
it('falls back to Copilot utility models when only Copilot is logged in', () => {
const result = resolveSmallModel({
auth: { 'github-copilot': { type: 'oauth', access: 't', refresh: 't', expires: 0 } },
catalog,
configSmallModel: null,
});
expect(result?.providerID).toBe('github-copilot');
expect(result?.source).toBe('copilot-utility');
});
it('returns null when nothing is authenticated', () => {
expect(resolveSmallModel({ auth: {}, catalog, configSmallModel: null })).toBeNull();
});
it('prefers the session provider over other authenticated providers', () => {
const result = resolveSmallModel({
auth: {
google: { type: 'api', key: 'g-key' },
anthropic: { type: 'api', key: 'sk-x' },
},
catalog,
configSmallModel: null,
preferredProviderID: 'anthropic',
});
expect(result).toEqual({ providerID: 'anthropic', modelID: 'claude-haiku-4-5', source: 'family-scan' });
});
it('ignores a preferred provider without a usable login', () => {
const result = resolveSmallModel({
auth: { google: { type: 'api', key: 'g-key' } },
catalog,
configSmallModel: null,
preferredProviderID: 'anthropic',
});
expect(result).toEqual({ providerID: 'google', modelID: 'gemini-2.5-flash', source: 'family-scan' });
});
it('never uses a session provider without a login (opencode free models)', () => {
// Vanilla setups default the picker to opencode/big-pickle with no
// opencode token — those free models only work through OpenCode itself
// and must never be called directly, so the session context is ignored.
const result = resolveSmallModel({
auth: { openai: { type: 'oauth', access: 'a', refresh: 'r', expires: Date.now() + 60_000 } },
catalog,
configSmallModel: null,
preferredProviderID: 'opencode',
preferredModelID: 'big-pickle',
});
expect(result).toEqual({ providerID: 'openai', modelID: 'gpt-5.4-mini', source: 'codex-small' });
});
it('resolves nothing on a vanilla setup with no logins at all', () => {
const result = resolveSmallModel({
auth: {},
catalog,
configSmallModel: null,
preferredProviderID: 'opencode',
preferredModelID: 'big-pickle',
});
expect(result).toBeNull();
});
it('falls back to the session model instead of scanning other providers', () => {
const result = resolveSmallModel({
auth: {
'opencode-go': { type: 'api', key: 'oc-key' },
openai: { type: 'oauth', access: 'a', refresh: 'r', expires: Date.now() + 60_000 },
},
catalog: {
'opencode-go': {
id: 'opencode-go',
models: {
'deepseek-v4-flash': { id: 'deepseek-v4-flash', family: 'deepseek-flash', release_date: '2026-01-01' },
},
},
},
configSmallModel: null,
preferredProviderID: 'opencode-go',
preferredModelID: 'deepseek-v4-flash',
});
expect(result).toEqual({ providerID: 'opencode-go', modelID: 'deepseek-v4-flash', source: 'session-model' });
});
it('falls back to the session model itself when nothing resolves', () => {
const result = resolveSmallModel({
auth: { mistral: { type: 'api', key: 'm-key' } },
catalog,
configSmallModel: null,
preferredProviderID: 'mistral',
preferredModelID: 'mistral-large-latest',
});
expect(result).toEqual({ providerID: 'mistral', modelID: 'mistral-large-latest', source: 'session-model' });
});
});