forked from WHC/Hermit
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.
164 lines
3.4 KiB
C
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;
|
|
}
|