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
@@ -0,0 +1,307 @@
import { existsSync, readFileSync } from 'fs'
import { writeFile } from 'fs/promises'
import { join } from 'path'
import { getActiveProfileName, getProfileDir } from '../../services/hermes/hermes-profile'
import { updateConfigYamlForProfile, saveEnvValueForProfile, PROVIDER_ENV_MAP } from '../../services/config-helpers'
import { getCompatibleCustomProviders, normalizeCustomProviderEntry } from '../../services/hermes/custom-providers-compat'
import { PROVIDER_PRESETS } from '../../shared/providers'
import { logger } from '../../services/logger'
const OPTIONAL_API_KEY_PROVIDERS = new Set(['cliproxyapi', 'xai-oauth', 'openai-codex', 'google-gemini-cli', 'claude-oauth'])
const DIRECT_CONFIG_PROVIDERS = new Set(['xai-oauth', 'openai-codex', 'google-gemini-cli', 'claude-oauth'])
type ProviderApiMode = 'chat_completions' | 'codex_responses' | 'anthropic_messages' | 'bedrock_converse' | 'codex_app_server'
function requestedProfile(ctx: any): string {
return ctx.state?.profile?.name || getActiveProfileName() || 'default'
}
function authPathForProfile(profile: string): string {
return join(getProfileDir(profile), 'auth.json')
}
async function clearStoredAuthProvider(profile: string, poolKey: string) {
try {
const authPath = authPathForProfile(profile)
if (!existsSync(authPath)) return
const auth = JSON.parse(readFileSync(authPath, 'utf-8'))
let changed = false
if (auth.providers && Object.prototype.hasOwnProperty.call(auth.providers, poolKey)) {
delete auth.providers[poolKey]
changed = true
}
if (auth.credential_pool && Object.prototype.hasOwnProperty.call(auth.credential_pool, poolKey)) {
delete auth.credential_pool[poolKey]
changed = true
}
if (changed) {
await writeFile(authPath, JSON.stringify(auth, null, 2) + '\n', 'utf-8')
}
} catch (err: any) { logger.error(err, 'Failed to clear auth credentials for %s', poolKey) }
}
function normalizeApiMode(value: unknown): ProviderApiMode | undefined {
const apiMode = String(value || '').trim()
return apiMode === 'chat_completions' ||
apiMode === 'codex_responses' ||
apiMode === 'anthropic_messages' ||
apiMode === 'bedrock_converse' ||
apiMode === 'codex_app_server'
? apiMode
: undefined
}
function buildProviderEntry(name: string, base_url: string, api_key: string, model: string, context_length?: number, api_mode?: ProviderApiMode) {
const entry: any = { name, base_url, api_key, model }
if (api_mode) {
entry.api_mode = api_mode
}
if (context_length && context_length > 0) {
entry.models = { [model]: { context_length } }
}
return entry
}
function normalizeBaseUrl(url: string): string {
return String(url || '').trim().replace(/\/+$/, '')
}
function builtinBaseUrl(poolKey: string, requestedBaseUrl: string): string {
return requestedBaseUrl || PROVIDER_PRESETS.find(p => p.value === poolKey)?.base_url || ''
}
function shouldPersistBuiltinBaseUrl(poolKey: string, requestedBaseUrl: string): boolean {
const presetBaseUrl = PROVIDER_PRESETS.find(p => p.value === poolKey)?.base_url || ''
if (!requestedBaseUrl || !presetBaseUrl) return !!requestedBaseUrl
return normalizeBaseUrl(requestedBaseUrl) !== normalizeBaseUrl(presetBaseUrl)
}
function providerKeyForCustomName(name: string): string {
return `custom:${String(name || '').trim().toLowerCase().replace(/ /g, '-')}`
}
function findLegacyCustomProviderIndex(config: any, poolKey: string): number {
return Array.isArray(config.custom_providers)
? (config.custom_providers as any[]).findIndex((e: any) => providerKeyForCustomName(e?.name) === poolKey)
: -1
}
function findProviderDictKey(config: any, poolKey: string, requestedProviderKey = ''): string {
const dict = config.providers
if (!dict || typeof dict !== 'object' || Array.isArray(dict)) return ''
if (requestedProviderKey && Object.prototype.hasOwnProperty.call(dict, requestedProviderKey)) {
return requestedProviderKey
}
for (const [key, entry] of Object.entries(dict)) {
const normalized = normalizeCustomProviderEntry(entry, key, 'providers')
if (normalized && providerKeyForCustomName(normalized.name) === poolKey) return key
}
return ''
}
export async function create(ctx: any) {
const { name, base_url, api_key, model, context_length, providerKey, api_mode } = ctx.request.body as {
name: string; base_url: string; api_key: string; model: string; context_length?: number; providerKey?: string | null; api_mode?: ProviderApiMode
}
const normalizedName = String(name || '').trim()
const poolKey = providerKey || `custom:${normalizedName.toLowerCase().replace(/ /g, '-')}`
const isBuiltin = poolKey in PROVIDER_ENV_MAP
const effectiveBaseUrl = isBuiltin ? builtinBaseUrl(poolKey, base_url) : base_url
const customApiMode = normalizeApiMode(api_mode)
if (!normalizedName || !effectiveBaseUrl || !model) {
ctx.status = 400; ctx.body = { error: 'Missing name, base_url, or model' }; return
}
if (!api_key && !OPTIONAL_API_KEY_PROVIDERS.has(String(providerKey || ''))) {
ctx.status = 400; ctx.body = { error: 'Missing API key' }; return
}
try {
const profile = requestedProfile(ctx)
await updateConfigYamlForProfile(profile, async (config) => {
if (typeof config.model !== 'object' || config.model === null) { config.model = {} }
if (!isBuiltin) {
if (!Array.isArray(config.custom_providers)) { config.custom_providers = [] }
const existing = (config.custom_providers as any[]).find(
(e: any) => `custom:${e.name}` === poolKey
)
if (existing) {
existing.base_url = effectiveBaseUrl
existing.api_key = api_key
existing.model = model
const preset = PROVIDER_PRESETS.find(p => p.value === poolKey.replace('custom:', ''))
if (preset?.api_mode) existing.api_mode = preset.api_mode
else if (customApiMode) existing.api_mode = customApiMode
if (context_length && context_length > 0) {
if (!existing.models) existing.models = {}
existing.models[model] = existing.models[model] || {}
existing.models[model].context_length = context_length
}
} else {
const entry = buildProviderEntry(normalizedName.toLowerCase().replace(/ /g, '-'), effectiveBaseUrl, api_key, model, context_length, customApiMode)
const preset = PROVIDER_PRESETS.find(p => p.value === poolKey.replace('custom:', ''))
if (preset?.api_mode) entry.api_mode = preset.api_mode
config.custom_providers.push(entry)
}
config.model.default = model
config.model.provider = poolKey
} else {
if (PROVIDER_ENV_MAP[poolKey].api_key_env) {
await saveEnvValueForProfile(profile, PROVIDER_ENV_MAP[poolKey].api_key_env, api_key)
if (PROVIDER_ENV_MAP[poolKey].base_url_env && shouldPersistBuiltinBaseUrl(poolKey, base_url)) { await saveEnvValueForProfile(profile, PROVIDER_ENV_MAP[poolKey].base_url_env, effectiveBaseUrl) }
config.model.default = model
config.model.provider = poolKey
} else if (DIRECT_CONFIG_PROVIDERS.has(poolKey)) {
if (PROVIDER_ENV_MAP[poolKey].base_url_env && shouldPersistBuiltinBaseUrl(poolKey, base_url)) { await saveEnvValueForProfile(profile, PROVIDER_ENV_MAP[poolKey].base_url_env, effectiveBaseUrl) }
config.model.default = model
config.model.provider = poolKey
} else {
if (!Array.isArray(config.custom_providers)) { config.custom_providers = [] }
const existing = (config.custom_providers as any[]).find(
(e: any) => `custom:${e.name}` === `custom:${poolKey}`
)
if (existing) {
existing.base_url = effectiveBaseUrl
existing.api_key = api_key
existing.model = model
const preset = PROVIDER_PRESETS.find(p => p.value === poolKey)
if (preset?.api_mode) existing.api_mode = preset.api_mode
else if (customApiMode) existing.api_mode = customApiMode
if (context_length && context_length > 0) {
if (!existing.models) existing.models = {}
existing.models[model] = existing.models[model] || {}
existing.models[model].context_length = context_length
}
} else {
const entry = buildProviderEntry(poolKey, effectiveBaseUrl, api_key, model, context_length, customApiMode)
const preset = PROVIDER_PRESETS.find(p => p.value === poolKey)
if (preset?.api_mode) entry.api_mode = preset.api_mode
config.custom_providers.push(entry)
}
config.model.default = model
config.model.provider = `custom:${poolKey}`
}
}
delete config.model.base_url
delete config.model.api_key
return config
})
// TODO: Test if provider works without gateway restart
// try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
ctx.body = { success: true }
} catch (err: any) {
ctx.status = 500; ctx.body = { error: err.message }
}
}
export async function update(ctx: any) {
const poolKey = decodeURIComponent(ctx.params.poolKey)
const { name, base_url, api_key, model, api_mode } = ctx.request.body as {
name?: string; base_url?: string; api_key?: string; model?: string; api_mode?: ProviderApiMode
}
const customApiMode = normalizeApiMode(api_mode)
try {
const profile = requestedProfile(ctx)
const isCustom = poolKey.startsWith('custom:')
if (isCustom) {
const found = await updateConfigYamlForProfile(profile, (config) => {
if (!Array.isArray(config.custom_providers)) return { data: config, result: false, write: false }
const entry = (config.custom_providers as any[]).find((e: any) => {
return `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}` === poolKey
})
if (!entry) return { data: config, result: false, write: false }
if (name !== undefined) entry.name = name
if (base_url !== undefined) entry.base_url = base_url
if (api_key !== undefined) entry.api_key = api_key
if (model !== undefined) entry.model = model
if (customApiMode !== undefined) entry.api_mode = customApiMode
return { data: config, result: true }
})
if (!found) {
ctx.status = 404; ctx.body = { error: `Custom provider "${poolKey}" not found` }; return
}
} else {
const envMapping = PROVIDER_ENV_MAP[poolKey]
if (!envMapping?.api_key_env) {
ctx.status = 400; ctx.body = { error: `Cannot update credentials for "${poolKey}"` }; return
}
if (api_key !== undefined) { await saveEnvValueForProfile(profile, envMapping.api_key_env, api_key) }
}
// TODO: Test if provider works without gateway restart
// try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
ctx.body = { success: true }
} catch (err: any) {
ctx.status = 500; ctx.body = { error: err.message }
}
}
export async function remove(ctx: any) {
const poolKey = decodeURIComponent(ctx.params.poolKey)
const query = ctx.query as { source?: string; providerKey?: string }
const requestedSource = query?.source === 'providers' || query?.source === 'custom_providers'
? query.source
: ''
const requestedProviderKey = typeof query?.providerKey === 'string' ? query.providerKey.trim() : ''
try {
const profile = requestedProfile(ctx)
const isCustom = poolKey.startsWith('custom:')
const removed = await updateConfigYamlForProfile(profile, async (config) => {
if (isCustom) {
const removeLegacy = requestedSource !== 'providers'
const removeDict = requestedSource !== 'custom_providers'
let didRemove = false
if (removeLegacy) {
const idx = findLegacyCustomProviderIndex(config, poolKey)
if (idx !== -1) {
;(config.custom_providers as any[]).splice(idx, 1)
didRemove = true
}
}
if (!didRemove && removeDict) {
const dictKey = findProviderDictKey(config, poolKey, requestedProviderKey)
if (dictKey) {
delete config.providers[dictKey]
didRemove = true
}
}
if (!didRemove) return { data: config, result: false, write: false }
} else {
const envMapping = PROVIDER_ENV_MAP[poolKey]
if (envMapping?.api_key_env) {
await saveEnvValueForProfile(profile, envMapping.api_key_env, '')
}
if (envMapping?.base_url_env) {
await saveEnvValueForProfile(profile, envMapping.base_url_env, '')
}
}
if (config.model?.provider === poolKey) {
const remaining = getCompatibleCustomProviders(config)
if (remaining.length > 0) {
const fallbackCp = remaining[0]
const fallbackKey = providerKeyForCustomName(fallbackCp.name)
if (typeof config.model !== 'object' || config.model === null) { config.model = {} }
config.model.default = fallbackCp.model || Object.keys(fallbackCp.models || {})[0] || ''
config.model.provider = fallbackKey
delete config.model.base_url
delete config.model.api_key
} else {
config.model = {}
}
}
return { data: config, result: true }
})
if (!removed) {
ctx.status = 404; ctx.body = { error: `Custom provider "${poolKey}" not found` }; return
}
if (!isCustom) {
const envMapping = PROVIDER_ENV_MAP[poolKey]
if (!envMapping) {
ctx.status = 404; ctx.body = { error: `Provider "${poolKey}" not found` }; return
}
}
await clearStoredAuthProvider(profile, poolKey)
// TODO: Test if provider works without gateway restart
// try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
ctx.body = { success: true }
} catch (err: any) {
ctx.status = 500; ctx.body = { error: err.message }
}
}