diff --git a/run_agent.py b/run_agent.py index 1dd4219b22e..61699607d1f 100644 --- a/run_agent.py +++ b/run_agent.py @@ -5046,63 +5046,6 @@ class AIAgent: return True return False - @staticmethod - def _decorate_xai_entitlement_error(detail: str) -> str: - """Append a neutral hint when xAI's OAuth surface returns the - permission-denied 403. - - xAI's ``/v1/responses`` endpoint replies to several distinct failure - modes with the SAME body:: - - {"code": "The caller does not have permission to execute the - specified operation", "error": "You have either run out of - available resources or do not have an active Grok subscription. - Manage subscriptions at https://grok.com/?_s=usage or subscribe - at https://grok.com/supergrok"} - - That body covers several real causes we cannot distinguish without - more info from xAI. The most common (and least obvious) one is - that **X Premium+ does NOT include API access** — only standalone - SuperGrok subscribers can use Hermes against xai-oauth. Lots of - users see Grok in their X app, assume it works here too, and hit - this 403 with no idea why. Lead the hint with that. - - Other possible causes: - * No Grok subscription at all - * SuperGrok tier doesn't include the requested model (e.g. - grok-4.3 may need a higher tier) - * Monthly quota exhausted (the ``?_s=usage`` URL hints at this) - - Surface the raw xAI text verbatim and point at - https://grok.com/?_s=usage where the user can see WHICH applies. - - Matched once per detail string — won't double-decorate if the - upstream already concatenated the same text. - """ - if not detail: - return detail - lower = detail.lower() - is_entitlement = ( - "do not have an active grok subscription" in lower - or ("out of available resources" in lower and "grok" in lower) - or ("does not have permission" in lower and "grok" in lower) - ) - if not is_entitlement: - return detail - hint = ( - " — xAI rejected this OAuth account. NOTE: X Premium+ does NOT " - "include xAI API access — only standalone SuperGrok subscribers " - "can use this provider. Other possible causes: no Grok " - "subscription, your tier doesn't include this model, or your " - "quota is exhausted. Check https://grok.com/?_s=usage to see " - "which, or run `/model` to switch providers." - ) - # Idempotency: detect prior decoration by a substring unique to the - # hint (not present in xAI's own body text). - if "X Premium+ does NOT include" in detail: - return detail - return f"{detail}{hint}" - @staticmethod def _summarize_api_error(error: Exception) -> str: """Extract a human-readable one-liner from an API error. @@ -5142,12 +5085,12 @@ class AIAgent: if msg: status_code = getattr(error, "status_code", None) prefix = f"HTTP {status_code}: " if status_code else "" - return AIAgent._decorate_xai_entitlement_error(f"{prefix}{msg[:300]}") + return f"{prefix}{msg[:300]}" # Fallback: truncate the raw string but give more room than 200 chars status_code = getattr(error, "status_code", None) prefix = f"HTTP {status_code}: " if status_code else "" - return AIAgent._decorate_xai_entitlement_error(f"{prefix}{raw[:500]}") + return f"{prefix}{raw[:500]}" def _mask_api_key_for_logs(self, key: Optional[str]) -> Optional[str]: if not key: diff --git a/tests/run_agent/test_codex_xai_oauth_recovery.py b/tests/run_agent/test_codex_xai_oauth_recovery.py index 9192d50695b..9eb641cc895 100644 --- a/tests/run_agent/test_codex_xai_oauth_recovery.py +++ b/tests/run_agent/test_codex_xai_oauth_recovery.py @@ -158,19 +158,22 @@ def test_codex_stream_postlude_error_still_falls_back(): # --------------------------------------------------------------------------- -# Fix B: friendly entitlement message +# Fix B: surface xAI's entitlement body verbatim (no editorializing) +# +# The original PR #26644 appended a hint that led with "X Premium+ does NOT +# include xAI API access — only standalone SuperGrok subscribers can use this +# provider." xAI announced on 2026-05-16 that X Premium subs now work in +# Hermes (https://x.ai/news/grok-hermes), making that hint actively wrong: +# a Premium+ user hitting a real entitlement issue (no Grok sub, wrong tier, +# exhausted quota) would be misdirected to switch subscriptions when their +# Premium sub is in fact valid. We now surface xAI's own body text verbatim +# (which already says "Manage subscriptions at https://grok.com/?_s=usage") +# and leave the diagnosis to xAI's wording. # --------------------------------------------------------------------------- -def test_summarize_api_error_decorates_xai_entitlement_403(): - """xAI's OAuth 403 must surface the X Premium+ gotcha + neutral causes. - - Wording deliberately leads with the X Premium+ gotcha because that's - the #1 confusing case: people see Grok in their X app, assume it - works here too, and hit this 403 with no idea API access is a - separate SKU. Other causes (no subscription, wrong tier, exhausted - quota) follow. - """ +def test_summarize_api_error_surfaces_xai_entitlement_body_verbatim(): + """xAI's OAuth 403 body must surface as-is, with no Hermes-side hint.""" from run_agent import AIAgent error = RuntimeError( @@ -180,45 +183,15 @@ def test_summarize_api_error_decorates_xai_entitlement_403(): "subscriptions at https://grok.com'}" ) summary = AIAgent._summarize_api_error(error) - # The original xAI text must survive — it's still useful diagnostic info. + # xAI's own body text must reach the user — they need it to diagnose. assert "do not have an active Grok subscription" in summary - # The hint MUST lead with the X Premium+ gotcha (most likely cause - # for users who think they're subscribed). - assert "X Premium+ does NOT include" in summary - assert "standalone SuperGrok subscribers" in summary - # Other causes still listed. - assert "no Grok subscription" in summary - assert "tier doesn't include this model" in summary - assert "quota is exhausted" in summary - # The hint must point at the usage page where the user can verify. - assert "https://grok.com/?_s=usage" in summary - # Switching providers is still a valid escape hatch. - assert "/model" in summary + # No stale claim that X Premium is incompatible with Hermes. + assert "X Premium+ does NOT include" not in summary + assert "standalone SuperGrok subscribers" not in summary -def test_summarize_api_error_does_not_accuse_subscribers(): - """Hint must not confidently say the user has no subscription. - - Don Piedro reported his subscription is active. The hint must not - contradict him — leading with the X Premium+ gotcha gives subscribers - a plausible reason ("oh, I'm on Premium+ not pure SuperGrok") instead - of accusing them of lying about having a subscription. - """ - from run_agent import AIAgent - - error = RuntimeError( - "HTTP 403: do not have an active Grok subscription" - ) - summary = AIAgent._summarize_api_error(error) - # MUST NOT contain language that flatly assumes the user is unsubscribed. - assert "lacks SuperGrok" not in summary - assert "you are not subscribed" not in summary.lower() - # MUST lead with the most-likely-but-non-accusatory cause. - assert "X Premium+ does NOT include" in summary - - -def test_summarize_api_error_decorates_xai_body_message(): - """SDK-style error with structured body must also get the hint.""" +def test_summarize_api_error_xai_body_message_unwrapped(): + """SDK-style error with structured body surfaces the message cleanly.""" from run_agent import AIAgent class _XaiErr(Exception): @@ -235,19 +208,9 @@ def test_summarize_api_error_decorates_xai_body_message(): summary = AIAgent._summarize_api_error(_XaiErr("403")) assert "HTTP 403" in summary - assert "X Premium+ does NOT include" in summary - - -def test_summarize_api_error_idempotent_for_entitlement_hint(): - """Decorating twice must not double up the hint.""" - from run_agent import AIAgent - - raw = "HTTP 403: do not have an active Grok subscription" - once = AIAgent._decorate_xai_entitlement_error(raw) - twice = AIAgent._decorate_xai_entitlement_error(once) - assert once == twice - # Sanity: the hint did fire on the first pass. - assert "X Premium+ does NOT include" in once + assert "do not have an active Grok subscription" in summary + # No editorializing on top of xAI's own wording. + assert "X Premium+ does NOT include" not in summary def test_summarize_api_error_passes_through_unrelated_errors():