chore: bootstrap hermes-ui skeleton from 0.6.29 snapshot
Build / build (push) Canceled after 0s
NPM Lockfile Check / npm ci --ignore-scripts (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

Import the client, server, skills, tests, and build scripts as the starting
point for the rebuild, then trim the surfaces this project does not ship:
Electron desktop packaging, the marketing website, and the ESP32 firmware
tree. CI keeps the web build, e2e, lockfile, release, and Docker workflows.

Rename the package and CLI entry points to hermes-ui, drop the prepare hook so
npm ci no longer triggers a build, and drop the removed tsconfig and nodemon
references. Rewrite AGENTS.md, ARCHITECTURE.md, DEVELOPMENT.md, and the READMEs
so the documented package map and hard rules match this repository.

reference_code/ is ignored: it is a read-only snapshot, not an input to the build.
This commit is contained in:
2026-10-16 10:34:35 +08:00
commit 1e9ea89d86
912 changed files with 287328 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
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')
})
})