diff --git a/apps/desktop/src/lib/model-status-label.test.ts b/apps/desktop/src/lib/model-status-label.test.ts index 6f3f75681e..54150daca5 100644 --- a/apps/desktop/src/lib/model-status-label.test.ts +++ b/apps/desktop/src/lib/model-status-label.test.ts @@ -13,6 +13,7 @@ describe('model-status-label', () => { expect(displayModelName('anthropic/claude-opus-4.8-fast')).toBe('Opus 4.8') expect(displayModelName('openai/gpt-5.5-fast')).toBe('GPT-5.5') expect(displayModelName('deepseek/deepseek-v4-pro-thinking')).toBe('Deepseek V4 Pro') + expect(displayModelName('deepseek/deepseek-flash')).toBe('DeepSeek V4.1 Flash') expect(displayModelName('openai/gpt-5.5')).toBe('GPT-5.5') }) diff --git a/apps/desktop/src/lib/model-status-label.ts b/apps/desktop/src/lib/model-status-label.ts index 38e9430163..eff96c4f44 100644 --- a/apps/desktop/src/lib/model-status-label.ts +++ b/apps/desktop/src/lib/model-status-label.ts @@ -73,6 +73,10 @@ const VARIANT_TAGS: ReadonlyArray = [ const titleCase = (text: string): string => text.replace(/\b\w/g, char => char.toUpperCase()).trim() function prettifyBase(base: string): string { + if (/^deepseek-flash$/i.test(base)) { + return 'DeepSeek V4.1 Flash' + } + if (/^claude-/i.test(base)) { // Anthropic ids spell the version with hyphens (`haiku-4-5`, `fable-5-1`); // the human name is dotted ("Haiku 4.5"), not "Haiku 4 5". diff --git a/hermes_cli/models.py b/hermes_cli/models.py index b31397f648..be8267f7a2 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -1266,8 +1266,7 @@ def _model_dedup_key(model_id: str) -> str: def _merge_with_models_dev(provider: str, curated: list[str]) -> list[str]: - """models.dev entries first (their order), then curated-only extras, case-insensitively deduped - while preserving curated casing. Curated unchanged when models.dev is unreachable/empty.""" + """Merge registry additions, with native DeepSeek's live names authoritative.""" try: from agent.models_dev import list_agentic_models mdev = list_agentic_models(provider) @@ -1275,6 +1274,10 @@ def _merge_with_models_dev(provider: str, curated: list[str]) -> list[str]: mdev = [] if not mdev: return list(curated) + if normalize_provider(provider) == "deepseek": + retired = {"deepseek-v4-flash", "deepseek-v4-flash-vision-exp"} + mdev = [model for model in mdev if model.lower() not in retired] + return _merge_unique([], mdev + list(curated), key=_model_dedup_key) return _merge_unique(_merge_unique([], mdev), curated) diff --git a/hermes_cli/models_catalog_static.py b/hermes_cli/models_catalog_static.py index 33e961ebf0..fe79673652 100644 --- a/hermes_cli/models_catalog_static.py +++ b/hermes_cli/models_catalog_static.py @@ -210,7 +210,7 @@ _PROVIDER_MODELS: dict[str, list[str]] = { "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-flash"], + "deepseek": ["deepseek-flash", "deepseek-v4-pro"], "xiaomi": ["mimo-v2.5-pro", "mimo-v2.5", "mimo-v2-pro", "mimo-v2-omni", "mimo-v2-flash"], "tencent-tokenhub": list(_TENCENT_MODELS), "tencent-tokenplan": list(_TENCENT_MODELS), @@ -539,7 +539,7 @@ _OPENAI_FAST_MODE_PREFIXES: tuple[str, ...] = ("gpt-", "o1", "o3", "o4") # /models are the subscription-tier source of truth), and providers with dedicated live-endpoint # branches (copilot, anthropic, ai-gateway, ollama-cloud, custom, stepfun, openai-codex). _MODELS_DEV_PREFERRED: frozenset[str] = frozenset({ - "opencode-go", "opencode-zen", "deepseek", "kilocode", "fireworks", "mistral", "togetherai", "cohere", + "opencode-go", "opencode-zen", "kilocode", "fireworks", "mistral", "togetherai", "cohere", "perplexity", "groq", "nvidia", "huggingface", "zai", "gemini", "google", "xai", "xai-oauth", }) diff --git a/tests/hermes_cli/test_models_dev_preferred_merge.py b/tests/hermes_cli/test_models_dev_preferred_merge.py index 095f9d6769..2da007c96a 100644 --- a/tests/hermes_cli/test_models_dev_preferred_merge.py +++ b/tests/hermes_cli/test_models_dev_preferred_merge.py @@ -29,6 +29,15 @@ from hermes_cli.models import ( class TestMergeHelper: + def test_deepseek_merge_omits_retired_ids_and_puts_live_models_first(self): + with patch( + "agent.models_dev.list_agentic_models", + return_value=["deepseek-v4-flash-vision-exp", "deepseek-v4-flash", "deepseek-flash", "deepseek-v4-pro"], + ): + out = _merge_with_models_dev("deepseek", ["deepseek-v4-pro", "deepseek-flash"]) + + assert out == ["deepseek-flash", "deepseek-v4-pro"] + def test_merge_empty_mdev_returns_curated(self): """When models.dev returns nothing, curated list is preserved verbatim.""" with patch("agent.models_dev.list_agentic_models", return_value=[]):