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:
@@ -72,11 +72,15 @@ export async function getAvailable(ctx: any) {
|
|||||||
if (!cp.base_url) return null
|
if (!cp.base_url) return null
|
||||||
const providerKey = `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`
|
const providerKey = `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`
|
||||||
const baseUrl = cp.base_url.replace(/\/+$/, '')
|
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) {
|
if (cp.api_key) {
|
||||||
try { const fetched = await fetchProviderModels(baseUrl, cp.api_key); if (fetched.length > 0) models = fetched } catch { }
|
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 {
|
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
|
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) {
|
if (!name || !base_url || !model) {
|
||||||
ctx.status = 400; ctx.body = { error: 'Missing name, base_url, or model' }; return
|
ctx.status = 400; ctx.body = { error: 'Missing name, base_url, or model' }; return
|
||||||
}
|
}
|
||||||
@@ -18,31 +19,48 @@ export async function create(ctx: any) {
|
|||||||
try {
|
try {
|
||||||
const poolKey = providerKey || `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`
|
const poolKey = providerKey || `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`
|
||||||
const isBuiltin = poolKey in PROVIDER_ENV_MAP
|
const isBuiltin = poolKey in PROVIDER_ENV_MAP
|
||||||
|
const config = await readConfigYaml()
|
||||||
|
if (typeof config.model !== 'object' || config.model === null) { config.model = {} }
|
||||||
if (!isBuiltin) {
|
if (!isBuiltin) {
|
||||||
const config = await readConfigYaml()
|
|
||||||
if (!Array.isArray(config.custom_providers)) { config.custom_providers = [] }
|
if (!Array.isArray(config.custom_providers)) { config.custom_providers = [] }
|
||||||
const existing = (config.custom_providers as any[]).find(
|
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) {
|
if (existing) {
|
||||||
existing.base_url = base_url
|
existing.base_url = base_url
|
||||||
existing.api_key = api_key
|
existing.api_key = api_key
|
||||||
existing.model = model
|
existing.model = model
|
||||||
} else {
|
} 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
|
delete config.model.base_url
|
||||||
if (envMapping) {
|
delete config.model.api_key
|
||||||
await saveEnvValue(envMapping.api_key_env, api_key)
|
await writeConfigYaml(config)
|
||||||
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)
|
|
||||||
try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
|
try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
|
||||||
ctx.body = { success: true }
|
ctx.body = { success: true }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -95,8 +113,8 @@ export async function remove(ctx: any) {
|
|||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
const idx = Array.isArray(config.custom_providers)
|
const idx = Array.isArray(config.custom_providers)
|
||||||
? (config.custom_providers as any[]).findIndex((e: any) => {
|
? (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
|
: -1
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
ctx.status = 404; ctx.body = { error: `Custom provider "${poolKey}" not found` }; return
|
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) {
|
if (currentProvider === poolKey) {
|
||||||
const freshConfig = await readConfigYaml()
|
const freshConfig = await readConfigYaml()
|
||||||
const remaining = Array.isArray(freshConfig.custom_providers) ? freshConfig.custom_providers as any[] : []
|
const remaining = Array.isArray(freshConfig.custom_providers) ? freshConfig.custom_providers as any[] : []
|
||||||
const fallbackCp = remaining[0]
|
if (remaining.length > 0) {
|
||||||
if (fallbackCp) {
|
const fallbackCp = remaining[0]
|
||||||
const fallbackKey = `custom:${fallbackCp.name.trim().toLowerCase().replace(/ /g, '-')}`
|
const fallbackKey = `custom:${fallbackCp.name.trim().toLowerCase().replace(/ /g, '-')}`
|
||||||
if (typeof freshConfig.model !== 'object' || freshConfig.model === null) { freshConfig.model = {} }
|
if (typeof freshConfig.model !== 'object' || freshConfig.model === null) { freshConfig.model = {} }
|
||||||
freshConfig.model.default = fallbackCp.model
|
freshConfig.model.default = fallbackCp.model
|
||||||
freshConfig.model.provider = fallbackKey
|
freshConfig.model.provider = fallbackKey
|
||||||
|
delete freshConfig.model.base_url
|
||||||
|
delete freshConfig.model.api_key
|
||||||
|
await writeConfigYaml(freshConfig)
|
||||||
|
} else {
|
||||||
|
freshConfig.model = {}
|
||||||
await writeConfigYaml(freshConfig)
|
await writeConfigYaml(freshConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try { await hermesCli.restartGateway() } catch (e: any) { logger.error(e, 'Gateway restart failed') }
|
||||||
ctx.body = { success: true }
|
ctx.body = { success: true }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ctx.status = 500; ctx.body = { error: err.message }
|
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) ---
|
// --- 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 }> = {
|
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: '' },
|
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: '' },
|
zai: { api_key_env: 'GLM_API_KEY', base_url_env: '' },
|
||||||
'kimi-coding-cn': { api_key_env: 'KIMI_CN_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: '' },
|
moonshot: { api_key_env: 'MOONSHOT_API_KEY', base_url_env: '' },
|
||||||
|
|||||||
@@ -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'],
|
models: ['glm-5.1', 'glm-5', 'glm-5v-turbo', 'glm-5-turbo', 'glm-4.7', 'glm-4.5', 'glm-4.5-flash'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'GLMCodingPlan',
|
label: 'GLM-Coding-Plan',
|
||||||
value: 'glm',
|
value: 'glm-coding-plan',
|
||||||
builtin: true,
|
builtin: true,
|
||||||
base_url: 'https://api.z.ai/api/anthropic',
|
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'],
|
models: ['glm-5.1', 'glm-5', 'glm-5-turbo', 'glm-4.7', 'glm-4.5', 'glm-4.5-flash'],
|
||||||
|
|||||||
Reference in New Issue
Block a user