forked from WHC/Hermit
OpenCode-style TUI, model probing, sandboxed auto approval
- TUI: centered home screen with logo, boxed input with mode line, sidebar (>=120 cols), Ctrl+P palette, Tab Build/Plan, Shift+Tab approval cycle, mouse (cards, cursor, pickers, wheel), /sidebar /mouse /approval - /models probing: key check on entry (401 re-asks), API models in /model - Shell sandbox in C: Linux Landlock (writes limited to workspace/tmp, ABI>=4 blocks TCP), macOS sandbox_init, rules fallback elsewhere - Approval modes ask/auto/yolo (default auto); read-only command whitelist; ask to rerun outside the sandbox when a command looks blocked - Upgradable bootstrap scripts (git pull --ff-only, rebuild, reinstall) and cst-agent-update; cst-agent --version - tests: test_sandbox, test_models
This commit is contained in:
@@ -698,3 +698,115 @@ finish:
|
||||
free(body);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* --- model listing -------------------------------------------------------- */
|
||||
|
||||
#define MODELS_BODY_MAX (2 * 1024 * 1024)
|
||||
|
||||
static size_t models_write_cb(char *ptr, size_t size, size_t nmemb, void *ud) {
|
||||
cst_buf_t *b = ud;
|
||||
size_t n = size * nmemb;
|
||||
if (b->len + n > MODELS_BODY_MAX || cst_buf_append(b, ptr, n) != 0)
|
||||
return 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int add_model(char out[][128], int n, int max_out, const char *id) {
|
||||
if (!id || !id[0] || n >= max_out || strlen(id) >= 128)
|
||||
return n;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (strcmp(out[i], id) == 0)
|
||||
return n;
|
||||
snprintf(out[n], 128, "%s", id);
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
/* {"data":[{"id":..}]}, {"models":[{"id"|"name":..}]} or a bare array. */
|
||||
static int parse_models(const char *json, char out[][128], int max_out) {
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
if (!root)
|
||||
return -1;
|
||||
const cJSON *arr = root;
|
||||
if (!cJSON_IsArray(arr))
|
||||
arr = cJSON_GetObjectItemCaseSensitive(root, "data");
|
||||
if (!cJSON_IsArray(arr))
|
||||
arr = cJSON_GetObjectItemCaseSensitive(root, "models");
|
||||
int n = -1;
|
||||
if (cJSON_IsArray(arr)) {
|
||||
n = 0;
|
||||
const cJSON *it;
|
||||
cJSON_ArrayForEach(it, arr) {
|
||||
const cJSON *id = cJSON_GetObjectItemCaseSensitive(it, "id");
|
||||
if (!cJSON_IsString(id))
|
||||
id = cJSON_GetObjectItemCaseSensitive(it, "name");
|
||||
if (cJSON_IsString(id))
|
||||
n = add_model(out, n, max_out, id->valuestring);
|
||||
else if (cJSON_IsString(it))
|
||||
n = add_model(out, n, max_out, it->valuestring);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return n;
|
||||
}
|
||||
|
||||
int cst_llm_list_models(const char *api_base, const char *api_key,
|
||||
char out[][128], int max_out, char *err,
|
||||
size_t err_sz) {
|
||||
char dummy[8];
|
||||
if (!err || !err_sz) {
|
||||
err = dummy;
|
||||
err_sz = sizeof(dummy);
|
||||
}
|
||||
err[0] = 0;
|
||||
if (!api_base || !api_base[0] || !out || max_out <= 0) {
|
||||
snprintf(err, err_sz, "bad request");
|
||||
return -1;
|
||||
}
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
snprintf(err, err_sz, "curl init failed");
|
||||
return -1;
|
||||
}
|
||||
char url[1024];
|
||||
size_t bl = strlen(api_base);
|
||||
while (bl && api_base[bl - 1] == '/')
|
||||
bl--;
|
||||
snprintf(url, sizeof(url), "%.*s/models", (int)bl, api_base);
|
||||
struct curl_slist *hdrs = curl_slist_append(NULL, "Accept: application/json");
|
||||
char auth[700];
|
||||
if (api_key && api_key[0]) {
|
||||
snprintf(auth, sizeof(auth), "Authorization: Bearer %s", api_key);
|
||||
hdrs = curl_slist_append(hdrs, auth);
|
||||
}
|
||||
cst_buf_t body = {0};
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, models_write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
CURLcode rc = curl_easy_perform(curl);
|
||||
long http = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http);
|
||||
curl_slist_free_all(hdrs);
|
||||
curl_easy_cleanup(curl);
|
||||
memset(auth, 0, sizeof(auth));
|
||||
|
||||
int n = -1;
|
||||
if (rc != CURLE_OK) {
|
||||
snprintf(err, err_sz, "curl: %s", curl_easy_strerror(rc));
|
||||
} else if (http == 401 || http == 403) {
|
||||
snprintf(err, err_sz, "HTTP %ld: the API key was rejected", http);
|
||||
n = CST_MODELS_AUTH;
|
||||
} else if (http >= 400) {
|
||||
snprintf(err, err_sz, "HTTP %ld from %s", http, url);
|
||||
} else {
|
||||
n = parse_models(body.buf ? body.buf : "", out, max_out);
|
||||
if (n < 0)
|
||||
snprintf(err, err_sz, "unexpected /models response");
|
||||
}
|
||||
cst_buf_free(&body);
|
||||
return n;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user