427 lines
15 KiB
C
427 lines
15 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "tools_internal.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#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_global_llm_user_settings_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* recall_args = cJSON_CreateObject();
|
|
if (!recall_args) {
|
|
if (out_error) *out_error = strdup("allocation failure while building config_recall args");
|
|
return -1;
|
|
}
|
|
cJSON_AddStringToObject(recall_args, "d_tag", "user-settings");
|
|
|
|
char* recall_args_json = cJSON_PrintUnformatted(recall_args);
|
|
cJSON_Delete(recall_args);
|
|
if (!recall_args_json) {
|
|
if (out_error) *out_error = strdup("failed to serialize config_recall args");
|
|
return -1;
|
|
}
|
|
|
|
char* recall_result = execute_config_recall(ctx, recall_args_json);
|
|
free(recall_args_json);
|
|
if (!recall_result) {
|
|
if (out_error) *out_error = strdup("config_recall returned no response");
|
|
return -1;
|
|
}
|
|
|
|
cJSON* recall_root = cJSON_Parse(recall_result);
|
|
free(recall_result);
|
|
if (!recall_root || !cJSON_IsObject(recall_root)) {
|
|
cJSON_Delete(recall_root);
|
|
if (out_error) *out_error = strdup("config_recall returned invalid JSON");
|
|
return -1;
|
|
}
|
|
|
|
cJSON* recall_success = cJSON_GetObjectItemCaseSensitive(recall_root, "success");
|
|
if (!recall_success || !cJSON_IsBool(recall_success) || !cJSON_IsTrue(recall_success)) {
|
|
cJSON* err = cJSON_GetObjectItemCaseSensitive(recall_root, "error");
|
|
if (out_error) {
|
|
if (err && cJSON_IsString(err) && err->valuestring) {
|
|
*out_error = strdup(err->valuestring);
|
|
} else {
|
|
*out_error = strdup("config_recall failed");
|
|
}
|
|
}
|
|
cJSON_Delete(recall_root);
|
|
return -1;
|
|
}
|
|
|
|
cJSON* found_j = cJSON_GetObjectItemCaseSensitive(recall_root, "found");
|
|
int found = (found_j && cJSON_IsBool(found_j) && cJSON_IsTrue(found_j)) ? 1 : 0;
|
|
|
|
cJSON* content_json = cJSON_GetObjectItemCaseSensitive(recall_root, "content_json");
|
|
cJSON* user_settings = NULL;
|
|
if (found && content_json && cJSON_IsObject(content_json)) {
|
|
user_settings = cJSON_Duplicate(content_json, 1);
|
|
}
|
|
if (!user_settings) {
|
|
user_settings = cJSON_CreateObject();
|
|
}
|
|
cJSON_Delete(recall_root);
|
|
|
|
if (!user_settings) {
|
|
if (out_error) *out_error = strdup("allocation failure while preparing user-settings payload");
|
|
return -1;
|
|
}
|
|
|
|
cJSON_DeleteItemFromObjectCaseSensitive(user_settings, "v");
|
|
cJSON_AddNumberToObject(user_settings, "v", 2);
|
|
cJSON_DeleteItemFromObjectCaseSensitive(user_settings, "updatedAt");
|
|
cJSON_AddNumberToObject(user_settings, "updatedAt", (double)time(NULL));
|
|
|
|
cJSON* global_llm = cJSON_GetObjectItemCaseSensitive(user_settings, "global_llm");
|
|
if (global_llm && !cJSON_IsObject(global_llm)) {
|
|
cJSON_DeleteItemFromObjectCaseSensitive(user_settings, "global_llm");
|
|
global_llm = NULL;
|
|
}
|
|
if (!global_llm) {
|
|
cJSON* new_global_llm = cJSON_CreateObject();
|
|
if (!new_global_llm) {
|
|
cJSON_Delete(user_settings);
|
|
if (out_error) *out_error = strdup("allocation failure while preparing global_llm payload");
|
|
return -1;
|
|
}
|
|
cJSON_AddItemToObject(user_settings, "global_llm", new_global_llm);
|
|
global_llm = new_global_llm;
|
|
}
|
|
|
|
cJSON_DeleteItemFromObjectCaseSensitive(global_llm, "provider");
|
|
cJSON_AddStringToObject(global_llm, "provider", cfg->provider);
|
|
cJSON_DeleteItemFromObjectCaseSensitive(global_llm, "api_key");
|
|
cJSON_AddStringToObject(global_llm, "api_key", cfg->api_key);
|
|
cJSON_DeleteItemFromObjectCaseSensitive(global_llm, "model");
|
|
cJSON_AddStringToObject(global_llm, "model", cfg->model);
|
|
cJSON_DeleteItemFromObjectCaseSensitive(global_llm, "base_url");
|
|
cJSON_AddStringToObject(global_llm, "base_url", cfg->base_url);
|
|
cJSON_DeleteItemFromObjectCaseSensitive(global_llm, "max_tokens");
|
|
cJSON_AddNumberToObject(global_llm, "max_tokens", cfg->max_tokens);
|
|
cJSON_DeleteItemFromObjectCaseSensitive(global_llm, "temperature");
|
|
cJSON_AddNumberToObject(global_llm, "temperature", cfg->temperature);
|
|
|
|
cJSON* didactyl = cJSON_GetObjectItemCaseSensitive(user_settings, "didactyl");
|
|
if (didactyl && !cJSON_IsObject(didactyl)) {
|
|
cJSON_DeleteItemFromObjectCaseSensitive(user_settings, "didactyl");
|
|
didactyl = NULL;
|
|
}
|
|
if (!didactyl) {
|
|
cJSON* new_didactyl = cJSON_CreateObject();
|
|
if (!new_didactyl) {
|
|
cJSON_Delete(user_settings);
|
|
if (out_error) *out_error = strdup("allocation failure while preparing didactyl payload");
|
|
return -1;
|
|
}
|
|
cJSON_AddItemToObject(user_settings, "didactyl", new_didactyl);
|
|
didactyl = new_didactyl;
|
|
}
|
|
|
|
cJSON_DeleteItemFromObjectCaseSensitive(didactyl, "max_turns");
|
|
cJSON_AddNumberToObject(didactyl,
|
|
"max_turns",
|
|
(ctx->cfg->tools.max_turns > 0) ? ctx->cfg->tools.max_turns : 40);
|
|
|
|
cJSON* store_args = cJSON_CreateObject();
|
|
if (!store_args) {
|
|
cJSON_Delete(user_settings);
|
|
if (out_error) *out_error = strdup("allocation failure while building config_store args");
|
|
return -1;
|
|
}
|
|
|
|
cJSON_AddStringToObject(store_args, "d_tag", "user-settings");
|
|
cJSON_AddItemToObject(store_args, "content", user_settings);
|
|
|
|
char* store_args_json = cJSON_PrintUnformatted(store_args);
|
|
cJSON_Delete(store_args);
|
|
if (!store_args_json) {
|
|
if (out_error) *out_error = strdup("failed to serialize user-settings 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* store_root = cJSON_Parse(store_result);
|
|
free(store_result);
|
|
if (!store_root || !cJSON_IsObject(store_root)) {
|
|
cJSON_Delete(store_root);
|
|
if (out_error) *out_error = strdup("config_store returned invalid JSON");
|
|
return -1;
|
|
}
|
|
|
|
cJSON* store_success = cJSON_GetObjectItemCaseSensitive(store_root, "success");
|
|
if (!store_success || !cJSON_IsBool(store_success) || !cJSON_IsTrue(store_success)) {
|
|
cJSON* err = cJSON_GetObjectItemCaseSensitive(store_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(store_root);
|
|
return -1;
|
|
}
|
|
|
|
cJSON_Delete(store_root);
|
|
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_global_llm_user_settings_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;
|
|
}
|