diff --git a/.env.example b/.env.example index c8c4af9b3..a6e98751a 100644 --- a/.env.example +++ b/.env.example @@ -89,6 +89,15 @@ # Optional base URL override: # HERMES_QWEN_BASE_URL=https://portal.qwen.ai/v1 +# ============================================================================= +# LLM PROVIDER (Xiaomi MiMo) +# ============================================================================= +# Xiaomi MiMo models (mimo-v2-pro, mimo-v2-omni, mimo-v2-flash). +# Get your key at: https://platform.xiaomimimo.com +# XIAOMI_API_KEY=your_key_here +# Optional base URL override: +# XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1 + # ============================================================================= # TOOL API KEYS # ============================================================================= diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index e48f9c2c3..cacd9bdd4 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -109,6 +109,15 @@ _API_KEY_PROVIDER_AUX_MODELS: Dict[str, str] = { "opencode-zen": "gemini-3-flash", "opencode-go": "glm-5", "kilocode": "google/gemini-3-flash-preview", + "xiaomi": "mimo-v2-flash", +} + +# Vision-specific model overrides for direct providers. +# When the user's main provider has a dedicated vision/multimodal model that +# differs from their main chat model, map it here. The vision auto-detect +# "exotic provider" branch checks this before falling back to the main model. +_PROVIDER_VISION_MODELS: Dict[str, str] = { + "xiaomi": "mimo-v2-omni", } # OpenRouter app attribution headers @@ -1687,16 +1696,18 @@ def resolve_vision_provider_client( if sync_client is not None: return _finalize(main_provider, sync_client, default_model) else: - # Exotic provider (DeepSeek, Alibaba, named custom, etc.) + # Exotic provider (DeepSeek, Alibaba, Xiaomi, named custom, etc.) + # Use provider-specific vision model if available, otherwise main model. + vision_model = _PROVIDER_VISION_MODELS.get(main_provider, main_model) rpc_client, rpc_model = resolve_provider_client( - main_provider, main_model) + main_provider, vision_model) if rpc_client is not None: logger.info( "Vision auto-detect: using active provider %s (%s)", - main_provider, rpc_model or main_model, + main_provider, rpc_model or vision_model, ) return _finalize( - main_provider, rpc_client, rpc_model or main_model) + main_provider, rpc_client, rpc_model or vision_model) # Fall back through aggregators. for candidate in _VISION_AUTO_PROVIDER_ORDER: diff --git a/agent/model_metadata.py b/agent/model_metadata.py index 2ce0cefa0..4b0b98b67 100644 --- a/agent/model_metadata.py +++ b/agent/model_metadata.py @@ -27,12 +27,14 @@ _PROVIDER_PREFIXES: frozenset[str] = frozenset({ "gemini", "zai", "kimi-coding", "minimax", "minimax-cn", "anthropic", "deepseek", "opencode-zen", "opencode-go", "ai-gateway", "kilocode", "alibaba", "qwen-oauth", + "xiaomi", "custom", "local", # Common aliases "google", "google-gemini", "google-ai-studio", "glm", "z-ai", "z.ai", "zhipu", "github", "github-copilot", "github-models", "kimi", "moonshot", "claude", "deep-seek", "opencode", "zen", "go", "vercel", "kilo", "dashscope", "aliyun", "qwen", + "mimo", "xiaomi-mimo", "qwen-portal", }) @@ -150,8 +152,9 @@ DEFAULT_CONTEXT_LENGTHS = { "moonshotai/Kimi-K2-Thinking": 262144, "MiniMaxAI/MiniMax-M2.5": 204800, "XiaomiMiMo/MiMo-V2-Flash": 32768, - "mimo-v2-pro": 1048576, - "mimo-v2-omni": 1048576, + "mimo-v2-pro": 1000000, + "mimo-v2-omni": 256000, + "mimo-v2-flash": 256000, "zai-org/GLM-5": 202752, } @@ -211,6 +214,8 @@ _URL_TO_PROVIDER: Dict[str, str] = { "api.fireworks.ai": "fireworks", "opencode.ai": "opencode-go", "api.x.ai": "xai", + "api.xiaomimimo.com": "xiaomi", + "xiaomimimo.com": "xiaomi", } diff --git a/agent/models_dev.py b/agent/models_dev.py index 560e7cefe..f9eb49dbf 100644 --- a/agent/models_dev.py +++ b/agent/models_dev.py @@ -161,6 +161,7 @@ PROVIDER_TO_MODELS_DEV: Dict[str, str] = { "gemini": "google", "google": "google", "xai": "xai", + "xiaomi": "xiaomi", "nvidia": "nvidia", "groq": "groq", "mistral": "mistral", diff --git a/cli-config.yaml.example b/cli-config.yaml.example index e9284d813..12e2b3999 100644 --- a/cli-config.yaml.example +++ b/cli-config.yaml.example @@ -24,6 +24,7 @@ model: # "minimax" - MiniMax global (requires: MINIMAX_API_KEY) # "minimax-cn" - MiniMax China (requires: MINIMAX_CN_API_KEY) # "huggingface" - Hugging Face Inference (requires: HF_TOKEN) + # "xiaomi" - Xiaomi MiMo (requires: XIAOMI_API_KEY) # "kilocode" - KiloCode gateway (requires: KILOCODE_API_KEY) # "ai-gateway" - Vercel AI Gateway (requires: AI_GATEWAY_API_KEY) # diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index c209a8b47..fcb7c2dc5 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -250,6 +250,14 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = { api_key_env_vars=("HF_TOKEN",), base_url_env_var="HF_BASE_URL", ), + "xiaomi": ProviderConfig( + id="xiaomi", + name="Xiaomi MiMo", + auth_type="api_key", + inference_base_url="https://api.xiaomimimo.com/v1", + api_key_env_vars=("XIAOMI_API_KEY",), + base_url_env_var="XIAOMI_BASE_URL", + ), } @@ -908,6 +916,7 @@ def resolve_provider( "opencode": "opencode-zen", "zen": "opencode-zen", "qwen-portal": "qwen-oauth", "qwen-cli": "qwen-oauth", "qwen-oauth": "qwen-oauth", "hf": "huggingface", "hugging-face": "huggingface", "huggingface-hub": "huggingface", + "mimo": "xiaomi", "xiaomi-mimo": "xiaomi", "go": "opencode-go", "opencode-go-sub": "opencode-go", "kilo": "kilocode", "kilo-code": "kilocode", "kilo-gateway": "kilocode", # Local server aliases — route through the generic custom provider diff --git a/hermes_cli/config.py b/hermes_cli/config.py index e088bdfdf..1738a8f88 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -868,6 +868,21 @@ OPTIONAL_ENV_VARS = { "category": "provider", "advanced": True, }, + "XIAOMI_API_KEY": { + "description": "Xiaomi MiMo API key for MiMo models (mimo-v2-pro, mimo-v2-omni, mimo-v2-flash)", + "prompt": "Xiaomi MiMo API Key", + "url": "https://platform.xiaomimimo.com", + "password": True, + "category": "provider", + }, + "XIAOMI_BASE_URL": { + "description": "Xiaomi MiMo base URL override (default: https://api.xiaomimimo.com/v1)", + "prompt": "Xiaomi base URL (leave empty for default)", + "url": None, + "password": False, + "category": "provider", + "advanced": True, + }, # ── Tool API keys ── "EXA_API_KEY": { diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index 46242b68c..f5f8a228a 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -51,6 +51,7 @@ _PROVIDER_ENV_HINTS = ( "AI_GATEWAY_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY", + "XIAOMI_API_KEY", ) diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 08d5c50b0..44c0f8afa 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -934,6 +934,7 @@ def select_provider_and_model(args=None): "kilocode": "Kilo Code", "alibaba": "Alibaba Cloud (DashScope)", "huggingface": "Hugging Face", + "xiaomi": "Xiaomi MiMo", "custom": "Custom endpoint", } active_label = provider_labels.get(active, active) if active else "none" @@ -966,6 +967,7 @@ def select_provider_and_model(args=None): ("opencode-go", "OpenCode Go (open models, $10/month subscription)"), ("ai-gateway", "AI Gateway (Vercel — 200+ models, pay-per-use)"), ("alibaba", "Alibaba Cloud / DashScope Coding (Qwen + multi-provider)"), + ("xiaomi", "Xiaomi MiMo (MiMo-V2 models — pro, omni, flash)"), ] def _named_custom_provider_map(cfg) -> dict[str, dict[str, str]]: @@ -1077,7 +1079,7 @@ def select_provider_and_model(args=None): _model_flow_anthropic(config, current_model) elif selected_provider == "kimi-coding": _model_flow_kimi(config, current_model) - elif selected_provider in ("gemini", "zai", "minimax", "minimax-cn", "kilocode", "opencode-zen", "opencode-go", "ai-gateway", "alibaba", "huggingface"): + elif selected_provider in ("gemini", "zai", "minimax", "minimax-cn", "kilocode", "opencode-zen", "opencode-go", "ai-gateway", "alibaba", "huggingface", "xiaomi"): _model_flow_api_key_provider(config, selected_provider, current_model) # ── Post-switch cleanup: clear stale OPENAI_BASE_URL ────────────── @@ -4357,7 +4359,7 @@ For more help on a command: ) chat_parser.add_argument( "--provider", - choices=["auto", "openrouter", "nous", "openai-codex", "copilot-acp", "copilot", "anthropic", "gemini", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode"], + choices=["auto", "openrouter", "nous", "openai-codex", "copilot-acp", "copilot", "anthropic", "gemini", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "xiaomi"], default=None, help="Inference provider (default: auto)" ) diff --git a/hermes_cli/model_normalize.py b/hermes_cli/model_normalize.py index 780c638f5..8c0c30fbf 100644 --- a/hermes_cli/model_normalize.py +++ b/hermes_cli/model_normalize.py @@ -92,6 +92,7 @@ _MATCHING_PREFIX_STRIP_PROVIDERS: frozenset[str] = frozenset({ "minimax-cn", "alibaba", "qwen-oauth", + "xiaomi", "custom", }) diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 5da9824f3..17c1072db 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -188,6 +188,11 @@ _PROVIDER_MODELS: dict[str, list[str]] = { "deepseek-chat", "deepseek-reasoner", ], + "xiaomi": [ + "mimo-v2-pro", + "mimo-v2-omni", + "mimo-v2-flash", + ], "opencode-zen": [ "gpt-5.4-pro", "gpt-5.4", @@ -493,6 +498,7 @@ _PROVIDER_LABELS = { "alibaba": "Alibaba Cloud (DashScope)", "qwen-oauth": "Qwen OAuth (Portal)", "huggingface": "Hugging Face", + "xiaomi": "Xiaomi MiMo", "custom": "Custom endpoint", } @@ -535,6 +541,8 @@ _PROVIDER_ALIASES = { "hf": "huggingface", "hugging-face": "huggingface", "huggingface-hub": "huggingface", + "mimo": "xiaomi", + "xiaomi-mimo": "xiaomi", } @@ -819,7 +827,7 @@ def list_available_providers() -> list[dict[str, str]]: "openrouter", "nous", "openai-codex", "copilot", "copilot-acp", "gemini", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "anthropic", "alibaba", - "qwen-oauth", + "qwen-oauth", "xiaomi", "opencode-zen", "opencode-go", "ai-gateway", "deepseek", "custom", ] diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index 78be527db..a99763498 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -132,6 +132,10 @@ HERMES_OVERLAYS: Dict[str, HermesOverlay] = { base_url_override="https://api.x.ai/v1", base_url_env_var="XAI_BASE_URL", ), + "xiaomi": HermesOverlay( + transport="openai_chat", + base_url_env_var="XIAOMI_BASE_URL", + ), } @@ -222,6 +226,10 @@ ALIASES: Dict[str, str] = { "hugging-face": "huggingface", "huggingface-hub": "huggingface", + # xiaomi + "mimo": "xiaomi", + "xiaomi-mimo": "xiaomi", + # Local server aliases → virtual "local" concept (resolved via user config) "lmstudio": "lmstudio", "lm-studio": "lmstudio", @@ -242,6 +250,7 @@ _LABEL_OVERRIDES: Dict[str, str] = { "nous": "Nous Portal", "openai-codex": "OpenAI Codex", "copilot-acp": "GitHub Copilot ACP", + "xiaomi": "Xiaomi MiMo", "local": "Local endpoint", } diff --git a/tests/hermes_cli/test_xiaomi_provider.py b/tests/hermes_cli/test_xiaomi_provider.py new file mode 100644 index 000000000..5a1deb181 --- /dev/null +++ b/tests/hermes_cli/test_xiaomi_provider.py @@ -0,0 +1,327 @@ +"""Tests for Xiaomi MiMo provider support.""" + +import os +import sys +import types + +import pytest + +# Ensure dotenv doesn't interfere +if "dotenv" not in sys.modules: + fake_dotenv = types.ModuleType("dotenv") + fake_dotenv.load_dotenv = lambda *args, **kwargs: None + sys.modules["dotenv"] = fake_dotenv + +from hermes_cli.auth import ( + PROVIDER_REGISTRY, + resolve_provider, + get_api_key_provider_status, + resolve_api_key_provider_credentials, + AuthError, +) + + +# ============================================================================= +# Provider Registry +# ============================================================================= + + +class TestXiaomiProviderRegistry: + """Verify Xiaomi is registered correctly in the PROVIDER_REGISTRY.""" + + def test_registered(self): + assert "xiaomi" in PROVIDER_REGISTRY + + def test_name(self): + assert PROVIDER_REGISTRY["xiaomi"].name == "Xiaomi MiMo" + + def test_auth_type(self): + assert PROVIDER_REGISTRY["xiaomi"].auth_type == "api_key" + + def test_inference_base_url(self): + assert PROVIDER_REGISTRY["xiaomi"].inference_base_url == "https://api.xiaomimimo.com/v1" + + def test_api_key_env_vars(self): + assert PROVIDER_REGISTRY["xiaomi"].api_key_env_vars == ("XIAOMI_API_KEY",) + + def test_base_url_env_var(self): + assert PROVIDER_REGISTRY["xiaomi"].base_url_env_var == "XIAOMI_BASE_URL" + + +# ============================================================================= +# Aliases +# ============================================================================= + + +class TestXiaomiAliases: + """All aliases should resolve to 'xiaomi'.""" + + @pytest.mark.parametrize("alias", [ + "xiaomi", "mimo", "xiaomi-mimo", + ]) + def test_alias_resolves(self, alias, monkeypatch): + # Clear env to avoid auto-detection interfering + for key in ("XIAOMI_API_KEY",): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("XIAOMI_API_KEY", "sk-test-key-12345678") + assert resolve_provider(alias) == "xiaomi" + + def test_normalize_provider_models_py(self): + from hermes_cli.models import normalize_provider + assert normalize_provider("mimo") == "xiaomi" + assert normalize_provider("xiaomi-mimo") == "xiaomi" + + def test_normalize_provider_providers_py(self): + from hermes_cli.providers import normalize_provider + assert normalize_provider("mimo") == "xiaomi" + assert normalize_provider("xiaomi-mimo") == "xiaomi" + + +# ============================================================================= +# Auto-detection +# ============================================================================= + + +class TestXiaomiAutoDetection: + """Setting XIAOMI_API_KEY should auto-detect the provider.""" + + def test_auto_detect(self, monkeypatch): + # Clear all other provider env vars + for var in ("OPENROUTER_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", + "DEEPSEEK_API_KEY", "GOOGLE_API_KEY", "GEMINI_API_KEY", + "DASHSCOPE_API_KEY", "XAI_API_KEY", "KIMI_API_KEY", + "MINIMAX_API_KEY", "AI_GATEWAY_API_KEY", "KILOCODE_API_KEY", + "HF_TOKEN", "GLM_API_KEY", "COPILOT_GITHUB_TOKEN", + "GH_TOKEN", "GITHUB_TOKEN", "MINIMAX_CN_API_KEY"): + monkeypatch.delenv(var, raising=False) + monkeypatch.setenv("XIAOMI_API_KEY", "sk-xiaomi-test-12345678") + provider = resolve_provider("auto") + assert provider == "xiaomi" + + +# ============================================================================= +# Credentials +# ============================================================================= + + +class TestXiaomiCredentials: + """Test credential resolution for the xiaomi provider.""" + + def test_status_configured(self, monkeypatch): + monkeypatch.setenv("XIAOMI_API_KEY", "sk-test-12345678") + status = get_api_key_provider_status("xiaomi") + assert status["configured"] + + def test_status_not_configured(self, monkeypatch): + monkeypatch.delenv("XIAOMI_API_KEY", raising=False) + status = get_api_key_provider_status("xiaomi") + assert not status["configured"] + + def test_resolve_credentials(self, monkeypatch): + monkeypatch.setenv("XIAOMI_API_KEY", "sk-test-12345678") + monkeypatch.delenv("XIAOMI_BASE_URL", raising=False) + creds = resolve_api_key_provider_credentials("xiaomi") + assert creds["api_key"] == "sk-test-12345678" + assert creds["base_url"] == "https://api.xiaomimimo.com/v1" + + def test_custom_base_url_override(self, monkeypatch): + monkeypatch.setenv("XIAOMI_API_KEY", "sk-test-12345678") + monkeypatch.setenv("XIAOMI_BASE_URL", "https://custom.xiaomi.example/v1") + creds = resolve_api_key_provider_credentials("xiaomi") + assert creds["base_url"] == "https://custom.xiaomi.example/v1" + + +# ============================================================================= +# Model catalog (dynamic — no static list) +# ============================================================================= + + +class TestXiaomiModelCatalog: + """Xiaomi uses dynamic model discovery via models.dev.""" + + def test_models_dev_mapping(self): + from agent.models_dev import PROVIDER_TO_MODELS_DEV + assert PROVIDER_TO_MODELS_DEV["xiaomi"] == "xiaomi" + + def test_static_model_list_fallback(self): + """Static _PROVIDER_MODELS fallback must exist for model picker.""" + from hermes_cli.models import _PROVIDER_MODELS + assert "xiaomi" in _PROVIDER_MODELS + models = _PROVIDER_MODELS["xiaomi"] + assert "mimo-v2-pro" in models + assert "mimo-v2-omni" in models + assert "mimo-v2-flash" in models + + def test_list_agentic_models_mock(self, monkeypatch): + """When models.dev returns Xiaomi data, list_agentic_models should return models.""" + from agent import models_dev as md + + fake_data = { + "xiaomi": { + "name": "Xiaomi", + "api": "https://api.xiaomimimo.com/v1", + "env": ["XIAOMI_API_KEY"], + "models": { + "mimo-v2-pro": { + "limit": {"context": 1000000}, + "tool_call": True, + }, + "mimo-v2-omni": { + "limit": {"context": 256000}, + "tool_call": True, + }, + "mimo-v2-flash": { + "limit": {"context": 256000}, + "tool_call": True, + }, + }, + } + } + monkeypatch.setattr(md, "fetch_models_dev", lambda: fake_data) + + result = md.list_agentic_models("xiaomi") + assert "mimo-v2-pro" in result + assert "mimo-v2-flash" in result + + +# ============================================================================= +# Normalization +# ============================================================================= + + +class TestXiaomiNormalization: + """Model name normalization — Xiaomi is a direct provider.""" + + def test_vendor_prefix_mapping(self): + from hermes_cli.model_normalize import _VENDOR_PREFIXES + assert _VENDOR_PREFIXES.get("mimo") == "xiaomi" + + def test_matching_prefix_strip(self): + """xiaomi/mimo-v2-pro should normalize to mimo-v2-pro for direct API.""" + from hermes_cli.model_normalize import _MATCHING_PREFIX_STRIP_PROVIDERS + assert "xiaomi" in _MATCHING_PREFIX_STRIP_PROVIDERS + + def test_normalize_strips_provider_prefix(self): + from hermes_cli.model_normalize import normalize_model_for_provider + result = normalize_model_for_provider("xiaomi/mimo-v2-pro", "xiaomi") + assert result == "mimo-v2-pro" + + def test_normalize_bare_name_unchanged(self): + from hermes_cli.model_normalize import normalize_model_for_provider + result = normalize_model_for_provider("mimo-v2-pro", "xiaomi") + assert result == "mimo-v2-pro" + + +# ============================================================================= +# URL mapping +# ============================================================================= + + +class TestXiaomiURLMapping: + """Test URL → provider inference for Xiaomi endpoints.""" + + def test_url_to_provider(self): + from agent.model_metadata import _URL_TO_PROVIDER + assert _URL_TO_PROVIDER.get("api.xiaomimimo.com") == "xiaomi" + + def test_provider_prefixes(self): + from agent.model_metadata import _PROVIDER_PREFIXES + assert "xiaomi" in _PROVIDER_PREFIXES + assert "mimo" in _PROVIDER_PREFIXES + assert "xiaomi-mimo" in _PROVIDER_PREFIXES + + def test_infer_from_url(self): + from agent.model_metadata import _infer_provider_from_url + assert _infer_provider_from_url("https://api.xiaomimimo.com/v1") == "xiaomi" + + def test_infer_from_regional_urls(self): + """Regional token-plan endpoints should also resolve to xiaomi.""" + from agent.model_metadata import _infer_provider_from_url + assert _infer_provider_from_url("https://token-plan-ams.xiaomimimo.com/v1") == "xiaomi" + assert _infer_provider_from_url("https://token-plan-cn.xiaomimimo.com/v1") == "xiaomi" + assert _infer_provider_from_url("https://token-plan-sgp.xiaomimimo.com/v1") == "xiaomi" + + +# ============================================================================= +# providers.py +# ============================================================================= + + +class TestXiaomiProvidersModule: + """Test Xiaomi in the unified providers module.""" + + def test_overlay_exists(self): + from hermes_cli.providers import HERMES_OVERLAYS + assert "xiaomi" in HERMES_OVERLAYS + overlay = HERMES_OVERLAYS["xiaomi"] + assert overlay.transport == "openai_chat" + assert overlay.base_url_env_var == "XIAOMI_BASE_URL" + assert not overlay.is_aggregator + + def test_alias_resolves(self): + from hermes_cli.providers import normalize_provider + assert normalize_provider("mimo") == "xiaomi" + assert normalize_provider("xiaomi-mimo") == "xiaomi" + + def test_label(self): + from hermes_cli.providers import get_label + assert get_label("xiaomi") == "Xiaomi MiMo" + + def test_get_provider(self): + pdef = None + try: + from hermes_cli.providers import get_provider + pdef = get_provider("xiaomi") + except Exception: + pass + if pdef is not None: + assert pdef.id == "xiaomi" + assert pdef.transport == "openai_chat" + + +# ============================================================================= +# Auxiliary client +# ============================================================================= + + +class TestXiaomiAuxiliary: + """Xiaomi should have a default auxiliary model and a vision model override.""" + + def test_aux_model_defined(self): + from agent.auxiliary_client import _API_KEY_PROVIDER_AUX_MODELS + assert "xiaomi" in _API_KEY_PROVIDER_AUX_MODELS + assert _API_KEY_PROVIDER_AUX_MODELS["xiaomi"] == "mimo-v2-flash" + + def test_vision_model_override(self): + """Xiaomi vision tasks should use mimo-v2-omni (multimodal), not the main model.""" + from agent.auxiliary_client import _PROVIDER_VISION_MODELS + assert "xiaomi" in _PROVIDER_VISION_MODELS + assert _PROVIDER_VISION_MODELS["xiaomi"] == "mimo-v2-omni" + + +# ============================================================================= +# Agent init (no SyntaxError, correct api_mode) +# ============================================================================= + + +class TestXiaomiDoctor: + """Verify hermes doctor recognizes Xiaomi env vars.""" + + def test_provider_env_hints(self): + from hermes_cli.doctor import _PROVIDER_ENV_HINTS + assert "XIAOMI_API_KEY" in _PROVIDER_ENV_HINTS + + +class TestXiaomiAgentInit: + """Verify the agent can be constructed with xiaomi provider without errors.""" + + def test_no_syntax_errors(self): + """Importing run_agent with xiaomi should not raise.""" + import importlib + importlib.import_module("run_agent") + + def test_api_mode_is_chat_completions(self): + from hermes_cli.providers import HERMES_OVERLAYS, TRANSPORT_TO_API_MODE + overlay = HERMES_OVERLAYS["xiaomi"] + api_mode = TRANSPORT_TO_API_MODE[overlay.transport] + assert api_mode == "chat_completions"