fix(ai): repair custom setting name autocompletion
All checks were successful
CI Code / Check spelling (pull_request) Successful in 13s
CI Code / Check coding style (pull_request) Successful in 23s
CI Code / Code Coverage (pull_request) Successful in 3m45s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m24s
CI Code / Linux (arch) (pull_request) Successful in 7m7s
CI Code / Linux (debian) (pull_request) Successful in 8m41s

The setting-name branch for /ai set custom never fired: it passed the
prefix "/ai set custom " with a trailing space to
autocomplete_param_with_ac(), which appends a space itself, so the
matched prefix contained a double space no real input ever has. Even
without that, the primitive completes the first token after the command
while the setting name is the second, and the num_args guard only held
before the name was started. The suggestion list (tools/search/memory/
plugins) predated the /ai set custom backend and matched no real
payload parameter.

Replace the branch with token-position completion (arg 5 via
autocomplete_param_no_with_func) fed by a list rebuilt on demand from
common payload parameters plus the keys already set on the provider,
exposed through the new ai_get_provider_setting_keys() (snapshot taken
under settings_lock). Drop the now-unused parse_args() call whose NULL
result was fed to g_strv_length() unguarded, spamming a glib CRITICAL
on TAB at /ai with zero or five-plus arguments.
This commit is contained in:
2026-07-06 11:08:58 +03:00
parent 14f76ab1db
commit b3a269342d
6 changed files with 112 additions and 22 deletions

View File

@@ -1192,11 +1192,6 @@ cmd_ac_init(void)
autocomplete_add_unsorted(ai_api_types_ac, "responses", FALSE);
autocomplete_add_unsorted(ai_api_types_ac, "auto", FALSE);
autocomplete_add_unsorted(ai_set_custom_subcommands_ac, "tools", FALSE);
autocomplete_add_unsorted(ai_set_custom_subcommands_ac, "search", FALSE);
autocomplete_add_unsorted(ai_set_custom_subcommands_ac, "memory", FALSE);
autocomplete_add_unsorted(ai_set_custom_subcommands_ac, "plugins", FALSE);
autocomplete_add_unsorted(ai_remove_subcommands_ac, "provider", FALSE);
autocomplete_add(avatar_ac, "set");
@@ -4545,17 +4540,61 @@ _ai_api_type_find(const char* const search_str, gboolean previous, void* context
return autocomplete_complete(ai_api_types_ac, search_str, FALSE, previous);
}
static char*
_ai_custom_setting_find(const char* const search_str, gboolean previous, void* context)
{
return autocomplete_complete(ai_set_custom_subcommands_ac, search_str, FALSE, previous);
}
/**
* Autocomplete the setting name for /ai set custom <provider> <setting>.
* Offers a few common payload parameters merged with the keys already set
* on the provider, so existing settings can be retyped or overridden.
*/
static char*
_ai_custom_setting_autocomplete(const char* const input, gboolean previous)
{
if (!g_str_has_prefix(input, "/ai set custom ")) {
return NULL;
}
/* Quote-aware parse: args[2] is the provider, args[3] the setting being
* typed; skip the rebuild when the caret is not at the setting position */
gboolean parse_result = FALSE;
auto_gcharv gchar** args = parse_args(input, 1, 5, &parse_result);
if (!parse_result) {
return NULL;
}
guint num_args = g_strv_length(args);
if (num_args < 3 || num_args > 4 || !ai_get_provider(args[2])) {
return NULL;
}
/* Rebuild the completion list: common parameters + the provider's keys */
gchar* suggestions[] = { "temperature", "max_tokens", "top_p" };
GList* keys = ai_get_provider_setting_keys(args[2]);
char** items = g_new0(char*, ARRAY_SIZE(suggestions) + g_list_length(keys) + 1);
guint i = 0;
for (; i < ARRAY_SIZE(suggestions); i++) {
items[i] = suggestions[i];
}
for (GList* curr = keys; curr; curr = g_list_next(curr)) {
items[i++] = curr->data;
}
autocomplete_update(ai_set_custom_subcommands_ac, items);
g_free(items);
g_list_free_full(keys, g_free);
/* /ai(1) set(2) custom(3) <provider>(4) <setting>(5) */
return autocomplete_param_no_with_func(input, "/ai set custom", 5, _ai_custom_setting_find, previous, NULL);
}
static char*
_ai_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
char* result = NULL;
/* Parse once for reuse - /ai <subcommand> [<arg1>] [<arg2>] */
gboolean parse_result = FALSE;
auto_gcharv gchar** args = parse_args(input, 1, 4, &parse_result);
int num_args = g_strv_length(args);
/* Top-level /ai <subcommand> autocomplete (e.g., /ai s<tab> -> /ai set) */
// /ai set provider <name> <url> - autocomplete provider names (for updating)
result = autocomplete_param_with_func(input, "/ai set provider", ai_providers_find, previous, NULL);
if (result) {
return result;
@@ -4591,18 +4630,12 @@ _ai_autocomplete(ProfWin* window, const char* const input, gboolean previous)
return result;
}
// /ai set custom <provider> <setting> - autocomplete settings
/* args[0]="set", args[1]="custom", args[2]=provider (if typed), args[3]=setting (if typed) */
if (num_args == 3 && g_strcmp0(args[1], "custom") == 0) {
/* /ai set custom <provider> - check if provider is valid */
if (ai_get_provider(args[2])) {
/* Valid provider, try settings autocomplete */
result = autocomplete_param_with_ac(input, "/ai set custom ", ai_set_custom_subcommands_ac, TRUE, previous);
if (result) {
return result;
}
}
// /ai set custom <provider> <setting> - autocomplete setting names
result = _ai_custom_setting_autocomplete(input, previous);
if (result) {
return result;
}
// /ai set <subcommand> - autocomplete subcommands
result = autocomplete_param_with_ac(input, "/ai set", ai_set_subcommands_ac, TRUE, previous);
if (result) {