fix(auth): set active_provider after hermes auth add qwen-oauth
hermes auth add qwen-oauth called pool.add_entry() but never wrote to providers["qwen-oauth"] or set active_provider in auth.json. _model_section_has_credentials() checks get_active_provider() first; with active_provider unset and no api_key_env_vars configured for oauth_external providers, the setup wizard reported "No inference provider configured" even after a successful Qwen CLI OAuth login. Add _mark_qwen_oauth_active() in auth.py: writes a minimal provider state entry (base_url for display only) and calls _save_provider_state() to set active_provider. The function deliberately does not copy the api_key — that lives in the Qwen CLI credential file managed by _save_qwen_cli_tokens / resolve_qwen_runtime_credentials and must not be duplicated in auth.json where it would become stale. pool.add_entry() is retained so "hermes auth list" continues to show the entry. Runtime credential resolution continues to use resolve_qwen_runtime_credentials. Mirrors the fix applied to openai-codex (#37517) and xai-oauth (#37576).
This commit is contained in:
@ -2037,6 +2037,25 @@ def _refresh_qwen_cli_tokens(tokens: Dict[str, Any], timeout_seconds: float = 20
|
||||
return refreshed
|
||||
|
||||
|
||||
def _mark_qwen_oauth_active(creds: Dict[str, Any]) -> None:
|
||||
"""Set active_provider to qwen-oauth in auth.json.
|
||||
|
||||
Qwen OAuth tokens live in the Qwen CLI credential file managed by
|
||||
_save_qwen_cli_tokens / resolve_qwen_runtime_credentials. This function
|
||||
only writes a minimal provider-state entry (base_url for display) and
|
||||
sets active_provider so that get_active_provider() and
|
||||
_model_section_has_credentials() detect the provider for the setup wizard
|
||||
and status commands.
|
||||
"""
|
||||
with _auth_store_lock():
|
||||
auth_store = _load_auth_store()
|
||||
state: Dict[str, Any] = {}
|
||||
if creds.get("base_url"):
|
||||
state["base_url"] = str(creds["base_url"])
|
||||
_save_provider_state(auth_store, "qwen-oauth", state)
|
||||
_save_auth_store(auth_store)
|
||||
|
||||
|
||||
def resolve_qwen_runtime_credentials(
|
||||
*,
|
||||
force_refresh: bool = False,
|
||||
|
||||
@ -367,6 +367,7 @@ def auth_add_command(args) -> None:
|
||||
|
||||
if provider == "qwen-oauth":
|
||||
creds = auth_mod.resolve_qwen_runtime_credentials(refresh_if_expiring=False)
|
||||
auth_mod._mark_qwen_oauth_active(creds)
|
||||
label = (getattr(args, "label", None) or "").strip() or label_from_token(
|
||||
creds["api_key"],
|
||||
_oauth_default_label(provider, len(pool.entries()) + 1),
|
||||
|
||||
@ -142,6 +142,55 @@ def test_auth_add_google_gemini_cli_sets_active_provider(tmp_path, monkeypatch):
|
||||
assert entry["access_token"] == "ya29.test-token"
|
||||
|
||||
|
||||
def test_auth_add_qwen_oauth_sets_active_provider(tmp_path, monkeypatch):
|
||||
"""hermes auth add qwen-oauth must set active_provider in auth.json.
|
||||
|
||||
Tokens are managed by the Qwen CLI credential file via
|
||||
resolve_qwen_runtime_credentials(). The auth.json entry must record
|
||||
active_provider — without storing tokens that would become stale.
|
||||
"""
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes"))
|
||||
_write_auth_store(tmp_path, {"version": 1, "providers": {}})
|
||||
_fake_creds = {
|
||||
"provider": "qwen-oauth",
|
||||
"base_url": "https://portal.qwen.ai/v1",
|
||||
"api_key": "qwen-test-token",
|
||||
"source": "qwen-cli",
|
||||
"expires_at_ms": None,
|
||||
"auth_file": "/home/user/.qwen/oauth_creds.json",
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.resolve_qwen_runtime_credentials",
|
||||
lambda **kw: _fake_creds,
|
||||
)
|
||||
# Prevent _seed_from_singletons from calling the real Qwen CLI file path
|
||||
monkeypatch.setattr(
|
||||
"agent.credential_pool._seed_from_singletons",
|
||||
lambda provider, entries: (False, set()),
|
||||
)
|
||||
|
||||
from hermes_cli.auth_commands import auth_add_command
|
||||
|
||||
class _Args:
|
||||
provider = "qwen-oauth"
|
||||
auth_type = "oauth"
|
||||
api_key = None
|
||||
label = None
|
||||
|
||||
auth_add_command(_Args())
|
||||
|
||||
payload = json.loads((tmp_path / "hermes" / "auth.json").read_text())
|
||||
assert payload["active_provider"] == "qwen-oauth"
|
||||
state = payload["providers"]["qwen-oauth"]
|
||||
# Only base_url stored — no api_key (that lives in the Qwen CLI file).
|
||||
assert state.get("base_url") == "https://portal.qwen.ai/v1"
|
||||
assert "api_key" not in state
|
||||
# pool entry from pool.add_entry() still present for hermes auth list
|
||||
entries = payload["credential_pool"]["qwen-oauth"]
|
||||
entry = next(item for item in entries if item["source"] == "manual:qwen_cli")
|
||||
assert entry["access_token"] == "qwen-test-token"
|
||||
|
||||
|
||||
def test_auth_add_nous_oauth_persists_pool_entry(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes"))
|
||||
_write_auth_store(tmp_path, {"version": 1, "providers": {}})
|
||||
|
||||
Reference in New Issue
Block a user