Persist custom Hermes models (#913)
This commit is contained in:
@@ -14,6 +14,7 @@ const PROVIDER_MODEL_CATALOG = buildProviderModelMap()
|
||||
type ModelMeta = { preview?: boolean; disabled?: boolean; alias?: string }
|
||||
type AvailableGroup = { provider: string; label: string; base_url: string; models: string[]; api_key: string; builtin?: boolean; model_meta?: Record<string, ModelMeta>; available_models?: string[] }
|
||||
type ModelVisibility = Record<string, ModelVisibilityRule>
|
||||
type CustomModels = Record<string, string[]>
|
||||
|
||||
const RESERVED_ALIAS_KEYS = new Set(['__proto__', 'prototype', 'constructor'])
|
||||
|
||||
@@ -67,6 +68,28 @@ function uniqueStrings(values: unknown): string[] {
|
||||
return Array.from(new Set(values.map(v => String(v || '').trim()).filter(Boolean)))
|
||||
}
|
||||
|
||||
function normalizeCustomModels(input: unknown): CustomModels {
|
||||
if (!input || typeof input !== 'object' || Array.isArray(input)) return {}
|
||||
const out: CustomModels = {}
|
||||
for (const [provider, rawModels] of Object.entries(input as Record<string, unknown>)) {
|
||||
const providerKey = String(provider || '').trim()
|
||||
if (!providerKey) continue
|
||||
const models = uniqueStrings(rawModels)
|
||||
if (models.length > 0) out[providerKey] = models
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function applyCustomModels(groups: AvailableGroup[], customModels: CustomModels): AvailableGroup[] {
|
||||
return groups.map(group => {
|
||||
const extra = customModels[group.provider] || []
|
||||
if (!extra.length) return group
|
||||
const models = [...new Set([...group.models, ...extra])]
|
||||
const availableModels = [...new Set([...(group.available_models || group.models), ...extra])]
|
||||
return { ...group, models, available_models: availableModels }
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeModelVisibility(input: unknown): ModelVisibility {
|
||||
if (!input || typeof input !== 'object' || Array.isArray(input)) return {}
|
||||
const out: ModelVisibility = {}
|
||||
@@ -349,8 +372,9 @@ async function buildAvailableForProfile(
|
||||
g.models = Array.from(new Set(g.models))
|
||||
g.available_models = Array.from(new Set(g.available_models || g.models))
|
||||
}
|
||||
const groupsWithCustomModels = applyCustomModels(groups, normalizeCustomModels(appConfig.customModels))
|
||||
|
||||
return { profile, default: currentDefault, default_provider: currentDefaultProvider, groups }
|
||||
return { profile, default: currentDefault, default_provider: currentDefaultProvider, groups: groupsWithCustomModels }
|
||||
}
|
||||
|
||||
export async function getAvailable(ctx: any) {
|
||||
@@ -362,6 +386,7 @@ export async function getAvailable(ctx: any) {
|
||||
const appConfig = await readAppConfig()
|
||||
const modelAliases = normalizeAliases(appConfig.modelAliases)
|
||||
const modelVisibility = normalizeModelVisibility(appConfig.modelVisibility)
|
||||
const customModels = normalizeCustomModels(appConfig.customModels)
|
||||
const fetchCache: ProviderFetchCache = new Map()
|
||||
const profileResults = await Promise.all(
|
||||
listProfileNamesFromDisk().map(profile => buildAvailableForProfile(profile, fetchCache, appConfig)),
|
||||
@@ -392,6 +417,7 @@ export async function getAvailable(ctx: any) {
|
||||
allProviders: applyModelAliases(allProvidersBase, modelAliases),
|
||||
model_aliases: modelAliases,
|
||||
model_visibility: modelVisibility,
|
||||
custom_models: customModels,
|
||||
profiles: profileResults.map(result => ({
|
||||
profile: result.profile,
|
||||
default: result.default,
|
||||
@@ -405,6 +431,7 @@ export async function getAvailable(ctx: any) {
|
||||
const appConfigForProfile = await readAppConfig()
|
||||
const modelAliasesForProfile = normalizeAliases(appConfigForProfile.modelAliases)
|
||||
const modelVisibilityForProfile = normalizeModelVisibility(appConfigForProfile.modelVisibility)
|
||||
const customModelsForProfile = normalizeCustomModels(appConfigForProfile.customModels)
|
||||
const profileResult = await buildAvailableForProfile(requestedProfile, new Map(), appConfigForProfile)
|
||||
const profileGroupsWithAliases = applyModelAliases(profileResult.groups, modelAliasesForProfile)
|
||||
const visibleProfileGroups = applyModelVisibility(profileGroupsWithAliases, modelVisibilityForProfile)
|
||||
@@ -422,6 +449,7 @@ export async function getAvailable(ctx: any) {
|
||||
})), modelAliasesForProfile),
|
||||
model_aliases: modelAliasesForProfile,
|
||||
model_visibility: modelVisibilityForProfile,
|
||||
custom_models: customModelsForProfile,
|
||||
profiles: [{
|
||||
profile: profileResult.profile,
|
||||
default: profileResult.default,
|
||||
@@ -511,6 +539,7 @@ export async function getAvailable(ctx: any) {
|
||||
const copilotEnabled = appConfig.copilotEnabled === true
|
||||
const modelAliases = normalizeAliases(appConfig.modelAliases)
|
||||
const modelVisibility = normalizeModelVisibility(appConfig.modelVisibility)
|
||||
const customModels = normalizeCustomModels(appConfig.customModels)
|
||||
|
||||
// 兼容老用户:上一版本会"自动 fallback discovery"出 Copilot;升级后这些用户的
|
||||
// config.yaml 可能仍把 model.default 指向某个 copilot 模型。若此时 copilot 已不
|
||||
@@ -598,7 +627,7 @@ export async function getAvailable(ctx: any) {
|
||||
}
|
||||
|
||||
for (const g of groups) { g.models = Array.from(new Set(g.models)) }
|
||||
const groupsWithAliases = applyModelAliases(groups, modelAliases)
|
||||
const groupsWithAliases = applyModelAliases(applyCustomModels(groups, customModels), modelAliases)
|
||||
const visibleGroups = applyModelVisibility(groupsWithAliases, modelVisibility)
|
||||
const visibleDefault = resolveVisibleDefault(currentDefault, currentDefaultProvider, visibleGroups)
|
||||
|
||||
@@ -638,6 +667,7 @@ export async function getAvailable(ctx: any) {
|
||||
allProviders,
|
||||
model_aliases: modelAliases,
|
||||
model_visibility: modelVisibility,
|
||||
custom_models: customModels,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -649,6 +679,7 @@ export async function getAvailable(ctx: any) {
|
||||
allProviders,
|
||||
model_aliases: modelAliases,
|
||||
model_visibility: modelVisibility,
|
||||
custom_models: customModels,
|
||||
}
|
||||
} catch (err: any) {
|
||||
ctx.status = 500
|
||||
@@ -656,6 +687,54 @@ export async function getAvailable(ctx: any) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function addCustomModel(ctx: any) {
|
||||
const { provider, model } = (ctx.request.body || {}) as { provider?: string; model?: string }
|
||||
const providerKey = String(provider || '').trim()
|
||||
const modelId = String(model || '').trim()
|
||||
if (!providerKey || !modelId) {
|
||||
ctx.status = 400
|
||||
ctx.body = { error: 'Missing provider or model' }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const appConfig = await readAppConfig()
|
||||
const customModels = normalizeCustomModels(appConfig.customModels)
|
||||
customModels[providerKey] = Array.from(new Set([...(customModels[providerKey] || []), modelId]))
|
||||
const saved = await writeAppConfig({ customModels })
|
||||
ctx.body = { success: true, custom_models: normalizeCustomModels(saved.customModels) }
|
||||
} catch (err: any) {
|
||||
ctx.status = 500
|
||||
ctx.body = { error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeCustomModel(ctx: any) {
|
||||
const body = (ctx.request.body || {}) as { provider?: string; model?: string }
|
||||
const provider = body.provider ?? ctx.query?.provider
|
||||
const model = body.model ?? ctx.query?.model
|
||||
const providerKey = String(provider || '').trim()
|
||||
const modelId = String(model || '').trim()
|
||||
if (!providerKey || !modelId) {
|
||||
ctx.status = 400
|
||||
ctx.body = { error: 'Missing provider or model' }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const appConfig = await readAppConfig()
|
||||
const customModels = normalizeCustomModels(appConfig.customModels)
|
||||
const remaining = (customModels[providerKey] || []).filter(item => item !== modelId)
|
||||
if (remaining.length > 0) customModels[providerKey] = remaining
|
||||
else delete customModels[providerKey]
|
||||
const saved = await writeAppConfig({ customModels })
|
||||
ctx.body = { success: true, custom_models: normalizeCustomModels(saved.customModels) }
|
||||
} catch (err: any) {
|
||||
ctx.status = 500
|
||||
ctx.body = { error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchProviderModelList(ctx: any) {
|
||||
try {
|
||||
const body = ctx.request.body as { base_url?: string; api_key?: string; freeOnly?: boolean }
|
||||
|
||||
Reference in New Issue
Block a user