feat(ai): implement robust JSON model parsing

Rewrote internal JSON parsing to correctly handle whitespace, escaped
quotes, and strict field context extraction. This prevents incorrect
field names or provider names from being added to the model list.

Added `ai_parse_models_from_json` public API to facilitate testing.

Changed `/ai models` default behavior to always fetch fresh models.
Replaced `--refresh` flag with `--cached` to display local cache.

Added comprehensive unit tests covering OpenAI and Perplexity formats,
array format, empty/null JSON, escaped quotes, and whitespace handling.
This commit is contained in:
2026-05-09 13:56:59 +00:00
parent 07d267b5bc
commit a898cb212d
6 changed files with 396 additions and 39 deletions

View File

@@ -11134,14 +11134,16 @@ cmd_ai_switch(ProfWin* window, const char* const command, gchar** args)
gboolean
cmd_ai_models(ProfWin* window, const char* const command, gchar** args)
{
// /ai models <provider> [--refresh]
// /ai models <provider> [--cached]
// Default: fetch fresh models from API
// --cached: display cached models (if available)
if (args[1] == NULL) {
cons_bad_cmd_usage(command);
return TRUE;
}
const gchar* provider_name = args[1];
gboolean refresh = (g_strv_length(args) >= 3) && (g_strcmp0(args[2], "--refresh") == 0);
gboolean use_cached = (g_strv_length(args) >= 3) && (g_strcmp0(args[2], "--cached") == 0);
AIProvider* provider = ai_get_provider(provider_name);
if (!provider) {
@@ -11149,12 +11151,12 @@ cmd_ai_models(ProfWin* window, const char* const command, gchar** args)
return TRUE;
}
if (refresh || !ai_models_are_fresh(provider_name)) {
// Fetch models from API
ProfAiWin* aiwin = (window && window->type == WIN_AI) ? (ProfAiWin*)window : wins_get_ai();
ai_fetch_models(provider_name, aiwin);
} else {
// Display cached models
if (use_cached) {
// Display cached models (if available)
if (!provider->models) {
cons_show("No cached models for provider '%s'. Use '/ai models %s' to fetch from API.", provider_name, provider_name);
return TRUE;
}
cons_show("Cached models for provider '%s':", provider_name);
GList* curr = provider->models;
while (curr) {
@@ -11162,7 +11164,11 @@ cmd_ai_models(ProfWin* window, const char* const command, gchar** args)
curr = g_list_next(curr);
}
cons_show("");
cons_show("Use '/ai models %s --refresh' to fetch fresh models from the API.", provider_name);
cons_show("Use '/ai models %s' to fetch fresh models from the API.", provider_name);
} else {
// Default: fetch fresh models from API
ProfAiWin* aiwin = (window && window->type == WIN_AI) ? (ProfAiWin*)window : wins_get_ai();
ai_fetch_models(provider_name, aiwin);
}
return TRUE;