fix: Nous static models use @nous: prefix — v0.50.164 (#885)

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.
This commit is contained in:
nesquena-hermes
2026-04-22 22:56:21 -07:00
committed by GitHub
parent d39d30a213
commit 666d385c03
3 changed files with 38 additions and 26 deletions

View File

@@ -2,6 +2,9 @@
## [Unreleased] ## [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 ### Added
- **Workspace path autocomplete in Spaces** — the "Add workspace path" field in - **Workspace path autocomplete in Spaces** — the "Add workspace path" field in
the Spaces panel now suggests trusted directories as you type, supports the Spaces panel now suggests trusted directories as you type, supports

View File

@@ -613,10 +613,10 @@ _PROVIDER_MODELS = {
{"id": "deepseek-reasoner", "label": "DeepSeek Reasoner"}, {"id": "deepseek-reasoner", "label": "DeepSeek Reasoner"},
], ],
"nous": [ "nous": [
{"id": "anthropic/claude-opus-4.6", "label": "Claude Opus 4.6 (via Nous)"}, {"id": "@nous: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": "@nous: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": "@nous: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:google/gemini-3.1-pro-preview", "label": "Gemini 3.1 Pro Preview (via Nous)"},
], ],
"zai": [ "zai": [
{"id": "glm-5.1", "label": "GLM-5.1"}, {"id": "glm-5.1", "label": "GLM-5.1"},

View File

@@ -36,45 +36,54 @@ def _models_with_provider(provider, monkeypatch):
class TestNousModelIds: 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): def test_nous_models_use_at_prefix(self):
"""All Nous static models must carry a provider/model slash prefix.""" """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 from api.config import _PROVIDER_MODELS
nous_models = _PROVIDER_MODELS.get("nous", []) nous_models = _PROVIDER_MODELS.get("nous", [])
assert nous_models, "Nous must have at least one static model" assert nous_models, "Nous must have at least one static model"
for m in nous_models: for m in nous_models:
mid = m["id"] mid = m["id"]
assert "/" in mid, ( assert mid.startswith("@nous:"), (
f"Nous model '{mid}' must be in provider/model format " f"Nous model '{mid}' must start with '@nous:' "
f"(e.g. anthropic/claude-opus-4.6) so Nous routes it correctly. " f"(e.g. @nous:anthropic/claude-opus-4.6) so it routes through "
f"Bare names cause Nous to reject the request." f"the explicit provider hint branch, not the weaker portal guard."
) )
def test_nous_known_models_present(self): 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 from api.config import _PROVIDER_MODELS
nous_ids = {m["id"] for m in _PROVIDER_MODELS.get("nous", [])} nous_ids = {m["id"] for m in _PROVIDER_MODELS.get("nous", [])}
assert "anthropic/claude-opus-4.6" in nous_ids, ( assert "@nous:anthropic/claude-opus-4.6" in nous_ids, (
"anthropic/claude-opus-4.6 must be in Nous model list" "@nous:anthropic/claude-opus-4.6 must be in Nous model list"
) )
assert "anthropic/claude-sonnet-4.6" in nous_ids, ( assert "@nous:anthropic/claude-sonnet-4.6" in nous_ids, (
"anthropic/claude-sonnet-4.6 must be in Nous model list" "@nous:anthropic/claude-sonnet-4.6 must be in Nous model list"
) )
assert "openai/gpt-5.4-mini" in nous_ids, ( assert "@nous:openai/gpt-5.4-mini" in nous_ids, (
"openai/gpt-5.4-mini must be in Nous model list" "@nous:openai/gpt-5.4-mini must be in Nous model list"
) )
def test_nous_models_no_bare_names(self): def test_nous_models_no_bare_or_slash_only(self):
"""No Nous model should use a bare name without a slash prefix.""" """No Nous static model should be bare or slash-only without @nous: prefix."""
from api.config import _PROVIDER_MODELS from api.config import _PROVIDER_MODELS
bare_names = {"claude-opus-4.6", "claude-sonnet-4.6", "gpt-5.4-mini", bad_ids = {
"gemini-3.1-pro-preview"} "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", [])} nous_ids = {m["id"] for m in _PROVIDER_MODELS.get("nous", [])}
for bare in bare_names: for bad in bad_ids:
assert bare not in nous_ids, ( assert bad not in nous_ids, (
f"Bare model ID '{bare}' found in Nous model list. " f"Model ID '{bad}' found in Nous static list without @nous: prefix. "
f"Must be slash-prefixed (e.g. anthropic/{bare})." f"Use '@nous:{bad}' so routing matches the live-fetched path."
) )