mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
The new resolve_xai_http_credentials() resolver was using os.getenv() for the XAI_API_KEY/XAI_BASE_URL fallback path, which dropped the ~/.hermes/.env contract guarded by PR #17140 / #17163. Users with XAI_API_KEY in dotenv only would see "No xAI credentials found" even though the key was configured. Separately, _transcribe_xai started consulting creds["base_url"] (which always returns at least the default https://api.x.ai/v1) ahead of the public XAI_STT_BASE_URL env override, so the per-tool override stopped working. - tools/xai_http.py: add module-level get_env_value() wrapper that reads ~/.hermes/.env first (via hermes_cli.config.get_env_value), then os.environ. Resolver uses it for the API-key/base-url fallback. - tools/transcription_tools.py: restore precedence so XAI_STT_BASE_URL wins over creds["base_url"]. - tests/tools/test_transcription_dotenv_fallback.py + tests/tools/test_tts_dotenv_fallback.py: repoint the per-call-site patches at the new resolution point (tools.xai_http.get_env_value). The end-to-end regression-guard test (which patches load_env) is unchanged and still passes.
83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
"""Shared helpers for direct xAI HTTP integrations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Dict
|
|
|
|
try:
|
|
from hermes_cli.config import get_env_value as _hermes_get_env_value
|
|
except Exception:
|
|
_hermes_get_env_value = None
|
|
|
|
|
|
def get_env_value(name: str, default=None):
|
|
"""Read ``name`` from ``~/.hermes/.env`` first, then ``os.environ``.
|
|
|
|
Wraps :func:`hermes_cli.config.get_env_value` so tests can patch
|
|
``tools.xai_http.get_env_value`` to inject dotenv-only secrets into the
|
|
xAI credential resolver.
|
|
"""
|
|
if _hermes_get_env_value is not None:
|
|
value = _hermes_get_env_value(name)
|
|
if value is not None:
|
|
return value
|
|
return os.environ.get(name, default)
|
|
|
|
|
|
def hermes_xai_user_agent() -> str:
|
|
"""Return a stable Hermes-specific User-Agent for xAI HTTP calls."""
|
|
try:
|
|
from hermes_cli import __version__
|
|
except Exception:
|
|
__version__ = "unknown"
|
|
return f"Hermes-Agent/{__version__}"
|
|
|
|
|
|
def resolve_xai_http_credentials() -> Dict[str, str]:
|
|
"""Resolve bearer credentials for direct xAI HTTP endpoints.
|
|
|
|
Prefers Hermes-managed xAI OAuth credentials when available, then falls back
|
|
to ``XAI_API_KEY`` resolved via ``hermes_cli.config.get_env_value`` so keys
|
|
stored in ``~/.hermes/.env`` (the standard Hermes location) are honored —
|
|
not just ones already exported into ``os.environ``. This keeps direct xAI
|
|
endpoints (images, TTS, STT, etc.) aligned with the main runtime auth model
|
|
and preserves the regression contract from PR #17140 / #17163.
|
|
"""
|
|
try:
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
|
|
runtime = resolve_runtime_provider(requested="xai-oauth")
|
|
access_token = str(runtime.get("api_key") or "").strip()
|
|
base_url = str(runtime.get("base_url") or "").strip().rstrip("/")
|
|
if access_token:
|
|
return {
|
|
"provider": "xai-oauth",
|
|
"api_key": access_token,
|
|
"base_url": base_url or "https://api.x.ai/v1",
|
|
}
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
from hermes_cli.auth import resolve_xai_oauth_runtime_credentials
|
|
|
|
creds = resolve_xai_oauth_runtime_credentials()
|
|
access_token = str(creds.get("api_key") or "").strip()
|
|
base_url = str(creds.get("base_url") or "").strip().rstrip("/")
|
|
if access_token:
|
|
return {
|
|
"provider": "xai-oauth",
|
|
"api_key": access_token,
|
|
"base_url": base_url or "https://api.x.ai/v1",
|
|
}
|
|
except Exception:
|
|
pass
|
|
|
|
api_key = str(get_env_value("XAI_API_KEY") or "").strip()
|
|
base_url = str(get_env_value("XAI_BASE_URL") or "https://api.x.ai/v1").strip().rstrip("/")
|
|
return {
|
|
"provider": "xai",
|
|
"api_key": api_key,
|
|
"base_url": base_url,
|
|
}
|