diff --git a/hermes_cli/model_normalize.py b/hermes_cli/model_normalize.py index c5123f391..780c638f5 100644 --- a/hermes_cli/model_normalize.py +++ b/hermes_cli/model_normalize.py @@ -76,17 +76,22 @@ _STRIP_VENDOR_ONLY_PROVIDERS: frozenset[str] = frozenset({ "copilot-acp", }) -# Providers whose own naming is authoritative -- pass through unchanged. -_PASSTHROUGH_PROVIDERS: frozenset[str] = frozenset({ +# Providers whose native naming is authoritative -- pass through unchanged. +_AUTHORITATIVE_NATIVE_PROVIDERS: frozenset[str] = frozenset({ "gemini", + "huggingface", + "openai-codex", +}) + +# Direct providers that accept bare native names but should repair a matching +# provider/ prefix when users copy the aggregator form into config.yaml. +_MATCHING_PREFIX_STRIP_PROVIDERS: frozenset[str] = frozenset({ "zai", "kimi-coding", "minimax", "minimax-cn", "alibaba", "qwen-oauth", - "huggingface", - "openai-codex", "custom", }) @@ -363,10 +368,14 @@ def normalize_model_for_provider(model_input: str, target_provider: str) -> str: return bare return _normalize_for_deepseek(bare) - # --- Native passthrough providers: strip only matching provider prefixes --- - if provider in _PASSTHROUGH_PROVIDERS - {"custom", "huggingface", "openai-codex"}: + # --- Direct providers: repair matching provider prefixes only --- + if provider in _MATCHING_PREFIX_STRIP_PROVIDERS: return _strip_matching_provider_prefix(name, provider) + # --- Authoritative native providers: preserve user-facing slugs as-is --- + if provider in _AUTHORITATIVE_NATIVE_PROVIDERS: + return name + # --- Custom & all others: pass through as-is --- return name diff --git a/run_agent.py b/run_agent.py index 16509f69b..129eb1679 100644 --- a/run_agent.py +++ b/run_agent.py @@ -5020,7 +5020,7 @@ class AIAgent: # when no explicit key is in the fallback config. if fb_base_url_hint and "ollama.com" in fb_base_url_hint.lower() and not fb_api_key_hint: fb_api_key_hint = os.getenv("OLLAMA_API_KEY") or None - fb_client, resolved_fb_model = resolve_provider_client( + fb_client, _resolved_fb_model = resolve_provider_client( fb_provider, model=fb_model, raw_codex=True, explicit_base_url=fb_base_url_hint, explicit_api_key=fb_api_key_hint) @@ -5029,7 +5029,12 @@ class AIAgent: "Fallback to %s failed: provider not configured", fb_provider) return self._try_activate_fallback() # try next in chain - fb_model = resolved_fb_model or fb_model + try: + from hermes_cli.model_normalize import normalize_model_for_provider + + fb_model = normalize_model_for_provider(fb_model, fb_provider) + except Exception: + pass # Determine api_mode from provider / base URL fb_api_mode = "chat_completions" diff --git a/tests/agent/test_auxiliary_named_custom_providers.py b/tests/agent/test_auxiliary_named_custom_providers.py index a07833cc7..4c16bcb01 100644 --- a/tests/agent/test_auxiliary_named_custom_providers.py +++ b/tests/agent/test_auxiliary_named_custom_providers.py @@ -12,6 +12,17 @@ def _isolate(tmp_path, monkeypatch): hermes_home = tmp_path / ".hermes" hermes_home.mkdir() monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + for env_var in ( + "AUXILIARY_VISION_PROVIDER", + "AUXILIARY_VISION_MODEL", + "AUXILIARY_VISION_BASE_URL", + "AUXILIARY_VISION_API_KEY", + "CONTEXT_VISION_PROVIDER", + "CONTEXT_VISION_MODEL", + "CONTEXT_VISION_BASE_URL", + "CONTEXT_VISION_API_KEY", + ): + monkeypatch.delenv(env_var, raising=False) # Write a minimal config so load_config doesn't fail (hermes_home / "config.yaml").write_text("model:\n default: test-model\n") diff --git a/tests/hermes_cli/test_model_normalize.py b/tests/hermes_cli/test_model_normalize.py index 531698cb6..0bca8d52e 100644 --- a/tests/hermes_cli/test_model_normalize.py +++ b/tests/hermes_cli/test_model_normalize.py @@ -105,7 +105,7 @@ class TestAggregatorProviders: class TestIssue6211NativeProviderPrefixNormalization: @pytest.mark.parametrize("model,target_provider,expected", [ ("zai/glm-5.1", "zai", "glm-5.1"), - ("google/gemini-2.5-pro", "gemini", "gemini-2.5-pro"), + ("google/gemini-2.5-pro", "gemini", "google/gemini-2.5-pro"), ("moonshot/kimi-k2.5", "kimi-coding", "kimi-k2.5"), ("anthropic/claude-sonnet-4.6", "openrouter", "anthropic/claude-sonnet-4.6"), ("Qwen/Qwen3.5-397B-A17B", "huggingface", "Qwen/Qwen3.5-397B-A17B"),