Retry HTTP 400s that wrap a transient upstream code in error.code

OpenRouter is a passthrough for providers it does not control, and some transient
provider failures (502/503/529/...) arrive as HTTP 400 with the real status in the
JSON error.code. Retry only those; ordinary 400 invalid-request responses (including
inner error.code == 400) stay non-retryable. Reuses the existing TemporaryError/backoff.

Note: this edits generated code because retry classification here is HTTP-status only
(x-speakeasy-retries) and SDK hooks run outside the retry loop, so a 400 body cannot be
inspected via an overlay or hook. Preserving it across regen needs a genignore or upstream
Speakeasy support -- flagging for maintainers.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-07-05 09:35:55 +08:00
co-authored by Claudypoo
parent 22803a327e
commit 1901a00e9e
2 changed files with 118 additions and 0 deletions
+31
View File
@@ -1,6 +1,7 @@
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
import asyncio
import json
import random
import time
from datetime import datetime
@@ -150,6 +151,24 @@ def _parse_retry_after_ms_header(response: httpx.Response) -> Optional[int]:
return None
TRANSIENT_INNER_ERROR_CODES = {408, 429, 500, 502, 503, 504, 524, 529}
def _wraps_transient_inner_code(response: httpx.Response) -> bool:
"""OpenRouter (a middleman) sometimes wraps a transient upstream failure in an HTTP
400 whose JSON error.code holds the real status; retry those. Ordinary 400
invalid-request responses (including inner error.code == 400) stay non-retryable."""
try:
body = response.json()
except (json.JSONDecodeError, httpx.ResponseNotRead):
return False
error = body.get("error") if isinstance(body, dict) else None
code = error.get("code") if isinstance(error, dict) else None
if isinstance(code, str) and code.isdigit():
code = int(code)
return code in TRANSIENT_INNER_ERROR_CODES
def _get_sleep_interval(
exception: Exception,
initial_interval: int,
@@ -207,6 +226,12 @@ def retry(func, retries: Retries):
if res.status_code == parsed_code:
raise TemporaryError(res)
if res.status_code == 400:
if not res.is_closed:
res.read()
if _wraps_transient_inner_code(res):
raise TemporaryError(res)
except (httpx.NetworkError, httpx.TimeoutException) as exception:
if retries.config.retry_connection_errors:
raise
@@ -252,6 +277,12 @@ async def retry_async(func, retries: Retries):
if res.status_code == parsed_code:
raise TemporaryError(res)
if res.status_code == 400:
if not res.is_closed:
await res.aread()
if _wraps_transient_inner_code(res):
raise TemporaryError(res)
except (httpx.NetworkError, httpx.TimeoutException) as exception:
if retries.config.retry_connection_errors:
raise