fix(tui): setup.runtime_check resolves like session creation and reports the model
Without an explicit `provider`, `setup.runtime_check` called the strict `resolve_runtime_provider(requested=None)` while `_make_agent` goes through `_resolve_agent_model_runtime` (startup model + provider pin, then the configured fallback chain). With the primary blocked and a working fallback entry the probe answered ok:False (the primary's auth error) and the Desktop showed onboarding for a backend whose sessions built fine. The probe also reported `model: null` because the resolver never populates that key. The default case now runs the session builder's resolver; an explicit `provider` stays a strict single-provider check (onboarding verifies the provider just connected — another provider's fallback must not mask a failed connection) and resolves against the startup model like the session would. Both branches report the selected model. Fixes #111775
This commit is contained in:
@@ -9158,9 +9158,10 @@ def test_probe_credentials_allows_keyless_custom_runtime():
|
||||
|
||||
def test_setup_runtime_check_rejects_empty_runtime_key(monkeypatch):
|
||||
monkeypatch.setattr("hermes_cli.main._has_any_provider_configured", lambda **_kw: True)
|
||||
monkeypatch.setattr(server, "_resolve_startup_runtime", lambda: ("openrouter/test-model", None))
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||
lambda requested=None: {
|
||||
lambda requested=None, **_kw: {
|
||||
"provider": "openrouter",
|
||||
"api_key": "",
|
||||
"source": "env/config",
|
||||
@@ -9172,7 +9173,7 @@ def test_setup_runtime_check_rejects_empty_runtime_key(monkeypatch):
|
||||
assert resp["result"] == {
|
||||
"ok": False,
|
||||
"provider": "openrouter",
|
||||
"model": None,
|
||||
"model": "openrouter/test-model",
|
||||
"source": "env/config",
|
||||
"error": "No usable credentials found for openrouter.",
|
||||
}
|
||||
@@ -9182,7 +9183,7 @@ def test_setup_runtime_check_allows_no_key_custom_runtime(monkeypatch):
|
||||
monkeypatch.setattr("hermes_cli.main._has_any_provider_configured", lambda **_kw: True)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||
lambda requested=None: {
|
||||
lambda requested=None, **_kw: {
|
||||
"provider": "custom",
|
||||
"api_key": "no-key-required",
|
||||
"source": "env/config",
|
||||
@@ -9199,7 +9200,7 @@ def test_setup_runtime_check_rejects_implicit_bedrock_when_unconfigured(monkeypa
|
||||
monkeypatch.setattr("hermes_cli.main._has_any_provider_configured", lambda **_kw: False)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||
lambda requested=None: {
|
||||
lambda requested=None, **_kw: {
|
||||
"provider": "bedrock",
|
||||
"api_key": "aws-sdk",
|
||||
"source": "iam-role",
|
||||
@@ -9245,6 +9246,23 @@ def test_setup_runtime_check_honors_requested_provider(monkeypatch):
|
||||
assert default["result"]["provider"] == "anthropic"
|
||||
|
||||
|
||||
def test_setup_runtime_check_reports_target_model_on_credential_failure(monkeypatch):
|
||||
"""#111775: the probe names the model session creation would use, never ``model: null``."""
|
||||
monkeypatch.setattr("hermes_cli.main._has_any_provider_configured", lambda **_kw: True)
|
||||
monkeypatch.setattr(server, "_resolve_startup_runtime", lambda: ("z-ai/glm-5.2", None))
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||
lambda *, requested=None, target_model=None: {
|
||||
"provider": "zai", "api_key": "", "source": "env/config"
|
||||
},
|
||||
)
|
||||
|
||||
resp = server.handle_request({"id": "1", "method": "setup.runtime_check", "params": {}})
|
||||
|
||||
assert resp["result"]["ok"] is False
|
||||
assert resp["result"]["model"] == "z-ai/glm-5.2"
|
||||
|
||||
|
||||
def test_setup_readiness_scopes_to_requested_profile(monkeypatch, tmp_path):
|
||||
"""#94071: the Desktop preflights a freshly created bot on its target
|
||||
backend. ``profile`` binds THAT profile's home + .env — launch-process
|
||||
|
||||
@@ -294,10 +294,15 @@ def _(rid, params: dict) -> dict:
|
||||
|
||||
@method("setup.runtime_check")
|
||||
def _(rid, params: dict) -> dict:
|
||||
"""Strict provider check via the same resolve_runtime_provider() the agent uses on session
|
||||
creation (setup.status is True if ANY provider auth state is discoverable): ok=False + the auth
|
||||
error when the model can't be served, so UIs surface onboarding before a doomed prompt.
|
||||
``profile`` answers for THAT profile's pin and ``.env``; unknown -> ``ok=False``."""
|
||||
"""Readiness probe for the session a client is about to open (setup.status is True if ANY
|
||||
provider auth state is discoverable): ok=False + the auth error when the model can't be served,
|
||||
so UIs surface onboarding before a doomed prompt. Without ``provider`` it runs the SAME
|
||||
resolver as session creation (``_resolve_agent_model_runtime``: startup model + provider pin,
|
||||
then the configured fallback chain) — a probe that ignores the chain shows onboarding for a
|
||||
backend whose sessions build fine. An explicit ``provider`` stays a strict single-provider
|
||||
check so onboarding can verify the provider just connected without another provider's
|
||||
fallback masking a failed connection. ``profile`` answers for THAT profile's pin and ``.env``;
|
||||
unknown -> ``ok=False``."""
|
||||
try:
|
||||
from hermes_cli.runtime_provider import resolve_runtime_provider
|
||||
from hermes_cli.auth import has_usable_secret
|
||||
@@ -305,13 +310,17 @@ def _(rid, params: dict) -> dict:
|
||||
requested = str(params.get("provider") or "").strip() or None
|
||||
|
||||
def probe(profile, scoped):
|
||||
runtime = resolve_runtime_provider(requested=requested)
|
||||
if requested:
|
||||
model, _startup_provider = _resolve_startup_runtime()
|
||||
runtime = resolve_runtime_provider(requested=requested, target_model=model or None)
|
||||
else:
|
||||
model, runtime = _resolve_agent_model_runtime(None, None)
|
||||
provider_configured = bool(_has_any_provider_configured(strict_profile_scope=bool(profile)))
|
||||
provider = runtime.get("provider") or "provider"
|
||||
source = str(runtime.get("source") or "")
|
||||
|
||||
def fail(error, src):
|
||||
return {"ok": False, "provider": provider, "model": runtime.get("model"),
|
||||
return {"ok": False, "provider": provider, "model": model,
|
||||
"source": src, "error": error, **scoped}
|
||||
if (not provider_configured and provider == "bedrock"
|
||||
and source in {"iam-role", "aws-sdk-default-chain"}):
|
||||
@@ -324,7 +333,7 @@ def _(rid, params: dict) -> dict:
|
||||
from hermes_cli.anon_auth import route_is_welcome_host
|
||||
# free_tier is keyed on the SELECTED route (the welcome host serves only nous/welcome), not
|
||||
# on profile state: a paid Nous key beside a free-tier identity must not read as free.
|
||||
return {"ok": True, "provider": runtime.get("provider"), "model": runtime.get("model"),
|
||||
return {"ok": True, "provider": runtime.get("provider"), "model": model,
|
||||
"source": runtime.get("source"),
|
||||
"free_tier": provider == "nous" and route_is_welcome_host(runtime.get("base_url")),
|
||||
**scoped}
|
||||
|
||||
Reference in New Issue
Block a user