feat(desktop): mirror hermes model free/paid curation in GUI onboarding
GUI onboarding picked models[0] from /api/model/options, which ignores the
Nous free/paid tier — a free user could land on a paid default (e.g.
anthropic/claude-opus-4). Now the recommended default mirrors what `hermes
model` does.
- web_server.py: new GET /api/model/recommended-default?provider=<slug>. For
Nous it runs the same curation as the CLI (get_curated_nous_model_ids +
pricing + check_nous_free_tier + union_with_portal_{free,paid}_recommendations
+ partition_nous_models_by_tier) so free users get a free model and paid users
get the curated default. Other providers fall back to the first curated model.
Never 500s — returns empty model on error so onboarding degrades gracefully.
- hermes.ts: getRecommendedDefaultModel client + RecommendedDefaultModel type.
- onboarding.ts: fetchProviderDefaultModel prefers the recommended endpoint,
falls back to models[0] when unavailable.
- tests: free-tier picks free model, paid-tier picks curated default, failure
returns empty without 500.
This commit is contained in:
@@ -670,6 +670,68 @@ class TestWebServerEndpoints:
|
||||
assert data["ok"] is True
|
||||
assert data.get("gateway_tools", []) == []
|
||||
|
||||
def test_recommended_default_nous_honors_free_tier(self, monkeypatch):
|
||||
"""For a free-tier Nous user, the recommended default must be a free
|
||||
model (mirroring `hermes model`), not the first curated paid entry."""
|
||||
import hermes_cli.models as models_mod
|
||||
|
||||
monkeypatch.setattr(models_mod, "get_curated_nous_model_ids", lambda: ["paid/expensive", "free/cheap"])
|
||||
monkeypatch.setattr(
|
||||
models_mod, "get_pricing_for_provider",
|
||||
lambda provider: {"paid/expensive": {"input": "1"}, "free/cheap": {"input": "0"}},
|
||||
)
|
||||
monkeypatch.setattr(models_mod, "check_nous_free_tier", lambda *, force_fresh=False: True)
|
||||
monkeypatch.setattr(
|
||||
models_mod, "union_with_portal_free_recommendations",
|
||||
lambda ids, pricing, url: (ids, pricing),
|
||||
)
|
||||
# Free partition keeps only the free model selectable.
|
||||
monkeypatch.setattr(
|
||||
models_mod, "partition_nous_models_by_tier",
|
||||
lambda ids, pricing, free_tier: (["free/cheap"], ["paid/expensive"]),
|
||||
)
|
||||
|
||||
resp = self.client.get("/api/model/recommended-default?provider=nous")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["provider"] == "nous"
|
||||
assert data["model"] == "free/cheap"
|
||||
assert data["free_tier"] is True
|
||||
|
||||
def test_recommended_default_nous_paid_uses_curated_default(self, monkeypatch):
|
||||
"""A paid Nous user gets the first curated/paid-augmented model."""
|
||||
import hermes_cli.models as models_mod
|
||||
|
||||
monkeypatch.setattr(models_mod, "get_curated_nous_model_ids", lambda: ["top/model", "other/model"])
|
||||
monkeypatch.setattr(models_mod, "get_pricing_for_provider", lambda provider: {})
|
||||
monkeypatch.setattr(models_mod, "check_nous_free_tier", lambda *, force_fresh=False: False)
|
||||
monkeypatch.setattr(
|
||||
models_mod, "union_with_portal_paid_recommendations",
|
||||
lambda ids, pricing, url: (ids, pricing),
|
||||
)
|
||||
|
||||
resp = self.client.get("/api/model/recommended-default?provider=nous")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["provider"] == "nous"
|
||||
assert data["model"] == "top/model"
|
||||
assert data["free_tier"] is False
|
||||
|
||||
def test_recommended_default_handles_failure_gracefully(self, monkeypatch):
|
||||
"""Endpoint never 500s — returns empty model on internal error."""
|
||||
import hermes_cli.models as models_mod
|
||||
|
||||
def boom():
|
||||
raise RuntimeError("portal down")
|
||||
|
||||
monkeypatch.setattr(models_mod, "get_curated_nous_model_ids", boom)
|
||||
|
||||
resp = self.client.get("/api/model/recommended-default?provider=nous")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["model"] == ""
|
||||
assert data["free_tier"] is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _build_schema_from_config tests
|
||||
|
||||
Reference in New Issue
Block a user