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.
This commit is contained in:
+77
-2
@@ -6,6 +6,28 @@
|
||||
|
||||
#define MODEL_MAX_LEN 128
|
||||
|
||||
static const cst_provider_t PROVIDERS[] = {
|
||||
{"minimax", "https://api.minimax.cn/v1", "MiniMax-M2.7", "MINIMAX_API_KEY"},
|
||||
{"deepseek", "https://api.deepseek.com/v1", "deepseek-flash",
|
||||
"DEEPSEEK_API_KEY"},
|
||||
{"openai", "https://api.openai.com/v1", "gpt-4.1-mini", "OPENAI_API_KEY"},
|
||||
};
|
||||
|
||||
const cst_provider_t *cst_provider_find(const char *name) {
|
||||
if (!name || !name[0])
|
||||
return NULL;
|
||||
for (size_t i = 0; i < sizeof(PROVIDERS) / sizeof(PROVIDERS[0]); i++) {
|
||||
if (strcmp(PROVIDERS[i].name, name) == 0)
|
||||
return &PROVIDERS[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const cst_provider_t *cst_provider_default(void) {
|
||||
const cst_provider_t *p = cst_provider_find(getenv("CST_PROVIDER"));
|
||||
return p ? p : &PROVIDERS[0];
|
||||
}
|
||||
|
||||
static int already(char out[][MODEL_MAX_LEN], int n, const char *name) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (strcmp(out[i], name) == 0)
|
||||
@@ -44,7 +66,6 @@ int cst_models_list(const char *current, char out[][128], int max_out) {
|
||||
p++;
|
||||
char save = *p;
|
||||
*p = 0;
|
||||
/* trim trailing spaces */
|
||||
char *end = start + strlen(start);
|
||||
while (end > start && (end[-1] == ' ' || end[-1] == '\t'))
|
||||
*--end = 0;
|
||||
@@ -54,10 +75,12 @@ int cst_models_list(const char *current, char out[][128], int max_out) {
|
||||
p++;
|
||||
}
|
||||
} else {
|
||||
push(out, &n, max_out, "deepseek-flash");
|
||||
push(out, &n, max_out, "deepseek-v4-pro");
|
||||
push(out, &n, max_out, "MiniMax-M2.7");
|
||||
push(out, &n, max_out, "MiniMax-M2.5");
|
||||
push(out, &n, max_out, "gpt-4.1-mini");
|
||||
push(out, &n, max_out, "gpt-4o");
|
||||
push(out, &n, max_out, "gpt-4o-mini");
|
||||
}
|
||||
|
||||
if (current && current[0])
|
||||
@@ -65,3 +88,55 @@ int cst_models_list(const char *current, char out[][128], int max_out) {
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Context window in tokens (CST_CTX_TOKENS overrides). Prefix match. */
|
||||
long cst_model_context_tokens(const char *model) {
|
||||
const char *e = getenv("CST_CTX_TOKENS");
|
||||
if (e && atol(e) > 0)
|
||||
return atol(e);
|
||||
static const struct {
|
||||
const char *prefix;
|
||||
long tokens;
|
||||
} T[] = {{"deepseek", 128000}, {"MiniMax", 196608}, {"minimax", 196608},
|
||||
{"gpt-4.1", 1047576}, {"gpt-4o", 128000}, {"gpt-5", 400000},
|
||||
{"o3", 200000}, {"o4", 200000}, {"claude", 200000},
|
||||
{"qwen", 131072}, {"glm", 128000}, {"kimi", 131072}};
|
||||
if (model) {
|
||||
for (size_t i = 0; i < sizeof(T) / sizeof(T[0]); i++) {
|
||||
if (strncmp(model, T[i].prefix, strlen(T[i].prefix)) == 0)
|
||||
return T[i].tokens;
|
||||
}
|
||||
}
|
||||
return 128000;
|
||||
}
|
||||
|
||||
int cst_effort_valid(const char *effort) {
|
||||
static const char *const ok[] = {"low", "medium", "high", "max"};
|
||||
for (size_t i = 0; effort && i < sizeof(ok) / sizeof(ok[0]); i++) {
|
||||
if (strcmp(effort, ok[i]) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cst_effort_wire(const char *provider, const char *model,
|
||||
const char *effort) {
|
||||
if (!cst_effort_valid(effort) || !provider || !model)
|
||||
return NULL;
|
||||
if (strcmp(provider, "deepseek") == 0)
|
||||
return effort;
|
||||
/* OpenAI reasoning models take low | medium | high. */
|
||||
if (strcmp(provider, "openai") == 0 &&
|
||||
(strncmp(model, "o1", 2) == 0 || strncmp(model, "o3", 2) == 0 ||
|
||||
strncmp(model, "o4", 2) == 0 || strncmp(model, "gpt-5", 5) == 0))
|
||||
return strcmp(effort, "max") == 0 ? "high" : effort;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cst_provider_count(void) {
|
||||
return (int)(sizeof(PROVIDERS) / sizeof(PROVIDERS[0]));
|
||||
}
|
||||
|
||||
const cst_provider_t *cst_provider_at(int i) {
|
||||
return i >= 0 && i < cst_provider_count() ? &PROVIDERS[i] : NULL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user