diff --git a/hermes_cli/models.py b/hermes_cli/models.py index b592b65056..5428fa5d82 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -812,8 +812,24 @@ def fetch_ai_gateway_models( if not curated: return list(_ai_gateway_catalog_cache or fallback) - first_id, _ = curated[0] - curated[0] = (first_id, "recommended") + # If the live catalog offers a free Moonshot model, auto-promote it to + # position #1 as "recommended" — dynamic discovery without a PR. + free_moonshot = next( + ( + mid + for mid, item in live_by_id.items() + if mid.startswith("moonshotai/") + and _ai_gateway_model_is_free(item.get("pricing")) + ), + None, + ) + if free_moonshot: + curated = [(mid, desc) for mid, desc in curated if mid != free_moonshot] + curated.insert(0, (free_moonshot, "recommended")) + else: + first_id, _ = curated[0] + curated[0] = (first_id, "recommended") + _ai_gateway_catalog_cache = curated return list(curated) diff --git a/tests/hermes_cli/test_ai_gateway_models.py b/tests/hermes_cli/test_ai_gateway_models.py index 0a175b8344..236060870d 100644 --- a/tests/hermes_cli/test_ai_gateway_models.py +++ b/tests/hermes_cli/test_ai_gateway_models.py @@ -122,6 +122,38 @@ def test_fetch_ai_gateway_models_tags_free_models(): assert by_id[second_id] == "free" +def test_free_moonshot_model_auto_promoted_to_top_even_if_not_curated(): + _reset_caches() + first_curated = AI_GATEWAY_MODELS[0][0] + unlisted_free_moonshot = "moonshotai/kimi-coder-free-preview" + payload = { + "data": [ + {"id": first_curated, "pricing": {"input": "0.001", "output": "0.002"}}, + {"id": unlisted_free_moonshot, "pricing": {"input": "0", "output": "0"}}, + ] + } + with patch("urllib.request.urlopen", return_value=_mock_urlopen(payload)): + result = fetch_ai_gateway_models(force_refresh=True) + + assert result[0] == (unlisted_free_moonshot, "recommended") + assert any(mid == first_curated for mid, _ in result) + + +def test_paid_moonshot_does_not_get_auto_promoted(): + _reset_caches() + first_curated = AI_GATEWAY_MODELS[0][0] + payload = { + "data": [ + {"id": first_curated, "pricing": {"input": "0.001", "output": "0.002"}}, + {"id": "moonshotai/some-paid-variant", "pricing": {"input": "0.001", "output": "0.002"}}, + ] + } + with patch("urllib.request.urlopen", return_value=_mock_urlopen(payload)): + result = fetch_ai_gateway_models(force_refresh=True) + + assert result[0][0] == first_curated + + def test_fetch_ai_gateway_models_falls_back_on_error(): _reset_caches() with patch("urllib.request.urlopen", side_effect=OSError("network")):