fix(models): fix builtin provider detection and model matching (#120)

- Add glm-coding-plan to PROVIDER_ENV_MAP for proper env mapping
- Rename GLMCodingPlan value from 'glm' to 'glm-coding-plan' (kebab-case)
- Match custom providers against PROVIDER_PRESETS to reuse builtin models
- Fix provider key matching in create/update (use entry.name consistently)
- Clear stale base_url/api_key from config on provider create
- Clear model config when all providers are removed
- Add gateway restart on provider remove

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-22 00:11:39 +08:00
committed by GitHub
parent c4bea63a5e
commit 83ad9642e2
4 changed files with 51 additions and 22 deletions
@@ -72,11 +72,15 @@ export async function getAvailable(ctx: any) {
if (!cp.base_url) return null
const providerKey = `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`
const baseUrl = cp.base_url.replace(/\/+$/, '')
let models = [cp.model]
const bareKey = cp.name.trim().toLowerCase().replace(/ /g, '-')
const builtinPreset = PROVIDER_PRESETS.find(p => p.value === bareKey)
let models = builtinPreset?.models?.length ? [...builtinPreset.models] : [cp.model]
if (cp.api_key) {
try { const fetched = await fetchProviderModels(baseUrl, cp.api_key); if (fetched.length > 0) models = fetched } catch { }
}
return { providerKey, label: cp.name, base_url: baseUrl, models, api_key: cp.api_key || '' }
const label = builtinPreset?.label || cp.name
const presetBaseUrl = builtinPreset?.base_url || ''
return { providerKey, label, base_url: presetBaseUrl || baseUrl, models, api_key: cp.api_key || '' }
}),
)
@@ -9,6 +9,7 @@ export async function create(ctx: any) {
const { name, base_url, api_key, model, providerKey } = ctx.request.body as {
name: string; base_url: string; api_key: string; model: string; providerKey?: string | null
}
console.log(name, base_url, api_key, model, providerKey)
if (!name || !base_url || !model) {
ctx.status = 400; ctx.body = { error: 'Missing name, base_url, or model' }; return
}
@@ -18,31 +19,48 @@ export async function create(ctx: any) {
try {
const poolKey = providerKey || `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`
const isBuiltin = poolKey in PROVIDER_ENV_MAP
const config = await readConfigYaml()
if (typeof config.model !== 'object' || config.model === null) { config.model = {} }
if (!isBuiltin) {
const config = await readConfigYaml()
if (!Array.isArray(config.custom_providers)) { config.custom_providers = [] }
const existing = (config.custom_providers as any[]).find(
(e: any) => `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}` === poolKey
(e: any) => `custom:${e.name}` === poolKey
)
if (existing) {
existing.base_url = base_url
existing.api_key = api_key
existing.model = model
} else {
config.custom_providers.push({ name, base_url, api_key, model })
config.custom_providers.push({ name: name.trim().toLowerCase().replace(/ /g, '-'), base_url, api_key, model })
}
config.model.default = model
config.model.provider = poolKey
} else {
console.log(PROVIDER_ENV_MAP[poolKey])
if (PROVIDER_ENV_MAP[poolKey].api_key_env) {
await saveEnvValue(PROVIDER_ENV_MAP[poolKey].api_key_env, api_key)
if (PROVIDER_ENV_MAP[poolKey].base_url_env) { await saveEnvValue(PROVIDER_ENV_MAP[poolKey].base_url_env, base_url) }
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 = base_url
existing.api_key = api_key
existing.model = model
} else {
config.custom_providers.push({ name: poolKey, base_url, api_key, model })
}
config.model.default = model
config.model.provider = `custom:${poolKey}`
}
await writeConfigYaml(config)
}
const envMapping = isBuiltin ? (PROVIDER_ENV_MAP[poolKey] || PROVIDER_ENV_MAP[providerKey || '']) : null
if (envMapping) {
await saveEnvValue(envMapping.api_key_env, api_key)
if (envMapping.base_url_env) { await saveEnvValue(envMapping.base_url_env, base_url) }
}
const config2 = await readConfigYaml()
if (typeof config2.model !== 'object' || config2.model === null) { config2.model = {} }
config2.model.default = model
config2.model.provider = poolKey
await writeConfigYaml(config2)
delete config.model.base_url
delete config.model.api_key
await writeConfigYaml(config)
try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
ctx.body = { success: true }
} catch (err: any) {
@@ -95,8 +113,8 @@ export async function remove(ctx: any) {
if (isCustom) {
const idx = Array.isArray(config.custom_providers)
? (config.custom_providers as any[]).findIndex((e: any) => {
return `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}` === poolKey
})
return `custom:${e.name.trim().toLowerCase().replace(/ /g, '-')}` === poolKey
})
: -1
if (idx === -1) {
ctx.status = 404; ctx.body = { error: `Custom provider "${poolKey}" not found` }; return
@@ -123,15 +141,21 @@ export async function remove(ctx: any) {
if (currentProvider === poolKey) {
const freshConfig = await readConfigYaml()
const remaining = Array.isArray(freshConfig.custom_providers) ? freshConfig.custom_providers as any[] : []
const fallbackCp = remaining[0]
if (fallbackCp) {
if (remaining.length > 0) {
const fallbackCp = remaining[0]
const fallbackKey = `custom:${fallbackCp.name.trim().toLowerCase().replace(/ /g, '-')}`
if (typeof freshConfig.model !== 'object' || freshConfig.model === null) { freshConfig.model = {} }
freshConfig.model.default = fallbackCp.model
freshConfig.model.provider = fallbackKey
delete freshConfig.model.base_url
delete freshConfig.model.api_key
await writeConfigYaml(freshConfig)
} else {
freshConfig.model = {}
await writeConfigYaml(freshConfig)
}
}
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 }
@@ -9,6 +9,7 @@ import { logger } from './logger'
// --- Provider env var mapping (from hermes providers.py HERMES_OVERLAYS + config.py) ---
export const PROVIDER_ENV_MAP: Record<string, { api_key_env: string; base_url_env: string }> = {
openrouter: { api_key_env: 'OPENROUTER_API_KEY', base_url_env: '' },
'glm-coding-plan': { api_key_env: '', base_url_env: '' },
zai: { api_key_env: 'GLM_API_KEY', base_url_env: '' },
'kimi-coding-cn': { api_key_env: 'KIMI_CN_API_KEY', base_url_env: '' },
moonshot: { api_key_env: 'MOONSHOT_API_KEY', base_url_env: '' },
+2 -2
View File
@@ -59,8 +59,8 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
models: ['glm-5.1', 'glm-5', 'glm-5v-turbo', 'glm-5-turbo', 'glm-4.7', 'glm-4.5', 'glm-4.5-flash'],
},
{
label: 'GLMCodingPlan',
value: 'glm',
label: 'GLM-Coding-Plan',
value: 'glm-coding-plan',
builtin: true,
base_url: 'https://api.z.ai/api/anthropic',
models: ['glm-5.1', 'glm-5', 'glm-5-turbo', 'glm-4.7', 'glm-4.5', 'glm-4.5-flash'],