Files
CST-agent/src/util.c
T
WHC 2fde898da0 Usability pass: tool registry, TUI polish, saved keys and settings
Tools
- Table-driven tool registry with permission classes and default/minimal
  profiles; new tools: edit_file, list_dir, glob, grep, delete_file,
  move_file, todo_write, web_fetch, ask_user (plus read_file paging).
- Built-in Myers diff; write tools return unified diffs.
- Persistent permission rules (.agent/permissions.json), per-turn undo
  snapshots (.agent/undo), AGENTS.md project notes, auto-compact at 80%
  of the model context, cancellable run_shell with process-group kill.

TUI
- Table-driven slash commands: /provider /key /effort /status /tools
  /permissions /yolo /undo /retry /diff /export /copy /rename /delete
  /init /max-steps /todo, with / and @ completion.
- Permission modal with diff and prefix rules, ask_user modal, todo
  panel, header with model/effort/context, bracketed paste, line editing,
  wide-char cursor, NO_COLOR and ASCII fallback; Esc never quits.

Startup and configuration
- API keys are entered in the TUI (hidden) and saved per provider in
  <config>/credentials (0600); env variables are only a fallback.
- Last provider, per-provider model and reasoning effort are remembered
  in <config>/settings.json; --effort/-e and /effort send
  reasoning_effort (low|medium|high|max) to models that accept it.
- DeepSeek preset uses deepseek-flash / deepseek-v4-pro.
- Session flags --new/--continue/--resume/--list-sessions, --yolo,
  --confirm.

Compatibility and tests
- ncurses detection, macOS/Homebrew support, make install, XDG config
  dir, POSIX cleanups; Gitea CI (gcc/clang/asan/musl/macOS).
- Unit tests for diff, fs tools, registry, undo/perms and config;
  e2e fixtures multi_file_refactor and ask_or_todo; vendored cJSON.
2026-10-05 01:47:01 +08:00

164 lines
3.4 KiB
C

#include "cst_util.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int buf_reserve(cst_buf_t *b, size_t extra) {
if (b->len + extra + 1 <= b->cap)
return 0;
size_t nc = b->cap ? b->cap : 256;
while (nc < b->len + extra + 1)
nc *= 2;
char *nb = realloc(b->buf, nc);
if (!nb)
return -1;
b->buf = nb;
b->cap = nc;
return 0;
}
int cst_buf_append(cst_buf_t *b, const char *s, size_t n) {
if (!b)
return -1;
if (buf_reserve(b, n) != 0)
return -1;
if (n)
memcpy(b->buf + b->len, s, n);
b->len += n;
b->buf[b->len] = 0;
return 0;
}
int cst_buf_puts(cst_buf_t *b, const char *s) {
return cst_buf_append(b, s ? s : "", s ? strlen(s) : 0);
}
int cst_buf_printf(cst_buf_t *b, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
va_list ap2;
va_copy(ap2, ap);
int n = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (n < 0 || buf_reserve(b, (size_t)n) != 0) {
va_end(ap2);
return -1;
}
vsnprintf(b->buf + b->len, (size_t)n + 1, fmt, ap2);
va_end(ap2);
b->len += (size_t)n;
return 0;
}
void cst_buf_free(cst_buf_t *b) {
if (!b)
return;
free(b->buf);
b->buf = NULL;
b->len = b->cap = 0;
}
char *cst_buf_steal(cst_buf_t *b) {
if (buf_reserve(b, 0) != 0)
return NULL;
char *s = b->buf;
b->buf = NULL;
b->len = b->cap = 0;
return s;
}
size_t cst_utf8_floor(const char *s, size_t n) {
if (!s || n == 0)
return 0;
size_t i = n;
/* walk back over at most 3 continuation bytes to the lead byte */
size_t back = 0;
while (i > 0 && back < 3 && ((unsigned char)s[i - 1] & 0xC0) == 0x80) {
i--;
back++;
}
if (i == 0)
return n;
unsigned char lead = (unsigned char)s[i - 1];
size_t need = 1;
if (lead >= 0xF0)
need = 4;
else if (lead >= 0xE0)
need = 3;
else if (lead >= 0xC0)
need = 2;
if (need == 1)
return n; /* ASCII lead; any continuation bytes after it are invalid
anyway and get sanitized later */
if (back + 1 >= need)
return n;
return i - 1; /* sequence incomplete: cut before lead byte */
}
static size_t utf8_seq_ok(const unsigned char *p, size_t left) {
unsigned char c = p[0];
size_t need;
unsigned cp;
if (c < 0x80)
return 1;
if ((c & 0xE0) == 0xC0) {
need = 2;
cp = c & 0x1F;
} else if ((c & 0xF0) == 0xE0) {
need = 3;
cp = c & 0x0F;
} else if ((c & 0xF8) == 0xF0) {
need = 4;
cp = c & 0x07;
} else {
return 0;
}
if (left < need)
return 0;
for (size_t i = 1; i < need; i++) {
if ((p[i] & 0xC0) != 0x80)
return 0;
cp = (cp << 6) | (p[i] & 0x3F);
}
if ((need == 2 && cp < 0x80) || (need == 3 && cp < 0x800) ||
(need == 4 && (cp < 0x10000 || cp > 0x10FFFF)) ||
(cp >= 0xD800 && cp <= 0xDFFF))
return 0;
return need;
}
char *cst_utf8_sanitize(const char *s, size_t n, size_t *out_len) {
char *out = malloc(n + 1);
if (!out)
return NULL;
size_t o = 0;
const unsigned char *p = (const unsigned char *)(s ? s : "");
size_t i = 0;
while (i < n) {
size_t k = utf8_seq_ok(p + i, n - i);
if (k == 0) {
out[o++] = '?';
i++;
continue;
}
memcpy(out + o, p + i, k);
o += k;
i += k;
}
out[o] = 0;
if (out_len)
*out_len = o;
return out;
}
uint64_t cst_fnv1a64(uint64_t h, const void *data, size_t n) {
const unsigned char *p = data;
for (size_t i = 0; i < n; i++) {
h ^= p[i];
h *= 1099511628211ULL;
}
return h;
}