2163ea45d2
The OpenCode provider allows using a variety of models with an agent harness that can gather more information from the codebase as required (like with claude-code, codex, or gemini-cli). This is an alternative to using OpenRouter directly, where the api provider is more like a chatbot and cannot gather any additional context beyond what was handed to it.
148 lines
5.7 KiB
TypeScript
148 lines
5.7 KiB
TypeScript
// tests/providers/factory.test.ts
|
|
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
import { createProvider, getProviderForModel } from '../../src/providers/factory.js'
|
|
import type { MagpieConfig } from '../../src/config/types.js'
|
|
|
|
describe('Provider Factory', () => {
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs()
|
|
})
|
|
|
|
const mockConfig: MagpieConfig = {
|
|
providers: {
|
|
anthropic: { api_key: 'ant-key' },
|
|
openai: { api_key: 'oai-key' },
|
|
'claude-code': { enabled: true },
|
|
'codex-cli': { enabled: true }
|
|
},
|
|
defaults: { max_rounds: 3, output_format: 'markdown' },
|
|
reviewers: {},
|
|
summarizer: { model: 'claude-sonnet-4-20250514', prompt: '' },
|
|
analyzer: { model: 'claude-sonnet-4-20250514', prompt: '' }
|
|
}
|
|
|
|
describe('getProviderForModel', () => {
|
|
it('should return anthropic for claude models', () => {
|
|
expect(getProviderForModel('claude-sonnet-4-20250514')).toBe('anthropic')
|
|
expect(getProviderForModel('claude-3-opus-20240229')).toBe('anthropic')
|
|
})
|
|
|
|
it('should return openai for gpt models', () => {
|
|
expect(getProviderForModel('gpt-4o')).toBe('openai')
|
|
expect(getProviderForModel('gpt-4-turbo')).toBe('openai')
|
|
})
|
|
|
|
it('should return google for gemini models', () => {
|
|
expect(getProviderForModel('gemini-pro')).toBe('google')
|
|
})
|
|
|
|
it('should return claude-code for claude-code model', () => {
|
|
expect(getProviderForModel('claude-code')).toBe('claude-code')
|
|
})
|
|
|
|
it('should return codex-cli for codex-cli model', () => {
|
|
expect(getProviderForModel('codex-cli')).toBe('codex-cli')
|
|
})
|
|
|
|
it('should return opencode-cli for opencode-cli model (with and without :model suffix)', () => {
|
|
expect(getProviderForModel('opencode-cli')).toBe('opencode-cli')
|
|
expect(getProviderForModel('opencode-cli:openrouter/anthropic/claude-sonnet-4')).toBe('opencode-cli')
|
|
})
|
|
|
|
it('should return openrouter for openrouter/ prefixed models', () => {
|
|
expect(getProviderForModel('openrouter/anthropic/claude-3.5-sonnet')).toBe('openrouter')
|
|
expect(getProviderForModel('openrouter/meta-llama/llama-3-70b-instruct')).toBe('openrouter')
|
|
expect(getProviderForModel('openrouter/openai/gpt-4o')).toBe('openrouter')
|
|
})
|
|
})
|
|
|
|
describe('createProvider', () => {
|
|
it('should create anthropic provider', () => {
|
|
const provider = createProvider('claude-sonnet-4-20250514', mockConfig)
|
|
expect(provider.name).toBe('anthropic')
|
|
})
|
|
|
|
it('should create openai provider', () => {
|
|
const provider = createProvider('gpt-4o', mockConfig)
|
|
expect(provider.name).toBe('openai')
|
|
})
|
|
|
|
it('should throw for missing provider config', () => {
|
|
const configWithoutOpenAI = { ...mockConfig, providers: { anthropic: { api_key: 'key' } } }
|
|
expect(() => createProvider('gpt-4o', configWithoutOpenAI)).toThrow()
|
|
})
|
|
|
|
it('should create claude-code provider', () => {
|
|
const provider = createProvider('claude-code', mockConfig)
|
|
expect(provider.name).toBe('claude-code')
|
|
})
|
|
|
|
it('should create gemini provider', () => {
|
|
const configWithGoogle = {
|
|
...mockConfig,
|
|
providers: { ...mockConfig.providers, google: { api_key: 'google-key' } }
|
|
}
|
|
const provider = createProvider('gemini-pro', configWithGoogle)
|
|
expect(provider.name).toBe('gemini')
|
|
})
|
|
|
|
it('should create codex-cli provider', () => {
|
|
const provider = createProvider('codex-cli', mockConfig)
|
|
expect(provider.name).toBe('codex-cli')
|
|
})
|
|
|
|
it('should create opencode-cli provider with no extra config', () => {
|
|
const provider = createProvider('opencode-cli', mockConfig)
|
|
expect(provider.name).toBe('opencode-cli')
|
|
})
|
|
|
|
it('should create opencode-cli provider with a model suffix', () => {
|
|
const provider = createProvider('opencode-cli:openrouter/anthropic/claude-sonnet-4', mockConfig)
|
|
expect(provider.name).toBe('opencode-cli')
|
|
})
|
|
|
|
it('should pass base_url through to API providers', () => {
|
|
const configWithBaseUrl: MagpieConfig = {
|
|
...mockConfig,
|
|
providers: {
|
|
anthropic: { api_key: 'ant-key', base_url: 'https://my-proxy.example.com' },
|
|
openai: { api_key: 'oai-key', base_url: 'https://my-openai-proxy.example.com/v1' },
|
|
}
|
|
}
|
|
const anthropicProvider = createProvider('claude-sonnet-4-20250514', configWithBaseUrl)
|
|
expect(anthropicProvider.name).toBe('anthropic')
|
|
|
|
const openaiProvider = createProvider('gpt-4o', configWithBaseUrl)
|
|
expect(openaiProvider.name).toBe('openai')
|
|
})
|
|
|
|
it('should work without base_url (backwards compatible)', () => {
|
|
const provider = createProvider('claude-sonnet-4-20250514', mockConfig)
|
|
expect(provider.name).toBe('anthropic')
|
|
})
|
|
|
|
it('should create openrouter provider (via openai client) with api key from config', () => {
|
|
const configWithOpenrouter: MagpieConfig = {
|
|
...mockConfig,
|
|
providers: { ...mockConfig.providers, openrouter: { api_key: 'or-key' } }
|
|
}
|
|
const provider = createProvider('openrouter/anthropic/claude-3.5-sonnet', configWithOpenrouter)
|
|
// OpenRouter is routed through the OpenAI client, so .name === 'openai'
|
|
expect(provider.name).toBe('openai')
|
|
})
|
|
|
|
it('should pick up OPENROUTER_API_KEY env var when config is absent', () => {
|
|
vi.stubEnv('OPENROUTER_API_KEY', 'env-or-key')
|
|
const provider = createProvider('openrouter/anthropic/claude-3.5-sonnet', mockConfig)
|
|
expect(provider.name).toBe('openai')
|
|
})
|
|
|
|
it('should throw when OpenRouter has no api key configured', () => {
|
|
vi.stubEnv('OPENROUTER_API_KEY', '')
|
|
expect(() =>
|
|
createProvider('openrouter/anthropic/claude-3.5-sonnet', mockConfig)
|
|
).toThrow(/OpenRouter API key/)
|
|
})
|
|
})
|
|
})
|