fix(stt): retry xAI OAuth after auth rejection

This commit is contained in:
BenSheridanEdwards 2026-07-21 10:40:11 +01:00 committed by Teknium
parent d889c980f5
commit 36de3c5c3e
2 changed files with 105 additions and 19 deletions

View file

@ -11,7 +11,7 @@ import struct
import subprocess
import types
import wave
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, call, patch
import pytest
@ -1570,6 +1570,59 @@ class TestTranscribeXAI:
assert "HTTP 400" in result["error"]
assert "Invalid audio format" in result["error"]
def test_retries_403_with_refreshed_oauth_credentials(
self, sample_ogg, mock_xai_http_module
):
mock_xai_http_module.resolve_xai_http_credentials.side_effect = [
{
"api_key": "stale-oauth-token",
"base_url": "https://api.x.ai/v1",
"provider": "xai-oauth",
},
{
"api_key": "fresh-oauth-token",
"base_url": "https://api.x.ai/v1",
"provider": "xai-oauth",
},
]
rejected = MagicMock()
rejected.status_code = 403
rejected.json.return_value = {
"error": {"message": "OAuth2 access token could not be validated"}
}
accepted = MagicMock()
accepted.status_code = 200
accepted.json.return_value = {
"text": "fleet speech transcription proof",
"language": "en",
"duration": 2.1,
}
stt_config = {"provider": "xai"}
with patch("tools.transcription_tools._load_stt_config", return_value=stt_config), \
patch("tools.transcription_tools._get_provider", return_value="xai"), \
patch("requests.post", side_effect=[rejected, accepted]) as mock_post:
from tools.transcription_tools import transcribe_audio
result = transcribe_audio(sample_ogg)
assert result == {
"success": True,
"transcript": "fleet speech transcription proof",
"provider": "xai",
}
assert mock_post.call_count == 2
assert mock_post.call_args_list[0].kwargs["headers"]["Authorization"] == (
"Bearer stale-oauth-token"
)
assert mock_post.call_args_list[1].kwargs["headers"]["Authorization"] == (
"Bearer fresh-oauth-token"
)
assert mock_xai_http_module.resolve_xai_http_credentials.call_args_list == [
call(),
call(force_refresh=True, api_key_hint="stale-oauth-token"),
]
def test_empty_transcript_returns_failure(self, monkeypatch, sample_ogg, mock_xai_http_module):
monkeypatch.setenv("XAI_API_KEY", "xai-test-key")

View file

@ -1819,12 +1819,16 @@ def _transcribe_xai(file_path: str, model_name: str) -> Dict[str, Any]:
stt_config = _load_stt_config()
xai_config = stt_config.get("xai") or {}
base_url = str(
xai_config.get("base_url")
or get_env_value("XAI_STT_BASE_URL")
or creds.get("base_url")
or XAI_STT_BASE_URL
).strip().rstrip("/")
def _resolve_base_url(resolved_creds: Dict[str, str]) -> str:
return str(
xai_config.get("base_url")
or get_env_value("XAI_STT_BASE_URL")
or resolved_creds.get("base_url")
or XAI_STT_BASE_URL
).strip().rstrip("/")
base_url = _resolve_base_url(creds)
language = _resolve_stt_language("xai", stt_config) or ""
# .get("format", True) already defaults to True when the key is absent;
# is_truthy_value only normalizes truthy/falsy strings from config.
@ -1843,19 +1847,48 @@ def _transcribe_xai(file_path: str, model_name: str) -> Dict[str, Any]:
if use_diarize:
data["diarize"] = "true"
with open(file_path, "rb") as audio_file:
response = requests.post(
f"{base_url}/stt",
headers={
"Authorization": f"Bearer {api_key}",
"User-Agent": hermes_xai_user_agent(),
},
files={
"file": (Path(file_path).name, audio_file),
},
data=data,
timeout=120,
def _post_transcription(bearer: str, endpoint_base_url: str):
with open(file_path, "rb") as audio_file:
return requests.post(
f"{endpoint_base_url}/stt",
headers={
"Authorization": f"Bearer {bearer}",
"User-Agent": hermes_xai_user_agent(),
},
files={
"file": (Path(file_path).name, audio_file),
},
data=data,
timeout=120,
)
response = _post_transcription(api_key, base_url)
if (
response.status_code in {401, 403}
and creds.get("provider") == "xai-oauth"
):
logger.info(
"xAI STT got HTTP %d; refreshing OAuth credentials and retrying once",
response.status_code,
)
try:
refreshed_creds = resolve_xai_http_credentials(
force_refresh=True,
api_key_hint=api_key,
)
refreshed_key = str(refreshed_creds.get("api_key") or "").strip()
if refreshed_key and refreshed_key != api_key:
response = _post_transcription(
refreshed_key,
_resolve_base_url(refreshed_creds),
)
except Exception as refresh_exc:
logger.warning(
"xAI STT OAuth refresh after HTTP %d failed: %s",
response.status_code,
refresh_exc,
)
if response.status_code != 200:
detail = ""