139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
LOCAL_AI_PROVIDER_KINDS,
|
|
agentTargetId,
|
|
agentTargets,
|
|
aiModelProviderCatalog,
|
|
aiModelProviderCatalogEntry,
|
|
isLocalAiProvider,
|
|
normalizeAiModelProviders,
|
|
resolveAiTarget,
|
|
type AiModelProvider,
|
|
} from './aiTargets'
|
|
import { AI_AGENT_DEFINITIONS } from './aiAgents'
|
|
import type { Settings } from '../types'
|
|
|
|
function provider(kind: AiModelProvider['kind']): AiModelProvider {
|
|
return {
|
|
id: ' Demo ',
|
|
name: ' Demo Provider ',
|
|
kind,
|
|
base_url: ' https://example.com/v1 ',
|
|
api_key_storage: null,
|
|
api_key_env_var: ' DEMO_API_KEY ',
|
|
headers: null,
|
|
models: [{
|
|
id: ' demo-model ',
|
|
display_name: ' Demo Model ',
|
|
context_window: null,
|
|
max_output_tokens: null,
|
|
capabilities: {
|
|
streaming: true,
|
|
tools: false,
|
|
vision: false,
|
|
json_mode: true,
|
|
reasoning: false,
|
|
},
|
|
}],
|
|
}
|
|
}
|
|
|
|
describe('ai target provider contract', () => {
|
|
it('builds selectable targets for every supported coding agent', () => {
|
|
expect(agentTargets().map((target) => target.id)).toEqual(
|
|
AI_AGENT_DEFINITIONS.map((definition) => agentTargetId(definition.id)),
|
|
)
|
|
})
|
|
|
|
it('resolves Copilot as a persisted default agent target', () => {
|
|
const target = resolveAiTarget({
|
|
default_ai_agent: 'claude_code',
|
|
default_ai_target: 'agent:copilot',
|
|
} as Settings)
|
|
|
|
expect(target).toMatchObject({
|
|
kind: 'agent',
|
|
agent: 'copilot',
|
|
id: 'agent:copilot',
|
|
label: 'GitHub Copilot',
|
|
})
|
|
})
|
|
|
|
it('falls back safely when the default target contains a hidden legacy agent', () => {
|
|
const target = resolveAiTarget({
|
|
default_ai_agent: 'claude_code',
|
|
default_ai_target: 'kiro',
|
|
} as Settings)
|
|
|
|
expect(target).toMatchObject({
|
|
kind: 'agent',
|
|
agent: 'claude_code',
|
|
id: 'agent:claude_code',
|
|
})
|
|
})
|
|
|
|
it('falls back safely when the saved legacy default agent is no longer selectable', () => {
|
|
const target = resolveAiTarget({
|
|
default_ai_agent: 'kiro',
|
|
default_ai_target: 'agent:claude_code',
|
|
} as Settings)
|
|
|
|
expect(target).toMatchObject({
|
|
kind: 'agent',
|
|
agent: 'claude_code',
|
|
id: 'agent:claude_code',
|
|
})
|
|
})
|
|
|
|
it('keeps provider defaults in one catalog with stable grouping metadata', () => {
|
|
const entries = aiModelProviderCatalog()
|
|
const kinds = entries.map((entry) => entry.kind)
|
|
|
|
expect(kinds).toEqual([
|
|
'ollama',
|
|
'lm_studio',
|
|
'open_ai',
|
|
'anthropic',
|
|
'gemini',
|
|
'open_router',
|
|
'open_ai_compatible',
|
|
])
|
|
expect(new Set(kinds).size).toBe(kinds.length)
|
|
expect(LOCAL_AI_PROVIDER_KINDS).toEqual(['ollama', 'lm_studio'])
|
|
expect(aiModelProviderCatalogEntry('anthropic')).toMatchObject({
|
|
name: 'Anthropic',
|
|
base_url: 'https://api.anthropic.com/v1',
|
|
api_key_storage: 'local_file',
|
|
api_key_env_var: 'ANTHROPIC_API_KEY',
|
|
default_model_id: 'claude-3-5-sonnet-latest',
|
|
local: false,
|
|
})
|
|
expect(aiModelProviderCatalogEntry('open_ai_compatible')).toMatchObject({
|
|
base_url: 'https://api.example.com/v1',
|
|
api_key_env_var: 'OPENAI_API_KEY',
|
|
local: false,
|
|
})
|
|
})
|
|
|
|
it('normalizes saved providers while using the catalog for local/provider classification', () => {
|
|
const normalized = normalizeAiModelProviders([
|
|
provider('open_ai_compatible'),
|
|
{ ...provider('ollama'), id: ' ', name: 'Missing ID' },
|
|
])
|
|
|
|
expect(normalized).toHaveLength(1)
|
|
expect(normalized[0]).toMatchObject({
|
|
id: 'demo',
|
|
name: 'Demo Provider',
|
|
base_url: 'https://example.com/v1',
|
|
api_key_env_var: 'DEMO_API_KEY',
|
|
api_key_storage: 'env',
|
|
})
|
|
expect(normalized[0].models[0]).toMatchObject({
|
|
id: 'demo-model',
|
|
display_name: 'Demo Model',
|
|
})
|
|
expect(isLocalAiProvider(provider('lm_studio'))).toBe(true)
|
|
expect(isLocalAiProvider(provider('open_router'))).toBe(false)
|
|
})
|
|
})
|