feat(ai): add model caching, settings, and commands
Some checks failed
CI Code / Check spelling (pull_request) Successful in 23s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Code Coverage (pull_request) Successful in 2m28s
CI Code / Linux (debian) (pull_request) Failing after 6m18s
CI Code / Linux (arch) (pull_request) Failing after 8m46s
CI Code / Linux (ubuntu) (pull_request) Failing after 11m4s
Some checks failed
CI Code / Check spelling (pull_request) Successful in 23s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Code Coverage (pull_request) Successful in 2m28s
CI Code / Linux (debian) (pull_request) Failing after 6m18s
CI Code / Linux (arch) (pull_request) Failing after 8m46s
CI Code / Linux (ubuntu) (pull_request) Failing after 11m4s
- Introduce model caching with persistence to preferences - Add provider default model and custom settings management - Implement `/ai switch`, `/ai models`, and improve `/ai start` - Add model name autocomplete for chat commands - Update command definitions and help text - Add unit tests for new functionality
This commit is contained in:
@@ -461,3 +461,186 @@ test_ai_providers_find_case_insensitive(void** state)
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* AI Autocomplete Integration Tests
|
||||
* ======================================================================== */
|
||||
|
||||
void
|
||||
test_ai_start_provider_autocomplete_only_on_exact(void** state)
|
||||
{
|
||||
auto_gchar gchar* result = ai_providers_find("", FALSE, NULL);
|
||||
assert_non_null(result);
|
||||
assert_string_equal("openai", result);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_models_find_null_session(void** state)
|
||||
{
|
||||
auto_gchar gchar* result = ai_models_find("gpt", FALSE, NULL);
|
||||
assert_null(result);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_models_find_null_provider(void** state)
|
||||
{
|
||||
ai_add_provider("temp", "https://temp.api.com/v1", NULL);
|
||||
AISession* session = ai_session_create("temp", "gpt-4");
|
||||
assert_non_null(session);
|
||||
ai_session_unref(session);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_providers_find_cycling(void** state)
|
||||
{
|
||||
/* NULL search_str should cycle through providers */
|
||||
auto_gchar gchar* result1 = ai_providers_find(NULL, FALSE, NULL);
|
||||
assert_non_null(result1);
|
||||
|
||||
auto_gchar gchar* result2 = ai_providers_find(NULL, FALSE, NULL);
|
||||
assert_non_null(result2);
|
||||
|
||||
/* Cycling: consecutive calls with NULL should return different providers */
|
||||
assert_string_not_equal(result1, result2);
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* Provider Default Model and Settings Tests
|
||||
* ======================================================================== */
|
||||
|
||||
void
|
||||
test_ai_set_provider_default_model(void** state)
|
||||
{
|
||||
/* Set default model for openai provider */
|
||||
ai_set_provider_default_model("openai", "gpt-5");
|
||||
|
||||
/* Verify the model was set */
|
||||
const gchar* model = ai_get_provider_default_model("openai");
|
||||
assert_non_null(model);
|
||||
assert_string_equal("gpt-5", model);
|
||||
|
||||
/* Update default model */
|
||||
ai_set_provider_default_model("openai", "gpt-4o");
|
||||
model = ai_get_provider_default_model("openai");
|
||||
assert_non_null(model);
|
||||
assert_string_equal("gpt-4o", model);
|
||||
|
||||
/* Set default model for perplexity */
|
||||
ai_set_provider_default_model("perplexity", "sonar-pro");
|
||||
model = ai_get_provider_default_model("perplexity");
|
||||
assert_non_null(model);
|
||||
assert_string_equal("sonar-pro", model);
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_get_provider_default_model(void** state)
|
||||
{
|
||||
/* NULL provider returns NULL */
|
||||
assert_null(ai_get_provider_default_model(NULL));
|
||||
|
||||
/* Non-existent provider returns NULL */
|
||||
assert_null(ai_get_provider_default_model("nonexistent"));
|
||||
|
||||
/* Default providers may or may not have default models set initially */
|
||||
/* After setting, they should return the model */
|
||||
ai_set_provider_default_model("openai", "test-model");
|
||||
assert_string_equal("test-model", ai_get_provider_default_model("openai"));
|
||||
|
||||
/* NULL model argument should be ignored (no change) */
|
||||
ai_set_provider_default_model("openai", NULL);
|
||||
/* After setting NULL, the model should still be "test-model" because
|
||||
* ai_set_provider_default_model returns early on NULL model */
|
||||
assert_string_equal("test-model", ai_get_provider_default_model("openai"));
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_set_provider_setting(void** state)
|
||||
{
|
||||
/* NULL provider should be ignored */
|
||||
ai_set_provider_setting(NULL, "tools", "enabled");
|
||||
|
||||
/* NULL setting should be ignored */
|
||||
ai_set_provider_setting("openai", NULL, "enabled");
|
||||
|
||||
/* Non-existent provider should log warning but not crash */
|
||||
ai_set_provider_setting("nonexistent", "tools", "enabled");
|
||||
|
||||
/* Set a setting for openai */
|
||||
ai_set_provider_setting("openai", "tools", "enabled");
|
||||
ai_set_provider_setting("openai", "search", "disabled");
|
||||
|
||||
/* Verify settings were set */
|
||||
gchar* tools = ai_get_provider_setting("openai", "tools");
|
||||
assert_non_null(tools);
|
||||
assert_string_equal("enabled", tools);
|
||||
g_free(tools);
|
||||
|
||||
gchar* search = ai_get_provider_setting("openai", "search");
|
||||
assert_non_null(search);
|
||||
assert_string_equal("disabled", search);
|
||||
g_free(search);
|
||||
|
||||
/* NULL value removes the setting */
|
||||
ai_set_provider_setting("openai", "tools", NULL);
|
||||
tools = ai_get_provider_setting("openai", "tools");
|
||||
assert_null(tools);
|
||||
|
||||
/* Setting that was never set returns NULL */
|
||||
assert_null(ai_get_provider_setting("openai", "nonexistent_setting"));
|
||||
}
|
||||
|
||||
void
|
||||
test_ai_get_provider_setting(void** state)
|
||||
{
|
||||
/* NULL provider returns NULL */
|
||||
assert_null(ai_get_provider_setting(NULL, "tools"));
|
||||
|
||||
/* NULL setting returns NULL */
|
||||
assert_null(ai_get_provider_setting("openai", NULL));
|
||||
|
||||
/* Non-existent provider returns NULL */
|
||||
assert_null(ai_get_provider_setting("nonexistent", "tools"));
|
||||
|
||||
/* Provider without settings returns NULL */
|
||||
assert_null(ai_get_provider_setting("perplexity", "tools"));
|
||||
|
||||
/* Set and get setting */
|
||||
ai_set_provider_setting("openai", "temperature", "0.7");
|
||||
gchar* temp = ai_get_provider_setting("openai", "temperature");
|
||||
assert_non_null(temp);
|
||||
assert_string_equal("0.7", temp);
|
||||
g_free(temp);
|
||||
|
||||
/* Setting value is a copy (caller must free) */
|
||||
ai_set_provider_setting("openai", "max_tokens", "2048");
|
||||
temp = ai_get_provider_setting("openai", "max_tokens");
|
||||
assert_non_null(temp);
|
||||
assert_string_equal("2048", temp);
|
||||
g_free(temp);
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
* Model Caching Tests
|
||||
* ======================================================================== */
|
||||
|
||||
void
|
||||
test_ai_models_are_fresh_initial(void** state)
|
||||
{
|
||||
/* After init, models should be fresh (or at least not crash) */
|
||||
/* The ai_client_init() loads models from prefs, which may be empty initially */
|
||||
/* So we just verify the function works correctly */
|
||||
|
||||
/* NULL provider returns FALSE */
|
||||
assert_false(ai_models_are_fresh(NULL));
|
||||
|
||||
/* Non-existent provider returns FALSE */
|
||||
assert_false(ai_models_are_fresh("nonexistent"));
|
||||
|
||||
/* Default providers - check that function doesn't crash */
|
||||
/* Freshness depends on whether models were loaded from prefs */
|
||||
gboolean fresh_openai = ai_models_are_fresh("openai");
|
||||
assert_true(fresh_openai == TRUE || fresh_openai == FALSE); /* Just verify no crash */
|
||||
|
||||
gboolean fresh_perplexity = ai_models_are_fresh("perplexity");
|
||||
assert_true(fresh_perplexity == TRUE || fresh_perplexity == FALSE); /* Just verify no crash */
|
||||
}
|
||||
|
||||
@@ -40,3 +40,15 @@ void test_ai_providers_find_previous(void** state);
|
||||
void test_ai_providers_find_null_search_str(void** state);
|
||||
void test_ai_providers_find_empty_search_str(void** state);
|
||||
void test_ai_providers_find_case_insensitive(void** state);
|
||||
/* Provider default model and settings tests */
|
||||
void test_ai_set_provider_default_model(void** state);
|
||||
void test_ai_get_provider_default_model(void** state);
|
||||
void test_ai_set_provider_setting(void** state);
|
||||
void test_ai_get_provider_setting(void** state);
|
||||
/* Model caching tests */
|
||||
void test_ai_models_are_fresh_initial(void** state);
|
||||
/* AI autocomplete integration tests */
|
||||
void test_ai_start_provider_autocomplete_only_on_exact(void** state);
|
||||
void test_ai_models_find_null_session(void** state);
|
||||
void test_ai_models_find_null_provider(void** state);
|
||||
void test_ai_providers_find_cycling(void** state);
|
||||
|
||||
@@ -690,6 +690,18 @@ main(int argc, char* argv[])
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_null_search_str, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_empty_search_str, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_case_insensitive, ai_client_setup, ai_client_teardown),
|
||||
/* Provider default model and settings tests */
|
||||
cmocka_unit_test_setup_teardown(test_ai_set_provider_default_model, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_get_provider_default_model, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_set_provider_setting, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_get_provider_setting, ai_client_setup, ai_client_teardown),
|
||||
/* Model caching tests */
|
||||
cmocka_unit_test_setup_teardown(test_ai_models_are_fresh_initial, ai_client_setup, ai_client_teardown),
|
||||
/* AI autocomplete integration tests */
|
||||
cmocka_unit_test_setup_teardown(test_ai_start_provider_autocomplete_only_on_exact, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_models_find_null_session, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_models_find_null_provider, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test_setup_teardown(test_ai_providers_find_cycling, ai_client_setup, ai_client_teardown),
|
||||
cmocka_unit_test(test_ai_json_escape),
|
||||
cmocka_unit_test(test_ai_json_escape_null),
|
||||
cmocka_unit_test(test_ai_json_escape_empty),
|
||||
|
||||
Reference in New Issue
Block a user