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:
@@ -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
|
||||||
|
|||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user