From 27a30d8515d3cda18bc60a581aaa9708fe0a503e Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Fri, 18 Sep 2026 03:10:16 -0700 Subject: [PATCH] fix(setup): xAI TTS wizard checks XAI_API_KEY before OAuth to match runtime _tts_xai_step still announced OAuth-first ordering (docstring and printed message) while the synthesis and availability paths now prefer an explicit XAI_API_KEY over the subscription OAuth bearer. Check the key first and fix the copy; regression test under tests/hermes_cli. --- hermes_cli/setup_tts.py | 11 +++++----- tests/hermes_cli/test_setup_tts_xai_oauth.py | 21 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/hermes_cli/setup_tts.py b/hermes_cli/setup_tts.py index 8c5cca8d4b..199897d946 100644 --- a/hermes_cli/setup_tts.py +++ b/hermes_cli/setup_tts.py @@ -198,12 +198,13 @@ def _xai_api_key_path(): def _tts_xai_step(config: dict) -> str: - """xAI TTS auth. Order: existing OAuth tokens (free for SuperGrok) > existing - XAI_API_KEY > offer both paths — xAI TTS works with OAuth bearer tokens too.""" - if _xai_oauth_logged_in_for_setup(): + """xAI TTS auth. Order: existing XAI_API_KEY > existing OAuth tokens > offer both + paths — matches runtime, where an explicit key wins over the subscription OAuth + bearer (which 403s on metered /v1/tts). See #87045, #113727.""" + if _setup.get_env_value("XAI_API_KEY"): + _setup.print_success("xAI TTS will use your existing XAI_API_KEY (preferred over xAI Grok OAuth)") + elif _xai_oauth_logged_in_for_setup(): _setup.print_success("xAI TTS will use your xAI Grok OAuth (SuperGrok / Premium+) credentials") - elif _setup.get_env_value("XAI_API_KEY"): - _setup.print_success("xAI TTS will use your existing XAI_API_KEY") else: print() choice_idx = _setup.prompt_choice( diff --git a/tests/hermes_cli/test_setup_tts_xai_oauth.py b/tests/hermes_cli/test_setup_tts_xai_oauth.py index 57b5446792..e814c6c3df 100644 --- a/tests/hermes_cli/test_setup_tts_xai_oauth.py +++ b/tests/hermes_cli/test_setup_tts_xai_oauth.py @@ -2,6 +2,8 @@ import json +import pytest + import yaml @@ -82,3 +84,22 @@ def test_run_xai_oauth_login_from_setup_does_not_hijack_active_provider( assert config["model"]["provider"] == "openrouter" assert config["model"]["base_url"] == "https://openrouter.ai/api/v1" assert config["model"]["default"] == "anthropic/claude-sonnet-4" + + +def test_tts_xai_step_prefers_existing_api_key_over_oauth(monkeypatch): + """Wizard copy must match runtime: an explicit XAI_API_KEY wins over stored OAuth + tokens (the subscription bearer 403s on metered /v1/tts — #87045, #113727).""" + import hermes_cli.setup_tts as setup_tts + + messages = [] + monkeypatch.setattr(setup_tts, "_xai_oauth_logged_in_for_setup", lambda: True) + monkeypatch.setattr(setup_tts._setup, "get_env_value", lambda key: "xai-key" if key == "XAI_API_KEY" else "") + monkeypatch.setattr(setup_tts._setup, "print_success", lambda msg: messages.append(msg)) + monkeypatch.setattr(setup_tts._setup, "prompt", lambda *a, **k: "") + monkeypatch.setattr(setup_tts._setup, "prompt_choice", lambda *a, **k: pytest.fail("no prompt expected")) + + config = {} + assert setup_tts._tts_xai_step(config) == "xai" + assert len(messages) == 1 and "XAI_API_KEY" in messages[0] + assert "OAuth credentials" not in messages[0] + assert "XAI_API_KEY" in setup_tts._tts_xai_step.__doc__.split(">")[0]