#include "cst_agent.h" #include "cst_util.h" #include "os_compat.h" #include "cJSON.h" #include #include #include static int settings_path(char *out, size_t n) { char dir[OS_PATH_MAX]; if (os_config_dir(dir, sizeof(dir)) != 0) return -1; return os_path_join(out, n, dir, "settings.json"); } static void copy_str(char *dst, size_t n, const cJSON *v) { if (cJSON_IsString(v) && v->valuestring) snprintf(dst, n, "%s", v->valuestring); } int cst_settings_load(cst_settings_t *s) { memset(s, 0, sizeof(*s)); char path[OS_PATH_MAX]; if (settings_path(path, sizeof(path)) != 0) return -1; FILE *fp = fopen(path, "rb"); if (!fp) return -1; static _Thread_local char buf[65536]; size_t n = fread(buf, 1, sizeof(buf) - 1, fp); fclose(fp); buf[n] = 0; cJSON *root = cJSON_Parse(buf + cst_bom_len(buf, n)); if (!cJSON_IsObject(root)) { cJSON_Delete(root); return -1; } copy_str(s->provider, sizeof(s->provider), cJSON_GetObjectItemCaseSensitive(root, "provider")); copy_str(s->effort, sizeof(s->effort), cJSON_GetObjectItemCaseSensitive(root, "effort")); copy_str(s->approval, sizeof(s->approval), cJSON_GetObjectItemCaseSensitive(root, "approval")); s->show_thinking = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(root, "show_thinking")); const cJSON *tr = cJSON_GetObjectItemCaseSensitive(root, "trusted_workspaces"); const cJSON *w; cJSON_ArrayForEach(w, tr) { if (s->ntrusted < CST_SETTINGS_MAX_TRUSTED && cJSON_IsString(w)) copy_str(s->trusted[s->ntrusted++], sizeof(s->trusted[0]), w); } const cJSON *models = cJSON_GetObjectItemCaseSensitive(root, "models"); const cJSON *m; cJSON_ArrayForEach(m, models) { if (s->nmodels >= CST_SETTINGS_MAX_MODELS) break; if (!m->string || !cJSON_IsString(m)) continue; snprintf(s->models[s->nmodels].provider, sizeof(s->models[s->nmodels].provider), "%s", m->string); copy_str(s->models[s->nmodels].model, sizeof(s->models[s->nmodels].model), m); s->nmodels++; } cJSON_Delete(root); return 0; } int cst_settings_save(const cst_settings_t *s) { char dir[OS_PATH_MAX], path[OS_PATH_MAX], tmp[OS_PATH_MAX + 8]; if (os_config_dir(dir, sizeof(dir)) != 0 || os_mkdir_p(dir, 0700) != 0 || settings_path(path, sizeof(path)) != 0) return -1; if (snprintf(tmp, sizeof(tmp), "%s.tmp", path) >= (int)sizeof(tmp)) return -1; cJSON *root = cJSON_CreateObject(); if (s->provider[0]) cJSON_AddStringToObject(root, "provider", s->provider); if (s->effort[0]) cJSON_AddStringToObject(root, "effort", s->effort); if (s->approval[0]) cJSON_AddStringToObject(root, "approval", s->approval); if (s->show_thinking) cJSON_AddTrueToObject(root, "show_thinking"); if (s->ntrusted) { cJSON *tr = cJSON_AddArrayToObject(root, "trusted_workspaces"); for (int i = 0; i < s->ntrusted; i++) cJSON_AddItemToArray(tr, cJSON_CreateString(s->trusted[i])); } cJSON *models = cJSON_AddObjectToObject(root, "models"); for (int i = 0; i < s->nmodels; i++) cJSON_AddStringToObject(models, s->models[i].provider, s->models[i].model); char *text = cJSON_Print(root); cJSON_Delete(root); if (!text) return -1; FILE *fp = fopen(tmp, "wb"); int rc = -1; if (fp) { int ok = fputs(text, fp) >= 0 && fputc('\n', fp) != EOF; if (fclose(fp) == 0 && ok) { #ifdef _WIN32 remove(path); #endif rc = rename(tmp, path) == 0 ? 0 : -1; } if (rc != 0) remove(tmp); } cJSON_free(text); return rc; } static const char *const APPROVAL_NAMES[] = {"ask", "auto", "yolo"}; const char *cst_approval_name(int a) { return a >= 0 && a <= CST_APPROVAL_YOLO ? APPROVAL_NAMES[a] : "auto"; } int cst_approval_parse(const char *s, int *out) { for (int i = 0; s && i <= CST_APPROVAL_YOLO; i++) { if (strcmp(s, APPROVAL_NAMES[i]) == 0) { *out = i; return 0; } } return -1; } const char *cst_settings_model(const cst_settings_t *s, const char *provider) { for (int i = 0; provider && i < s->nmodels; i++) { if (strcmp(s->models[i].provider, provider) == 0 && s->models[i].model[0]) return s->models[i].model; } return NULL; } int cst_settings_remember(const cst_agent_config_t *cfg) { cst_settings_t s; cst_settings_load(&s); snprintf(s.provider, sizeof(s.provider), "%s", cfg->provider); snprintf(s.effort, sizeof(s.effort), "%s", cfg->effort); int i = 0; while (i < s.nmodels && strcmp(s.models[i].provider, cfg->provider) != 0) i++; if (i == s.nmodels) { if (s.nmodels >= CST_SETTINGS_MAX_MODELS) return -1; s.nmodels++; } snprintf(s.models[i].provider, sizeof(s.models[i].provider), "%s", cfg->provider); snprintf(s.models[i].model, sizeof(s.models[i].model), "%s", cfg->model); return cst_settings_save(&s); } int cst_settings_remember_approval(int approval) { cst_settings_t s; cst_settings_load(&s); snprintf(s.approval, sizeof(s.approval), "%s", cst_approval_name(approval)); return cst_settings_save(&s); } int cst_settings_remember_thinking(int show_thinking) { cst_settings_t s; cst_settings_load(&s); s.show_thinking = show_thinking != 0; return cst_settings_save(&s); } static void real_ws(const char *ws, char *out, size_t n) { if (!ws || os_realpath(ws, out, n) != 0) snprintf(out, n, "%s", ws ? ws : ""); } int cst_settings_trusted(const char *workspace) { char real[OS_PATH_MAX]; real_ws(workspace, real, sizeof(real)); cst_settings_t s; cst_settings_load(&s); for (int i = 0; i < s.ntrusted; i++) if (strcmp(s.trusted[i], real) == 0) return 1; return 0; } int cst_settings_trust(const char *workspace, int on) { char real[OS_PATH_MAX]; real_ws(workspace, real, sizeof(real)); if (!real[0] || strlen(real) >= sizeof(((cst_settings_t *)0)->trusted[0])) return -1; cst_settings_t s; cst_settings_load(&s); int k = 0, found = 0; for (int i = 0; i < s.ntrusted; i++) { if (strcmp(s.trusted[i], real) == 0) { found = 1; if (!on) continue; } if (k != i) memcpy(s.trusted[k], s.trusted[i], sizeof(s.trusted[0])); k++; } s.ntrusted = k; if (on && !found) { if (s.ntrusted >= CST_SETTINGS_MAX_TRUSTED) { memmove(s.trusted[0], s.trusted[1], sizeof(s.trusted[0]) * (CST_SETTINGS_MAX_TRUSTED - 1)); s.ntrusted--; } snprintf(s.trusted[s.ntrusted++], sizeof(s.trusted[0]), "%.511s", real); } return cst_settings_save(&s); }