hololake-system-architecture/product-source/hololake-platform/src/lib/aiTargets.test.ts

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)
})
})