feat(desktop): route Nous subscribers onto the Tool Gateway from the GUI
When the GUI sets the main provider to Nous via POST /api/model/set, call the same apply_nous_managed_defaults the CLI uses after model selection, so GUI/onboarding users land on the Nous Tool Gateway the same way CLI users do — no separate prompt, no duplicated logic. Purely additive: apply_nous_managed_defaults skips any tool where the user has a direct key (FIRECRAWL_API_KEY, FAL_KEY, etc.) or explicit config, so it never overwrites a user's own setup. Only unconfigured tools get routed. - web_server.py: in set_model_assignment (scope=main, provider=nous), resolve enabled toolsets and apply managed defaults; guarded so a Portal hiccup never blocks saving the model. Returns routed tools as gateway_tools. - onboarding.ts: surface a 'Tool Gateway enabled' toast listing routed tools. - types/hermes.ts: add gateway_tools to ModelAssignmentResponse. - tests: cover nous-applies, non-nous-skips, and failure-doesnt-block-save.
This commit is contained in:
@@ -605,6 +605,71 @@ class TestWebServerEndpoints:
|
||||
if resp.status_code == 200:
|
||||
assert "FastAPI" not in resp.text # Should not serve the actual source
|
||||
|
||||
def test_set_model_main_nous_applies_gateway_defaults(self, monkeypatch):
|
||||
"""Switching the main provider to Nous calls apply_nous_managed_defaults
|
||||
(mirroring the CLI's post-model-selection Tool Gateway routing) and
|
||||
surfaces the routed tools in the response."""
|
||||
import hermes_cli.nous_subscription as ns
|
||||
|
||||
called = {}
|
||||
|
||||
def fake_apply(config, *, enabled_toolsets=None, force_fresh=False):
|
||||
called["enabled"] = set(enabled_toolsets or ())
|
||||
called["force_fresh"] = force_fresh
|
||||
# Simulate routing the unconfigured web tool through the gateway.
|
||||
web = config.setdefault("web", {})
|
||||
web["backend"] = "firecrawl"
|
||||
return {"web"}
|
||||
|
||||
monkeypatch.setattr(ns, "apply_nous_managed_defaults", fake_apply)
|
||||
|
||||
resp = self.client.post(
|
||||
"/api/model/set",
|
||||
json={"scope": "main", "provider": "nous", "model": "hermes-4"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["ok"] is True
|
||||
assert data["provider"] == "nous"
|
||||
assert data["gateway_tools"] == ["web"]
|
||||
assert called["force_fresh"] is True
|
||||
|
||||
def test_set_model_main_non_nous_skips_gateway_defaults(self, monkeypatch):
|
||||
"""Non-Nous providers must NOT trigger Tool Gateway auto-routing."""
|
||||
import hermes_cli.nous_subscription as ns
|
||||
|
||||
def boom(*args, **kwargs): # pragma: no cover - must not be called
|
||||
raise AssertionError("apply_nous_managed_defaults called for non-nous provider")
|
||||
|
||||
monkeypatch.setattr(ns, "apply_nous_managed_defaults", boom)
|
||||
|
||||
resp = self.client.post(
|
||||
"/api/model/set",
|
||||
json={"scope": "main", "provider": "openrouter", "model": "anthropic/claude-opus-4.8"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["ok"] is True
|
||||
assert data.get("gateway_tools", []) == []
|
||||
|
||||
def test_set_model_main_gateway_failure_does_not_block_save(self, monkeypatch):
|
||||
"""A Portal/gateway hiccup must never prevent saving the model."""
|
||||
import hermes_cli.nous_subscription as ns
|
||||
|
||||
def boom(*args, **kwargs):
|
||||
raise RuntimeError("portal unreachable")
|
||||
|
||||
monkeypatch.setattr(ns, "apply_nous_managed_defaults", boom)
|
||||
|
||||
resp = self.client.post(
|
||||
"/api/model/set",
|
||||
json={"scope": "main", "provider": "nous", "model": "hermes-4"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["ok"] is True
|
||||
assert data.get("gateway_tools", []) == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _build_schema_from_config tests
|
||||
|
||||
Reference in New Issue
Block a user