@@ -278,42 +278,47 @@ sub handle_error_response {
278278 }
279279 }
280280 if ($error_obj ) {
281- $error = $error_obj -> {message } || $error_obj || $error ;
282- # Extract detailed error from OpenRouter metadata.raw for better user messages
283- if ($error_obj -> {metadata } && $error_obj -> {metadata }{raw }) {
284- my $raw = eval { decode_json($error_obj -> {metadata }{raw }) };
285- if ($raw ) {
286- my $inner_error ;
287- if (ref ($raw ) eq ' ARRAY' && @$raw && ref ($raw -> [0]) eq ' HASH' && $raw -> [0]{error }) {
288- $inner_error = $raw -> [0]{error };
289- } elsif (ref ($raw ) eq ' HASH' && $raw -> {error }) {
290- $inner_error = $raw -> {error };
291- }
292- if ($inner_error && $inner_error -> {message }) {
293- $error = $inner_error -> {message };
294- log_debug(' ResponseHandler' , " Extracted inner error from provider metadata: $error " );
281+ # Handle both string errors ("Internal Server Error") and hash errors ({message => "...", code => ...})
282+ if (ref ($error_obj ) eq ' HASH' ) {
283+ $error = $error_obj -> {message } // $error ;
284+ # Extract detailed error from OpenRouter metadata.raw for better user messages
285+ if ($error_obj -> {metadata } && ref ($error_obj -> {metadata }) eq ' HASH' && $error_obj -> {metadata }{raw }) {
286+ my $raw = eval { decode_json($error_obj -> {metadata }{raw }) };
287+ if ($raw ) {
288+ my $inner_error ;
289+ if (ref ($raw ) eq ' ARRAY' && @$raw && ref ($raw -> [0]) eq ' HASH' && $raw -> [0]{error }) {
290+ $inner_error = $raw -> [0]{error };
291+ } elsif (ref ($raw ) eq ' HASH' && $raw -> {error }) {
292+ $inner_error = $raw -> {error };
293+ }
294+ if ($inner_error && ref ($inner_error ) eq ' HASH' && $inner_error -> {message }) {
295+ $error = $inner_error -> {message };
296+ log_debug(' ResponseHandler' , " Extracted inner error from provider metadata: $error " );
297+ }
295298 }
296299 }
300+ # Use embedded error code when HTTP status is uninformative (200 or 599)
301+ if (($status == 200 || $status >= 500) && $error_obj -> {code } && $error_obj -> {code } =~ / ^\d +$ / ) {
302+ $status = int ($error_obj -> {code });
303+ log_debug(' ResponseHandler' , " Using embedded error code $status from response body" );
304+ }
305+ # Detect rate limit from semantic string codes (e.g. GitHub's user_model_rate_limited)
306+ if ($status == 200 && $error_obj -> {code } && $error_obj -> {code } =~ / rate.lim/i ) {
307+ $status = 429;
308+ log_debug(' ResponseHandler' , " Detected rate limit via code '$error_obj ->{code}', treating as 429" );
309+ }
310+ } else {
311+ # $error_obj is a plain string error from the provider
312+ $error = $error_obj ;
297313 }
298- # Use embedded error code when HTTP status is uninformative (200 or 599)
299- if (($status == 200 || $status >= 500) && $error_obj -> {code } && $error_obj -> {code } =~ / ^\d +$ / ) {
300- $status = int ($error_obj -> {code });
301- log_debug(' ResponseHandler' , " Using embedded error code $status from response body" );
302- }
303- # Detect rate limit from semantic string codes (e.g. GitHub's user_model_rate_limited)
304- if ($status == 200 && $error_obj -> {code } && $error_obj -> {code } =~ / rate.lim/i ) {
305- $status = 429;
306- log_debug(' ResponseHandler' , " Detected rate limit via code '$error_obj ->{code}', treating as 429" );
307- }
308-
309314 }
310315
311316 my $retryable = 0;
312317 my $retry_after = undef ;
313318 my $retry_info = ' ' ;
314319 my $is_retryable_error = 0;
315320 my $error_type = undef ;
316- my $detected_rate_limit_code = $error_obj -> {code } if $error_obj && $error_obj -> {code };
321+ my $detected_rate_limit_code = ( ref ( $error_obj ) eq ' HASH ' && $error_obj -> {code }) ? $error_obj -> {code } : undef ;
317322
318323 # Handle rate limiting (429)
319324 if ($status == 429) {
@@ -348,12 +353,12 @@ sub handle_error_response {
348353 $retry_source = ' header' ;
349354 }
350355 # Also check for retryAfter in the error body (GitHub Copilot may return this)
351- elsif ($error_obj && $error_obj -> {retryAfter }) {
356+ elsif (ref ( $error_obj ) eq ' HASH ' && $error_obj -> {retryAfter }) {
352357 $retry_after = $error_obj -> {retryAfter };
353358 $retry_source = ' body_retryAfter' ;
354359 }
355360 # Check for reset timestamp (GitHub may return epoch seconds)
356- elsif ($error_obj && $error_obj -> {retryAfterTimestamp }) {
361+ elsif (ref ( $error_obj ) eq ' HASH ' && $error_obj -> {retryAfterTimestamp }) {
357362 $retry_after = int ($error_obj -> {retryAfterTimestamp } - time ());
358363 $retry_after = 1 if $retry_after < 1;
359364 $retry_source = ' body_timestamp' ;
@@ -600,7 +605,7 @@ sub handle_error_response {
600605 }
601606 # Handle quota exceeded errors (non-retryable - user must take action)
602607 # Detected via semantic codes in error body, not HTTP status
603- elsif ($error_obj && $error_obj -> {code } &&
608+ elsif (ref ( $error_obj ) eq ' HASH ' && $error_obj -> {code } &&
604609 ($error_obj -> {code } eq ' quota_exceeded' ||
605610 $error_obj -> {code } eq ' free_quota_exceeded' ||
606611 $error_obj -> {code } eq ' overage_limit_reached' ||
@@ -741,7 +746,7 @@ sub handle_error_response {
741746 # Content was flagged by the safety system - user needs to modify their request
742747 elsif (($status == 400 || $status == 403) &&
743748 ($error =~ / content.?filter|content.?policy|safety|harmful|inappropriate/i ||
744- ($error_obj && $error_obj -> {code } && $error_obj -> {code } =~ / content.?filter|content.?policy/i ))) {
749+ (ref ( $error_obj ) eq ' HASH ' && $error_obj -> {code } && $error_obj -> {code } =~ / content.?filter|content.?policy/i ))) {
745750 $is_retryable_error = 0;
746751 $retryable = 0;
747752 $error_type = ' content_filter' ;
0 commit comments