import { describe, it, expect } from 'vitest'; import { createClientFromConfig, anthropicToGitHubModel, createAutoFallbackClient } from './index.js'; import { AnthropicClient } from '../models/anthropic.js'; import { OpenAIClient } from '../models/openai.js'; import { OllamaClient } from '../models/local/ollama.js'; import { LlamaCppClient } from '../models/local/llamacpp.js'; import { GeminiClient } from '../models/gemini.js'; import { BedrockClient } from '../models/bedrock.js'; import { GitHubModelsClient } from '../models/github.js'; describe('createClientFromConfig', () => { it('creates AnthropicClient for anthropic provider', () => { const client = createClientFromConfig({ provider: 'anthropic', model: 'claude-sonnet-4-5-20250514', api_key: 'sk-ant-test', }); expect(client).toBeInstanceOf(AnthropicClient); }); it('creates OpenAIClient for openai provider', () => { const client = createClientFromConfig({ provider: 'openai', model: 'gpt-4o', api_key: 'sk-test', }); expect(client).toBeInstanceOf(OpenAIClient); }); it('creates OllamaClient for ollama provider', () => { const client = createClientFromConfig({ provider: 'ollama', model: 'llama3.2:1b', endpoint: 'http://localhost:11434', }); expect(client).toBeInstanceOf(OllamaClient); }); it('creates OllamaClient with num_gpu option', () => { const client = createClientFromConfig({ provider: 'ollama', model: 'llama3.2:1b', num_gpu: 0, }); expect(client).toBeInstanceOf(OllamaClient); }); it('creates LlamaCppClient for llamacpp provider', () => { const client = createClientFromConfig({ provider: 'llamacpp', model: 'ministral-reasoning', endpoint: 'http://localhost:8080', }); expect(client).toBeInstanceOf(LlamaCppClient); }); it('defaults llamacpp endpoint to localhost:8080', () => { const client = createClientFromConfig({ provider: 'llamacpp', model: 'test-model', }); expect(client).toBeInstanceOf(LlamaCppClient); }); it('creates GeminiClient for gemini provider', () => { const client = createClientFromConfig({ provider: 'gemini', model: 'gemini-2.5-pro', api_key: 'test-key', }); expect(client).toBeInstanceOf(GeminiClient); }); it('throws for unknown provider', () => { expect(() => createClientFromConfig({ provider: 'unknown' as 'anthropic', model: 'test', })).toThrow('Unknown model provider: unknown'); }); it('creates OpenAIClient with OpenRouter baseURL for openrouter provider', () => { const client = createClientFromConfig({ provider: 'openrouter', model: 'meta-llama/llama-3.1-70b', api_key: 'test-key', }); expect(client).toBeInstanceOf(OpenAIClient); }); it('creates OpenAIClient with Zhipu AI baseURL for zhipuai provider', () => { const client = createClientFromConfig({ provider: 'zhipuai', model: 'glm-4.5', api_key: 'test-key', }); expect(client).toBeInstanceOf(OpenAIClient); }); it('creates BedrockClient for bedrock provider', () => { const client = createClientFromConfig({ provider: 'bedrock', model: 'anthropic.claude-3-sonnet', }); expect(client).toBeInstanceOf(BedrockClient); }); it('creates GitHubModelsClient for github provider', () => { const client = createClientFromConfig({ provider: 'github', model: 'claude-sonnet-4.5', }); expect(client).toBeInstanceOf(GitHubModelsClient); }); it('auto-maps Anthropic model names to GitHub equivalents for github provider', () => { // User might copy-paste the Anthropic model name into a github fallback block const client = createClientFromConfig({ provider: 'github', model: 'claude-sonnet-4-5-20250929', }); expect(client).toBeInstanceOf(GitHubModelsClient); }); }); describe('anthropicToGitHubModel', () => { it('maps claude-sonnet-4-20250514 to claude-sonnet-4', () => { expect(anthropicToGitHubModel('claude-sonnet-4-20250514')).toBe('claude-sonnet-4'); }); it('maps claude-sonnet-4-5-20250929 to claude-sonnet-4.5', () => { expect(anthropicToGitHubModel('claude-sonnet-4-5-20250929')).toBe('claude-sonnet-4.5'); }); it('maps claude-opus-4-20250514 to claude-opus-4', () => { expect(anthropicToGitHubModel('claude-opus-4-20250514')).toBe('claude-opus-4'); }); it('maps claude-opus-4-5-20250918 to claude-opus-4.5', () => { expect(anthropicToGitHubModel('claude-opus-4-5-20250918')).toBe('claude-opus-4.5'); }); it('maps claude-opus-4-6-20250715 to claude-opus-4.6', () => { expect(anthropicToGitHubModel('claude-opus-4-6-20250715')).toBe('claude-opus-4.6'); }); it('maps claude-3-5-haiku-20241022 to claude-haiku-4.5', () => { expect(anthropicToGitHubModel('claude-3-5-haiku-20241022')).toBe('claude-haiku-4.5'); }); it('maps claude-haiku-4-5-20251001 to claude-haiku-4.5', () => { expect(anthropicToGitHubModel('claude-haiku-4-5-20251001')).toBe('claude-haiku-4.5'); }); it('strips date suffix and converts trailing version number with dot for unknown models', () => { // "claude-sonnet-5-7-20260101" → strip date → "claude-sonnet-5-7" → dot → "claude-sonnet-5.7" expect(anthropicToGitHubModel('claude-sonnet-5-7-20260101')).toBe('claude-sonnet-5.7'); }); it('strips date suffix for models without sub-version', () => { // "claude-sonnet-5-20260101" → strip date → "claude-sonnet-5" (no trailing -N to dot-convert) expect(anthropicToGitHubModel('claude-sonnet-5-20260101')).toBe('claude-sonnet-5'); }); it('returns undefined for models without date suffix', () => { expect(anthropicToGitHubModel('llama3.2:1b')).toBeUndefined(); }); }); describe('createAutoFallbackClient', () => { it('creates a GitHubModelsClient for anthropic provider', () => { const client = createAutoFallbackClient({ provider: 'anthropic', model: 'claude-sonnet-4-20250514', }); expect(client).toBeInstanceOf(GitHubModelsClient); }); it('returns undefined for non-anthropic providers', () => { expect(createAutoFallbackClient({ provider: 'openai', model: 'gpt-4o' })).toBeUndefined(); expect(createAutoFallbackClient({ provider: 'ollama', model: 'llama3.2:1b' })).toBeUndefined(); expect(createAutoFallbackClient({ provider: 'gemini', model: 'gemini-2.5-pro' })).toBeUndefined(); }); it('returns undefined for unmappable anthropic models', () => { expect(createAutoFallbackClient({ provider: 'anthropic', model: 'custom-model' })).toBeUndefined(); }); });