feat(hermes): bind per-provider models to top-level model: on switch

Hermes custom_providers entries now carry an ordered models array
(id / context_length / max_tokens) plus suggestedDefaults. The backend
serializes the array to the YAML dict shape Hermes expects on write and
inverts it on read, preserving insertion order via the preserve_order
feature on serde_json.

When a user switches providers, switch_normal calls apply_switch_defaults
so the top-level model.default / model.provider follow the selected
provider's first model. Previously switching a Hermes provider only
shuffled custom_providers[] and left Hermes pointing at whatever
model.provider was set before.

Seven existing Hermes presets now ship with a curated models list so
switching lands on a working default without a detour through the
Model panel.
This commit is contained in:
Jason
2026-04-18 22:08:23 +08:00
parent 63aa310576
commit f935bac633
3 changed files with 560 additions and 4 deletions
+193
View File
@@ -5,6 +5,48 @@
import type { ProviderCategory } from "../types";
import type { PresetTheme, TemplateValueConfig } from "./claudeProviderPresets";
/**
* A model entry under a Hermes custom_provider.
*
* Serialized to YAML as a dict keyed by `id`:
*
* ```yaml
* models:
* anthropic/claude-opus-4-7:
* context_length: 200000
* max_tokens: 32000
* ```
*/
export interface HermesModel {
/** Model ID — becomes the YAML key and the value written to top-level model.default. */
id: string;
/** Optional display label (UI only, not serialized to YAML). */
name?: string;
/** Override the auto-detected context window. */
context_length?: number;
/** Response-length cap. */
max_tokens?: number;
}
/**
* Top-level `model:` defaults suggested by a preset.
*
* Written to the YAML `model:` section when the user switches to this provider.
* Per-model `context_length` / `max_tokens` live on the individual `HermesModel`
* entries and flow through `custom_providers[].models`, not this object.
*/
export interface HermesSuggestedDefaults {
model: {
/** Model ID for `model.default`. Typically equals `models[0].id`. */
default: string;
/** Value for `model.provider`. Omit to use the custom_provider name. */
provider?: string;
};
}
/** Hermes custom_provider protocol mode (optional; auto-detected when omitted). */
export type HermesApiMode = "chat_completions" | "anthropic_messages";
export interface HermesProviderPreset {
name: string;
nameKey?: string;
@@ -20,12 +62,17 @@ export interface HermesProviderPreset {
icon?: string;
iconColor?: string;
isCustomTemplate?: boolean;
/** Optional top-level `model:` defaults written on switch. */
suggestedDefaults?: HermesSuggestedDefaults;
}
export interface HermesProviderSettingsConfig {
name: string;
base_url?: string;
api_key?: string;
api_mode?: HermesApiMode;
/** UI-side ordered list; serialized to YAML as a dict keyed by id. */
models?: HermesModel[];
[key: string]: unknown;
}
@@ -39,10 +86,38 @@ export const hermesProviderPresets: HermesProviderPreset[] = [
name: "openrouter",
base_url: "https://openrouter.ai/api/v1",
api_key: "",
api_mode: "chat_completions",
models: [
{
id: "anthropic/claude-opus-4-7",
name: "Claude Opus 4.7",
context_length: 200000,
max_tokens: 32000,
},
{
id: "anthropic/claude-sonnet-4-6",
name: "Claude Sonnet 4.6",
context_length: 200000,
max_tokens: 32000,
},
{
id: "openai/gpt-5",
name: "GPT-5",
context_length: 400000,
},
{
id: "google/gemini-3-pro",
name: "Gemini 3 Pro",
context_length: 1000000,
},
],
},
category: "aggregator",
icon: "openrouter",
iconColor: "#6366F1",
suggestedDefaults: {
model: { default: "anthropic/claude-opus-4-7", provider: "openrouter" },
},
},
{
name: "Anthropic",
@@ -53,11 +128,35 @@ export const hermesProviderPresets: HermesProviderPreset[] = [
name: "anthropic",
base_url: "https://api.anthropic.com",
api_key: "",
api_mode: "anthropic_messages",
models: [
{
id: "claude-opus-4-7",
name: "Claude Opus 4.7",
context_length: 200000,
max_tokens: 32000,
},
{
id: "claude-sonnet-4-6",
name: "Claude Sonnet 4.6",
context_length: 200000,
max_tokens: 32000,
},
{
id: "claude-haiku-4-5-20251001",
name: "Claude Haiku 4.5",
context_length: 200000,
max_tokens: 16000,
},
],
},
isOfficial: true,
category: "official",
icon: "anthropic",
iconColor: "#D4915D",
suggestedDefaults: {
model: { default: "claude-opus-4-7", provider: "anthropic" },
},
},
{
name: "OpenAI",
@@ -68,11 +167,32 @@ export const hermesProviderPresets: HermesProviderPreset[] = [
name: "openai",
base_url: "https://api.openai.com/v1",
api_key: "",
api_mode: "chat_completions",
models: [
{
id: "gpt-5",
name: "GPT-5",
context_length: 400000,
},
{
id: "gpt-5-codex",
name: "GPT-5 Codex",
context_length: 400000,
},
{
id: "o3-mini",
name: "o3-mini",
context_length: 200000,
},
],
},
isOfficial: true,
category: "official",
icon: "openai",
iconColor: "#000000",
suggestedDefaults: {
model: { default: "gpt-5", provider: "openai" },
},
},
{
name: "Google AI",
@@ -82,11 +202,26 @@ export const hermesProviderPresets: HermesProviderPreset[] = [
settingsConfig: {
name: "google",
api_key: "",
models: [
{
id: "gemini-3-pro",
name: "Gemini 3 Pro",
context_length: 1000000,
},
{
id: "gemini-3-flash",
name: "Gemini 3 Flash",
context_length: 1000000,
},
],
},
isOfficial: true,
category: "official",
icon: "gemini",
iconColor: "#4285F4",
suggestedDefaults: {
model: { default: "gemini-3-pro", provider: "google" },
},
},
{
name: "DeepSeek",
@@ -97,10 +232,28 @@ export const hermesProviderPresets: HermesProviderPreset[] = [
name: "deepseek",
base_url: "https://api.deepseek.com",
api_key: "",
api_mode: "chat_completions",
models: [
{
id: "deepseek-chat",
name: "DeepSeek V3.2",
context_length: 64000,
max_tokens: 8000,
},
{
id: "deepseek-reasoner",
name: "DeepSeek R1",
context_length: 64000,
max_tokens: 8000,
},
],
},
category: "cn_official",
icon: "deepseek",
iconColor: "#4D6BFE",
suggestedDefaults: {
model: { default: "deepseek-chat", provider: "deepseek" },
},
},
{
name: "Together AI",
@@ -111,10 +264,34 @@ export const hermesProviderPresets: HermesProviderPreset[] = [
name: "together",
base_url: "https://api.together.xyz/v1",
api_key: "",
api_mode: "chat_completions",
models: [
{
id: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
name: "Qwen3 Coder 480B",
context_length: 262144,
},
{
id: "deepseek-ai/DeepSeek-V3.2",
name: "DeepSeek V3.2",
context_length: 64000,
},
{
id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
name: "Llama 4 Maverick",
context_length: 131072,
},
],
},
category: "aggregator",
icon: "together",
iconColor: "#0F6FFF",
suggestedDefaults: {
model: {
default: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
provider: "together",
},
},
},
{
name: "Nous Research",
@@ -123,10 +300,26 @@ export const hermesProviderPresets: HermesProviderPreset[] = [
name: "nous",
base_url: "https://inference.nous.hermes.dev/v1",
api_key: "",
api_mode: "chat_completions",
models: [
{
id: "Hermes-4-405B",
name: "Hermes 4 405B",
context_length: 131072,
},
{
id: "Hermes-4-70B",
name: "Hermes 4 70B",
context_length: 131072,
},
],
},
isOfficial: true,
category: "official",
icon: "hermes",
iconColor: "#7C3AED",
suggestedDefaults: {
model: { default: "Hermes-4-405B", provider: "nous" },
},
},
];