From 43d557f2dcb543dda326014b28c0e2e0686dad9a Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Thu, 14 May 2026 17:34:54 +0300 Subject: [PATCH] fix(ai): keep in-memory defaults seeded when prefs is unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The defaults_initialized sentinel only makes sense when prefs is loaded. Three unit tests use ai_client_setup, which calls ai_client_init without loading prefs — prefs_ai_get_providers was bailing on the seeding branch because of the prefs NULL check, leaving ai_client with zero providers and breaking tests that expect at least one default. Decouple the two concerns: - Always seed openai/perplexity into the in-memory result list when the configured set is empty and we haven't already seeded. - Only persist the seeded providers and write the defaults_initialized flag when prefs is actually available. In production this is identical to the previous behaviour; in tests without prefs the old "always-return-defaults" semantics is preserved without a persistence side effect. Also reflow ai_client.c JSON escape switch and HTTP-error ternary to satisfy clang-format 18 (CI checks). --- src/ai/ai_client.c | 50 ++++++++++++++++++++++++++++++---------- src/config/preferences.c | 27 ++++++++++++++-------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/ai/ai_client.c b/src/ai/ai_client.c index 84ac4a1e..b5adde4f 100644 --- a/src/ai/ai_client.c +++ b/src/ai/ai_client.c @@ -197,15 +197,41 @@ _json_unescape_substring(const gchar* start, const gchar* end) for (const gchar* in = start; in < end;) { if (*in == '\\' && in + 1 < end) { switch (in[1]) { - case '"': *w++ = '"'; in += 2; break; - case '\\': *w++ = '\\'; in += 2; break; - case '/': *w++ = '/'; in += 2; break; - case 'n': *w++ = '\n'; in += 2; break; - case 't': *w++ = '\t'; in += 2; break; - case 'r': *w++ = '\r'; in += 2; break; - case 'b': *w++ = '\b'; in += 2; break; - case 'f': *w++ = '\f'; in += 2; break; - default: *w++ = *in++; break; + case '"': + *w++ = '"'; + in += 2; + break; + case '\\': + *w++ = '\\'; + in += 2; + break; + case '/': + *w++ = '/'; + in += 2; + break; + case 'n': + *w++ = '\n'; + in += 2; + break; + case 't': + *w++ = '\t'; + in += 2; + break; + case 'r': + *w++ = '\r'; + in += 2; + break; + case 'b': + *w++ = '\b'; + in += 2; + break; + case 'f': + *w++ = '\f'; + in += 2; + break; + default: + *w++ = *in++; + break; } } else { *w++ = *in++; @@ -688,9 +714,9 @@ _curl_exec_and_handle(CURL* curl, const gchar* url, struct curl_slist* headers, } else if (http_code >= 400) { auto_gchar gchar* parsed = ai_parse_error_message(response->data); auto_gchar gchar* error_msg = parsed - ? g_strdup_printf("HTTP %ld: %s", http_code, parsed) - : g_strdup_printf("HTTP %ld: %s", http_code, - response->data && strlen(response->data) > 0 ? response->data : "Unknown error"); + ? g_strdup_printf("HTTP %ld: %s", http_code, parsed) + : g_strdup_printf("HTTP %ld: %s", http_code, + response->data && strlen(response->data) > 0 ? response->data : "Unknown error"); log_error("Request HTTP error for '%s': %s", provider->name, error_msg); _aiwin_display_error(user_data, error_msg); } else { diff --git a/src/config/preferences.c b/src/config/preferences.c index b39fc26b..6db29913 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -67,7 +67,7 @@ #define PREF_GROUP_PLUGINS "plugins" #define PREF_GROUP_EXECUTABLES "executables" #define PREF_GROUP_AI "ai" -#define PREF_GROUP_AI_PREFIX "ai/" /* per-provider subsection prefix: [ai/] */ +#define PREF_GROUP_AI_PREFIX "ai/" /* per-provider subsection prefix: [ai/] */ #define PREF_AI_SETTING_PREFIX "setting." /* per-provider custom setting key prefix */ #define INPBLOCK_DEFAULT 1000 @@ -2045,18 +2045,25 @@ prefs_ai_get_providers(void) } prefs_free_ai_providers(configured); - /* Seed openai/perplexity once on very first init only — driven by the - * "defaults_initialized" sentinel so a user who removes every provider - * does not get them silently re-added. */ - if (prefs && !g_key_file_get_boolean(prefs, PREF_GROUP_AI, "defaults_initialized", NULL)) { - if (!result) { + /* Seed openai/perplexity into the in-memory result on very first init + * only. In production the "defaults_initialized" sentinel in [ai] is what + * tells us we've already seeded — a user who removes every provider must + * not get them silently re-added on the next start. When prefs is NULL + * (e.g. unit tests that bring up ai_client without prefs_load) we still + * seed the in-memory list so behaviour matches a fresh first run, but + * skip the persistence write. */ + gboolean already_seeded = prefs && g_key_file_get_boolean(prefs, PREF_GROUP_AI, "defaults_initialized", NULL); + if (!already_seeded && !result) { + if (prefs) { prefs_ai_set_provider("openai", "https://api.openai.com/"); prefs_ai_set_provider("perplexity", "https://api.perplexity.ai/"); - result = g_list_append(result, g_strdup("openai")); - result = g_list_append(result, g_strdup("https://api.openai.com/")); - result = g_list_append(result, g_strdup("perplexity")); - result = g_list_append(result, g_strdup("https://api.perplexity.ai/")); } + result = g_list_append(result, g_strdup("openai")); + result = g_list_append(result, g_strdup("https://api.openai.com/")); + result = g_list_append(result, g_strdup("perplexity")); + result = g_list_append(result, g_strdup("https://api.perplexity.ai/")); + } + if (prefs && !already_seeded) { g_key_file_set_boolean(prefs, PREF_GROUP_AI, "defaults_initialized", TRUE); _save_prefs(); }