mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
fix(auxiliary): resolve xai oauth compression from pool
This commit is contained in:
parent
c7db6a5800
commit
97a32afdc4
3 changed files with 119 additions and 10 deletions
|
|
@ -1272,12 +1272,40 @@ def _resolve_nous_runtime_api(*, force_refresh: bool = False) -> Optional[tuple[
|
||||||
def _resolve_xai_oauth_for_aux() -> Optional[Tuple[str, str]]:
|
def _resolve_xai_oauth_for_aux() -> Optional[Tuple[str, str]]:
|
||||||
"""Resolve a fresh xAI OAuth (api_key, base_url) for auxiliary clients.
|
"""Resolve a fresh xAI OAuth (api_key, base_url) for auxiliary clients.
|
||||||
|
|
||||||
Routes through ``hermes_cli.auth``'s runtime resolver so the auto-refresh
|
Prefer the credential pool, matching the main runtime/provider status
|
||||||
path is shared with the main agent, instead of relying on whatever raw
|
path. Some xAI OAuth logins live only as pool entries; falling straight
|
||||||
tokens happen to be sitting in auth.json or the credential pool. Returns
|
to the singleton auth-store resolver would make auxiliary tasks such as
|
||||||
``None`` if the user is not authenticated with xAI Grok OAuth (so
|
compression report "no provider configured" even though ``hermes auth
|
||||||
``_resolve_auto`` Step 1 falls through to the next provider in the chain).
|
status`` shows xAI OAuth as logged in.
|
||||||
|
|
||||||
|
Falls back to ``hermes_cli.auth``'s singleton runtime resolver for older
|
||||||
|
auth-store-only logins. Returns ``None`` if the user is not authenticated
|
||||||
|
with xAI Grok OAuth.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
from hermes_cli.auth import DEFAULT_XAI_OAUTH_BASE_URL
|
||||||
|
|
||||||
|
pool = load_pool("xai-oauth")
|
||||||
|
if pool and pool.has_credentials():
|
||||||
|
entry = pool.select()
|
||||||
|
if entry is not None:
|
||||||
|
api_key = str(
|
||||||
|
getattr(entry, "runtime_api_key", None)
|
||||||
|
or getattr(entry, "access_token", "")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
|
base_url = str(
|
||||||
|
os.getenv("HERMES_XAI_BASE_URL", "").strip().rstrip("/")
|
||||||
|
or os.getenv("XAI_BASE_URL", "").strip().rstrip("/")
|
||||||
|
or getattr(entry, "runtime_base_url", None)
|
||||||
|
or getattr(entry, "base_url", None)
|
||||||
|
or DEFAULT_XAI_OAUTH_BASE_URL
|
||||||
|
).strip().rstrip("/")
|
||||||
|
if api_key and base_url:
|
||||||
|
return api_key, base_url
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("Auxiliary xAI OAuth pool credential resolution failed: %s", exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from hermes_cli.auth import resolve_xai_oauth_runtime_credentials
|
from hermes_cli.auth import resolve_xai_oauth_runtime_credentials
|
||||||
|
|
||||||
|
|
|
||||||
19
run_agent.py
19
run_agent.py
|
|
@ -3237,11 +3237,20 @@ class AIAgent:
|
||||||
except Exception:
|
except Exception:
|
||||||
_aux_cfg_provider = ""
|
_aux_cfg_provider = ""
|
||||||
if client is None or not aux_model:
|
if client is None or not aux_model:
|
||||||
msg = (
|
if _aux_cfg_provider and _aux_cfg_provider != "auto":
|
||||||
"⚠ No auxiliary LLM provider configured — context "
|
msg = (
|
||||||
"compression will drop middle turns without a summary. "
|
"⚠ Configured auxiliary compression provider "
|
||||||
"Run `hermes setup` or set OPENROUTER_API_KEY."
|
f"'{_aux_cfg_provider}' is unavailable — context "
|
||||||
)
|
"compression will drop middle turns without a summary. "
|
||||||
|
"Check auxiliary.compression in config.yaml and "
|
||||||
|
"reauthenticate that provider."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
"⚠ No auxiliary LLM provider configured — context "
|
||||||
|
"compression will drop middle turns without a summary. "
|
||||||
|
"Run `hermes setup` or set OPENROUTER_API_KEY."
|
||||||
|
)
|
||||||
self._compression_warning = msg
|
self._compression_warning = msg
|
||||||
self._emit_status(msg)
|
self._emit_status(msg)
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ from agent.auxiliary_client import (
|
||||||
_normalize_aux_provider,
|
_normalize_aux_provider,
|
||||||
_try_payment_fallback,
|
_try_payment_fallback,
|
||||||
_resolve_auto,
|
_resolve_auto,
|
||||||
|
_resolve_xai_oauth_for_aux,
|
||||||
_CodexCompletionsAdapter,
|
_CodexCompletionsAdapter,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -221,6 +222,77 @@ class TestReadCodexAccessToken:
|
||||||
assert result == "plain-token-no-jwt"
|
assert result == "plain-token-no-jwt"
|
||||||
|
|
||||||
|
|
||||||
|
class TestResolveXaiOAuthForAux:
|
||||||
|
def test_uses_pool_backed_credentials_without_singleton(self, tmp_path, monkeypatch):
|
||||||
|
"""Auxiliary xAI OAuth must see pool-only credentials.
|
||||||
|
|
||||||
|
``hermes auth status`` already reports these as logged in; compression
|
||||||
|
should not fall through to "no auxiliary provider configured" just
|
||||||
|
because the singleton auth-store entry is absent.
|
||||||
|
"""
|
||||||
|
from agent.credential_pool import AUTH_TYPE_OAUTH, PooledCredential, load_pool
|
||||||
|
from hermes_cli.auth import DEFAULT_XAI_OAUTH_BASE_URL
|
||||||
|
|
||||||
|
hermes_home = tmp_path / "hermes"
|
||||||
|
hermes_home.mkdir(parents=True, exist_ok=True)
|
||||||
|
(hermes_home / "auth.json").write_text(json.dumps({
|
||||||
|
"version": 1,
|
||||||
|
"providers": {},
|
||||||
|
}))
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.delenv("HERMES_XAI_BASE_URL", raising=False)
|
||||||
|
monkeypatch.delenv("XAI_BASE_URL", raising=False)
|
||||||
|
|
||||||
|
pool = load_pool("xai-oauth")
|
||||||
|
pool.add_entry(PooledCredential(
|
||||||
|
provider="xai-oauth",
|
||||||
|
id="xai123",
|
||||||
|
label="pool-only",
|
||||||
|
auth_type=AUTH_TYPE_OAUTH,
|
||||||
|
priority=0,
|
||||||
|
source="manual:xai_pkce",
|
||||||
|
access_token="pool-access-token",
|
||||||
|
refresh_token="pool-refresh-token",
|
||||||
|
base_url=DEFAULT_XAI_OAUTH_BASE_URL,
|
||||||
|
))
|
||||||
|
|
||||||
|
assert _resolve_xai_oauth_for_aux() == (
|
||||||
|
"pool-access-token",
|
||||||
|
DEFAULT_XAI_OAUTH_BASE_URL,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_pool_backed_credentials_honor_base_url_env_override(self, tmp_path, monkeypatch):
|
||||||
|
from agent.credential_pool import AUTH_TYPE_OAUTH, PooledCredential, load_pool
|
||||||
|
from hermes_cli.auth import DEFAULT_XAI_OAUTH_BASE_URL
|
||||||
|
|
||||||
|
hermes_home = tmp_path / "hermes"
|
||||||
|
hermes_home.mkdir(parents=True, exist_ok=True)
|
||||||
|
(hermes_home / "auth.json").write_text(json.dumps({
|
||||||
|
"version": 1,
|
||||||
|
"providers": {},
|
||||||
|
}))
|
||||||
|
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||||
|
monkeypatch.setenv("HERMES_XAI_BASE_URL", "https://example.x.ai/v1/")
|
||||||
|
|
||||||
|
pool = load_pool("xai-oauth")
|
||||||
|
pool.add_entry(PooledCredential(
|
||||||
|
provider="xai-oauth",
|
||||||
|
id="xai456",
|
||||||
|
label="pool-only",
|
||||||
|
auth_type=AUTH_TYPE_OAUTH,
|
||||||
|
priority=0,
|
||||||
|
source="manual:xai_pkce",
|
||||||
|
access_token="pool-access-token",
|
||||||
|
refresh_token="pool-refresh-token",
|
||||||
|
base_url=DEFAULT_XAI_OAUTH_BASE_URL,
|
||||||
|
))
|
||||||
|
|
||||||
|
assert _resolve_xai_oauth_for_aux() == (
|
||||||
|
"pool-access-token",
|
||||||
|
"https://example.x.ai/v1",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestAnthropicOAuthFlag:
|
class TestAnthropicOAuthFlag:
|
||||||
"""Test that OAuth tokens get is_oauth=True in auxiliary Anthropic client."""
|
"""Test that OAuth tokens get is_oauth=True in auxiliary Anthropic client."""
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue