Skip to content

Commit e1cd3df

Browse files
committed
fix(api): handle string error bodies from providers like Ollama
Problem: When Ollama Cloud returns `{"error": "Internal Server Error"}` instead of `{"error": {"message": "...", "code": ...}}`, the code tried to use the string as a hash ref, throwing: "Can't use string as HASH ref" Solution: Check `ref($error_obj) eq 'HASH'` before accessing hash keys. When error is a plain string, use it directly as the error message. Testing: Syntax check passed, integration verified.
1 parent 358a175 commit e1cd3df

1 file changed

Lines changed: 35 additions & 30 deletions

File tree

lib/CLIO/Core/API/ResponseHandler.pm

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)