import { describe, it, expect, vi } from 'vitest' import { OpenAIProvider } from '../../src/providers/openai' import { createProvider } from '../../src/providers/factory' import type { MagpieConfig } from '../../src/config/types' let lastConstructorOptions: Record = {} let lastCreateOptions: Record = {} vi.mock('openai', () => ({ default: class MockOpenAI { chat = { completions: { create: vi.fn().mockImplementation((opts: Record) => { lastCreateOptions = opts return Promise.resolve({ choices: [{ message: { content: 'Mock response' } }] }) }) } } constructor(options: Record) { lastConstructorOptions = options } } })) describe('OpenAIProvider', () => { it('should have correct name', () => { const provider = new OpenAIProvider({ apiKey: 'test', model: 'gpt-4o' }) expect(provider.name).toBe('openai') }) it('should call chat and return response', async () => { const provider = new OpenAIProvider({ apiKey: 'test', model: 'gpt-4o' }) const result = await provider.chat([{ role: 'user', content: 'Hello' }]) expect(result).toBe('Mock response') }) it('should pass baseURL to SDK when provided', () => { new OpenAIProvider({ apiKey: 'test', model: 'gpt-4o', baseURL: 'https://my-proxy.example.com/v1' }) expect(lastConstructorOptions.baseURL).toBe('https://my-proxy.example.com/v1') }) it('should not set baseURL when not provided', () => { new OpenAIProvider({ apiKey: 'test', model: 'gpt-4o' }) expect(lastConstructorOptions.baseURL).toBeUndefined() }) }) describe('OpenRouter via OpenAI client', () => { const baseConfig: MagpieConfig = { providers: {}, defaults: { max_rounds: 3, output_format: 'markdown' }, reviewers: {}, summarizer: { model: 'openrouter/anthropic/claude-3.5-sonnet', prompt: '' }, analyzer: { model: 'openrouter/anthropic/claude-3.5-sonnet', prompt: '' } } it('strips the openrouter/ prefix from the model and defaults baseURL to OpenRouter', async () => { const config: MagpieConfig = { ...baseConfig, providers: { openrouter: { api_key: 'or-key' } } } const provider = createProvider('openrouter/anthropic/claude-3.5-sonnet', config) expect(lastConstructorOptions.apiKey).toBe('or-key') expect(lastConstructorOptions.baseURL).toBe('https://openrouter.ai/api/v1') // Invoke chat() so the stripped model reaches chat.completions.create await provider.chat([{ role: 'user', content: 'hi' }]) expect(lastCreateOptions.model).toBe('anthropic/claude-3.5-sonnet') }) it('honors a custom base_url from config and forwards the stripped model', async () => { const config: MagpieConfig = { ...baseConfig, providers: { openrouter: { api_key: 'or-key', base_url: 'https://my-openrouter-proxy.example.com/v1' } } } const provider = createProvider('openrouter/meta-llama/llama-3-70b-instruct', config) expect(lastConstructorOptions.baseURL).toBe('https://my-openrouter-proxy.example.com/v1') await provider.chat([{ role: 'user', content: 'hi' }]) expect(lastCreateOptions.model).toBe('meta-llama/llama-3-70b-instruct') }) it('throws when the model is just "openrouter/" with no ID after it', () => { const config: MagpieConfig = { ...baseConfig, providers: { openrouter: { api_key: 'or-key' } } } expect(() => createProvider('openrouter/', config)).toThrow(/must include a model ID/) }) })