mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-24 02:36:27 +00:00
Stage 373: PR #2415 — fix: ignore provider config flags in model picker by @Michaelyklam (fixes #2399)
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- **PR #2415** by @Michaelyklam (fixes #2399) — `providers.only_configured` and other scalar flags under the top-level `providers:` config mapping no longer appear as fake provider groups in the model picker. Provider detection now only seeds picker groups from known provider ids/aliases or dict-shaped provider configs, so filtering flags cannot render as `Only-Configured`.
|
||||
|
||||
## [v0.51.79] — 2026-05-16 — Release BC (stage-372 — 5-PR batch — text-mode image history fix + Activity-group compression boundary + named custom provider routing + quota chip Settings toggle + RFC docs)
|
||||
|
||||
### Added
|
||||
|
||||
+21
-3
@@ -2957,6 +2957,13 @@ def get_available_models() -> dict:
|
||||
# A user may configure a provider key via config.yaml providers.<name>.api_key
|
||||
# without setting the corresponding env var. (#604)
|
||||
#
|
||||
# Gating: only seed picker groups for keys whose canonical id is known
|
||||
# to ``_PROVIDER_MODELS`` / ``_PROVIDER_DISPLAY``, or whose value is a
|
||||
# dict-shaped provider config (custom/local). Scalar siblings under
|
||||
# ``providers:`` (e.g. ``providers.only_configured: true``) are config
|
||||
# flags, not providers, and must not render as phantom picker groups
|
||||
# like ``Only-Configured`` (#2399).
|
||||
#
|
||||
# Canonicalise the id slug here so a user with ``providers.opencode_go``
|
||||
# (underscore variant) doesn't see TWO provider groups in the picker —
|
||||
# one for the canonical ``opencode-go`` from active_provider detection
|
||||
@@ -2969,13 +2976,24 @@ def get_available_models() -> dict:
|
||||
# provider_cfg values (#2245).
|
||||
_canonical_to_raw_provider_key: dict[str, str] = {}
|
||||
if isinstance(_cfg_providers, dict):
|
||||
for _pid_key in _cfg_providers:
|
||||
for _pid_key, _provider_cfg in _cfg_providers.items():
|
||||
_canonical = _canonicalise_provider_id(_pid_key)
|
||||
if not _canonical:
|
||||
continue
|
||||
|
||||
# See the gating comment on the block above. ``_PROVIDER_MODELS``
|
||||
# / ``_PROVIDER_DISPLAY`` membership accepts known providers and
|
||||
# aliases; ``isinstance(_provider_cfg, dict)`` accepts custom
|
||||
# entries that supply their own models/api_key/base_url. (#2399)
|
||||
_is_known_provider = (
|
||||
_canonical in _PROVIDER_MODELS or _canonical in _PROVIDER_DISPLAY
|
||||
)
|
||||
_is_provider_config = isinstance(_provider_cfg, dict)
|
||||
if not (_is_known_provider or _is_provider_config):
|
||||
continue
|
||||
|
||||
_canonical_to_raw_provider_key.setdefault(_canonical, _pid_key)
|
||||
if _canonical in _PROVIDER_MODELS or _canonical in _cfg_providers or _pid_key in _cfg_providers:
|
||||
detected_providers.add(_canonical)
|
||||
detected_providers.add(_canonical)
|
||||
|
||||
def _configured_provider_for_base_url(base_url: object) -> str:
|
||||
target = _normalize_base_url_for_match(base_url)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Regression coverage for provider-level config flags in the model picker."""
|
||||
|
||||
import pathlib
|
||||
|
||||
import api.config as config
|
||||
|
||||
|
||||
def _reset_models_cache():
|
||||
config._available_models_cache = None
|
||||
config._available_models_cache_ts = 0.0
|
||||
|
||||
|
||||
def _provider_ids(payload: dict) -> set[str]:
|
||||
return {str(group.get("provider_id") or "") for group in payload.get("groups", [])}
|
||||
|
||||
|
||||
def test_providers_only_configured_flag_does_not_create_picker_group(monkeypatch):
|
||||
"""providers.only_configured is a filter flag, not a provider id (#2399)."""
|
||||
_reset_models_cache()
|
||||
monkeypatch.setattr(
|
||||
config,
|
||||
"cfg",
|
||||
{
|
||||
"model": {"provider": "openai", "default": "gpt-4o-mini"},
|
||||
"providers": {
|
||||
"only_configured": True,
|
||||
"openai": {"models": ["gpt-4o-mini"]},
|
||||
},
|
||||
},
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(config, "_cfg_has_in_memory_overrides", lambda: True)
|
||||
monkeypatch.setattr(
|
||||
config,
|
||||
"_get_auth_store_path",
|
||||
lambda: pathlib.Path("/tmp/hermes-webui-missing-auth-store-issue2399.json"),
|
||||
)
|
||||
|
||||
try:
|
||||
payload = config.get_available_models()
|
||||
finally:
|
||||
_reset_models_cache()
|
||||
|
||||
provider_ids = _provider_ids(payload)
|
||||
assert "openai" in provider_ids
|
||||
assert "only-configured" not in provider_ids
|
||||
assert all("Only-Configured" not in str(group.get("provider")) for group in payload["groups"])
|
||||
|
||||
|
||||
def test_unknown_scalar_provider_config_flags_are_ignored(monkeypatch):
|
||||
"""Unknown scalar siblings under providers must not seed phantom groups."""
|
||||
_reset_models_cache()
|
||||
monkeypatch.setattr(
|
||||
config,
|
||||
"cfg",
|
||||
{
|
||||
"model": {"provider": "openai", "default": "gpt-4o-mini"},
|
||||
"providers": {
|
||||
"future_toggle": "enabled",
|
||||
"openai": {"models": ["gpt-4o-mini"]},
|
||||
},
|
||||
},
|
||||
raising=False,
|
||||
)
|
||||
monkeypatch.setattr(config, "_cfg_has_in_memory_overrides", lambda: True)
|
||||
monkeypatch.setattr(
|
||||
config,
|
||||
"_get_auth_store_path",
|
||||
lambda: pathlib.Path("/tmp/hermes-webui-missing-auth-store-issue2399.json"),
|
||||
)
|
||||
|
||||
try:
|
||||
payload = config.get_available_models()
|
||||
finally:
|
||||
_reset_models_cache()
|
||||
|
||||
provider_ids = _provider_ids(payload)
|
||||
assert "openai" in provider_ids
|
||||
assert "future-toggle" not in provider_ids
|
||||
Reference in New Issue
Block a user