1222 lines
47 KiB
Python
1222 lines
47 KiB
Python
"""Static provider/model catalog tables: curated per-provider model lists, canonical provider registry, display groups, alias maps.
|
|
|
|
Split out of ``hermes_cli.models``; every moved name is re-imported there, so
|
|
``hermes_cli.models.<name>`` keeps resolving (and monkeypatching) as before.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import NamedTuple
|
|
|
|
|
|
# Fallback OpenRouter snapshot used when the live catalog is unavailable.
|
|
# (model_id, display description shown in menus)
|
|
OPENROUTER_MODELS: list[tuple[str, str]] = [
|
|
# Anthropic
|
|
("anthropic/claude-fable-5.1", ""),
|
|
("anthropic/claude-fable-5", ""),
|
|
("anthropic/claude-opus-5", ""),
|
|
("anthropic/claude-opus-5-fast", "2x price, higher output speed"),
|
|
("anthropic/claude-opus-4.8", ""),
|
|
("anthropic/claude-opus-4.8-fast", "2x price, higher output speed"),
|
|
("anthropic/claude-sonnet-5", ""),
|
|
("anthropic/claude-haiku-4.5", ""),
|
|
# OpenAI
|
|
("openai/gpt-5.6-sol", ""),
|
|
("openai/gpt-5.6-sol-pro", ""),
|
|
("openai/gpt-5.6-terra", ""),
|
|
("openai/gpt-5.6-terra-pro", ""),
|
|
("openai/gpt-5.6-luna", ""),
|
|
("openai/gpt-5.6-luna-pro", ""),
|
|
("openai/gpt-5.5", ""),
|
|
("openai/gpt-5.5-pro", ""),
|
|
("openai/gpt-5.4-mini", ""),
|
|
# Google
|
|
("google/gemini-3.1-pro-preview", ""),
|
|
("google/gemini-3.8-flash", ""),
|
|
("google/gemini-3.7-flash", ""),
|
|
# xAI
|
|
("x-ai/grok-4.6", ""),
|
|
# DeepSeek
|
|
("deepseek/deepseek-v4-pro", ""),
|
|
("deepseek/deepseek-v4-pro-0813", "dated snapshot of v4-pro"),
|
|
("deepseek/deepseek-v4-flash", ""),
|
|
("deepseek/deepseek-v4-flash-0731", "dated snapshot of v4-flash"),
|
|
# Qwen
|
|
("qwen/qwen3.8-max", ""),
|
|
("qwen/qwen3.8-flash", ""),
|
|
# MoonshotAI
|
|
("moonshotai/kimi-k3", "recommended"),
|
|
# MiniMax
|
|
("minimax/minimax-m3", ""),
|
|
# Z-AI
|
|
("z-ai/glm-5.3", ""),
|
|
("z-ai/glm-5.3-flash", ""),
|
|
("z-ai/glm-5.2", "default"),
|
|
# Xiaomi
|
|
("xiaomi/mimo-v2.5-pro", ""),
|
|
# Tencent
|
|
("tencent/hy4-preview", ""),
|
|
("tencent/hy3", ""),
|
|
# StepFun
|
|
("stepfun/step-3.7-flash", ""),
|
|
# NVIDIA
|
|
("nvidia/nemotron-3-super-120b-a12b", ""),
|
|
# Meta
|
|
("meta/muse-spark-1.2", ""),
|
|
# Sakana
|
|
("sakana/fugu-ultra", ""),
|
|
# OpenRouter routers
|
|
("openrouter/pareto-code", "auto-routes to cheapest coder meeting openrouter.min_coding_score"),
|
|
# Free tier
|
|
("thinkingmachines/inkling:free", "free"),
|
|
("thinkingmachines/inkling-small:free", "free"),
|
|
("minimax/minimax-m3:free", "free"),
|
|
("z-ai/glm-5.2:free", "free"),
|
|
("poolside/laguna-s-2.1:free", "free"),
|
|
("poolside/laguna-xs-2.1:free", "free"),
|
|
("nvidia/nemotron-3-super-120b-a12b:free", "free"),
|
|
("nvidia/nemotron-3-ultra-550b-a55b:free", "free"),
|
|
("nvidia/nemotron-3.5-lightning:free", "free"),
|
|
]
|
|
|
|
|
|
# Fallback Vercel AI Gateway snapshot used when the live catalog is unavailable.
|
|
# OSS / open-weight models prioritized first, then closed-source by family.
|
|
# Slugs match Vercel's actual /v1/models catalog (e.g. alibaba/ for Qwen,
|
|
# zai/ and xai/ without hyphens).
|
|
VERCEL_AI_GATEWAY_MODELS: list[tuple[str, str]] = [
|
|
("moonshotai/kimi-k2.6", "recommended"),
|
|
("alibaba/qwen3.6-plus", ""),
|
|
("zai/glm-5.1", ""),
|
|
("minimax/minimax-m2.7", ""),
|
|
("anthropic/claude-sonnet-4.6", ""),
|
|
("anthropic/claude-opus-4.7", ""),
|
|
("anthropic/claude-opus-4.6", ""),
|
|
("anthropic/claude-haiku-4.5", ""),
|
|
("openai/gpt-5.4", ""),
|
|
("openai/gpt-5.4-mini", ""),
|
|
("openai/gpt-5.3-codex", ""),
|
|
("google/gemini-3.1-pro-preview", ""),
|
|
("google/gemini-3-flash", ""),
|
|
("google/gemini-3.1-flash-lite-preview", ""),
|
|
("xai/grok-4.20-reasoning", ""),
|
|
]
|
|
|
|
|
|
def _codex_curated_models() -> list[str]:
|
|
"""Derive the openai-codex curated list from codex_models.py.
|
|
|
|
Single source of truth: DEFAULT_CODEX_MODELS + forward-compat synthesis. This keeps the gateway
|
|
/model picker in sync with the CLI `hermes model` flow without maintaining a separate static
|
|
list.
|
|
"""
|
|
from hermes_cli.codex_models import DEFAULT_CODEX_MODELS, _finalize_codex_models
|
|
return _finalize_codex_models(list(DEFAULT_CODEX_MODELS))
|
|
|
|
|
|
# Static fallback for xAI when the models.dev disk cache is empty (fresh
|
|
# install, offline first run, etc.). Mirrors the xAI-direct model IDs from
|
|
# $HERMES_HOME/models_dev_cache.json as of 2026-04-28. Whenever xAI renames
|
|
# or retires a model, the disk cache picks it up on the next refresh and the
|
|
# fallback here only matters until that refresh lands.
|
|
#
|
|
# Models retired by xAI on May 15, 2026 are excluded — see
|
|
# https://docs.x.ai/developers/migration/may-15-retirement
|
|
# (grok-4, grok-4-0709, grok-4-fast{,-reasoning,-non-reasoning},
|
|
# grok-4-1-fast{,-reasoning,-non-reasoning}, grok-code-fast-1 → grok-4.3).
|
|
_XAI_STATIC_FALLBACK: list[str] = [
|
|
"grok-4.6",
|
|
"grok-build-0.1",
|
|
"grok-4.5",
|
|
"grok-4.3",
|
|
"grok-4.20-0309-reasoning",
|
|
"grok-4.20-0309-non-reasoning",
|
|
"grok-4.20-multi-agent-0309",
|
|
]
|
|
|
|
|
|
# Callable via xAI OAuth but omitted from models.dev and /v1/models listings.
|
|
_XAI_CURATED_EXTRAS: list[str] = [
|
|
"grok-4.6", # GA 2026-08 — kept until the models.dev disk cache refreshes
|
|
"grok-4.5", # GA 2026-07 — kept until the models.dev disk cache refreshes
|
|
"grok-composer-2.5-fast",
|
|
]
|
|
|
|
|
|
_XAI_TOP_MODEL = "grok-4.6"
|
|
|
|
|
|
def _xai_promote_top(ids: list[str]) -> list[str]:
|
|
"""Pin the headline xAI model to the top of the curated list."""
|
|
if _XAI_TOP_MODEL in ids:
|
|
return [_XAI_TOP_MODEL] + [m for m in ids if m != _XAI_TOP_MODEL]
|
|
return ids
|
|
|
|
|
|
def _xai_merge_curated_extras(ids: list[str]) -> list[str]:
|
|
"""Append Hermes-curated xAI models that are missing from models.dev."""
|
|
out = list(ids)
|
|
for extra in _XAI_CURATED_EXTRAS:
|
|
if extra in out:
|
|
continue
|
|
# Keep the headline model pinned; slot extras immediately after it.
|
|
insert_at = 1 if out and out[0] == _XAI_TOP_MODEL else len(out)
|
|
out.insert(insert_at, extra)
|
|
return out
|
|
|
|
|
|
def _xai_finalize_catalog(ids: list[str]) -> list[str]:
|
|
return _xai_promote_top(_xai_merge_curated_extras(ids))
|
|
|
|
|
|
def _xai_curated_models() -> list[str]:
|
|
"""Offline curated floor for xAI / xAI OAuth pickers.
|
|
|
|
Reads $HERMES_HOME/models_dev_cache.json directly (no network). Falls back to
|
|
``_XAI_STATIC_FALLBACK`` when the cache is empty or unreadable.
|
|
"""
|
|
try:
|
|
from agent.models_dev import _load_disk_cache
|
|
data = _load_disk_cache()
|
|
xai = data.get("xai") if isinstance(data, dict) else None
|
|
models = xai.get("models") if isinstance(xai, dict) else None
|
|
if isinstance(models, dict) and models:
|
|
ids = [mid for mid in models.keys() if isinstance(mid, str)]
|
|
if ids:
|
|
return _xai_finalize_catalog(sorted(ids))
|
|
except Exception:
|
|
# Any failure (missing file, malformed JSON, import error)
|
|
# falls through to the static list.
|
|
pass
|
|
return _xai_finalize_catalog(list(_XAI_STATIC_FALLBACK))
|
|
|
|
|
|
_PROVIDER_MODELS: dict[str, list[str]] = {
|
|
"moa": ["default"],
|
|
"nous": [
|
|
# Anthropic
|
|
"anthropic/claude-fable-5.1",
|
|
"anthropic/claude-fable-5",
|
|
"anthropic/claude-opus-5",
|
|
"anthropic/claude-opus-4.8",
|
|
"anthropic/claude-sonnet-5",
|
|
"anthropic/claude-haiku-4.5",
|
|
# OpenAI
|
|
"openai/gpt-5.6-sol",
|
|
"openai/gpt-5.6-sol-pro",
|
|
"openai/gpt-5.6-terra",
|
|
"openai/gpt-5.6-terra-pro",
|
|
"openai/gpt-5.6-luna",
|
|
"openai/gpt-5.6-luna-pro",
|
|
"openai/gpt-5.5",
|
|
"openai/gpt-5.5-pro",
|
|
"openai/gpt-5.4-mini",
|
|
# Google
|
|
"google/gemini-3.1-pro-preview",
|
|
"google/gemini-3.8-flash",
|
|
"google/gemini-3.7-flash",
|
|
# xAI
|
|
"x-ai/grok-4.6",
|
|
# DeepSeek
|
|
"deepseek/deepseek-v4-pro",
|
|
"deepseek/deepseek-v4-pro-0813",
|
|
"deepseek/deepseek-v4-flash",
|
|
"deepseek/deepseek-v4-flash-0731",
|
|
# Qwen
|
|
"qwen/qwen3.8-max",
|
|
"qwen/qwen3.8-flash",
|
|
# MoonshotAI
|
|
"moonshotai/kimi-k3",
|
|
# MiniMax
|
|
"minimax/minimax-m3",
|
|
# Z-AI
|
|
"z-ai/glm-5.3",
|
|
"z-ai/glm-5.3-flash",
|
|
"z-ai/glm-5.2",
|
|
# Xiaomi
|
|
"xiaomi/mimo-v2.5-pro",
|
|
# Tencent
|
|
"tencent/hy4-preview",
|
|
"tencent/hy3",
|
|
# StepFun
|
|
"stepfun/step-3.7-flash",
|
|
# NVIDIA
|
|
"nvidia/nemotron-3-super-120b-a12b",
|
|
# Sakana
|
|
"sakana/fugu-ultra",
|
|
],
|
|
# Native OpenAI Chat Completions (api.openai.com). Used by /model counts and
|
|
# provider_model_ids fallback when /v1/models is unavailable.
|
|
"openai": [
|
|
"gpt-5.4",
|
|
"gpt-5.4-mini",
|
|
"gpt-5-mini",
|
|
"gpt-5.3-codex",
|
|
"gpt-5.2-codex",
|
|
"gpt-4.1",
|
|
"gpt-4o",
|
|
"gpt-4o-mini",
|
|
],
|
|
"openai-api": [
|
|
"gpt-5.6-sol",
|
|
"gpt-5.6-sol-pro",
|
|
"gpt-5.6-terra",
|
|
"gpt-5.6-terra-pro",
|
|
"gpt-5.6-luna",
|
|
"gpt-5.6-luna-pro",
|
|
"gpt-5.5",
|
|
"gpt-5.5-pro",
|
|
"gpt-5.4",
|
|
"gpt-5.4-mini",
|
|
"gpt-5.4-nano",
|
|
"gpt-5-mini",
|
|
"gpt-5.3-codex",
|
|
"gpt-4.1",
|
|
"gpt-4o",
|
|
"gpt-4o-mini",
|
|
],
|
|
"openai-codex": _codex_curated_models(),
|
|
"xai-oauth": _xai_curated_models(),
|
|
"copilot-acp": [
|
|
"copilot-acp",
|
|
],
|
|
"copilot": [
|
|
"gpt-5.4",
|
|
"gpt-5.4-mini",
|
|
"gpt-5-mini",
|
|
"gpt-5.3-codex",
|
|
"gpt-5.2-codex",
|
|
"gpt-4.1",
|
|
"gpt-4o",
|
|
"gpt-4o-mini",
|
|
"claude-sonnet-4.6",
|
|
"claude-sonnet-5",
|
|
"claude-sonnet-4",
|
|
"claude-sonnet-4.5",
|
|
"claude-haiku-4.5",
|
|
"gemini-3.1-pro-preview",
|
|
"gemini-3-pro-preview",
|
|
"gemini-3-flash-preview",
|
|
"gemini-2.5-pro",
|
|
],
|
|
"gemini": [
|
|
"gemini-3.1-pro-preview",
|
|
"gemini-3-pro-preview",
|
|
"gemini-3.6-flash",
|
|
"gemini-3.1-flash-lite-preview",
|
|
],
|
|
"zai": [
|
|
"glm-5.3",
|
|
"glm-5.3-flash",
|
|
"glm-5.2",
|
|
"glm-5.1",
|
|
"glm-5",
|
|
"glm-5v-turbo",
|
|
"glm-5-turbo",
|
|
"glm-4.7",
|
|
"glm-4.5",
|
|
"glm-4.5-flash",
|
|
],
|
|
"xai": _xai_curated_models(),
|
|
"nvidia": [
|
|
# NVIDIA flagship reasoning models
|
|
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
"nvidia/nemotron-3-super-120b-a12b",
|
|
"nvidia/nemotron-3.5-lightning-30b-a3b",
|
|
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
|
|
# Third-party agentic models hosted on build.nvidia.com
|
|
# (map to OpenRouter defaults — users get familiar picks on NIM)
|
|
"z-ai/glm-5.3",
|
|
"z-ai/glm-5.2",
|
|
"moonshotai/kimi-k2.6",
|
|
"minimaxai/minimax-m3",
|
|
],
|
|
"kimi-coding": [
|
|
"kimi-k3",
|
|
"kimi-k2.7-code",
|
|
"kimi-k2.6",
|
|
"kimi-k2.5",
|
|
"kimi-for-coding",
|
|
"kimi-for-coding-highspeed",
|
|
"kimi-k2-thinking",
|
|
"kimi-k2-thinking-turbo",
|
|
"kimi-k2-turbo-preview",
|
|
"kimi-k2-0905-preview",
|
|
],
|
|
"kimi-coding-cn": [
|
|
"kimi-k3",
|
|
"kimi-k2.7-code",
|
|
"kimi-k2.7-code-highspeed",
|
|
"kimi-k2.6",
|
|
"kimi-k2.5",
|
|
"kimi-k2-thinking",
|
|
"kimi-k2-turbo-preview",
|
|
"kimi-k2-0905-preview",
|
|
],
|
|
"stepfun": [
|
|
"step-3.5-flash",
|
|
"step-3.5-flash-2603",
|
|
],
|
|
"moonshot": [
|
|
"kimi-k3",
|
|
"kimi-k2.6",
|
|
"kimi-k2.5",
|
|
"kimi-k2-thinking",
|
|
"kimi-k2-turbo-preview",
|
|
"kimi-k2-0905-preview",
|
|
],
|
|
"minimax": [
|
|
"MiniMax-M3",
|
|
"MiniMax-M2.7",
|
|
"MiniMax-M2.5",
|
|
"MiniMax-M2.1",
|
|
"MiniMax-M2",
|
|
],
|
|
"minimax-oauth": [
|
|
"MiniMax-M3",
|
|
"MiniMax-M2.7",
|
|
"MiniMax-M2.7-highspeed",
|
|
],
|
|
"minimax-cn": [
|
|
"MiniMax-M3",
|
|
"MiniMax-M2.7",
|
|
"MiniMax-M2.5",
|
|
"MiniMax-M2.1",
|
|
"MiniMax-M2",
|
|
],
|
|
"anthropic": [
|
|
"claude-fable-5",
|
|
"claude-sonnet-5",
|
|
"claude-opus-4-8",
|
|
"claude-opus-4-7",
|
|
"claude-opus-4-6",
|
|
"claude-sonnet-4-6",
|
|
"claude-opus-4-5-20251101",
|
|
"claude-sonnet-4-5-20250929",
|
|
"claude-opus-4-20250514",
|
|
"claude-sonnet-4-20250514",
|
|
"claude-haiku-4-5-20251001",
|
|
],
|
|
"deepseek": [
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
],
|
|
"xiaomi": [
|
|
"mimo-v2.5-pro",
|
|
"mimo-v2.5",
|
|
"mimo-v2-pro",
|
|
"mimo-v2-omni",
|
|
"mimo-v2-flash",
|
|
],
|
|
"tencent-tokenhub": [
|
|
"hy4-preview",
|
|
"hy3",
|
|
"hy3-preview",
|
|
],
|
|
"tencent-tokenplan": [
|
|
"hy4-preview",
|
|
"hy3",
|
|
"hy3-preview",
|
|
],
|
|
"arcee": [
|
|
"trinity-large-thinking",
|
|
"trinity-large-preview",
|
|
"trinity-mini",
|
|
],
|
|
"gmi": [
|
|
"zai-org/GLM-5.1-FP8",
|
|
"deepseek-ai/DeepSeek-V3.2",
|
|
"moonshotai/Kimi-K2.5",
|
|
"google/gemini-3.1-flash-lite-preview",
|
|
"anthropic/claude-sonnet-5",
|
|
"anthropic/claude-sonnet-4.6",
|
|
"openai/gpt-5.4",
|
|
],
|
|
# Synced against https://opencode.ai/docs/zen/ + live GET /zen/v1/models
|
|
# (2026-08-20). Zen/Go are _LIVE_FIRST_PICKER_PROVIDERS, so this list is a
|
|
# discovery floor — live entries lead in the picker and stale curated
|
|
# names never pollute the top.
|
|
"opencode-zen": [
|
|
"x-preview-f-free", # "Ox Alpha" stealth model — free, 1M ctx, ZDR
|
|
"kimi-k3",
|
|
"kimi-k2.5",
|
|
"kimi-k2.6",
|
|
"gpt-5.6-sol",
|
|
"gpt-5.6-terra",
|
|
"gpt-5.6-luna",
|
|
"gpt-5.5",
|
|
"gpt-5.5-pro",
|
|
"gpt-5.4-pro",
|
|
"gpt-5.4",
|
|
"gpt-5.4-mini",
|
|
"gpt-5.4-nano",
|
|
"gpt-5.3-codex",
|
|
"gpt-5.3-codex-spark",
|
|
"gpt-5.2",
|
|
"gpt-5.2-codex",
|
|
"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-fable-5",
|
|
"claude-opus-5",
|
|
"claude-sonnet-5",
|
|
"claude-opus-4-8",
|
|
"claude-opus-4-7",
|
|
"claude-opus-4-6",
|
|
"claude-opus-4-5",
|
|
"claude-sonnet-4-6",
|
|
"claude-sonnet-4-5",
|
|
"claude-sonnet-4",
|
|
"claude-haiku-4-5",
|
|
"gemini-3.7-flash",
|
|
"gemini-3.6-flash",
|
|
"gemini-3.5-flash",
|
|
"gemini-3.5-flash-lite",
|
|
"gemini-3.1-pro",
|
|
"gemini-3-flash",
|
|
"grok-4.6",
|
|
"grok-4.5",
|
|
"grok-build-0.1",
|
|
"muse-spark-1.2",
|
|
"minimax-m3",
|
|
"minimax-m2.7",
|
|
"minimax-m2.5",
|
|
"glm-5.3",
|
|
"glm-5.3-flash",
|
|
"glm-5.2",
|
|
"glm-5.1",
|
|
"glm-5",
|
|
"kimi-k2.7-code",
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
"deepseek-v4-flash-free",
|
|
"qwen3.6-plus",
|
|
"qwen3.5-plus",
|
|
"big-pickle",
|
|
"mimo-v2.5-free",
|
|
"hy3-free",
|
|
"laguna-s-2.1-free",
|
|
"nemotron-3-ultra-free",
|
|
"nemotron-3.5-lightning-free",
|
|
"muse-spark-1.2-contributor-free",
|
|
],
|
|
# OpenCode free tier — keyless (no OpenCode account needed). This is the
|
|
# OFFLINE FLOOR only: provider_model_ids("opencode-free") revalidates live
|
|
# against GET /zen/v1/models (keyless) and filters to the anonymous free
|
|
# tier, so a relay-delisted model stops appearing in the picker and a
|
|
# newly-live one becomes selectable without a release. This floor keeps the
|
|
# picker populated when the relay is unreachable. Note: this floor may lag
|
|
# the live relay — that is intentional; the live revalidation is the
|
|
# source of truth when reachable. Known-delisted models are REMOVED from
|
|
# the floor (x-preview-f-free delisted 2026-08-26 — offline fallback must
|
|
# not offer a model that 401s). deepseek-v4-flash-free and mimo-v2.5-free
|
|
# are back on the live list.
|
|
"opencode-free": [
|
|
"deepseek-v4-flash-free",
|
|
"hy3-free",
|
|
"mimo-v2.5-free",
|
|
"laguna-s-2.1-free",
|
|
"nemotron-3-ultra-free",
|
|
"nemotron-3.5-lightning-free",
|
|
"muse-spark-1.2-contributor-free",
|
|
],
|
|
# Synced against https://opencode.ai/docs/go/ + live GET /zen/go/v1/models
|
|
# (2026-08-20).
|
|
"opencode-go": [
|
|
"kimi-k3",
|
|
"kimi-k2.7-code",
|
|
"kimi-k2.6",
|
|
"kimi-k2.5",
|
|
"gpt-5.6-luna",
|
|
"grok-4.5",
|
|
"glm-5.3",
|
|
"glm-5.3-flash",
|
|
"glm-5.2",
|
|
"glm-5.1",
|
|
"glm-5",
|
|
"mimo-v2.5-pro",
|
|
"mimo-v2.5",
|
|
"mimo-v2-pro",
|
|
"mimo-v2-omni",
|
|
"minimax-m3",
|
|
"minimax-m2.7",
|
|
"minimax-m2.5",
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
"qwen3.8-max",
|
|
"qwen3.7-max",
|
|
"qwen3.7-plus",
|
|
"qwen3.6-plus",
|
|
"qwen3.5-plus",
|
|
"hy3",
|
|
"hy3-preview",
|
|
"muse-spark-1.2-contributor",
|
|
# Go-subscription twin of the Zen keyless Ox Alpha (live go/v1
|
|
# catalog 2026-08-21; NOT keyless — Go relay requires a Go key).
|
|
"ox-alpha-free",
|
|
],
|
|
"kilocode": [
|
|
"anthropic/claude-opus-4.6",
|
|
"anthropic/claude-sonnet-4.6",
|
|
"openai/gpt-5.4",
|
|
"google/gemini-3-pro-preview",
|
|
"google/gemini-3-flash-preview",
|
|
],
|
|
# Alibaba DashScope Coding platform (coding-intl) — default endpoint.
|
|
# Supports Qwen models + third-party providers (GLM, Kimi, MiniMax).
|
|
# Users with classic DashScope keys should override DASHSCOPE_BASE_URL
|
|
# to https://dashscope-intl.aliyuncs.com/compatible-mode/v1 (OpenAI-compat)
|
|
# or https://dashscope-intl.aliyuncs.com/apps/anthropic (Anthropic-compat).
|
|
"alibaba": [
|
|
# Qwen 千问系列 (DashScope / Qwen Cloud)
|
|
"qwen3.8-max",
|
|
"qwen3.7-max",
|
|
"qwen3.7-plus",
|
|
"qwen3.6-plus",
|
|
"qwen3.6-flash",
|
|
"kimi-k2.5",
|
|
"qwen3.5-plus",
|
|
"qwen3-coder-plus",
|
|
"qwen3-coder-next",
|
|
# Third-party models available on coding-intl / DashScope
|
|
"glm-5.2",
|
|
"glm-5",
|
|
"glm-4.7",
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash-0731",
|
|
"MiniMax-M2.5",
|
|
],
|
|
# Alibaba DashScope (China) — same platform as alibaba, domestic endpoint
|
|
# (dashscope.aliyuncs.com); same catalog as the international tier.
|
|
"alibaba-cn": [
|
|
"qwen3.8-max",
|
|
"qwen3.7-max",
|
|
"qwen3.7-plus",
|
|
"qwen3.6-plus",
|
|
"qwen3.6-flash",
|
|
"kimi-k2.5",
|
|
"qwen3.5-plus",
|
|
"qwen3-coder-plus",
|
|
"qwen3-coder-next",
|
|
"glm-5.2",
|
|
"glm-5",
|
|
"glm-4.7",
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash-0731",
|
|
"MiniMax-M2.5",
|
|
],
|
|
# Alibaba Coding Plan — same platform as alibaba (DashScope coding-intl),
|
|
# separate provider ID with its own base_url_env_var.
|
|
"alibaba-coding-plan": [
|
|
"qwen3.7-plus",
|
|
"qwen3.6-plus",
|
|
"qwen3.5-plus",
|
|
"qwen3-max-2026-01-23",
|
|
"qwen3-coder-plus",
|
|
"qwen3-coder-next",
|
|
"kimi-k2.5",
|
|
"glm-5",
|
|
"glm-4.7",
|
|
"MiniMax-M2.5",
|
|
],
|
|
# Alibaba Coding Plan (China) — domestic coding endpoint
|
|
# (coding.dashscope.aliyuncs.com); same catalog as the international tier.
|
|
"alibaba-coding-plan-cn": [
|
|
"qwen3.7-plus",
|
|
"qwen3.6-plus",
|
|
"qwen3.5-plus",
|
|
"qwen3-max-2026-01-23",
|
|
"qwen3-coder-plus",
|
|
"qwen3-coder-next",
|
|
"kimi-k2.5",
|
|
"glm-5",
|
|
"glm-4.7",
|
|
"MiniMax-M2.5",
|
|
],
|
|
# Alibaba Token Plan (Personal Edition) — dedicated token-plan endpoint
|
|
# (token-plan.ap-southeast-1.maas.aliyuncs.com), key tier `sk-sp-...`.
|
|
# Catalog verified against a live Token Plan subscription (2026-08-03).
|
|
"alibaba-token-plan": [
|
|
"qwen3.8-max-preview",
|
|
"qwen3.7-max",
|
|
"qwen3.7-plus",
|
|
"qwen3.6-plus",
|
|
"qwen3.6-flash",
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
"deepseek-v3.2",
|
|
"kimi-k2.7-code",
|
|
"kimi-k2.6",
|
|
"kimi-k2.5",
|
|
"glm-5.2",
|
|
"glm-5.1",
|
|
"glm-5",
|
|
],
|
|
# Alibaba Token Plan (China) — domestic token-plan endpoint
|
|
# (token-plan.cn-beijing.maas.aliyuncs.com); same catalog as intl.
|
|
"alibaba-token-plan-cn": [
|
|
"qwen3.8-max-preview",
|
|
"qwen3.7-max",
|
|
"qwen3.7-plus",
|
|
"qwen3.6-plus",
|
|
"qwen3.6-flash",
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
"deepseek-v3.2",
|
|
"kimi-k2.7-code",
|
|
"kimi-k2.6",
|
|
"kimi-k2.5",
|
|
"glm-5.2",
|
|
"glm-5.1",
|
|
"glm-5",
|
|
],
|
|
# Curated HF model list — only agentic models that map to OpenRouter defaults.
|
|
"huggingface": [
|
|
"moonshotai/Kimi-K2.5",
|
|
"Qwen/Qwen3.5-397B-A17B",
|
|
"Qwen/Qwen3.5-35B-A3B",
|
|
"deepseek-ai/DeepSeek-V3.2",
|
|
"MiniMaxAI/MiniMax-M2.5",
|
|
"zai-org/GLM-5",
|
|
"XiaomiMiMo/MiMo-V2-Flash",
|
|
"moonshotai/Kimi-K2-Thinking",
|
|
"moonshotai/Kimi-K2.6",
|
|
],
|
|
# AWS Bedrock — static fallback list used when dynamic discovery is
|
|
# unavailable (no boto3, no credentials, or API error). The agent
|
|
# prefers live discovery via ListFoundationModels + ListInferenceProfiles.
|
|
# Use inference profile IDs (us.*) since most models require them.
|
|
"bedrock": [
|
|
"us.anthropic.claude-sonnet-5",
|
|
"us.anthropic.claude-sonnet-4-6",
|
|
"us.anthropic.claude-opus-4-6-v1",
|
|
"us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
"us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
"openai.gpt-5.5",
|
|
"openai.gpt-5.6-sol",
|
|
"openai.gpt-5.6-terra",
|
|
"openai.gpt-5.6-luna",
|
|
"us.amazon.nova-pro-v1:0",
|
|
"us.amazon.nova-lite-v1:0",
|
|
"us.amazon.nova-micro-v1:0",
|
|
"deepseek.v3.2",
|
|
"us.meta.llama4-maverick-17b-instruct-v1:0",
|
|
"us.meta.llama4-scout-17b-instruct-v1:0",
|
|
],
|
|
# Azure Foundry: user-provided endpoint and model.
|
|
# Empty list because models depend on the endpoint configuration.
|
|
"azure-foundry": [],
|
|
# Google Vertex AI — static curated list. Vertex's OpenAI-compatible
|
|
# endpoint has no /models listing route, so without this entry the
|
|
# /model picker only ever shows the currently-configured model.
|
|
# Model IDs use the "google/" publisher prefix Vertex's openapi
|
|
# endpoint expects (see hermes_cli/model_setup_flows.py).
|
|
# Entries validated live against a GCP project (global region,
|
|
# HTTP 200) as of 2026-07-21 (PR #68767).
|
|
"vertex": [
|
|
"google/gemini-3.1-pro-preview",
|
|
"google/gemini-3-pro-preview",
|
|
"google/gemini-3.6-flash",
|
|
"google/gemini-3.5-flash",
|
|
"google/gemini-3.5-flash-lite",
|
|
"google/gemini-3-flash-preview",
|
|
"google/gemini-3.1-flash-lite-preview",
|
|
"google/gemini-3.1-flash-lite",
|
|
],
|
|
"novita": [
|
|
"moonshotai/kimi-k2.5",
|
|
"minimax/minimax-m2.7",
|
|
"zai-org/glm-5",
|
|
"deepseek/deepseek-v3-0324",
|
|
"deepseek/deepseek-r1-0528",
|
|
"qwen/qwen3-235b-a22b-fp8",
|
|
],
|
|
}
|
|
|
|
|
|
# Vercel AI Gateway: derive the bare-model-id catalog from the curated
|
|
# ``VERCEL_AI_GATEWAY_MODELS`` snapshot so both the picker (tuples with descriptions)
|
|
# and the static fallback catalog (bare ids) stay in sync from a single
|
|
# source of truth.
|
|
_PROVIDER_MODELS["ai-gateway"] = [mid for mid, _ in VERCEL_AI_GATEWAY_MODELS]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Canonical provider list — single source of truth for provider identity.
|
|
# Every code path that lists, displays, or iterates providers derives from
|
|
# this list: hermes model, /model, list_authenticated_providers.
|
|
#
|
|
# Fields:
|
|
# slug — internal provider ID (used in config.yaml, --provider flag)
|
|
# label — short display name
|
|
# tui_desc — longer description for the `hermes model` interactive picker
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class ProviderEntry(NamedTuple):
|
|
slug: str
|
|
label: str
|
|
tui_desc: str # detailed description for `hermes model` TUI
|
|
|
|
|
|
CANONICAL_PROVIDERS: list[ProviderEntry] = [
|
|
ProviderEntry("nous", "Nous Portal", "Nous Portal (Everything your agent needs, 300+ models with bundled tool use)"),
|
|
ProviderEntry("fireworks", "Fireworks AI", "Fireworks AI (OpenAI-compatible direct model API)"),
|
|
ProviderEntry("openrouter", "OpenRouter", "OpenRouter (Pay-per-use API aggregator)"),
|
|
ProviderEntry("moa", "Mixture of Agents", "Mixture of Agents (named presets; aggregator acts after reference models)"),
|
|
ProviderEntry("novita", "NovitaAI", "NovitaAI (Cloud: Model API, Agent Sandbox, GPU Cloud)"),
|
|
ProviderEntry("lmstudio", "LM Studio", "LM Studio (Local desktop app with built-in model server)"),
|
|
ProviderEntry("anthropic", "Anthropic", "Anthropic (Claude models via API key or Claude Code)"),
|
|
ProviderEntry("openai-codex", "ChatGPT or Codex Subscription", "ChatGPT or Codex Subscription (Sign in with your ChatGPT account, uses Codex models)"),
|
|
ProviderEntry("openai-api", "OpenAI API", "OpenAI API (api.openai.com, API key)"),
|
|
ProviderEntry("alibaba", "Qwen Cloud", "Qwen Cloud / DashScope (Qwen + multi-provider)"),
|
|
ProviderEntry("xai-oauth", "xAI Grok OAuth (SuperGrok / Premium+)", "xAI Grok OAuth (SuperGrok / Premium+ subscription)"),
|
|
ProviderEntry("xiaomi", "Xiaomi MiMo", "Xiaomi MiMo (MiMo-V2.5 and V2 models: pro, omni, flash)"),
|
|
ProviderEntry("tencent-tokenhub", "Tencent TokenHub", "Tencent TokenHub (Hy4 preview via tokenhub.tencentmaas.com)"),
|
|
ProviderEntry("tencent-tokenplan", "Tencent TokenPlan", "Tencent TokenPlan (Hy4 preview via api.lkeap.cloud.tencent.com, Anthropic Messages)"),
|
|
ProviderEntry("nvidia", "NVIDIA NIM", "NVIDIA NIM (Nemotron models via build.nvidia.com or local NIM)"),
|
|
ProviderEntry("copilot", "GitHub Copilot", "GitHub Copilot (Uses GITHUB_TOKEN or gh auth token)"),
|
|
ProviderEntry("copilot-acp", "GitHub Copilot ACP", "GitHub Copilot ACP (Spawns copilot --acp --stdio)"),
|
|
ProviderEntry("huggingface", "Hugging Face", "Hugging Face Inference Providers"),
|
|
ProviderEntry("gemini", "Google AI Studio", "Google AI Studio (Native Gemini API)"),
|
|
ProviderEntry("vertex", "Google Vertex AI", "Google Vertex AI (Gemini via GCP; OAuth2 service account or ADC, GCP billing/quotas)"),
|
|
ProviderEntry("deepseek", "DeepSeek", "DeepSeek (V3, R1, coder, direct API)"),
|
|
ProviderEntry("xai", "xAI", "xAI Grok (Direct API)"),
|
|
ProviderEntry("zai", "Z.AI / GLM", "Z.AI / GLM (Zhipu direct API)"),
|
|
ProviderEntry("kimi-coding", "Kimi / Kimi Coding Plan", "Kimi Coding Plan (api.kimi.com & Moonshot API)"),
|
|
ProviderEntry("kimi-coding-cn", "Kimi / Moonshot (China)", "Kimi / Moonshot China (Domestic direct API)"),
|
|
ProviderEntry("stepfun", "StepFun Step Plan", "StepFun Step Plan (Agent / coding models via Step Plan API)"),
|
|
ProviderEntry("minimax", "MiniMax", "MiniMax (Global direct API)"),
|
|
ProviderEntry("minimax-oauth", "MiniMax (OAuth)", "MiniMax via OAuth browser login (Coding Plan, minimax.io)"),
|
|
ProviderEntry("minimax-cn", "MiniMax (China)", "MiniMax China (Domestic direct API)"),
|
|
ProviderEntry("ollama-cloud", "Ollama Cloud", "Ollama Cloud (Cloud-hosted open models, ollama.com)"),
|
|
ProviderEntry("arcee", "Arcee AI", "Arcee AI (Trinity models, direct API)"),
|
|
ProviderEntry("gmi", "GMI Cloud", "GMI Cloud (Multi-model direct API)"),
|
|
ProviderEntry("kilocode", "Kilo Code", "Kilo Code (Kilo Gateway API)"),
|
|
ProviderEntry("opencode-zen", "OpenCode Zen", "OpenCode Zen (Curated models, pay-as-you-go)"),
|
|
ProviderEntry("opencode-go", "OpenCode Go", "OpenCode Go (Open models subscription)"),
|
|
ProviderEntry("bedrock", "AWS Bedrock", "AWS Bedrock (Claude, Nova, Llama, DeepSeek; IAM or API key)"),
|
|
ProviderEntry("azure-foundry", "Azure Foundry", "Azure Foundry (OpenAI-style or Anthropic-style endpoint, your Azure AI deployment)"),
|
|
ProviderEntry("ai-gateway", "Vercel AI Gateway", "Vercel AI Gateway (Multi-model aggregator)"),
|
|
ProviderEntry("qwen-oauth", "Qwen OAuth (Portal)", "Qwen OAuth (Reuses local Qwen CLI login)"),
|
|
]
|
|
|
|
|
|
# Auto-extend CANONICAL_PROVIDERS with any provider registered in providers/
|
|
# that is not already in the list above. Adding plugins/model-providers/<name>/
|
|
# is sufficient to expose a new provider in the model picker, /model, and all
|
|
# downstream consumers — no edits to this file needed.
|
|
_canonical_slugs = {p.slug for p in CANONICAL_PROVIDERS}
|
|
|
|
|
|
try:
|
|
from providers import list_providers as _list_providers_for_canonical
|
|
for _pp in _list_providers_for_canonical():
|
|
if _pp.name in _canonical_slugs:
|
|
continue
|
|
if _pp.auth_type in {"oauth_device_code", "oauth_external", "external_process", "aws_sdk", "copilot", "vertex"}:
|
|
continue # non-api-key flows need bespoke picker UX; skip auto-inject
|
|
_label = _pp.display_name or _pp.name
|
|
_desc = _pp.description or f"{_label} (direct API)"
|
|
CANONICAL_PROVIDERS.append(ProviderEntry(_pp.name, _label, _desc))
|
|
_canonical_slugs.add(_pp.name)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
# Derived dicts — used throughout the codebase
|
|
_PROVIDER_LABELS = {p.slug: p.label for p in CANONICAL_PROVIDERS}
|
|
_PROVIDER_LABELS["custom"] = "Custom endpoint" # special case: not a named provider
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Provider groups — DISPLAY ONLY
|
|
#
|
|
# Some vendors expose several Hermes provider slugs (one per endpoint /
|
|
# auth method: global API, China API, OAuth coding plan, ...). Listing every
|
|
# slug as a top-level row in the interactive `hermes model` / setup wizard /
|
|
# Telegram `/model` pickers makes that list long and noisy.
|
|
#
|
|
# These groups fold related slugs under one top-level row in INTERACTIVE
|
|
# PICKERS only. They do NOT change ``CANONICAL_PROVIDERS``, slug identity,
|
|
# the ``--provider`` flag, ``/model <provider:model>``, or any typed path —
|
|
# every member slug remains individually addressable. Grouping is a pure
|
|
# display affordance; ``group_providers()`` is the single fold used by all
|
|
# three picker surfaces so they stay consistent.
|
|
#
|
|
# group_id -> (display_label, group_description, [member_slug, ...])
|
|
#
|
|
# ``group_description`` is a short blurb shown on the collapsed top-level group
|
|
# row in the interactive pickers (alongside the label). Member-specific detail
|
|
# lives in each member's ``tui_desc`` and shows in the drill-down sub-picker.
|
|
# Member order is the order shown inside the group submenu.
|
|
# ---------------------------------------------------------------------------
|
|
PROVIDER_GROUPS: dict[str, tuple[str, str, list[str]]] = {
|
|
"kimi": ("Kimi / Moonshot", "Coding Plan, Moonshot global & China endpoints", ["kimi-coding", "kimi-coding-cn"]),
|
|
"minimax": ("MiniMax", "Global, OAuth Coding Plan & China endpoints", ["minimax", "minimax-oauth", "minimax-cn"]),
|
|
"xai": ("xAI Grok", "Direct API or SuperGrok / Premium+ OAuth", ["xai", "xai-oauth"]),
|
|
"google": ("Google Gemini", "Google AI Studio (API key)", ["gemini"]),
|
|
"openai": ("OpenAI", "ChatGPT/Codex subscription or direct OpenAI API", ["openai-codex", "openai-api"]),
|
|
"qwen": ("Qwen", "Qwen Cloud / DashScope, Coding Plan, Token Plan & Qwen CLI OAuth", ["alibaba", "alibaba-cn", "alibaba-coding-plan", "alibaba-coding-plan-cn", "alibaba-token-plan", "alibaba-token-plan-cn", "qwen-oauth"]),
|
|
"opencode": ("OpenCode", "Zen pay-as-you-go, Go subscription, or free tier", ["opencode-zen", "opencode-go", "opencode-free"]),
|
|
"copilot": ("GitHub Copilot", "GitHub token API or copilot --acp process", ["copilot", "copilot-acp"]),
|
|
"tencent": ("Tencent Hy", "Hy4 / Hy3 via TokenHub & TokenPlan", ["tencent-tokenhub", "tencent-tokenplan"]),
|
|
}
|
|
|
|
|
|
# Reverse index: member slug -> group_id. Built once at import.
|
|
_SLUG_TO_GROUP: dict[str, str] = {
|
|
slug: gid for gid, (_label, _desc, members) in PROVIDER_GROUPS.items() for slug in members
|
|
}
|
|
|
|
|
|
def provider_group_for_slug(slug: str) -> str:
|
|
"""Return the group_id a provider slug belongs to, or "" if ungrouped."""
|
|
return _SLUG_TO_GROUP.get(str(slug or "").strip().lower(), "")
|
|
|
|
|
|
def group_providers(slugs):
|
|
"""Fold a flat ordered slug iterable into picker rows by provider group.
|
|
|
|
DISPLAY ONLY. Used by every interactive picker (``hermes model``, the setup wizard, the Telegram
|
|
``/model`` keyboard) so grouping is identical across surfaces.
|
|
|
|
Rules: * A group row appears at the position of its FIRST present member, in the input order.
|
|
Subsequent members fold into that row (and are not emitted again). * Member order inside a group
|
|
follows ``PROVIDER_GROUPS`` declaration, restricted to the members actually present in
|
|
``slugs``.
|
|
"""
|
|
seen: set[str] = set()
|
|
# Which present members each group has, in declaration order.
|
|
group_members: dict[str, list[str]] = {}
|
|
for gid, (_label, _desc, members) in PROVIDER_GROUPS.items():
|
|
present = [m for m in members if m in set(slugs)]
|
|
if present:
|
|
group_members[gid] = present
|
|
|
|
rows = []
|
|
emitted_groups: set[str] = set()
|
|
for slug in slugs:
|
|
s = str(slug or "").strip().lower()
|
|
if not s or s in seen:
|
|
continue
|
|
seen.add(s)
|
|
gid = _SLUG_TO_GROUP.get(s, "")
|
|
if not gid:
|
|
rows.append({"kind": "single", "slug": s})
|
|
continue
|
|
if gid in emitted_groups:
|
|
continue # already folded at the first member's position
|
|
emitted_groups.add(gid)
|
|
members = group_members.get(gid, [s])
|
|
if len(members) <= 1:
|
|
rows.append({"kind": "single", "slug": members[0]})
|
|
else:
|
|
label, desc, _ = PROVIDER_GROUPS[gid]
|
|
rows.append(
|
|
{"kind": "group", "group_id": gid, "label": label,
|
|
"description": desc, "members": list(members)}
|
|
)
|
|
return rows
|
|
|
|
|
|
_PROVIDER_ALIASES = {
|
|
"glm": "zai",
|
|
"z-ai": "zai",
|
|
"z.ai": "zai",
|
|
"zhipu": "zai",
|
|
"github": "copilot",
|
|
"github-copilot": "copilot",
|
|
"github-models": "copilot",
|
|
"github-model": "copilot",
|
|
"github-copilot-acp": "copilot-acp",
|
|
"copilot-acp-agent": "copilot-acp",
|
|
"google": "gemini",
|
|
"google-gemini": "gemini",
|
|
"google-ai-studio": "gemini",
|
|
"google-vertex": "vertex",
|
|
"vertex-ai": "vertex",
|
|
"gcp-vertex": "vertex",
|
|
"vertexai": "vertex",
|
|
"kimi": "kimi-coding",
|
|
"moonshot": "kimi-coding",
|
|
"kimi-cn": "kimi-coding-cn",
|
|
"moonshot-cn": "kimi-coding-cn",
|
|
"step": "stepfun",
|
|
"stepfun-coding-plan": "stepfun",
|
|
"arcee-ai": "arcee",
|
|
"arceeai": "arcee",
|
|
"gmi-cloud": "gmi",
|
|
"gmicloud": "gmi",
|
|
"fireworks-ai": "fireworks",
|
|
"fw": "fireworks",
|
|
"actual-computer": "actual",
|
|
"actualcomputer": "actual",
|
|
"aci": "actual",
|
|
"nebius": "nebius-token-factory",
|
|
"nebius-tokenfactory": "nebius-token-factory",
|
|
"nebius-tf": "nebius-token-factory",
|
|
"token-factory": "nebius-token-factory",
|
|
"tokenfactory": "nebius-token-factory",
|
|
"minimax-china": "minimax-cn",
|
|
"minimax_cn": "minimax-cn",
|
|
"minimax-portal": "minimax-oauth",
|
|
"minimax-global": "minimax-oauth",
|
|
"minimax_oauth": "minimax-oauth",
|
|
"claude": "anthropic",
|
|
"claude-code": "anthropic",
|
|
"deep-seek": "deepseek",
|
|
"opencode": "opencode-zen",
|
|
"zen": "opencode-zen",
|
|
"go": "opencode-go",
|
|
"opencode-go-sub": "opencode-go",
|
|
"free": "opencode-free",
|
|
"opencode_free": "opencode-free",
|
|
"aigateway": "ai-gateway",
|
|
"vercel": "ai-gateway",
|
|
"vercel-ai-gateway": "ai-gateway",
|
|
"kilo": "kilocode",
|
|
"kilo-code": "kilocode",
|
|
"kilo-gateway": "kilocode",
|
|
"dashscope": "alibaba",
|
|
"aliyun": "alibaba",
|
|
"qwen": "alibaba",
|
|
"alibaba-cloud": "alibaba",
|
|
"qwen-portal": "qwen-oauth",
|
|
"hf": "huggingface",
|
|
"hugging-face": "huggingface",
|
|
"huggingface-hub": "huggingface",
|
|
"novita-ai": "novita",
|
|
"novitaai": "novita",
|
|
"mimo": "xiaomi",
|
|
"xiaomi-mimo": "xiaomi",
|
|
"tencent": "tencent-tokenhub",
|
|
"tokenhub": "tencent-tokenhub",
|
|
"tencent-cloud": "tencent-tokenhub",
|
|
"tencentmaas": "tencent-tokenhub",
|
|
"tokenplan": "tencent-tokenplan",
|
|
"tencent-lkeap": "tencent-tokenplan",
|
|
"aws": "bedrock",
|
|
"aws-bedrock": "bedrock",
|
|
"amazon-bedrock": "bedrock",
|
|
"amazon": "bedrock",
|
|
"grok": "xai",
|
|
"grok-oauth": "xai-oauth",
|
|
"xai-oauth": "xai-oauth",
|
|
"x-ai-oauth": "xai-oauth",
|
|
"xai-grok-oauth": "xai-oauth",
|
|
"x-ai": "xai",
|
|
"x.ai": "xai",
|
|
"nim": "nvidia",
|
|
"nvidia-nim": "nvidia",
|
|
"build-nvidia": "nvidia",
|
|
"nemotron": "nvidia",
|
|
"lmstudio": "lmstudio",
|
|
"lm-studio": "lmstudio",
|
|
"lm_studio": "lmstudio",
|
|
"ollama": "custom", # bare "ollama" = local; use "ollama-cloud" for cloud
|
|
"ollama_cloud": "ollama-cloud",
|
|
}
|
|
|
|
|
|
# In-repo fallback for the model Hermes silently lands on when the user never
|
|
# picked one (GUI onboarding confirm card, empty ``model.default``,
|
|
# provider-set-but-model-missing resolution). The AUTHORITATIVE source is the
|
|
# remote model catalog: the manifest labels exactly one entry per provider
|
|
# with ``"default": true`` (see get_default_model_from_cache in
|
|
# model_catalog.py), so maintainers can rotate the default without shipping a
|
|
# release. This constant is the offline/fresh-install fallback and MUST match
|
|
# the labeled entry in website/static/api/model-catalog.json. Deliberately a
|
|
# capable low-cost model rather than the curated lists' entry [0]: aggregator
|
|
# lists are ordered most-capable-first, so [0] is the priciest Anthropic
|
|
# flagship (claude-fable-5 / opus) — silently billing the most expensive model
|
|
# for traffic the user never opted into.
|
|
PREFERRED_SILENT_DEFAULT_MODEL = "z-ai/glm-5.2"
|
|
|
|
|
|
# Providers whose *silent* auto-default must go through the cost-safe
|
|
# catalog-labeled default (``get_preferred_silent_default_model``) instead of
|
|
# curated-list entry [0]. Metered aggregators (Nous Portal, OpenRouter) order
|
|
# their lists best-/most-capable-first — entry [0] is the priciest flagship
|
|
# (``anthropic/claude-fable-5``). Using that as the non-interactive fallback
|
|
# when a profile sets a provider with no model silently bills the most
|
|
# expensive model for traffic the user never opted into (a missing default
|
|
# escalated to Opus and billed 863 requests before the user noticed). The
|
|
# catalog manifest labels the default entry (``"default": true``) so it can
|
|
# rotate without a release; a missing model must never escalate to the
|
|
# flagship.
|
|
#
|
|
# This is deliberately a network-free lookup for the hot resolution path
|
|
# (cache-only catalog read). The *interactive* default (GUI onboarding /
|
|
# ``hermes model``) uses the richer free/paid-tier-aware resolver — see
|
|
# ``get_recommended_default_model`` in hermes_cli/web_server.py and
|
|
# ``partition_nous_models_by_tier`` — which can hit the Portal.
|
|
_SILENT_DEFAULT_PROVIDERS: frozenset[str] = frozenset({"nous", "openrouter"})
|
|
|
|
|
|
# Retired model IDs kept for /model auto-detect only — not shown in pickers.
|
|
# DeepSeek cut these off on 2026-07-24; model_normalize remaps them on the wire.
|
|
_PROVIDER_RETIRED_ALIASES: dict[str, tuple[str, ...]] = {
|
|
"deepseek": ("deepseek-chat", "deepseek-reasoner"),
|
|
}
|
|
|
|
|
|
_AGGREGATOR_PROVIDERS = frozenset(
|
|
{"nous", "openrouter", "ai-gateway", "copilot", "kilocode"}
|
|
)
|
|
|
|
|
|
# OpenRouter request-time routing variants (docs: guides/routing/model-variants).
|
|
# These suffixes are per-request routing modifiers valid on ANY model id —
|
|
# ":nitro" sorts the endpoint pool by throughput and admits priority-tier
|
|
# endpoints, ":floor" sorts by price and admits flex-tier endpoints, ":exacto"
|
|
# applies quality-first provider sorting, ":online" attaches the web plugin.
|
|
# They are never separate catalog entries: /models lists only the base id.
|
|
# NOT in this set: ":free", ":batch", ":thinking", ":extended" — those ARE
|
|
# distinct catalog SKUs that appear in /models when they exist, so absence
|
|
# from the listing is authoritative for them and the direct-membership check
|
|
# above handles the valid ones.
|
|
_OPENROUTER_VARIANT_SUFFIXES = frozenset({"nitro", "floor", "exacto", "online"})
|
|
|
|
|
|
# Subscription/OAuth providers whose catalogs RE-EXPOSE other vendors' models
|
|
# would be listed here (tried only as a last resort for bare short-alias
|
|
# resolution, after every native-vendor catalog, so they never hijack an alias
|
|
# away from the model's native vendor). None are currently defined.
|
|
_BORROWED_MODEL_PROVIDERS: frozenset[str] = frozenset()
|
|
|
|
|
|
# Providers whose live /v1/models endpoint is the authoritative catalog, so the
|
|
# curated list is a discovery-only fallback. For these, the picker merges
|
|
# live-first (live entries lead, curated-only entries append). Every OTHER
|
|
# provider keeps curated-first (commit 658ac1d86, #46309) so a deliberately
|
|
# surfaced newest model stays at the top even when the live API lags. OpenCode
|
|
# Zen / Go re-expose dozens of upstream vendors and rotate them frequently, so
|
|
# their stale curated entries must not pollute the top of the picker. (#49129)
|
|
_LIVE_FIRST_PICKER_PROVIDERS: frozenset[str] = frozenset(
|
|
{"opencode-zen", "opencode-go"}
|
|
)
|
|
|
|
|
|
# Models that support OpenAI Priority Processing (service_tier="priority").
|
|
# See https://openai.com/api-priority-processing/ for the canonical list.
|
|
#
|
|
# Pattern-based matching — any OpenAI flagship model (gpt-*, o1*, o3*, o4*)
|
|
# is assumed to support Priority Processing. service_tier=priority is silently
|
|
# ignored by non-OpenAI endpoints (OpenRouter/Copilot/opencode-zen proxies
|
|
# strip the field), so false positives are harmless. Codex-series models
|
|
# (gpt-5-codex, gpt-5.3-codex, etc.) are excluded — they don't expose the
|
|
# service_tier parameter through the Codex Responses API.
|
|
_OPENAI_FAST_MODE_PREFIXES: tuple[str, ...] = (
|
|
"gpt-",
|
|
"o1",
|
|
"o3",
|
|
"o4",
|
|
)
|
|
|
|
|
|
# Providers where models.dev is treated as authoritative: curated static
|
|
# lists are kept only as an offline fallback and to capture custom additions
|
|
# the registry doesn't publish yet. Adding a provider here causes its
|
|
# curated list to be merged with fresh models.dev entries (fresh first, any
|
|
# curated-only names appended) for both the CLI and the gateway /model picker.
|
|
#
|
|
# DELIBERATELY EXCLUDED:
|
|
# - "openrouter": curated list is already a hand-picked agentic subset of
|
|
# OpenRouter's 400+ catalog. Blindly merging would dump everything.
|
|
# - "nous": curated list and Portal /models endpoint are the source of
|
|
# truth for the subscription tier.
|
|
# Also excluded: providers that already have dedicated live-endpoint
|
|
# branches below (copilot, anthropic, ai-gateway, ollama-cloud, custom,
|
|
# stepfun, openai-codex) — those paths handle freshness themselves.
|
|
_MODELS_DEV_PREFERRED: frozenset[str] = frozenset({
|
|
"opencode-go",
|
|
"opencode-zen",
|
|
"deepseek",
|
|
"kilocode",
|
|
"fireworks",
|
|
"mistral",
|
|
"togetherai",
|
|
"cohere",
|
|
"perplexity",
|
|
"groq",
|
|
"nvidia",
|
|
"huggingface",
|
|
"zai",
|
|
"gemini",
|
|
"google",
|
|
"xai",
|
|
"xai-oauth",
|
|
})
|
|
|
|
|
|
# Providers whose catalog is served with NO credential and therefore gets a
|
|
# stable (constant) credential fingerprint in the disk cache. The opencode-free
|
|
# catalog is anonymous — its freshness comes from TTL revalidation, not from
|
|
# user-rotatable credentials — so folding in unrelated auth.json mtimes would
|
|
# only needlessly bust the SWR cache.
|
|
_KEYLESS_STABLE_CACHE_PROVIDERS = frozenset({"opencode-free"})
|
|
|
|
|
|
_COPILOT_MODEL_ALIASES = {
|
|
"openai/gpt-5": "gpt-5-mini",
|
|
"openai/gpt-5-chat": "gpt-5-mini",
|
|
"openai/gpt-5-mini": "gpt-5-mini",
|
|
"openai/gpt-5-nano": "gpt-5-mini",
|
|
"openai/gpt-4.1": "gpt-4.1",
|
|
"openai/gpt-4.1-mini": "gpt-4.1",
|
|
"openai/gpt-4.1-nano": "gpt-4.1",
|
|
"openai/gpt-4o": "gpt-4o",
|
|
"openai/gpt-4o-mini": "gpt-4o-mini",
|
|
"openai/o1": "gpt-5.2",
|
|
"openai/o1-mini": "gpt-5-mini",
|
|
"openai/o1-preview": "gpt-5.2",
|
|
"openai/o3": "gpt-5.3-codex",
|
|
"openai/o3-mini": "gpt-5-mini",
|
|
"openai/o4-mini": "gpt-5-mini",
|
|
"anthropic/claude-opus-4.6": "claude-opus-4.6",
|
|
"anthropic/claude-sonnet-5": "claude-sonnet-5",
|
|
"anthropic/claude-sonnet-4.6": "claude-sonnet-4.6",
|
|
"anthropic/claude-sonnet-4": "claude-sonnet-4",
|
|
"anthropic/claude-sonnet-4.5": "claude-sonnet-4.5",
|
|
"anthropic/claude-haiku-4.5": "claude-haiku-4.5",
|
|
# Dash-notation fallbacks: Hermes' default Claude IDs elsewhere use
|
|
# hyphens (anthropic native format), but Copilot's API only accepts
|
|
# dot-notation. Accept both so users who configure copilot + a
|
|
# default hyphenated Claude model don't hit HTTP 400
|
|
# "model_not_supported". See issue #6879.
|
|
"claude-sonnet-5": "claude-sonnet-5",
|
|
"claude-opus-4-6": "claude-opus-4.6",
|
|
"claude-sonnet-4-6": "claude-sonnet-4.6",
|
|
"claude-sonnet-4-0": "claude-sonnet-4",
|
|
"claude-sonnet-4-5": "claude-sonnet-4.5",
|
|
"claude-haiku-4-5": "claude-haiku-4.5",
|
|
"anthropic/claude-opus-4-6": "claude-opus-4.6",
|
|
"anthropic/claude-sonnet-5": "claude-sonnet-5",
|
|
"anthropic/claude-sonnet-4-6": "claude-sonnet-4.6",
|
|
"anthropic/claude-sonnet-4-0": "claude-sonnet-4",
|
|
"anthropic/claude-sonnet-4-5": "claude-sonnet-4.5",
|
|
"anthropic/claude-haiku-4-5": "claude-haiku-4.5",
|
|
}
|
|
|
|
|
|
# Azure Foundry model families that require the Responses API. Azure
|
|
# rejects /chat/completions against these deployments with
|
|
# ``400 "The requested operation is unsupported."`` — the same payload Bob
|
|
# Dobolina hit in April 2026 on ``gpt-5.3-codex`` while ``gpt-4o-pure`` on
|
|
# the same endpoint worked fine. Keep the patterns broad enough to cover
|
|
# vendor-renamed deployments (e.g. ``gpt-5.3-codex``, ``gpt-5-codex``,
|
|
# ``gpt-5.4``, ``o1-preview``) but tight enough to leave GPT-4 / 3.5 / Llama /
|
|
# Mistral / Grok deployments on chat completions.
|
|
_AZURE_FOUNDRY_RESPONSES_PREFIXES = (
|
|
"codex", # codex-*, codex-mini
|
|
"gpt-5", # gpt-5, gpt-5.x, gpt-5-codex, gpt-5.x-codex
|
|
"o1", # o1, o1-preview, o1-mini
|
|
"o3", # o3, o3-mini
|
|
"o4", # o4, o4-mini
|
|
)
|