From 666d385c03c499deb891989e48f5f6235d2c0e91 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Wed, 22 Apr 2026 22:56:21 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20Nous=20static=20models=20use=20@nous:=20?= =?UTF-8?q?prefix=20=E2=80=94=20v0.50.164=20(#885)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: Nous static models use @nous: prefix — v0.50.164 (#885) Follow-up to #854 / PR #870. The previous fix made Nous static IDs slash-prefixed and added a portal-guard branch to resolve_model_provider(). This tightens the static list to use the explicit @nous: prefix, matching the format of live-fetched models after ui.js's _fetchLiveModels() portal- prefix step. The @provider:model branch in resolve_model_provider() is more explicit and reliable than the portal-guard fallback. Both static and live-fetched paths now converge on the same resolver output — and as a side effect, the dedup check in _fetchLiveModels() now correctly identifies static entries as already present, eliminating duplicate entries in the dropdown for Nous users. Verified: all 29 Nous models in the browser dropdown carry @nous: prefix, routing confirmed correct via resolve_model_provider() for all 4 static IDs, 1941 tests passing. Closes #854. --- CHANGELOG.md | 3 ++ api/config.py | 8 ++--- tests/test_nous_portal_routing.py | 53 ++++++++++++++++++------------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9265d8c..2353aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Fixed +- **Nous static models now use explicit `@nous:` prefix** — the four hardcoded "(via Nous)" models (`Claude Opus 4.6`, `Claude Sonnet 4.6`, `GPT-5.4 Mini`, `Gemini 3.1 Pro Preview`) now carry `@nous:` prefix IDs, matching the format of live-fetched Nous models. Previously they used slash-only IDs that relied on the portal provider guard; the explicit prefix routes them through the same bulletproof `@provider:model` branch and eliminates 404 errors on those entries. (`api/config.py`, `tests/test_nous_portal_routing.py`) + ### Added - **Workspace path autocomplete in Spaces** — the "Add workspace path" field in the Spaces panel now suggests trusted directories as you type, supports diff --git a/api/config.py b/api/config.py index 7dbd94b..ca8038b 100644 --- a/api/config.py +++ b/api/config.py @@ -613,10 +613,10 @@ _PROVIDER_MODELS = { {"id": "deepseek-reasoner", "label": "DeepSeek Reasoner"}, ], "nous": [ - {"id": "anthropic/claude-opus-4.6", "label": "Claude Opus 4.6 (via Nous)"}, - {"id": "anthropic/claude-sonnet-4.6", "label": "Claude Sonnet 4.6 (via Nous)"}, - {"id": "openai/gpt-5.4-mini", "label": "GPT-5.4 Mini (via Nous)"}, - {"id": "google/gemini-3.1-pro-preview", "label": "Gemini 3.1 Pro Preview (via Nous)"}, + {"id": "@nous:anthropic/claude-opus-4.6", "label": "Claude Opus 4.6 (via Nous)"}, + {"id": "@nous:anthropic/claude-sonnet-4.6", "label": "Claude Sonnet 4.6 (via Nous)"}, + {"id": "@nous:openai/gpt-5.4-mini", "label": "GPT-5.4 Mini (via Nous)"}, + {"id": "@nous:google/gemini-3.1-pro-preview", "label": "Gemini 3.1 Pro Preview (via Nous)"}, ], "zai": [ {"id": "glm-5.1", "label": "GLM-5.1"}, diff --git a/tests/test_nous_portal_routing.py b/tests/test_nous_portal_routing.py index fbf62ef..5014364 100644 --- a/tests/test_nous_portal_routing.py +++ b/tests/test_nous_portal_routing.py @@ -36,45 +36,54 @@ def _models_with_provider(provider, monkeypatch): class TestNousModelIds: - """Nous static model IDs must be slash-prefixed for Nous API compatibility.""" + """Nous static model IDs must use @nous: prefix for explicit portal routing.""" - def test_nous_models_use_slash_prefixed_ids(self): - """All Nous static models must carry a provider/model slash prefix.""" + def test_nous_models_use_at_prefix(self): + """All Nous static models must carry the @nous: explicit provider prefix. + + This ensures they route through the @provider:model branch of + resolve_model_provider() — identical to the live-fetched path — rather + than relying on the slash-only portal provider guard. + """ from api.config import _PROVIDER_MODELS nous_models = _PROVIDER_MODELS.get("nous", []) assert nous_models, "Nous must have at least one static model" for m in nous_models: mid = m["id"] - assert "/" in mid, ( - f"Nous model '{mid}' must be in provider/model format " - f"(e.g. anthropic/claude-opus-4.6) so Nous routes it correctly. " - f"Bare names cause Nous to reject the request." + assert mid.startswith("@nous:"), ( + f"Nous model '{mid}' must start with '@nous:' " + f"(e.g. @nous:anthropic/claude-opus-4.6) so it routes through " + f"the explicit provider hint branch, not the weaker portal guard." ) def test_nous_known_models_present(self): - """Key Nous models must be present with correct slash-prefixed IDs.""" + """Key Nous models must be present with correct @nous:-prefixed IDs.""" from api.config import _PROVIDER_MODELS nous_ids = {m["id"] for m in _PROVIDER_MODELS.get("nous", [])} - assert "anthropic/claude-opus-4.6" in nous_ids, ( - "anthropic/claude-opus-4.6 must be in Nous model list" + assert "@nous:anthropic/claude-opus-4.6" in nous_ids, ( + "@nous:anthropic/claude-opus-4.6 must be in Nous model list" ) - assert "anthropic/claude-sonnet-4.6" in nous_ids, ( - "anthropic/claude-sonnet-4.6 must be in Nous model list" + assert "@nous:anthropic/claude-sonnet-4.6" in nous_ids, ( + "@nous:anthropic/claude-sonnet-4.6 must be in Nous model list" ) - assert "openai/gpt-5.4-mini" in nous_ids, ( - "openai/gpt-5.4-mini must be in Nous model list" + assert "@nous:openai/gpt-5.4-mini" in nous_ids, ( + "@nous:openai/gpt-5.4-mini must be in Nous model list" ) - def test_nous_models_no_bare_names(self): - """No Nous model should use a bare name without a slash prefix.""" + def test_nous_models_no_bare_or_slash_only(self): + """No Nous static model should be bare or slash-only without @nous: prefix.""" from api.config import _PROVIDER_MODELS - bare_names = {"claude-opus-4.6", "claude-sonnet-4.6", "gpt-5.4-mini", - "gemini-3.1-pro-preview"} + bad_ids = { + "claude-opus-4.6", "claude-sonnet-4.6", "gpt-5.4-mini", + "gemini-3.1-pro-preview", + "anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4.6", + "openai/gpt-5.4-mini", "google/gemini-3.1-pro-preview", + } nous_ids = {m["id"] for m in _PROVIDER_MODELS.get("nous", [])} - for bare in bare_names: - assert bare not in nous_ids, ( - f"Bare model ID '{bare}' found in Nous model list. " - f"Must be slash-prefixed (e.g. anthropic/{bare})." + for bad in bad_ids: + assert bad not in nous_ids, ( + f"Model ID '{bad}' found in Nous static list without @nous: prefix. " + f"Use '@nous:{bad}' so routing matches the live-fetched path." )