@@ -76,27 +76,63 @@ def _status_to_code(status: int) -> ErrorCode:
7676
7777
7878def raise_for_status (response ) -> None :
79- """Raise appropriate exception based on response status."""
80- if response .status_code >= 400 :
81- try :
82- body = response .json ()
83- error = body .get ("error" , {})
84- code = error .get ("code" , _status_to_code (response .status_code ))
85- message = error .get ("message" , response .text or f"HTTP { response .status_code } " )
86- details = error .get ("details" )
87- except Exception :
88- code = _status_to_code (response .status_code )
89- message = response .text or f"HTTP { response .status_code } "
90- details = None
91-
92- if code == "authentication_failed" :
93- raise AuthenticationError (message )
94- elif code == "rate_limited" :
95- raise RateLimitError (message )
96- elif code == "not_found" :
97- raise NotFoundError (message )
98- elif code == "invalid_request" :
99- raise ValidationError (message , details )
100- else :
101- raise SubconsciousError (code , message , response .status_code , details )
79+ """Raise appropriate exception based on response status.
80+
81+ Extracts error info from three wire shapes the server may return:
82+
83+ - Canonical SDK shape: ``{"error": {"code", "message", "details"}}``
84+ - Express default: ``{"error": "Internal server error"}``
85+ - Plain text/unknown: falls back to ``response.text``
86+
87+ Enriches the message with method + URL so users get enough context
88+ to correlate with server logs (especially for 5xx responses where
89+ the body is generic).
90+ """
91+ if response .status_code < 400 :
92+ return
93+
94+ try :
95+ body = response .json ()
96+ except Exception :
97+ body = None
98+
99+ error_field = body .get ("error" ) if isinstance (body , dict ) else None
100+ if isinstance (error_field , dict ):
101+ code = error_field .get ("code" , _status_to_code (response .status_code ))
102+ message = error_field .get ("message" ) or str (error_field )
103+ details = error_field .get ("details" )
104+ elif isinstance (error_field , str ):
105+ code = _status_to_code (response .status_code )
106+ message = error_field
107+ details = None
108+ else :
109+ code = _status_to_code (response .status_code )
110+ message = response .text or f"HTTP { response .status_code } "
111+ details = None
112+
113+ # Context suffix: "[GET /v1/runs/<id>]" and a request id when the
114+ # server surfaces one. Skipped quietly if the request object isn't
115+ # reachable (e.g., requests.Response without a bound PreparedRequest).
116+ request = getattr (response , "request" , None )
117+ method = getattr (request , "method" , None )
118+ url = getattr (request , "url" , None )
119+ request_id = response .headers .get ("x-request-id" ) if response .headers else None
120+ suffix_parts = []
121+ if method and url :
122+ suffix_parts .append (f"{ method } { url } " )
123+ if request_id :
124+ suffix_parts .append (f"request_id={ request_id } " )
125+ if suffix_parts :
126+ message = f"{ message } [{ ' ' .join (suffix_parts )} ]"
127+
128+ if code == "authentication_failed" :
129+ raise AuthenticationError (message )
130+ elif code == "rate_limited" :
131+ raise RateLimitError (message )
132+ elif code == "not_found" :
133+ raise NotFoundError (message )
134+ elif code == "invalid_request" :
135+ raise ValidationError (message , details )
136+ else :
137+ raise SubconsciousError (code , message , response .status_code , details )
102138
0 commit comments