#define _POSIX_C_SOURCE 200809L #include "tools_internal.h" #include #include #include #include "cjson/cJSON.h" #include "../config.h" #include "../llm.h" static char* json_error_local(const char* msg) { cJSON* root = cJSON_CreateObject(); if (!root) return NULL; cJSON_AddBoolToObject(root, "success", 0); cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error"); char* out = cJSON_PrintUnformatted(root); cJSON_Delete(root); return out; } static cJSON* parse_args_local(const char* args_json) { const char* raw = args_json ? args_json : "{}"; cJSON* args = cJSON_Parse(raw); if (!args || !cJSON_IsObject(args)) { cJSON_Delete(args); return NULL; } return args; } static int persist_llm_config_nostr(tools_context_t* ctx, const llm_config_t* cfg, char** out_error) { if (!ctx || !ctx->cfg || !cfg) { if (out_error) *out_error = strdup("invalid persist context"); return -1; } if (out_error) *out_error = NULL; cJSON* args = cJSON_CreateObject(); cJSON* content = cJSON_CreateObject(); if (!args || !content) { cJSON_Delete(args); cJSON_Delete(content); if (out_error) *out_error = strdup("allocation failure while building llm_config payload"); return -1; } cJSON_AddStringToObject(args, "d_tag", "llm_config"); cJSON_AddStringToObject(content, "provider", cfg->provider); cJSON_AddStringToObject(content, "api_key", cfg->api_key); cJSON_AddStringToObject(content, "model", cfg->model); cJSON_AddStringToObject(content, "base_url", cfg->base_url); cJSON_AddNumberToObject(content, "max_tokens", cfg->max_tokens); cJSON_AddNumberToObject(content, "temperature", cfg->temperature); cJSON_AddItemToObject(args, "content", content); char* store_args_json = cJSON_PrintUnformatted(args); cJSON_Delete(args); if (!store_args_json) { if (out_error) *out_error = strdup("failed to serialize llm_config payload"); return -1; } char* store_result = execute_config_store(ctx, store_args_json); free(store_args_json); if (!store_result) { if (out_error) *out_error = strdup("config_store returned no response"); return -1; } cJSON* root = cJSON_Parse(store_result); if (!root || !cJSON_IsObject(root)) { cJSON_Delete(root); if (out_error) *out_error = strdup("config_store returned invalid JSON"); free(store_result); return -1; } cJSON* success = cJSON_GetObjectItemCaseSensitive(root, "success"); if (!success || !cJSON_IsBool(success) || !cJSON_IsTrue(success)) { cJSON* err = cJSON_GetObjectItemCaseSensitive(root, "error"); if (out_error) { if (err && cJSON_IsString(err) && err->valuestring) { *out_error = strdup(err->valuestring); } else { *out_error = strdup("config_store failed"); } } cJSON_Delete(root); free(store_result); return -1; } cJSON_Delete(root); free(store_result); return 0; } static int assign_string_field(cJSON* item, char* dst, size_t dst_size, int* changed) { if (!item) return 0; if (!cJSON_IsString(item) || !item->valuestring) return -1; size_t n = strlen(item->valuestring); if (n >= dst_size) return -1; memcpy(dst, item->valuestring, n + 1U); if (changed) *changed = 1; return 0; } static void mask_api_key_local(const char* api_key, char* out, size_t out_size) { if (!out || out_size == 0) return; out[0] = '\0'; if (!api_key || api_key[0] == '\0') { return; } size_t api_len = strlen(api_key); if (api_len >= 8U) { snprintf(out, out_size, "%.4s...%s", api_key, api_key + api_len - 4U); } else { snprintf(out, out_size, "%s", "(set)"); } } char* execute_model_get(const char* args_json) { cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); llm_config_t cfg; if (llm_get_config(&cfg) != 0) { return json_error_local("llm runtime unavailable"); } cJSON* out = cJSON_CreateObject(); if (!out) return NULL; char api_key_masked[64] = {0}; mask_api_key_local(cfg.api_key, api_key_masked, sizeof(api_key_masked)); cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "provider", cfg.provider); cJSON_AddStringToObject(out, "model", cfg.model); cJSON_AddStringToObject(out, "base_url", cfg.base_url); cJSON_AddStringToObject(out, "api_key", api_key_masked); cJSON_AddNumberToObject(out, "max_tokens", cfg.max_tokens); cJSON_AddNumberToObject(out, "temperature", cfg.temperature); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); return json; } char* execute_model_set(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); llm_config_t cfg; if (llm_get_config(&cfg) != 0) { cJSON_Delete(args); return json_error_local("llm runtime unavailable"); } int changed = 0; cJSON* provider = cJSON_GetObjectItemCaseSensitive(args, "provider"); cJSON* api_key = cJSON_GetObjectItemCaseSensitive(args, "api_key"); cJSON* model = cJSON_GetObjectItemCaseSensitive(args, "model"); cJSON* base_url = cJSON_GetObjectItemCaseSensitive(args, "base_url"); cJSON* max_tokens = cJSON_GetObjectItemCaseSensitive(args, "max_tokens"); cJSON* temperature = cJSON_GetObjectItemCaseSensitive(args, "temperature"); if (assign_string_field(provider, cfg.provider, sizeof(cfg.provider), &changed) != 0 || assign_string_field(api_key, cfg.api_key, sizeof(cfg.api_key), &changed) != 0 || assign_string_field(model, cfg.model, sizeof(cfg.model), &changed) != 0 || assign_string_field(base_url, cfg.base_url, sizeof(cfg.base_url), &changed) != 0) { cJSON_Delete(args); return json_error_local("model_set string field invalid or too long"); } if (max_tokens) { if (!cJSON_IsNumber(max_tokens)) { cJSON_Delete(args); return json_error_local("model_set max_tokens must be a number"); } cfg.max_tokens = (int)max_tokens->valuedouble; changed = 1; } if (temperature) { if (!cJSON_IsNumber(temperature)) { cJSON_Delete(args); return json_error_local("model_set temperature must be a number"); } cfg.temperature = temperature->valuedouble; changed = 1; } cJSON_Delete(args); if (!changed) { return json_error_local("model_set requires at least one field to update"); } if (llm_set_config(&cfg) != 0) { return json_error_local("failed to update runtime llm config"); } ctx->cfg->llm = cfg; char* persist_error = NULL; int persisted = (persist_llm_config_nostr(ctx, &cfg, &persist_error) == 0) ? 1 : 0; cJSON* out = cJSON_CreateObject(); if (!out) return NULL; char api_key_masked[64] = {0}; mask_api_key_local(cfg.api_key, api_key_masked, sizeof(api_key_masked)); cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "provider", cfg.provider); cJSON_AddStringToObject(out, "model", cfg.model); cJSON_AddStringToObject(out, "base_url", cfg.base_url); cJSON_AddStringToObject(out, "api_key", api_key_masked); cJSON_AddNumberToObject(out, "max_tokens", cfg.max_tokens); cJSON_AddNumberToObject(out, "temperature", cfg.temperature); cJSON_AddBoolToObject(out, "persisted", persisted ? 1 : 0); if (!persisted && persist_error && persist_error[0] != '\0') { cJSON_AddStringToObject(out, "persist_warning", persist_error); } char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); free(persist_error); return json; } static void append_model_id(cJSON* ids, cJSON* item) { if (!ids || !item) return; if (cJSON_IsString(item) && item->valuestring) { cJSON_AddItemToArray(ids, cJSON_CreateString(item->valuestring)); return; } if (cJSON_IsObject(item)) { cJSON* id = cJSON_GetObjectItemCaseSensitive(item, "id"); if (id && cJSON_IsString(id) && id->valuestring) { cJSON_AddItemToArray(ids, cJSON_CreateString(id->valuestring)); } } } char* execute_model_list(const char* args_json) { cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON* base_url = cJSON_GetObjectItemCaseSensitive(args, "base_url"); if (base_url && (!cJSON_IsString(base_url) || !base_url->valuestring)) { cJSON_Delete(args); return json_error_local("model_list base_url must be a string"); } const char* base_url_override = (base_url && base_url->valuestring && base_url->valuestring[0] != '\0') ? base_url->valuestring : NULL; char* raw = llm_list_models_json(base_url_override); cJSON_Delete(args); if (!raw) { return json_error_local("model_list request failed"); } cJSON* root = cJSON_Parse(raw); free(raw); if (!root) { cJSON_Delete(root); return json_error_local("model_list returned invalid JSON"); } cJSON* ids = cJSON_CreateArray(); cJSON* out = cJSON_CreateObject(); if (!ids || !out) { cJSON_Delete(ids); cJSON_Delete(out); cJSON_Delete(root); return NULL; } if (cJSON_IsArray(root)) { int n = cJSON_GetArraySize(root); for (int i = 0; i < n; i++) { append_model_id(ids, cJSON_GetArrayItem(root, i)); } } else if (cJSON_IsObject(root)) { cJSON* data = cJSON_GetObjectItemCaseSensitive(root, "data"); if (data && cJSON_IsArray(data)) { int n = cJSON_GetArraySize(data); for (int i = 0; i < n; i++) { append_model_id(ids, cJSON_GetArrayItem(data, i)); } } } cJSON_AddBoolToObject(out, "success", 1); cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(ids)); cJSON_AddItemToObject(out, "models", ids); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); cJSON_Delete(root); return json; }