diff --git a/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md b/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md index 83ea8a2..3132fd7 100644 --- a/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md +++ b/docs/03-one-endpoint-gpt-claude-gemini-deepseek.md @@ -76,7 +76,8 @@ blockrun/xai/grok-4.5 blockrun/minimax/minimax-m3 blockrun/moonshot/kimi-k3 blockrun/deepseek/deepseek-v4-pro -…46 curated entries (66 models via the gateway) +blockrun/free/deepseek-v4-flash +…47 curated entries (66 models via the gateway) ``` Set the model to `blockrun/auto` and the gateway's router picks a model per request diff --git a/src/clawrouter_hermes/models.py b/src/clawrouter_hermes/models.py index 15c6a28..eb64214 100644 --- a/src/clawrouter_hermes/models.py +++ b/src/clawrouter_hermes/models.py @@ -49,13 +49,14 @@ "blockrun/deepseek/deepseek-chat", "blockrun/deepseek/deepseek-reasoner", "blockrun/free/mistral-large-3-675b", - "blockrun/free/qwen3-next-80b-a3b-instruct", + "blockrun/free/deepseek-v4-flash", "blockrun/free/seed-oss-36b", "blockrun/free/nemotron-3-nano-omni-30b-a3b-reasoning", "blockrun/free/mistral-nemotron", "blockrun/free/step-3.7-flash", "blockrun/free/nemotron-nano-9b-v2", "blockrun/free/nemotron-nano-12b-v2-vl", + "blockrun/free/qwen3-next-80b-a3b-instruct", ) def chat_models() -> list[str]: diff --git a/src/clawrouter_hermes/provider_template/init.py.tmpl b/src/clawrouter_hermes/provider_template/init.py.tmpl index 9bb24e3..381b36b 100644 --- a/src/clawrouter_hermes/provider_template/init.py.tmpl +++ b/src/clawrouter_hermes/provider_template/init.py.tmpl @@ -84,13 +84,14 @@ _STATIC_FALLBACKS = ( "blockrun/deepseek/deepseek-chat", "blockrun/deepseek/deepseek-reasoner", "blockrun/free/mistral-large-3-675b", - "blockrun/free/qwen3-next-80b-a3b-instruct", + "blockrun/free/deepseek-v4-flash", "blockrun/free/seed-oss-36b", "blockrun/free/nemotron-3-nano-omni-30b-a3b-reasoning", "blockrun/free/mistral-nemotron", "blockrun/free/step-3.7-flash", "blockrun/free/nemotron-nano-9b-v2", "blockrun/free/nemotron-nano-12b-v2-vl", + "blockrun/free/qwen3-next-80b-a3b-instruct", ) diff --git a/src/clawrouter_hermes/skills/clawrouter/SKILL.md b/src/clawrouter_hermes/skills/clawrouter/SKILL.md index 10ded6d..fbd1fec 100644 --- a/src/clawrouter_hermes/skills/clawrouter/SKILL.md +++ b/src/clawrouter_hermes/skills/clawrouter/SKILL.md @@ -164,7 +164,7 @@ Rules handle ~80% of requests in <1ms. Only ambiguous queries hit the LLM classi ## Available Models -66 models including: claude-fable-5, claude-opus-5, claude-opus-4.8, claude-opus-4.7, claude-sonnet-5, claude-sonnet-4.6, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gemini-3.1-pro, gemini-3.5-flash, grok-4.5, grok-4.3, grok-build-0.1, deepseek-v4-pro, deepseek-chat, glm-5.2, kimi-k3, minimax-m3, qwen3.7-max, and the curated free models (mistral-large-3-675b, qwen3-next-80b-a3b-instruct, seed-oss-36b, nemotron-3-nano-omni-30b-a3b-reasoning [vision], mistral-nemotron, step-3.7-flash, nemotron-nano-9b-v2, nemotron-nano-12b-v2-vl [vision]). +66 models including: claude-fable-5, claude-opus-5, claude-opus-4.8, claude-opus-4.7, claude-sonnet-5, claude-sonnet-4.6, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gemini-3.1-pro, gemini-3.5-flash, grok-4.5, grok-4.3, grok-build-0.1, deepseek-v4-pro, deepseek-chat, glm-5.2, kimi-k3, minimax-m3, qwen3.7-max, and the curated free models (mistral-large-3-675b, deepseek-v4-flash, seed-oss-36b, nemotron-3-nano-omni-30b-a3b-reasoning [vision], mistral-nemotron, step-3.7-flash, nemotron-nano-9b-v2, nemotron-nano-12b-v2-vl [vision], qwen3-next-80b-a3b-instruct). ## Built-in Agent Tools diff --git a/tests/test_provider_template.py b/tests/test_provider_template.py index 40dd416..33726d1 100644 --- a/tests/test_provider_template.py +++ b/tests/test_provider_template.py @@ -4,6 +4,7 @@ import ast import importlib.resources as resources +import json import sys import types from pathlib import Path @@ -32,6 +33,7 @@ "blockrun/qwen/qwen3.7-max", "blockrun/deepseek/deepseek-v4-pro", "blockrun/free/mistral-large-3-675b", + "blockrun/free/deepseek-v4-flash", ) @@ -150,18 +152,23 @@ def test_curated_picker_catalog_orders_featured_models(): assert chat_models[-len(free_tail):] == free_tail assert free_tail == [ "blockrun/free/mistral-large-3-675b", - "blockrun/free/qwen3-next-80b-a3b-instruct", + "blockrun/free/deepseek-v4-flash", "blockrun/free/seed-oss-36b", "blockrun/free/nemotron-3-nano-omni-30b-a3b-reasoning", "blockrun/free/mistral-nemotron", "blockrun/free/step-3.7-flash", "blockrun/free/nemotron-nano-9b-v2", "blockrun/free/nemotron-nano-12b-v2-vl", + "blockrun/free/qwen3-next-80b-a3b-instruct", ] # Retired models must not reappear in the picker. Append here on every # catalog retirement so a bad merge can't resurrect them. retired_models = frozenset({ "blockrun/free/qwen3-coder-480b", # NVIDIA EOL 2026-06-14 + # Never a real SKU: the gateway removed paid V4 Flash and kept only a + # legacy alias to deepseek/deepseek-chat, so this ID smoke-tests green + # while duplicating deepseek-chat. The free model is free/deepseek-v4-flash. + "blockrun/deepseek/deepseek-v4-flash", # Dropped from the live BlockRun catalog by 2026-07-17: "blockrun/xai/grok-4-1-fast-reasoning", "blockrun/xai/grok-4-0709", @@ -174,6 +181,45 @@ def test_curated_picker_catalog_orders_featured_models(): assert not set(chat_models) & retired_models +#: Curated entries that postdate the ClawRouter top-models.json snapshot. +#: They are appended after the mirrored free tail rather than interleaved. +POST_TOP_MODELS_ADDITIONS = frozenset({ + "blockrun/free/qwen3-next-80b-a3b-instruct", +}) + + +def test_curated_picker_catalog_mirrors_clawrouter_top_models(): + """Membership + relative order must match ClawRouter's top-models.json. + + The gateway's MODEL_ALIASES table makes phantom IDs smoke-test green: + blockrun/deepseek/deepseek-v4-flash returned HTTP 200 via a legacy alias + to deepseek-chat while never being a real SKU (PR #27). Catalog membership + therefore has to be validated against top-models.json, not the wire. + Runs only where a sibling ClawRouter checkout exists; CI skips. + """ + top_models_path = ( + _repo_root().parent / "ClawRouter" / "src" / "top-models.json" + ) + if not top_models_path.is_file(): + pytest.skip("sibling ClawRouter checkout not available") + top_models = json.loads(top_models_path.read_text(encoding="utf-8")) + + from clawrouter_hermes import models + + mirrored = [ + model.removeprefix("blockrun/") + for model in models.chat_models() + if model not in POST_TOP_MODELS_ADDITIONS + ] + unknown = [model for model in mirrored if model not in top_models] + assert not unknown, f"curated IDs missing from top-models.json: {unknown}" + positions = {model: idx for idx, model in enumerate(top_models)} + order = [positions[model] for model in mirrored] + assert order == sorted(order), ( + "curated order diverges from top-models.json" + ) + + def test_patch_hermes_model_catalog_uses_curated_clawrouter_only(monkeypatch): import clawrouter_hermes.cli as cli_module from clawrouter_hermes import models