From 6f975b768e4ae8e6b032049ba4fa2fcb0482850e Mon Sep 17 00:00:00 2001 From: KoNit-K <124019182+KoNit-K@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:54:23 -0700 Subject: [PATCH] fix(tui): setup.runtime_check resolves like session creation and reports the model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- tests/tui_gateway/test_tui_gateway_server.py | 26 +++++++++++++++++--- tui_gateway/methods_config.py | 23 +++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/tests/tui_gateway/test_tui_gateway_server.py b/tests/tui_gateway/test_tui_gateway_server.py index 3361abb7b7..9bc2bc41e0 100644 --- a/tests/tui_gateway/test_tui_gateway_server.py +++ b/tests/tui_gateway/test_tui_gateway_server.py @@ -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 diff --git a/tui_gateway/methods_config.py b/tui_gateway/methods_config.py index fe00bca2be..87daa9480d 100644 --- a/tui_gateway/methods_config.py +++ b/tui_gateway/methods_config.py @@ -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}