From ecb07fd00c9d67864e2181c3ae034541161903c3 Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Sat, 9 May 2026 21:06:09 +0000 Subject: [PATCH] fix(ai): improve API error parsing and fallback Add early return for empty responses and fallback to console output when the AI window is closed or invalid. Implement JSON error parsing to extract readable messages from API failures, improving debugging and user feedback. --- src/ai/ai_client.c | 86 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index c40189c8..30a02f9d 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -121,18 +121,27 @@ _aiwin_display_error(gpointer user_data, const gchar* error_msg) } /** - * Display a response message in the AI window (thread-safe). + * Display a response message. If aiwin is provided and valid, display in AI window. + * Otherwise, display in console (for commands run from console). * @param user_data The original user_data pointer (may be NULL) * @param response The response message to display */ static void _aiwin_display_response(gpointer user_data, const gchar* response) { - ProfAiWin* aiwin = _aiwin_validate(user_data); - if (!aiwin) { - log_warning("[AI-THREAD] Cannot display response: aiwin is invalid or window was closed"); + if (!response || strlen(response) == 0) { return; } + + /* Try to display in AI window if available */ + ProfAiWin* aiwin = _aiwin_validate(user_data); + if (!aiwin) { + pthread_mutex_lock(&lock); + cons_show("%s", response); + pthread_mutex_unlock(&lock); + return; + } + pthread_mutex_lock(&lock); aiwin_display_response(aiwin, response); pthread_mutex_unlock(&lock); @@ -1270,6 +1279,64 @@ _parse_ai_response(const gchar* response_json) return NULL; } +/** + * Extract error message from an API error JSON response. + * Handles format: {"error":{"message":"...","type":"...","code":...}} + */ +static gchar* +_parse_error_response(const gchar* error_json) +{ + if (!error_json || strlen(error_json) == 0) + return NULL; + + /* Look for "message":"..." inside "error" object */ + const gchar* error_obj = strstr(error_json, "\"error\":"); + if (!error_obj) + return NULL; + + const gchar* message_key = strstr(error_obj, "\"message\":\""); + if (!message_key) + return NULL; + + message_key += strlen("\"message\":\""); + const gchar* msg_start = message_key; + const gchar* p = msg_start; + while (*p) { + if (*p == '\\' && *(p + 1) == '"') { + p += 2; + continue; + } + if (*p == '"') { + gsize len = p - msg_start; + gchar* result = g_new0(gchar, len + 1); + gchar* out = result; + const gchar* in = msg_start; + while (in < p) { + if (*in == '\\' && *(in + 1) == '"') { + *out++ = '"'; + in += 2; + } else if (*in == '\\' && *(in + 1) == 'n') { + *out++ = '\n'; + in += 2; + } else if (*in == '\\' && *(in + 1) == '\\') { + *out++ = '\\'; + in += 2; + } else if (*in == '\\' && *(in + 1) == 't') { + *out++ = '\t'; + in += 2; + } else { + *out++ = *in++; + } + } + *out = '\0'; + return result; + } + p++; + } + + return NULL; +} + static gpointer _ai_request_thread(gpointer data) { @@ -1328,7 +1395,11 @@ _ai_request_thread(gpointer data) log_debug("[AI-THREAD] API Request URL: %s", request_url); log_debug("[AI-THREAD] Model: %s", session->model); - /* Execute request with POST fields */ + /* Set URL and execute request */ + curl_easy_setopt(curl, CURLOPT_URL, request_url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L); @@ -1348,8 +1419,9 @@ _ai_request_thread(gpointer data) /* Handle HTTP errors */ log_debug("[AI-THREAD] HTTP error response body (%zu bytes): %s", response.size, response.data ? response.data : "NULL"); - auto_gchar gchar* error_msg = g_strdup_printf("HTTP %ld: %s", http_code, - response.data ? response.data : "Unknown error"); + /* Try to extract the actual error message from the JSON response */ + auto_gchar gchar* parsed_error = _parse_error_response(response.data); + auto_gchar gchar* error_msg = parsed_error ? g_strdup_printf("HTTP %ld: %s", http_code, parsed_error) : g_strdup_printf("HTTP %ld: %s", http_code, response.data && strlen(response.data) > 0 ? response.data : "Unknown error"); log_error("AI request failed for %s/%s: %s", session->provider_name, session->model, error_msg); _aiwin_display_error(user_data, error_msg); } else {