mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-04 02:21:47 +00:00
fix(cli): validate user-defined providers consistently
This commit is contained in:
parent
3aa1a41e88
commit
ccc8fccf77
4 changed files with 135 additions and 13 deletions
|
|
@ -296,16 +296,33 @@ def run_doctor(args):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
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:
|
except Exception:
|
||||||
_resolve_provider = None
|
_compatible_custom_providers = None
|
||||||
|
_resolve_provider_full = None
|
||||||
|
|
||||||
|
custom_providers = []
|
||||||
|
if _compatible_custom_providers is not None:
|
||||||
|
try:
|
||||||
|
custom_providers = _compatible_custom_providers(cfg)
|
||||||
|
except Exception:
|
||||||
|
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
|
canonical_provider = provider
|
||||||
if provider and _resolve_provider is not None and provider != "auto":
|
if provider and _resolve_provider_full is not None and provider != "auto":
|
||||||
try:
|
provider_def = _resolve_provider_full(provider, user_providers, custom_providers)
|
||||||
canonical_provider = _resolve_provider(provider)
|
canonical_provider = provider_def.id if provider_def is not None else None
|
||||||
except Exception:
|
|
||||||
canonical_provider = None
|
|
||||||
|
|
||||||
if provider and provider != "auto":
|
if provider and provider != "auto":
|
||||||
if canonical_provider is None or (known_providers and canonical_provider not in known_providers):
|
if canonical_provider is None or (known_providers and canonical_provider not in known_providers):
|
||||||
|
|
|
||||||
|
|
@ -1429,6 +1429,7 @@ def select_provider_and_model(args=None):
|
||||||
load_config,
|
load_config,
|
||||||
get_env_value,
|
get_env_value,
|
||||||
)
|
)
|
||||||
|
from hermes_cli.providers import resolve_provider_full
|
||||||
|
|
||||||
config = load_config()
|
config = load_config()
|
||||||
current_model = config.get("model")
|
current_model = config.get("model")
|
||||||
|
|
@ -1446,14 +1447,30 @@ def select_provider_and_model(args=None):
|
||||||
effective_provider = (
|
effective_provider = (
|
||||||
config_provider or os.getenv("HERMES_INFERENCE_PROVIDER") or "auto"
|
config_provider or os.getenv("HERMES_INFERENCE_PROVIDER") or "auto"
|
||||||
)
|
)
|
||||||
try:
|
compatible_custom_providers = get_compatible_custom_providers(config)
|
||||||
active = resolve_provider(effective_provider)
|
active = None
|
||||||
except AuthError as exc:
|
if effective_provider != "auto":
|
||||||
warning = format_auth_error(exc)
|
active_def = resolve_provider_full(
|
||||||
print(f"Warning: {warning} Falling back to auto provider detection.")
|
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:
|
try:
|
||||||
active = resolve_provider("auto")
|
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
|
active = None # no provider yet; default to first in list
|
||||||
|
|
||||||
# Detect custom endpoint
|
# Detect custom endpoint
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
import io
|
||||||
|
import contextlib
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
|
@ -255,6 +257,57 @@ def test_run_doctor_termux_treats_docker_and_browser_warnings_as_expected(monkey
|
||||||
assert "docker not found (optional)" not in out
|
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):
|
def test_run_doctor_termux_does_not_mark_browser_available_without_agent_browser(monkeypatch, tmp_path):
|
||||||
home = tmp_path / ".hermes"
|
home = tmp_path / ".hermes"
|
||||||
home.mkdir(parents=True, exist_ok=True)
|
home.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,41 @@ def test_select_provider_and_model_warns_if_named_custom_provider_disappears(
|
||||||
assert "selected saved custom provider is no longer available" in out
|
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):
|
def test_codex_setup_uses_runtime_access_token_for_live_model_list(tmp_path, monkeypatch):
|
||||||
"""Codex model list fetching uses the runtime access token."""
|
"""Codex model list fetching uses the runtime access token."""
|
||||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue