feat: support manual model input and sync provider catalogs
- Allow manual model name input when adding custom providers (NSelect tag mode) - Sync provider model catalogs with Hermes _PROVIDER_MODELS - Add new providers: kimi-coding-cn, moonshot, arcee - Fix provider key naming to match Hermes (kilo→kilocode, vercel→ai-gateway, etc.) - Ensure custom_providers from config.yaml always appear in available-models - Append configured default model to model list if not in catalog - Fix provider deletion with case-insensitive key matching - Add selectOrInput i18n key to all 8 locales Closes #24 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -214,7 +214,8 @@ function handleClose() {
|
|||||||
v-model:value="formData.model"
|
v-model:value="formData.model"
|
||||||
:options="modelOptions"
|
:options="modelOptions"
|
||||||
filterable
|
filterable
|
||||||
:placeholder="t('models.selectModel')"
|
tag
|
||||||
|
:placeholder="t('models.selectOrInput')"
|
||||||
style="flex: 1"
|
style="flex: 1"
|
||||||
/>
|
/>
|
||||||
<NButton
|
<NButton
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'API-Schlussel',
|
apiKey: 'API-Schlussel',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: 'Standardmodell',
|
defaultModel: 'Standardmodell',
|
||||||
|
selectOrInput: 'Modell auswählen oder eingeben...',
|
||||||
selectModel: 'Modell auswahlen...',
|
selectModel: 'Modell auswahlen...',
|
||||||
providerAdded: 'Anbieter hinzugefugt',
|
providerAdded: 'Anbieter hinzugefugt',
|
||||||
providerDeleted: 'Anbieter geloscht',
|
providerDeleted: 'Anbieter geloscht',
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'API Key',
|
apiKey: 'API Key',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: 'Default Model',
|
defaultModel: 'Default Model',
|
||||||
|
selectOrInput: 'Select or type a model name...',
|
||||||
selectModel: 'Select a model...',
|
selectModel: 'Select a model...',
|
||||||
providerAdded: 'Provider added',
|
providerAdded: 'Provider added',
|
||||||
providerDeleted: 'Provider deleted',
|
providerDeleted: 'Provider deleted',
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'Clave API',
|
apiKey: 'Clave API',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: 'Modelo predeterminado',
|
defaultModel: 'Modelo predeterminado',
|
||||||
|
selectOrInput: 'Seleccionar o ingresar un modelo...',
|
||||||
selectModel: 'Seleccionar un modelo...',
|
selectModel: 'Seleccionar un modelo...',
|
||||||
providerAdded: 'Proveedor anadido',
|
providerAdded: 'Proveedor anadido',
|
||||||
providerDeleted: 'Proveedor eliminado',
|
providerDeleted: 'Proveedor eliminado',
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'Cle API',
|
apiKey: 'Cle API',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: 'Modele par defaut',
|
defaultModel: 'Modele par defaut',
|
||||||
|
selectOrInput: 'Sélectionner ou saisir un modèle...',
|
||||||
selectModel: 'Selectionner un modele...',
|
selectModel: 'Selectionner un modele...',
|
||||||
providerAdded: 'Fournisseur ajoute',
|
providerAdded: 'Fournisseur ajoute',
|
||||||
providerDeleted: 'Fournisseur supprime',
|
providerDeleted: 'Fournisseur supprime',
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'API キー',
|
apiKey: 'API キー',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: 'デフォルトモデル',
|
defaultModel: 'デフォルトモデル',
|
||||||
|
selectOrInput: 'モデルを選択または入力...',
|
||||||
selectModel: 'モデルを選択...',
|
selectModel: 'モデルを選択...',
|
||||||
providerAdded: 'プロバイダーを追加しました',
|
providerAdded: 'プロバイダーを追加しました',
|
||||||
providerDeleted: 'プロバイダーを削除しました',
|
providerDeleted: 'プロバイダーを削除しました',
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'API Key',
|
apiKey: 'API Key',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: '기본 모델',
|
defaultModel: '기본 모델',
|
||||||
|
selectOrInput: '모델 선택 또는 직접 입력...',
|
||||||
selectModel: '모델 선택...',
|
selectModel: '모델 선택...',
|
||||||
providerAdded: 'Provider가 추가되었습니다',
|
providerAdded: 'Provider가 추가되었습니다',
|
||||||
providerDeleted: 'Provider가 삭제되었습니다',
|
providerDeleted: 'Provider가 삭제되었습니다',
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'Chave API',
|
apiKey: 'Chave API',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: 'Modelo padrao',
|
defaultModel: 'Modelo padrao',
|
||||||
|
selectOrInput: 'Selecionar ou digitar um modelo...',
|
||||||
selectModel: 'Selecionar um modelo...',
|
selectModel: 'Selecionar um modelo...',
|
||||||
providerAdded: 'Provedor adicionado',
|
providerAdded: 'Provedor adicionado',
|
||||||
providerDeleted: 'Provedor excluido',
|
providerDeleted: 'Provedor excluido',
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export default {
|
|||||||
apiKey: 'API Key',
|
apiKey: 'API Key',
|
||||||
apiKeyPlaceholder: 'sk-...',
|
apiKeyPlaceholder: 'sk-...',
|
||||||
defaultModel: '默认模型',
|
defaultModel: '默认模型',
|
||||||
|
selectOrInput: '选择或输入模型名称...',
|
||||||
selectModel: '选择模型...',
|
selectModel: '选择模型...',
|
||||||
providerAdded: 'Provider 已添加',
|
providerAdded: 'Provider 已添加',
|
||||||
providerDeleted: 'Provider 已删除',
|
providerDeleted: 'Provider 已删除',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
value: 'anthropic',
|
value: 'anthropic',
|
||||||
base_url: 'https://api.anthropic.com',
|
base_url: 'https://api.anthropic.com',
|
||||||
models: [
|
models: [
|
||||||
|
'claude-opus-4-7',
|
||||||
'claude-opus-4-6',
|
'claude-opus-4-6',
|
||||||
'claude-sonnet-4-6',
|
'claude-sonnet-4-6',
|
||||||
'claude-opus-4-5-20251101',
|
'claude-opus-4-5-20251101',
|
||||||
@@ -50,11 +51,11 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
label: 'Z.AI / GLM',
|
label: 'Z.AI / GLM',
|
||||||
value: 'zai',
|
value: 'zai',
|
||||||
base_url: 'https://api.z.ai/api/paas/v4',
|
base_url: 'https://api.z.ai/api/paas/v4',
|
||||||
models: ['glm-5', '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: 'Kimi for Coding',
|
label: 'Kimi for Coding',
|
||||||
value: 'kimi-for-coding',
|
value: 'kimi-coding',
|
||||||
base_url: 'https://api.kimi.com/coding/v1',
|
base_url: 'https://api.kimi.com/coding/v1',
|
||||||
models: [
|
models: [
|
||||||
'kimi-for-coding',
|
'kimi-for-coding',
|
||||||
@@ -65,22 +66,33 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
'kimi-k2-0905-preview',
|
'kimi-k2-0905-preview',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Kimi for Coding (CN)',
|
||||||
|
value: 'kimi-coding-cn',
|
||||||
|
base_url: 'https://api.kimi.com/coding/v1',
|
||||||
|
models: [
|
||||||
|
'kimi-k2.5',
|
||||||
|
'kimi-k2-thinking',
|
||||||
|
'kimi-k2-turbo-preview',
|
||||||
|
'kimi-k2-0905-preview',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Moonshot',
|
||||||
|
value: 'moonshot',
|
||||||
|
base_url: 'https://api.moonshot.cn/v1',
|
||||||
|
models: [
|
||||||
|
'kimi-k2.5',
|
||||||
|
'kimi-k2-thinking',
|
||||||
|
'kimi-k2-turbo-preview',
|
||||||
|
'kimi-k2-0905-preview',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'xAI',
|
label: 'xAI',
|
||||||
value: 'xai',
|
value: 'xai',
|
||||||
base_url: 'https://api.x.ai/v1',
|
base_url: 'https://api.x.ai/v1',
|
||||||
models: [
|
models: ['grok-4.20-reasoning', 'grok-4-1-fast-reasoning'],
|
||||||
'grok-4.20-0309-reasoning',
|
|
||||||
'grok-4.20-0309-non-reasoning',
|
|
||||||
'grok-4-1-fast-reasoning',
|
|
||||||
'grok-4-1-fast-non-reasoning',
|
|
||||||
'grok-4-fast-reasoning',
|
|
||||||
'grok-4-fast-non-reasoning',
|
|
||||||
'grok-4-0709',
|
|
||||||
'grok-code-fast-1',
|
|
||||||
'grok-3',
|
|
||||||
'grok-3-mini',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'MiniMax',
|
label: 'MiniMax',
|
||||||
@@ -131,7 +143,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Kilo Code',
|
label: 'Kilo Code',
|
||||||
value: 'kilo',
|
value: 'kilocode',
|
||||||
base_url: 'https://api.kilo.ai/api/gateway',
|
base_url: 'https://api.kilo.ai/api/gateway',
|
||||||
models: [
|
models: [
|
||||||
'anthropic/claude-opus-4.6',
|
'anthropic/claude-opus-4.6',
|
||||||
@@ -143,7 +155,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Vercel AI Gateway',
|
label: 'Vercel AI Gateway',
|
||||||
value: 'vercel',
|
value: 'ai-gateway',
|
||||||
base_url: 'https://ai-gateway.vercel.sh/v1',
|
base_url: 'https://ai-gateway.vercel.sh/v1',
|
||||||
models: [
|
models: [
|
||||||
'anthropic/claude-opus-4.6',
|
'anthropic/claude-opus-4.6',
|
||||||
@@ -162,32 +174,58 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'OpenCode Zen',
|
label: 'OpenCode Zen',
|
||||||
value: 'opencode',
|
value: 'opencode-zen',
|
||||||
base_url: 'https://opencode.ai/zen/v1',
|
base_url: 'https://opencode.ai/zen/v1',
|
||||||
models: [
|
models: [
|
||||||
'gpt-5.4-pro',
|
'gpt-5.4-pro',
|
||||||
'gpt-5.4',
|
'gpt-5.4',
|
||||||
'gpt-5.3-codex',
|
'gpt-5.3-codex',
|
||||||
|
'gpt-5.3-codex-spark',
|
||||||
'gpt-5.2',
|
'gpt-5.2',
|
||||||
|
'gpt-5.2-codex',
|
||||||
'gpt-5.1',
|
'gpt-5.1',
|
||||||
|
'gpt-5.1-codex',
|
||||||
|
'gpt-5.1-codex-max',
|
||||||
|
'gpt-5.1-codex-mini',
|
||||||
|
'gpt-5',
|
||||||
|
'gpt-5-codex',
|
||||||
|
'gpt-5-nano',
|
||||||
'claude-opus-4-6',
|
'claude-opus-4-6',
|
||||||
|
'claude-opus-4-5',
|
||||||
|
'claude-opus-4-1',
|
||||||
'claude-sonnet-4-6',
|
'claude-sonnet-4-6',
|
||||||
|
'claude-sonnet-4-5',
|
||||||
|
'claude-sonnet-4',
|
||||||
'claude-haiku-4-5',
|
'claude-haiku-4-5',
|
||||||
|
'claude-3-5-haiku',
|
||||||
'gemini-3.1-pro',
|
'gemini-3.1-pro',
|
||||||
'gemini-3-pro',
|
'gemini-3-pro',
|
||||||
'gemini-3-flash',
|
'gemini-3-flash',
|
||||||
'minimax-m2.7',
|
'minimax-m2.7',
|
||||||
'minimax-m2.5',
|
'minimax-m2.5',
|
||||||
|
'minimax-m2.5-free',
|
||||||
|
'minimax-m2.1',
|
||||||
'glm-5',
|
'glm-5',
|
||||||
'glm-4.7',
|
'glm-4.7',
|
||||||
|
'glm-4.6',
|
||||||
'kimi-k2.5',
|
'kimi-k2.5',
|
||||||
|
'kimi-k2-thinking',
|
||||||
|
'kimi-k2',
|
||||||
|
'qwen3-coder',
|
||||||
|
'big-pickle',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'OpenCode Go',
|
label: 'OpenCode Go',
|
||||||
value: 'opencode-go',
|
value: 'opencode-go',
|
||||||
base_url: 'https://opencode.ai/zen/go/v1',
|
base_url: 'https://opencode.ai/zen/go/v1',
|
||||||
models: ['glm-5', 'kimi-k2.5', 'mimo-v2-pro', 'mimo-v2-omni', 'minimax-m2.7', 'minimax-m2.5'],
|
models: ['glm-5.1', 'glm-5', 'kimi-k2.5', 'mimo-v2-pro', 'mimo-v2-omni', 'minimax-m2.7', 'minimax-m2.5'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Arcee AI',
|
||||||
|
value: 'arcee',
|
||||||
|
base_url: 'https://api.arcee.ai/v1',
|
||||||
|
models: ['trinity-large-thinking', 'trinity-large-preview', 'trinity-mini'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'OpenRouter',
|
label: 'OpenRouter',
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import * as hermesCli from '../../services/hermes/hermes-cli'
|
|||||||
const PROVIDER_ENV_MAP: Record<string, { api_key_env: string; base_url_env: string }> = {
|
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_BASE_URL' },
|
openrouter: { api_key_env: 'OPENROUTER_API_KEY', base_url_env: 'OPENROUTER_BASE_URL' },
|
||||||
zai: { api_key_env: 'ZAI_API_KEY', base_url_env: '' },
|
zai: { api_key_env: 'ZAI_API_KEY', base_url_env: '' },
|
||||||
'kimi-for-coding': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
|
'kimi-coding': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
|
||||||
|
'kimi-coding-cn': { api_key_env: 'KIMI_API_KEY', base_url_env: '' },
|
||||||
|
moonshot: { api_key_env: 'MOONSHOT_API_KEY', base_url_env: 'MOONSHOT_BASE_URL' },
|
||||||
minimax: { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_BASE_URL' },
|
minimax: { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_BASE_URL' },
|
||||||
'minimax-cn': { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_CN_BASE_URL' },
|
'minimax-cn': { api_key_env: 'MINIMAX_API_KEY', base_url_env: 'MINIMAX_CN_BASE_URL' },
|
||||||
deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
|
deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
|
||||||
@@ -19,11 +21,12 @@ const PROVIDER_ENV_MAP: Record<string, { api_key_env: string; base_url_env: stri
|
|||||||
xai: { api_key_env: 'XAI_API_KEY', base_url_env: 'XAI_BASE_URL' },
|
xai: { api_key_env: 'XAI_API_KEY', base_url_env: 'XAI_BASE_URL' },
|
||||||
xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: 'XIAOMI_BASE_URL' },
|
xiaomi: { api_key_env: 'XIAOMI_API_KEY', base_url_env: 'XIAOMI_BASE_URL' },
|
||||||
gemini: { api_key_env: 'GEMINI_API_KEY', base_url_env: '' },
|
gemini: { api_key_env: 'GEMINI_API_KEY', base_url_env: '' },
|
||||||
kilo: { api_key_env: 'KILO_API_KEY', base_url_env: 'KILOCODE_BASE_URL' },
|
kilocode: { api_key_env: 'KILO_API_KEY', base_url_env: 'KILOCODE_BASE_URL' },
|
||||||
vercel: { api_key_env: 'AI_GATEWAY_API_KEY', base_url_env: '' },
|
'ai-gateway': { api_key_env: 'AI_GATEWAY_API_KEY', base_url_env: '' },
|
||||||
opencode: { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_ZEN_BASE_URL' },
|
'opencode-zen': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_ZEN_BASE_URL' },
|
||||||
'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_GO_BASE_URL' },
|
'opencode-go': { api_key_env: 'OPENCODE_API_KEY', base_url_env: 'OPENCODE_GO_BASE_URL' },
|
||||||
huggingface: { api_key_env: 'HF_TOKEN', base_url_env: 'HF_BASE_URL' },
|
huggingface: { api_key_env: 'HF_TOKEN', base_url_env: 'HF_BASE_URL' },
|
||||||
|
arcee: { api_key_env: 'ARCEE_API_KEY', base_url_env: '' },
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveEnvValue(key: string, value: string): Promise<void> {
|
async function saveEnvValue(key: string, value: string): Promise<void> {
|
||||||
@@ -415,7 +418,6 @@ interface ModelGroup {
|
|||||||
// Build model list from user's actual config.yaml using js-yaml
|
// Build model list from user's actual config.yaml using js-yaml
|
||||||
function buildModelGroups(config: Record<string, any>): { default: string; groups: ModelGroup[] } {
|
function buildModelGroups(config: Record<string, any>): { default: string; groups: ModelGroup[] } {
|
||||||
let defaultModel = ''
|
let defaultModel = ''
|
||||||
let defaultProvider = ''
|
|
||||||
const groups: ModelGroup[] = []
|
const groups: ModelGroup[] = []
|
||||||
const allModelIds = new Set<string>()
|
const allModelIds = new Set<string>()
|
||||||
|
|
||||||
@@ -423,7 +425,6 @@ function buildModelGroups(config: Record<string, any>): { default: string; group
|
|||||||
const modelSection = config.model
|
const modelSection = config.model
|
||||||
if (typeof modelSection === 'object' && modelSection !== null) {
|
if (typeof modelSection === 'object' && modelSection !== null) {
|
||||||
defaultModel = String(modelSection.default || '').trim()
|
defaultModel = String(modelSection.default || '').trim()
|
||||||
defaultProvider = String(modelSection.provider || '').trim()
|
|
||||||
} else if (typeof modelSection === 'string') {
|
} else if (typeof modelSection === 'string') {
|
||||||
defaultModel = modelSection.trim()
|
defaultModel = modelSection.trim()
|
||||||
}
|
}
|
||||||
@@ -539,7 +540,51 @@ fsRoutes.get('/api/hermes/available-models', async (ctx) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: if no providers returned models, fall back to config.yaml parsing
|
// Merge custom_providers from config.yaml (ensures manually-input model names appear)
|
||||||
|
const customProviders = Array.isArray(config.custom_providers)
|
||||||
|
? config.custom_providers as Array<{ name: string; base_url: string; model: string }>
|
||||||
|
: []
|
||||||
|
for (const cp of customProviders) {
|
||||||
|
if (!cp.base_url || !cp.model) continue
|
||||||
|
const baseUrl = cp.base_url.replace(/\/+$/, '')
|
||||||
|
// Check if we already have a group for this base_url
|
||||||
|
const existing = dedupedGroups.find(g => g.base_url.replace(/\/+$/, '') === baseUrl)
|
||||||
|
if (existing) {
|
||||||
|
if (!existing.models.includes(cp.model)) {
|
||||||
|
existing.models.push(cp.model)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dedupedGroups.push({
|
||||||
|
provider: `custom:${cp.name.trim().toLowerCase().replace(/ /g, '-')}`,
|
||||||
|
label: cp.name,
|
||||||
|
base_url: baseUrl,
|
||||||
|
models: [cp.model],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure config's current default model appears in the model list
|
||||||
|
if (currentDefault) {
|
||||||
|
const currentProvider = typeof config.model === 'object' ? String(config.model.provider || '').trim() : ''
|
||||||
|
if (currentProvider) {
|
||||||
|
const targetGroup = dedupedGroups.find(g => g.provider === currentProvider)
|
||||||
|
if (targetGroup && !targetGroup.models.includes(currentDefault)) {
|
||||||
|
targetGroup.models.unshift(currentDefault)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No provider specified — add to the first group that matches via base_url
|
||||||
|
// or just prepend to all groups
|
||||||
|
let found = false
|
||||||
|
for (const g of dedupedGroups) {
|
||||||
|
if (!found && !g.models.includes(currentDefault)) {
|
||||||
|
g.models.unshift(currentDefault)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: if still no providers, fall back to config.yaml parsing
|
||||||
if (dedupedGroups.length === 0) {
|
if (dedupedGroups.length === 0) {
|
||||||
const fallback = buildModelGroups(config)
|
const fallback = buildModelGroups(config)
|
||||||
ctx.body = fallback
|
ctx.body = fallback
|
||||||
@@ -702,22 +747,29 @@ fsRoutes.delete('/api/hermes/config/providers/:poolKey', async (ctx) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Case-insensitive key lookup: normalize poolKey to match credential_pool
|
||||||
|
let resolvedKey = poolKey
|
||||||
if (!(poolKey in auth.credential_pool)) {
|
if (!(poolKey in auth.credential_pool)) {
|
||||||
|
const normalized = poolKey.toLowerCase()
|
||||||
|
const match = Object.keys(auth.credential_pool).find(k => k.toLowerCase() === normalized)
|
||||||
|
if (!match) {
|
||||||
ctx.status = 404
|
ctx.status = 404
|
||||||
ctx.body = { error: `Provider "${poolKey}" not found` }
|
ctx.body = { error: `Provider "${poolKey}" not found` }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resolvedKey = match
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this is the current active provider
|
// Check if this is the current active provider
|
||||||
const config = await readConfigYaml()
|
const config = await readConfigYaml()
|
||||||
const currentProvider = config.model?.provider
|
const currentProvider = config.model?.provider
|
||||||
const isCurrent = currentProvider === poolKey
|
const isCurrent = currentProvider === poolKey || currentProvider === resolvedKey
|
||||||
|
|
||||||
// Save base_url before deleting
|
// Save base_url before deleting
|
||||||
const deletedBaseUrl = auth.credential_pool[poolKey]?.[0]?.base_url
|
const deletedBaseUrl = auth.credential_pool[resolvedKey]?.[0]?.base_url
|
||||||
|
|
||||||
// 1. Delete from auth.json
|
// 1. Delete from auth.json
|
||||||
delete auth.credential_pool[poolKey]
|
delete auth.credential_pool[resolvedKey]
|
||||||
await saveAuthJson(auth)
|
await saveAuthJson(auth)
|
||||||
|
|
||||||
// 2. Remove matching entry from config.yaml custom_providers
|
// 2. Remove matching entry from config.yaml custom_providers
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
value: 'anthropic',
|
value: 'anthropic',
|
||||||
base_url: 'https://api.anthropic.com',
|
base_url: 'https://api.anthropic.com',
|
||||||
models: [
|
models: [
|
||||||
|
'claude-opus-4-7',
|
||||||
'claude-opus-4-6',
|
'claude-opus-4-6',
|
||||||
'claude-sonnet-4-6',
|
'claude-sonnet-4-6',
|
||||||
'claude-opus-4-5-20251101',
|
'claude-opus-4-5-20251101',
|
||||||
@@ -50,11 +51,11 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
label: 'Z.AI / GLM',
|
label: 'Z.AI / GLM',
|
||||||
value: 'zai',
|
value: 'zai',
|
||||||
base_url: 'https://api.z.ai/api/paas/v4',
|
base_url: 'https://api.z.ai/api/paas/v4',
|
||||||
models: ['glm-5', '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: 'Kimi for Coding',
|
label: 'Kimi for Coding',
|
||||||
value: 'kimi-for-coding',
|
value: 'kimi-coding',
|
||||||
base_url: 'https://api.kimi.com/coding/v1',
|
base_url: 'https://api.kimi.com/coding/v1',
|
||||||
models: [
|
models: [
|
||||||
'kimi-for-coding',
|
'kimi-for-coding',
|
||||||
@@ -65,22 +66,33 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
'kimi-k2-0905-preview',
|
'kimi-k2-0905-preview',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Kimi for Coding (CN)',
|
||||||
|
value: 'kimi-coding-cn',
|
||||||
|
base_url: 'https://api.kimi.com/coding/v1',
|
||||||
|
models: [
|
||||||
|
'kimi-k2.5',
|
||||||
|
'kimi-k2-thinking',
|
||||||
|
'kimi-k2-turbo-preview',
|
||||||
|
'kimi-k2-0905-preview',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Moonshot',
|
||||||
|
value: 'moonshot',
|
||||||
|
base_url: 'https://api.moonshot.cn/v1',
|
||||||
|
models: [
|
||||||
|
'kimi-k2.5',
|
||||||
|
'kimi-k2-thinking',
|
||||||
|
'kimi-k2-turbo-preview',
|
||||||
|
'kimi-k2-0905-preview',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'xAI',
|
label: 'xAI',
|
||||||
value: 'xai',
|
value: 'xai',
|
||||||
base_url: 'https://api.x.ai/v1',
|
base_url: 'https://api.x.ai/v1',
|
||||||
models: [
|
models: ['grok-4.20-reasoning', 'grok-4-1-fast-reasoning'],
|
||||||
'grok-4.20-0309-reasoning',
|
|
||||||
'grok-4.20-0309-non-reasoning',
|
|
||||||
'grok-4-1-fast-reasoning',
|
|
||||||
'grok-4-1-fast-non-reasoning',
|
|
||||||
'grok-4-fast-reasoning',
|
|
||||||
'grok-4-fast-non-reasoning',
|
|
||||||
'grok-4-0709',
|
|
||||||
'grok-code-fast-1',
|
|
||||||
'grok-3',
|
|
||||||
'grok-3-mini',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'MiniMax',
|
label: 'MiniMax',
|
||||||
@@ -131,7 +143,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Kilo Code',
|
label: 'Kilo Code',
|
||||||
value: 'kilo',
|
value: 'kilocode',
|
||||||
base_url: 'https://api.kilo.ai/api/gateway',
|
base_url: 'https://api.kilo.ai/api/gateway',
|
||||||
models: [
|
models: [
|
||||||
'anthropic/claude-opus-4.6',
|
'anthropic/claude-opus-4.6',
|
||||||
@@ -143,7 +155,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Vercel AI Gateway',
|
label: 'Vercel AI Gateway',
|
||||||
value: 'vercel',
|
value: 'ai-gateway',
|
||||||
base_url: 'https://ai-gateway.vercel.sh/v1',
|
base_url: 'https://ai-gateway.vercel.sh/v1',
|
||||||
models: [
|
models: [
|
||||||
'anthropic/claude-opus-4.6',
|
'anthropic/claude-opus-4.6',
|
||||||
@@ -162,32 +174,58 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'OpenCode Zen',
|
label: 'OpenCode Zen',
|
||||||
value: 'opencode',
|
value: 'opencode-zen',
|
||||||
base_url: 'https://opencode.ai/zen/v1',
|
base_url: 'https://opencode.ai/zen/v1',
|
||||||
models: [
|
models: [
|
||||||
'gpt-5.4-pro',
|
'gpt-5.4-pro',
|
||||||
'gpt-5.4',
|
'gpt-5.4',
|
||||||
'gpt-5.3-codex',
|
'gpt-5.3-codex',
|
||||||
|
'gpt-5.3-codex-spark',
|
||||||
'gpt-5.2',
|
'gpt-5.2',
|
||||||
|
'gpt-5.2-codex',
|
||||||
'gpt-5.1',
|
'gpt-5.1',
|
||||||
|
'gpt-5.1-codex',
|
||||||
|
'gpt-5.1-codex-max',
|
||||||
|
'gpt-5.1-codex-mini',
|
||||||
|
'gpt-5',
|
||||||
|
'gpt-5-codex',
|
||||||
|
'gpt-5-nano',
|
||||||
'claude-opus-4-6',
|
'claude-opus-4-6',
|
||||||
|
'claude-opus-4-5',
|
||||||
|
'claude-opus-4-1',
|
||||||
'claude-sonnet-4-6',
|
'claude-sonnet-4-6',
|
||||||
|
'claude-sonnet-4-5',
|
||||||
|
'claude-sonnet-4',
|
||||||
'claude-haiku-4-5',
|
'claude-haiku-4-5',
|
||||||
|
'claude-3-5-haiku',
|
||||||
'gemini-3.1-pro',
|
'gemini-3.1-pro',
|
||||||
'gemini-3-pro',
|
'gemini-3-pro',
|
||||||
'gemini-3-flash',
|
'gemini-3-flash',
|
||||||
'minimax-m2.7',
|
'minimax-m2.7',
|
||||||
'minimax-m2.5',
|
'minimax-m2.5',
|
||||||
|
'minimax-m2.5-free',
|
||||||
|
'minimax-m2.1',
|
||||||
'glm-5',
|
'glm-5',
|
||||||
'glm-4.7',
|
'glm-4.7',
|
||||||
|
'glm-4.6',
|
||||||
'kimi-k2.5',
|
'kimi-k2.5',
|
||||||
|
'kimi-k2-thinking',
|
||||||
|
'kimi-k2',
|
||||||
|
'qwen3-coder',
|
||||||
|
'big-pickle',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'OpenCode Go',
|
label: 'OpenCode Go',
|
||||||
value: 'opencode-go',
|
value: 'opencode-go',
|
||||||
base_url: 'https://opencode.ai/zen/go/v1',
|
base_url: 'https://opencode.ai/zen/go/v1',
|
||||||
models: ['glm-5', 'kimi-k2.5', 'mimo-v2-pro', 'mimo-v2-omni', 'minimax-m2.7', 'minimax-m2.5'],
|
models: ['glm-5.1', 'glm-5', 'kimi-k2.5', 'mimo-v2-pro', 'mimo-v2-omni', 'minimax-m2.7', 'minimax-m2.5'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Arcee AI',
|
||||||
|
value: 'arcee',
|
||||||
|
base_url: 'https://api.arcee.ai/v1',
|
||||||
|
models: ['trinity-large-thinking', 'trinity-large-preview', 'trinity-mini'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'OpenRouter',
|
label: 'OpenRouter',
|
||||||
|
|||||||
Reference in New Issue
Block a user