fix(anthropic): reactive recovery for OAuth 1M-context beta rejection (#17752)
Keep context-1m-2025-08-07 in OAuth requests by default so 1M-capable subscriptions retain full context. When Anthropic rejects a request with 400 'long context beta is not yet available for this subscription', disable the beta for the rest of the session, rebuild the client, and retry once. Addresses #17680 (thanks @JayGwod for the clean reproduction) without forcing every OAuth user off the 1M context window. Changes: - agent/error_classifier.py: new FailoverReason.oauth_long_context_beta_forbidden; pattern matches 400 + 'long context beta' + 'not yet available'. Narrow enough that the existing 429 tier-gate pattern keeps its own reason. - agent/anthropic_adapter.py: _common_betas_for_base_url, build_anthropic_client, build_anthropic_kwargs gain drop_context_1m_beta kwarg. Default=False (1M stays). OAuth OAUTH_ONLY_BETAS unchanged. - agent/transports/anthropic.py: build_kwargs forwards the flag. - run_agent.py: self._oauth_1m_beta_disabled flag, retry-once guard, recovery branch next to the image-shrink path. _rebuild_anthropic_client honors the flag. The main build_kwargs call site threads it through for fast-mode extra_headers. - hermes_cli/doctor.py, hermes_cli/models.py: sibling OAuth /v1/models probes get the same reactive retry — previously they'd falsely report the Anthropic API as unreachable for affected subscriptions. Tests: 2190 tests/agent/ + 94 adjacent integration tests pass. New unit tests cover the classifier pattern (including the collision guard against the 429 tier-gate) and the drop_context_1m_beta adapter behavior (default keeps 1M, flag strips only 1M while preserving every other beta).
This commit is contained in:
38
run_agent.py
38
run_agent.py
@@ -6210,7 +6210,12 @@ class AIAgent:
|
||||
correctly — rebuilding with the Bedrock SDK when provider is bedrock,
|
||||
rather than always falling back to build_anthropic_client() which
|
||||
requires a direct Anthropic API key.
|
||||
|
||||
Honors ``self._oauth_1m_beta_disabled`` (set by the reactive recovery
|
||||
path when an OAuth subscription rejects the 1M-context beta) so the
|
||||
rebuilt client carries the reduced beta set.
|
||||
"""
|
||||
_drop_1m = bool(getattr(self, "_oauth_1m_beta_disabled", False))
|
||||
if getattr(self, "provider", None) == "bedrock":
|
||||
from agent.anthropic_adapter import build_anthropic_bedrock_client
|
||||
region = getattr(self, "_bedrock_region", "us-east-1") or "us-east-1"
|
||||
@@ -6221,6 +6226,7 @@ class AIAgent:
|
||||
self._anthropic_api_key,
|
||||
getattr(self, "_anthropic_base_url", None),
|
||||
timeout=get_provider_request_timeout(self.provider, self.model),
|
||||
drop_context_1m_beta=_drop_1m,
|
||||
)
|
||||
|
||||
def _interruptible_api_call(self, api_kwargs: dict):
|
||||
@@ -8167,6 +8173,7 @@ class AIAgent:
|
||||
context_length=ctx_len,
|
||||
base_url=getattr(self, "_anthropic_base_url", None),
|
||||
fast_mode=(self.request_overrides or {}).get("speed") == "fast",
|
||||
drop_context_1m_beta=bool(getattr(self, "_oauth_1m_beta_disabled", False)),
|
||||
)
|
||||
|
||||
# AWS Bedrock native Converse API — bypasses the OpenAI client entirely.
|
||||
@@ -10752,6 +10759,7 @@ class AIAgent:
|
||||
copilot_auth_retry_attempted=False
|
||||
thinking_sig_retry_attempted = False
|
||||
image_shrink_retry_attempted = False
|
||||
oauth_1m_beta_retry_attempted = False
|
||||
has_retried_429 = False
|
||||
restart_with_compressed_messages = False
|
||||
restart_with_length_continuation = False
|
||||
@@ -11708,6 +11716,36 @@ class AIAgent:
|
||||
"or shrink didn't reduce size; surfacing original error."
|
||||
)
|
||||
|
||||
# Anthropic OAuth subscription rejected the 1M-context beta
|
||||
# header ("long context beta is not yet available for this
|
||||
# subscription"). Disable the beta for the rest of this
|
||||
# session, rebuild the client, and retry once. 1M-capable
|
||||
# subscriptions never hit this branch — they accept the
|
||||
# beta and keep full 1M context. See PR #17680 for the
|
||||
# original report (we chose reactive recovery over the
|
||||
# proposed unconditional omit so capable subscriptions
|
||||
# don't silently lose the capability).
|
||||
if (
|
||||
classified.reason == FailoverReason.oauth_long_context_beta_forbidden
|
||||
and self.api_mode == "anthropic_messages"
|
||||
and self._is_anthropic_oauth
|
||||
and not oauth_1m_beta_retry_attempted
|
||||
):
|
||||
oauth_1m_beta_retry_attempted = True
|
||||
if not getattr(self, "_oauth_1m_beta_disabled", False):
|
||||
self._oauth_1m_beta_disabled = True
|
||||
try:
|
||||
self._anthropic_client.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._rebuild_anthropic_client()
|
||||
self._vprint(
|
||||
f"{self.log_prefix}🔕 OAuth subscription doesn't support "
|
||||
f"the 1M-context beta — disabled for this session and retrying...",
|
||||
force=True,
|
||||
)
|
||||
continue
|
||||
|
||||
if (
|
||||
self.api_mode == "codex_responses"
|
||||
and self.provider == "openai-codex"
|
||||
|
||||
Reference in New Issue
Block a user