mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
feat(xai-oauth): add xAI Grok OAuth (SuperGrok Subscription) provider — port to extracted modules
Original commit b62c99797 by Jaaneek targeted six locations in
pre-refactor run_agent.py. Re-applied to the extracted post-PR locations:
- api_mode dispatch → agent/agent_init.py
- is_xai_responses build_api_kwargs → agent/chat_completion_helpers.py
- codex_auth_retry block + 401 hint → agent/conversation_loop.py
- _try_refresh_codex_client_credentials body → run_agent.py (kept)
The non-run_agent.py portions of the commit (auxiliary_client, codex
transport, hermes_cli/auth, tools/xai_http, tests, docs) merged cleanly
from main via the prior merge commit.
Co-authored-by: Jaaneek <Jaaneek@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -228,7 +228,7 @@ def init_agent(
|
||||
agent.api_mode = api_mode
|
||||
elif agent.provider == "openai-codex":
|
||||
agent.api_mode = "codex_responses"
|
||||
elif agent.provider == "xai":
|
||||
elif agent.provider in {"xai", "xai-oauth"}:
|
||||
agent.api_mode = "codex_responses"
|
||||
elif (provider_name is None) and (
|
||||
agent._base_url_hostname == "chatgpt.com"
|
||||
|
||||
@@ -284,7 +284,7 @@ def build_api_kwargs(agent, api_messages: list) -> dict:
|
||||
and "/backend-api/codex" in agent._base_url_lower
|
||||
)
|
||||
)
|
||||
is_xai_responses = agent.provider == "xai" or agent._base_url_hostname == "api.x.ai"
|
||||
is_xai_responses = agent.provider in {"xai", "xai-oauth"} or agent._base_url_hostname == "api.x.ai"
|
||||
_msgs_for_codex = agent._prepare_messages_for_non_vision_model(api_messages)
|
||||
return _ct.build_kwargs(
|
||||
model=agent.model,
|
||||
|
||||
@@ -1957,13 +1957,14 @@ def run_conversation(
|
||||
|
||||
if (
|
||||
agent.api_mode == "codex_responses"
|
||||
and agent.provider == "openai-codex"
|
||||
and agent.provider in {"openai-codex", "xai-oauth"}
|
||||
and status_code == 401
|
||||
and not codex_auth_retry_attempted
|
||||
):
|
||||
codex_auth_retry_attempted = True
|
||||
if agent._try_refresh_codex_client_credentials(force=True):
|
||||
agent._vprint(f"{agent.log_prefix}🔐 Codex auth refreshed after 401. Retrying request...")
|
||||
_label = "xAI OAuth" if agent.provider == "xai-oauth" else "Codex"
|
||||
agent._vprint(f"{agent.log_prefix}🔐 {_label} auth refreshed after 401. Retrying request...")
|
||||
continue
|
||||
if (
|
||||
agent.api_mode == "chat_completions"
|
||||
@@ -2603,11 +2604,15 @@ def run_conversation(
|
||||
agent._vprint(f"{agent.log_prefix} 🌐 Endpoint: {_base}", force=True)
|
||||
# Actionable guidance for common auth errors
|
||||
if classified.is_auth or classified.reason == FailoverReason.billing:
|
||||
if _provider == "openai-codex" and status_code == 401:
|
||||
agent._vprint(f"{agent.log_prefix} 💡 Codex OAuth token was rejected (HTTP 401). Your token may have been", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} refreshed by another client (Codex CLI, VS Code). To fix:", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} 1. Run `codex` in your terminal to generate fresh tokens.", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} 2. Then run `hermes auth` to re-authenticate.", force=True)
|
||||
if _provider in {"openai-codex", "xai-oauth"} and status_code == 401:
|
||||
if _provider == "openai-codex":
|
||||
agent._vprint(f"{agent.log_prefix} 💡 Codex OAuth token was rejected (HTTP 401). Your token may have been", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} refreshed by another client (Codex CLI, VS Code). To fix:", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} 1. Run `codex` in your terminal to generate fresh tokens.", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} 2. Then run `hermes auth` to re-authenticate.", force=True)
|
||||
else:
|
||||
agent._vprint(f"{agent.log_prefix} 💡 xAI OAuth token was rejected (HTTP 401). To fix:", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} re-authenticate with xAI Grok OAuth (SuperGrok Subscription) from `hermes model`.", force=True)
|
||||
else:
|
||||
agent._vprint(f"{agent.log_prefix} 💡 Your API key was rejected by the provider. Check:", force=True)
|
||||
agent._vprint(f"{agent.log_prefix} • Is the key valid? Run: hermes setup", force=True)
|
||||
|
||||
+50
-5
@@ -2449,15 +2449,60 @@ class AIAgent:
|
||||
return run_codex_create_stream_fallback(self, api_kwargs, client)
|
||||
|
||||
def _try_refresh_codex_client_credentials(self, *, force: bool = True) -> bool:
|
||||
if self.api_mode != "codex_responses" or self.provider != "openai-codex":
|
||||
if self.api_mode != "codex_responses" or self.provider not in {"openai-codex", "xai-oauth"}:
|
||||
return False
|
||||
|
||||
# Guard against silent account swap.
|
||||
#
|
||||
# When an agent is using a non-singleton credential — e.g. a manual
|
||||
# pool entry (``hermes auth add xai-oauth``) whose tokens belong to
|
||||
# a different account than the loopback_pkce singleton, or an agent
|
||||
# constructed with an explicit ``api_key=`` arg — force-refreshing
|
||||
# the singleton here and adopting its tokens silently re-routes the
|
||||
# rest of the conversation onto the singleton's account. The
|
||||
# credential pool's reactive recovery (``_recover_with_credential_pool``)
|
||||
# is the right channel for that case; this path is the
|
||||
# singleton-only fallback used when the pool can't recover, and
|
||||
# MUST only fire when the agent really is on singleton tokens.
|
||||
try:
|
||||
if self.provider == "openai-codex":
|
||||
from hermes_cli.auth import resolve_codex_runtime_credentials
|
||||
|
||||
singleton_now = resolve_codex_runtime_credentials(
|
||||
refresh_if_expiring=False,
|
||||
)
|
||||
else:
|
||||
from hermes_cli.auth import resolve_xai_oauth_runtime_credentials
|
||||
|
||||
singleton_now = resolve_xai_oauth_runtime_credentials(
|
||||
refresh_if_expiring=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.debug("%s singleton read failed: %s", self.provider, exc)
|
||||
return False
|
||||
|
||||
singleton_key = str(singleton_now.get("api_key") or "").strip()
|
||||
active_key = str(self.api_key or "").strip()
|
||||
if singleton_key and active_key and singleton_key != active_key:
|
||||
logger.debug(
|
||||
"%s singleton tokens differ from the active api_key; "
|
||||
"skipping singleton force-refresh to avoid silent account swap. "
|
||||
"Reactive credential rotation should go through the pool.",
|
||||
self.provider,
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
from hermes_cli.auth import resolve_codex_runtime_credentials
|
||||
if self.provider == "openai-codex":
|
||||
from hermes_cli.auth import resolve_codex_runtime_credentials
|
||||
|
||||
creds = resolve_codex_runtime_credentials(force_refresh=force)
|
||||
creds = resolve_codex_runtime_credentials(force_refresh=force)
|
||||
else:
|
||||
from hermes_cli.auth import resolve_xai_oauth_runtime_credentials
|
||||
|
||||
creds = resolve_xai_oauth_runtime_credentials(force_refresh=force)
|
||||
except Exception as exc:
|
||||
logger.debug("Codex credential refresh failed: %s", exc)
|
||||
logger.debug("%s credential refresh failed: %s", self.provider, exc)
|
||||
return False
|
||||
|
||||
api_key = creds.get("api_key")
|
||||
@@ -2472,7 +2517,7 @@ class AIAgent:
|
||||
self._client_kwargs["api_key"] = self.api_key
|
||||
self._client_kwargs["base_url"] = self.base_url
|
||||
|
||||
if not self._replace_primary_openai_client(reason="codex_credential_refresh"):
|
||||
if not self._replace_primary_openai_client(reason=f"{self.provider}_credential_refresh"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user