Yash030/claude-code-proxy
2
1"""Provider-specific exception mapping."""2 3import httpx4import openai5 6from core.anthropic import get_user_facing_error_message7from providers.exceptions import (8 APIError,9 AuthenticationError,10 InvalidRequestError,11 OverloadedError,12 RateLimitError,13)14from providers.rate_limit import GlobalRateLimiter15 16 17def user_visible_message_for_mapped_provider_error(18 mapped: Exception,19 *,20 provider_name: str,21 read_timeout_s: float | None,22) -> str:23 """Return the user-visible string after :func:`map_error` (405 + mapped types)."""24 if getattr(mapped, "status_code", None) == 405:25 return (26 f"Upstream provider {provider_name} rejected the request method "27 "or endpoint (HTTP 405)."28 )29 return get_user_facing_error_message(mapped, read_timeout_s=read_timeout_s)30 31 32def map_error(33 e: Exception, *, rate_limiter: GlobalRateLimiter | None = None34) -> Exception:35 """Map OpenAI or HTTPX exception to specific ProviderError.36 37 Streaming transports should pass their scoped limiter (``self._global_rate_limiter``)38 so reactive 429 handling applies to the correct provider. Tests may omit39 ``rate_limiter`` to use the process-wide singleton.40 """41 from loguru import logger42 43 message = get_user_facing_error_message(e)44 logger.info(45 "map_error: original_exc_type={} message={}", type(e).__name__, message[:100]46 )47 limiter = rate_limiter or GlobalRateLimiter.get_instance()48 49 if isinstance(e, openai.AuthenticationError):50 logger.info("map_error: mapped to AuthenticationError")51 return AuthenticationError(message, raw_error=str(e))52 if isinstance(e, openai.RateLimitError):53 limiter.set_blocked(60)54 logger.info("map_error: mapped to RateLimitError")55 return RateLimitError(message, raw_error=str(e))56 if isinstance(e, openai.BadRequestError):57 # Check if it's actually a rate limit in disguise58 raw = str(e).lower()59 if "too_many_requests" in raw or "rate_limit" in raw or "quota" in raw:60 limiter.set_blocked(60)61 logger.info("map_error: BadRequestError actually rate limit")62 return RateLimitError(message, raw_error=str(e))63 logger.info("map_error: mapped to InvalidRequestError")64 return InvalidRequestError(message, raw_error=str(e))65 if isinstance(e, openai.InternalServerError):66 raw_message = str(e)67 if "overloaded" in raw_message.lower() or "capacity" in raw_message.lower():68 logger.info("map_error: mapped to OverloadedError")69 return OverloadedError(message, raw_error=raw_message)70 logger.info("map_error: mapped to APIError (InternalServerError)")71 return APIError(message, status_code=500, raw_error=str(e))72 if isinstance(e, openai.APIError):73 logger.info("map_error: mapped to APIError (openai.APIError)")74 return APIError(75 message, status_code=getattr(e, "status_code", 500), raw_error=str(e)76 )77 78 if isinstance(e, httpx.HTTPStatusError):79 status = e.response.status_code80 logger.info("map_error: httpx.HTTPStatusError status={}", status)81 if status in (401, 403):82 logger.info("map_error: mapped to AuthenticationError (httpx)")83 return AuthenticationError(message, raw_error=str(e))84 if status == 429:85 limiter.set_blocked(60)86 logger.info("map_error: mapped to RateLimitError (httpx)")87 return RateLimitError(message, raw_error=str(e))88 if status == 413:89 # "Request too large" - often actually a rate limit or quota issue90 limiter.set_blocked(60)91 logger.info("map_error: mapped to RateLimitError (413)")92 return RateLimitError(message, raw_error=str(e))93 if status == 400:94 logger.info("map_error: mapped to InvalidRequestError (httpx)")95 return InvalidRequestError(message, raw_error=str(e))96 # Check response body for rate limit indicators97 try:98 body = e.response.json()99 if body and isinstance(body, dict):100 err_type = body.get("type", "")101 if "too_many_requests" in err_type or "rate_limit" in err_type.lower():102 limiter.set_blocked(60)103 logger.info("map_error: detected rate limit from response body")104 return RateLimitError(message, raw_error=str(e))105 except Exception:106 pass107 if status >= 500:108 if status in (502, 503, 504):109 logger.info("map_error: mapped to OverloadedError (httpx)")110 return OverloadedError(message, raw_error=str(e))111 logger.info("map_error: mapped to APIError (httpx 5xx)")112 return APIError(message, status_code=status, raw_error=str(e))113 logger.info("map_error: mapped to APIError (httpx)")114 return APIError(message, status_code=status, raw_error=str(e))115 116 logger.info("map_error: falling through, returning original exception")117 return e118 