Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions hermes_cli/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,16 +295,33 @@ def run_doctor(args):
except Exception:
pass
try:
from hermes_cli.auth import resolve_provider as _resolve_provider
from hermes_cli.config import get_compatible_custom_providers as _compatible_custom_providers
from hermes_cli.providers import resolve_provider_full as _resolve_provider_full
except Exception:
_resolve_provider = None
_compatible_custom_providers = None
_resolve_provider_full = None

canonical_provider = provider
if provider and _resolve_provider is not None and provider != "auto":
custom_providers = []
if _compatible_custom_providers is not None:
try:
canonical_provider = _resolve_provider(provider)
custom_providers = _compatible_custom_providers(cfg)
except Exception:
canonical_provider = None
custom_providers = []

user_providers = cfg.get("providers")
if isinstance(user_providers, dict):
known_providers.update(str(name).strip().lower() for name in user_providers if str(name).strip())
for entry in custom_providers:
if not isinstance(entry, dict):
continue
name = str(entry.get("name") or "").strip()
if name:
known_providers.add("custom:" + name.lower().replace(" ", "-"))

canonical_provider = provider
if provider and _resolve_provider_full is not None and provider != "auto":
provider_def = _resolve_provider_full(provider, user_providers, custom_providers)
canonical_provider = provider_def.id if provider_def is not None else None

if provider and provider != "auto":
if canonical_provider is None or (known_providers and canonical_provider not in known_providers):
Expand Down
29 changes: 23 additions & 6 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,7 @@ def select_provider_and_model(args=None):
load_config,
get_env_value,
)
from hermes_cli.providers import resolve_provider_full

config = load_config()
current_model = config.get("model")
Expand All @@ -1446,14 +1447,30 @@ def select_provider_and_model(args=None):
effective_provider = (
config_provider or os.getenv("HERMES_INFERENCE_PROVIDER") or "auto"
)
try:
active = resolve_provider(effective_provider)
except AuthError as exc:
warning = format_auth_error(exc)
print(f"Warning: {warning} Falling back to auto provider detection.")
compatible_custom_providers = get_compatible_custom_providers(config)
active = None
if effective_provider != "auto":
active_def = resolve_provider_full(
effective_provider,
config.get("providers"),
compatible_custom_providers,
)
if active_def is not None:
active = active_def.id
else:
warning = (
f"Unknown provider '{effective_provider}'. Check 'hermes model' for "
"available providers, or run 'hermes doctor' to diagnose config "
"issues."
)
print(f"Warning: {warning} Falling back to auto provider detection.")
if active is None:
try:
active = resolve_provider("auto")
except AuthError:
except AuthError as exc:
if effective_provider == "auto":
warning = format_auth_error(exc)
print(f"Warning: {warning} Falling back to auto provider detection.")
active = None # no provider yet; default to first in list

# Detect custom endpoint
Expand Down
53 changes: 53 additions & 0 deletions tests/hermes_cli/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import os
import sys
import types
import io
import contextlib
from argparse import Namespace
from types import SimpleNamespace

Expand Down Expand Up @@ -255,6 +257,57 @@ def fake_which(cmd):
assert "docker not found (optional)" not in out


def test_run_doctor_accepts_named_provider_from_providers_section(monkeypatch, tmp_path):
home = tmp_path / ".hermes"
home.mkdir(parents=True, exist_ok=True)

import yaml

(home / "config.yaml").write_text(
yaml.dump(
{
"model": {
"provider": "volcengine-plan",
"default": "doubao-seed-2.0-code",
},
"providers": {
"volcengine-plan": {
"name": "volcengine-plan",
"base_url": "https://ark.cn-beijing.volces.com/api/coding/v3",
"default_model": "doubao-seed-2.0-code",
"models": {"doubao-seed-2.0-code": {}},
}
},
}
)
)

monkeypatch.setattr(doctor_mod, "HERMES_HOME", home)
monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", tmp_path / "project")
monkeypatch.setattr(doctor_mod, "_DHH", str(home))
(tmp_path / "project").mkdir(exist_ok=True)

fake_model_tools = types.SimpleNamespace(
check_tool_availability=lambda *a, **kw: ([], []),
TOOLSET_REQUIREMENTS={},
)
monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools)

try:
from hermes_cli import auth as _auth_mod
monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {})
monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {})
except Exception:
pass

buf = io.StringIO()
with contextlib.redirect_stdout(buf):
doctor_mod.run_doctor(Namespace(fix=False))

out = buf.getvalue()
assert "model.provider 'volcengine-plan' is not a recognised provider" not in out


def test_run_doctor_termux_does_not_mark_browser_available_without_agent_browser(monkeypatch, tmp_path):
home = tmp_path / ".hermes"
home.mkdir(parents=True, exist_ok=True)
Expand Down
35 changes: 35 additions & 0 deletions tests/hermes_cli/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,41 @@ def fake_prompt_provider_choice(choices, default=0):
assert "selected saved custom provider is no longer available" in out


def test_select_provider_and_model_accepts_named_provider_from_providers_section(
tmp_path, monkeypatch, capsys
):
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
_clear_provider_env(monkeypatch)

cfg = load_config()
cfg["model"] = {
"provider": "volcengine-plan",
"default": "doubao-seed-2.0-code",
}
cfg["providers"] = {
"volcengine-plan": {
"name": "volcengine-plan",
"base_url": "https://ark.cn-beijing.volces.com/api/coding/v3",
"default_model": "doubao-seed-2.0-code",
"models": {"doubao-seed-2.0-code": {}},
}
}
save_config(cfg)

monkeypatch.setattr(
"hermes_cli.main._prompt_provider_choice",
lambda choices, default=0: len(choices) - 1,
)

from hermes_cli.main import select_provider_and_model

select_provider_and_model()

out = capsys.readouterr().out
assert "Warning: Unknown provider 'volcengine-plan'" not in out
assert "Active provider: volcengine-plan" in out


def test_codex_setup_uses_runtime_access_token_for_live_model_list(tmp_path, monkeypatch):
"""Codex model list fetching uses the runtime access token."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
Expand Down
Loading