Fix Xiaomi API key env detection

This commit is contained in:
Frank Song committed 2026-05-11 07:33:52 +08:00
1 parent b997067ae8
commit 128e734df4
13 files changed
+95 -4

No files matched your search

+6
View File
@@ -1,5 +1,11 @@
# Hermes Web UI -- Changelog
## [Unreleased]
### Fixed
- **fix(providers): detect Xiaomi MiMo from `XIAOMI_API_KEY`** ([#2025](https://github.com/nesquena/hermes-webui/issues/2025)). WebUI now treats Xiaomi like the other API-key providers: exported or `.env`-stored `XIAOMI_API_KEY` enables the Xiaomi model group, Settings provider-key detection, and onboarding help text without requiring a manual provider config entry.
## [v0.51.39] — 2026-05-10 — Release O (4-PR contributor batch — Railway docker fix + Stop-button race + provider resolver + live context tracking)
### Fixed
+1 -1
View File
@@ -375,7 +375,7 @@ across 100+ test files.
### Chat and agent
- Streaming responses via SSE (tokens appear as they are generated)
- Multi-provider model support -- any Hermes API provider (OpenAI, Anthropic, Google, DeepSeek, Nous Portal, OpenRouter, MiniMax, Z.AI); dynamic model dropdown populated from configured keys
- Multi-provider model support -- any Hermes API provider (OpenAI, Anthropic, Google, DeepSeek, Nous Portal, OpenRouter, MiniMax, Xiaomi MiMo, Z.AI); dynamic model dropdown populated from configured keys
- Send a message while one is processing -- it queues automatically
- Edit any past user message inline and regenerate from that point
- Retry the last assistant response with one click
+3
View File
@@ -2760,6 +2760,7 @@ def get_available_models() -> dict:
"GLM_API_KEY",
"KIMI_API_KEY",
"DEEPSEEK_API_KEY",
"XIAOMI_API_KEY",
"OPENCODE_ZEN_API_KEY",
"OPENCODE_GO_API_KEY",
"MINIMAX_API_KEY",
@@ -2795,6 +2796,8 @@ def get_available_models() -> dict:
detected_providers.add("minimax-cn")
if all_env.get("DEEPSEEK_API_KEY"):
detected_providers.add("deepseek")
if all_env.get("XIAOMI_API_KEY"):
detected_providers.add("xiaomi")
if all_env.get("XAI_API_KEY"):
detected_providers.add("x-ai")
if all_env.get("MISTRAL_API_KEY"):
+9
View File
@@ -139,6 +139,15 @@ _SUPPORTED_PROVIDER_SETUPS = {
"models": list(_PROVIDER_MODELS.get("deepseek", [])),
"category": "specialized",
},
"xiaomi": {
"label": "Xiaomi MiMo",
"env_var": "XIAOMI_API_KEY",
"default_model": "mimo-v2.5-pro",
"default_base_url": "https://api.xiaomimimo.com/v1",
"requires_base_url": False,
"models": list(_PROVIDER_MODELS.get("xiaomi", [])),
"category": "specialized",
},
"zai": {
"label": "Z.AI / GLM (智谱)",
"env_var": "GLM_API_KEY",
+1
View File
@@ -102,6 +102,7 @@ _PROVIDER_ENV_VAR: dict[str, str] = {
"minimax-cn": "MINIMAX_CN_API_KEY",
"mistralai": "MISTRAL_API_KEY",
"x-ai": "XAI_API_KEY",
"xiaomi": "XIAOMI_API_KEY",
"opencode-zen": "OPENCODE_ZEN_API_KEY",
"opencode-go": "OPENCODE_GO_API_KEY",
# NOTE: bare "ollama" (local) deliberately omitted — local Ollama is keyless
+1 -1
View File
@@ -291,7 +291,7 @@ def test_server():
for _k in list(env):
if any(_k.startswith(p) for p in (
'OPENROUTER_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY',
'GOOGLE_API_KEY', 'DEEPSEEK_API_KEY',
'GOOGLE_API_KEY', 'DEEPSEEK_API_KEY', 'XIAOMI_API_KEY',
)):
del env[_k]
env.update({
+1 -1
View File
@@ -85,7 +85,7 @@ def _setup_clean_config(monkeypatch, tmp_path):
"OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY",
"GOOGLE_API_KEY", "GEMINI_API_KEY", "GLM_API_KEY",
"KIMI_API_KEY", "DEEPSEEK_API_KEY", "MINIMAX_API_KEY",
"MISTRAL_API_KEY", "XAI_API_KEY", "OLLAMA_API_KEY",
"XIAOMI_API_KEY", "MISTRAL_API_KEY", "XAI_API_KEY", "OLLAMA_API_KEY",
"OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY",
]
for var in _provider_env_vars:
@@ -22,6 +22,7 @@ _PROVIDER_ENV_VARS = (
"GLM_API_KEY",
"KIMI_API_KEY",
"DEEPSEEK_API_KEY",
"XIAOMI_API_KEY",
"OPENCODE_ZEN_API_KEY",
"OPENCODE_GO_API_KEY",
"MINIMAX_API_KEY",
@@ -243,7 +243,7 @@ class TestIssue1420LMStudioProviderEnvVar:
"OPENAI_API_KEY", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY",
"GH_TOKEN", "GITHUB_TOKEN", "OLLAMA_API_KEY", "GOOGLE_API_KEY",
"GEMINI_API_KEY", "DEEPSEEK_API_KEY", "MINIMAX_API_KEY",
"MINIMAX_CN_API_KEY", "MISTRAL_API_KEY", "XAI_API_KEY",
"MINIMAX_CN_API_KEY", "XIAOMI_API_KEY", "MISTRAL_API_KEY", "XAI_API_KEY",
"GLM_API_KEY", "KIMI_API_KEY", "OPENCODE_ZEN_API_KEY",
"OPENCODE_GO_API_KEY", "NVIDIA_API_KEY", "LMSTUDIO_API_KEY",
):
@@ -126,6 +126,7 @@ def _scrub_provider_env(monkeypatch):
"OLLAMA_CLOUD_API_KEY", "OLLAMA_API_KEY",
"GLM_API_KEY", "KIMI_API_KEY", "MOONSHOT_API_KEY",
"MINIMAX_API_KEY", "MINIMAX_CN_API_KEY",
"XIAOMI_API_KEY",
"OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY",
"NOUS_API_KEY", "NVIDIA_API_KEY", "LM_API_KEY", "LMSTUDIO_API_KEY",
):
@@ -127,6 +127,7 @@ def _scrub_provider_env(monkeypatch):
"OLLAMA_CLOUD_API_KEY", "OLLAMA_API_KEY",
"GLM_API_KEY", "KIMI_API_KEY", "MOONSHOT_API_KEY",
"MINIMAX_API_KEY", "MINIMAX_CN_API_KEY",
"XIAOMI_API_KEY",
"OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY",
"NOUS_API_KEY", "NVIDIA_API_KEY", "LM_API_KEY", "LMSTUDIO_API_KEY",
):
@@ -85,6 +85,7 @@ def _scrub_provider_env(monkeypatch):
"OLLAMA_CLOUD_API_KEY", "OLLAMA_API_KEY",
"GLM_API_KEY", "KIMI_API_KEY", "MOONSHOT_API_KEY",
"MINIMAX_API_KEY", "MINIMAX_CN_API_KEY",
"XIAOMI_API_KEY",
"OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY",
"NOUS_API_KEY", "NVIDIA_API_KEY", "LM_API_KEY", "LMSTUDIO_API_KEY",
):
+68
View File
@@ -0,0 +1,68 @@
"""Regression coverage for #2025: Xiaomi MiMo should honor XIAOMI_API_KEY."""
from __future__ import annotations
import builtins
import api.config as config
import api.onboarding as onboarding
import api.providers as providers
def _force_env_fallback(monkeypatch):
"""Force get_available_models() down its explicit env-var fallback path."""
real_import = builtins.__import__
def fake_import(name, globals=None, locals=None, fromlist=(), level=0):
if name in ("hermes_cli.models", "hermes_cli.auth"):
raise ImportError(name)
return real_import(name, globals, locals, fromlist, level)
monkeypatch.setattr(builtins, "__import__", fake_import)
def _run_available_models_with_cfg(monkeypatch, tmp_path, cfg):
old_cfg = dict(config.cfg)
old_mtime = config._cfg_mtime
monkeypatch.setattr(config, "_models_cache_path", tmp_path / "models_cache.json")
monkeypatch.setattr(config, "_get_config_path", lambda: tmp_path / "missing-config.yaml")
monkeypatch.setattr("api.profiles.get_active_hermes_home", lambda: tmp_path, raising=False)
config.cfg.clear()
config.cfg.update(cfg)
config._cfg_mtime = 0.0
config.invalidate_models_cache()
try:
return config.get_available_models()
finally:
config.cfg.clear()
config.cfg.update(old_cfg)
config._cfg_mtime = old_mtime
config.invalidate_models_cache()
def test_xiaomi_api_key_env_var_detects_model_group(monkeypatch, tmp_path):
_force_env_fallback(monkeypatch)
monkeypatch.setenv("XIAOMI_API_KEY", "test-xiaomi-key")
result = _run_available_models_with_cfg(monkeypatch, tmp_path, {"model": {}})
groups = {group["provider_id"]: group for group in result["groups"]}
assert "xiaomi" in groups
assert groups["xiaomi"]["provider"] == "Xiaomi"
assert "mimo-v2.5-pro" in {model["id"] for model in groups["xiaomi"]["models"]}
def test_xiaomi_provider_settings_detects_env_key(monkeypatch, tmp_path):
monkeypatch.setattr(providers, "_get_hermes_home", lambda: tmp_path)
monkeypatch.setenv("XIAOMI_API_KEY", "test-xiaomi-key")
assert providers._PROVIDER_ENV_VAR["xiaomi"] == "XIAOMI_API_KEY"
assert providers._provider_has_key("xiaomi") is True
def test_onboarding_lists_xiaomi_api_key_help():
setup = onboarding._SUPPORTED_PROVIDER_SETUPS["xiaomi"]
assert setup["env_var"] == "XIAOMI_API_KEY"
assert setup["default_base_url"] == "https://api.xiaomimimo.com/v1"
assert {model["id"] for model in setup["models"]} >= {"mimo-v2.5-pro"}