mirror of
https://github.com/EKKOLearnAI/hermes-web-ui.git
synced 2026-07-13 12:10:31 +00:00
8ef700bee9
* feat: add MoA combination models * fix moa command bridge events * fix moa display history isolation * fix moa resume and locale coverage
159 lines
7.4 KiB
TypeScript
159 lines
7.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
PROVIDER_PRESETS as SERVER_PROVIDER_PRESETS,
|
|
buildProviderModelMap as buildServerProviderModelMap,
|
|
} from '../../packages/server/src/shared/providers'
|
|
import { PROVIDER_ENV_MAP } from '../../packages/server/src/services/config-helpers'
|
|
|
|
const OPENAI_CODEX_PROVIDER = 'openai-codex'
|
|
const COPILOT_PROVIDER = 'copilot'
|
|
const FUN_CODEX_PROVIDER = 'fun-codex'
|
|
const LONGCAT_PROVIDER = 'longcat'
|
|
const KIMI_CODING_PROVIDER = 'kimi-coding'
|
|
const KIMI_CODING_CN_PROVIDER = 'kimi-coding-cn'
|
|
const GLM_CODING_PLAN_PROVIDER = 'glm'
|
|
const ALIBABA_CODING_PLAN_PROVIDER = 'alibaba-coding-plan'
|
|
const MINIMAX_PROVIDER = 'minimax'
|
|
const MINIMAX_CN_PROVIDER = 'minimax-cn'
|
|
const NOUS_PROVIDER = 'nous'
|
|
const STEPFUN_PROVIDER = 'stepfun'
|
|
const XAI_OAUTH_PROVIDER = 'xai-oauth'
|
|
const GEMINI_OAUTH_PROVIDER = 'google-gemini-cli'
|
|
const CLAUDE_OAUTH_PROVIDER = 'claude-oauth'
|
|
const ANTHROPIC_PROVIDER = 'anthropic'
|
|
const GPT_5_5_MODEL = 'gpt-5.5'
|
|
|
|
function modelsForProvider(providerPresets: Array<{ value: string; models: string[] }>, provider: string): string[] {
|
|
const preset = providerPresets.find((candidate) => candidate.value === provider)
|
|
expect(preset).toBeDefined()
|
|
return preset?.models ?? []
|
|
}
|
|
|
|
describe('provider presets', () => {
|
|
it('keeps every built-in provider preset registered in the env map', () => {
|
|
const missingMappings = SERVER_PROVIDER_PRESETS
|
|
.filter(candidate => candidate.builtin)
|
|
.map(candidate => candidate.value)
|
|
.filter(provider => !Object.prototype.hasOwnProperty.call(PROVIDER_ENV_MAP, provider))
|
|
|
|
expect(missingMappings).toEqual([])
|
|
})
|
|
|
|
it('routes apikey.fun Codex through the Responses transport', () => {
|
|
const preset = SERVER_PROVIDER_PRESETS.find((candidate) => candidate.value === FUN_CODEX_PROVIDER)
|
|
expect(preset?.api_mode).toBe('codex_responses')
|
|
})
|
|
|
|
it('routes LongCat through the Responses transport', () => {
|
|
const preset = SERVER_PROVIDER_PRESETS.find((candidate) => candidate.value === LONGCAT_PROVIDER)
|
|
expect(preset?.api_mode).toBe('codex_responses')
|
|
})
|
|
|
|
it('lists GPT-5.5 for OpenAI Codex', () => {
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, OPENAI_CODEX_PROVIDER)).toContain(GPT_5_5_MODEL)
|
|
})
|
|
|
|
it('exposes GPT-5.5 through provider model maps', () => {
|
|
expect(buildServerProviderModelMap()[OPENAI_CODEX_PROVIDER]).toContain(GPT_5_5_MODEL)
|
|
})
|
|
|
|
it('treats xAI OAuth as OAuth-only for availability checks', () => {
|
|
expect(PROVIDER_ENV_MAP[XAI_OAUTH_PROVIDER]).toEqual({ api_key_env: '', base_url_env: '' })
|
|
})
|
|
|
|
it('treats Google Gemini OAuth as OAuth-only for availability checks', () => {
|
|
expect(PROVIDER_ENV_MAP[GEMINI_OAUTH_PROVIDER]).toEqual({ api_key_env: '', base_url_env: '' })
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, GEMINI_OAUTH_PROVIDER)).toContain('gemini-3.1-pro-preview')
|
|
})
|
|
|
|
it('treats Claude OAuth as OAuth-only while keeping Anthropic API key separate', () => {
|
|
expect(PROVIDER_ENV_MAP[CLAUDE_OAUTH_PROVIDER]).toEqual({ api_key_env: '', base_url_env: '' })
|
|
expect(PROVIDER_ENV_MAP.anthropic).toEqual({ api_key_env: 'ANTHROPIC_API_KEY', base_url_env: 'ANTHROPIC_BASE_URL' })
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, CLAUDE_OAUTH_PROVIDER)).toContain('claude-sonnet-4-6')
|
|
})
|
|
|
|
it('includes Claude Fable 5 for direct Anthropic and Claude OAuth', () => {
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, ANTHROPIC_PROVIDER)).toContain('claude-fable-5')
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, CLAUDE_OAUTH_PROVIDER)).toContain('claude-fable-5')
|
|
})
|
|
|
|
it('keeps Kimi Coding Plan and China credentials distinct without duplicate Moonshot presets', () => {
|
|
expect(PROVIDER_ENV_MAP[KIMI_CODING_PROVIDER]).toEqual({ api_key_env: 'KIMI_API_KEY', base_url_env: 'KIMI_BASE_URL' })
|
|
expect(PROVIDER_ENV_MAP[KIMI_CODING_CN_PROVIDER]).toEqual({ api_key_env: 'KIMI_CN_API_KEY', base_url_env: '' })
|
|
expect(PROVIDER_ENV_MAP).not.toHaveProperty('moonshot')
|
|
|
|
expect(SERVER_PROVIDER_PRESETS.find(candidate => candidate.value === KIMI_CODING_PROVIDER)?.base_url).toBe('https://api.kimi.com/coding/v1')
|
|
expect(SERVER_PROVIDER_PRESETS.find(candidate => candidate.value === KIMI_CODING_CN_PROVIDER)?.base_url).toBe('https://api.kimi.cn/coding/v1')
|
|
expect(SERVER_PROVIDER_PRESETS.find(candidate => candidate.value === KIMI_CODING_CN_PROVIDER)?.label).toBe('Kimi for Coding China')
|
|
expect(SERVER_PROVIDER_PRESETS.find(candidate => candidate.value === 'moonshot')).toBeUndefined()
|
|
})
|
|
|
|
it('aligns GLM Coding Plan with Hermes Agent China coding endpoint', () => {
|
|
const preset = SERVER_PROVIDER_PRESETS.find(candidate => candidate.value === GLM_CODING_PLAN_PROVIDER)
|
|
expect(preset?.base_url).toBe('https://open.bigmodel.cn/api/coding/paas/v4')
|
|
expect(preset?.api_mode).toBeUndefined()
|
|
expect(PROVIDER_ENV_MAP[GLM_CODING_PLAN_PROVIDER]).toEqual({ api_key_env: 'GLM_API_KEY', base_url_env: '' })
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, GLM_CODING_PLAN_PROVIDER)).toEqual([
|
|
'glm-5.2',
|
|
'glm-5.1',
|
|
'glm-5v-turbo',
|
|
'glm-4.7',
|
|
])
|
|
})
|
|
|
|
it('includes Qwen 3.7 Plus in the Alibaba Coding Plan fallback catalog', () => {
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, ALIBABA_CODING_PLAN_PROVIDER)).toContain('qwen3.7-plus')
|
|
})
|
|
|
|
it('does not expose incomplete built-in provider presets', () => {
|
|
expect(PROVIDER_ENV_MAP).not.toHaveProperty('azure-foundry')
|
|
expect(SERVER_PROVIDER_PRESETS.find(candidate => candidate.value === 'azure-foundry')).toBeUndefined()
|
|
expect(SERVER_PROVIDER_PRESETS.filter(candidate => candidate.builtin && !candidate.base_url && candidate.models.length === 0)).toEqual([])
|
|
})
|
|
|
|
it('includes Step 3.7 Flash in the StepFun fallback catalog', () => {
|
|
expect(modelsForProvider(SERVER_PROVIDER_PRESETS, STEPFUN_PROVIDER)).toContain('step-3.7-flash')
|
|
})
|
|
|
|
it('keeps MiniMax M3 first while retaining currently supported M2.x Anthropic models', () => {
|
|
expect(PROVIDER_ENV_MAP[MINIMAX_PROVIDER]).toEqual({ api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_BASE_URL' })
|
|
expect(PROVIDER_ENV_MAP[MINIMAX_CN_PROVIDER]).toEqual({ api_key_env: 'MINIMAX_CN_API_KEY', base_url_env: 'MINIMAX_CN_BASE_URL' })
|
|
|
|
for (const provider of [MINIMAX_PROVIDER, MINIMAX_CN_PROVIDER]) {
|
|
const models = modelsForProvider(SERVER_PROVIDER_PRESETS, provider)
|
|
expect(models[0]).toBe('MiniMax-M3')
|
|
expect(models).toEqual(expect.arrayContaining([
|
|
'MiniMax-M2.7',
|
|
'MiniMax-M2.7-highspeed',
|
|
'MiniMax-M2.5',
|
|
'MiniMax-M2.5-highspeed',
|
|
'MiniMax-M2.1',
|
|
'MiniMax-M2.1-highspeed',
|
|
'MiniMax-M2',
|
|
]))
|
|
}
|
|
})
|
|
|
|
it('includes current GitHub Copilot fallback models', () => {
|
|
const models = modelsForProvider(SERVER_PROVIDER_PRESETS, COPILOT_PROVIDER)
|
|
expect(models).toEqual(expect.arrayContaining([
|
|
'gpt-5.5',
|
|
'gpt-5.4',
|
|
'gpt-5.4-nano',
|
|
'claude-opus-4.8',
|
|
'gemini-3.5-flash',
|
|
'raptor-mini',
|
|
]))
|
|
expect(models).not.toContain('grok-code-fast-1')
|
|
})
|
|
|
|
it('hardcodes current Nous catalog and recommended models', () => {
|
|
const models = modelsForProvider(SERVER_PROVIDER_PRESETS, NOUS_PROVIDER)
|
|
expect(models).toContain('anthropic/claude-opus-4.8')
|
|
expect(models).toContain('qwen/qwen3.7-max')
|
|
expect(models).toContain('qwen/qwen3.6-35b-a3b')
|
|
expect(buildServerProviderModelMap()[NOUS_PROVIDER]).toContain('qwen/qwen3.7-max')
|
|
})
|
|
})
|