Initial CST-Agent: portable Linux/Termux/Windows coding agent.

Vendored sashidown, OpenAI-compatible env (OPENAI_*), and bootstrap scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-09-07 22:42:32 +08:00
co-authored by Cursor
commit 1f1fcba7ad
75 changed files with 14940 additions and 0 deletions
+419
View File
@@ -0,0 +1,419 @@
#include "ctl.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum {
ST_TEXT = 0, /* outside a call; accumulate text lines */
ST_LINE, /* collecting a line (call/arg/end or text) */
ST_ARG_BODY, /* reading nbytes of arg body */
ST_ERROR
} ctl_state_t;
struct ctl_parser {
ctl_on_event_fn on_event;
void *userdata;
ctl_state_t state;
char line[CTL_MAX_LINE];
size_t line_len;
ctl_tool_call_t call;
int in_call;
/* current arg being filled */
char arg_key[CTL_MAX_KEY];
char *arg_buf;
size_t arg_cap;
size_t arg_got;
size_t arg_need;
size_t pending;
size_t pending_peak;
char errbuf[256];
};
static void bump_pending(ctl_parser_t *p, size_t n) {
p->pending = n;
if (n > p->pending_peak)
p->pending_peak = n;
}
static void emit(ctl_parser_t *p, ctl_event_t *ev) {
if (p->on_event)
p->on_event(ev, p->userdata);
}
static void fail(ctl_parser_t *p, const char *msg) {
snprintf(p->errbuf, sizeof(p->errbuf), "%s", msg);
p->state = ST_ERROR;
ctl_event_t ev = {0};
ev.type = CTL_EV_ERROR;
ev.error = p->errbuf;
emit(p, &ev);
}
/* free args in current call */
static void clear_call_args(ctl_tool_call_t *c) {
for (int i = 0; i < c->nargs; i++) {
free(c->args[i].value);
c->args[i].value = NULL;
c->args[i].value_len = 0;
}
c->nargs = 0;
c->id[0] = 0;
c->name[0] = 0;
}
static void reset_arg_body(ctl_parser_t *p) {
free(p->arg_buf);
p->arg_buf = NULL;
p->arg_cap = 0;
p->arg_got = 0;
p->arg_need = 0;
p->arg_key[0] = 0;
}
ctl_parser_t *ctl_parser_create(ctl_on_event_fn on_event, void *userdata) {
ctl_parser_t *p = calloc(1, sizeof(*p));
if (!p)
return NULL;
p->on_event = on_event;
p->userdata = userdata;
p->state = ST_TEXT;
return p;
}
void ctl_parser_destroy(ctl_parser_t *p) {
if (!p)
return;
clear_call_args(&p->call);
reset_arg_body(p);
free(p);
}
void ctl_parser_reset(ctl_parser_t *p) {
if (!p)
return;
clear_call_args(&p->call);
reset_arg_body(p);
p->state = ST_TEXT;
p->line_len = 0;
p->in_call = 0;
p->pending = 0;
/* keep pending_peak for experiment */
}
size_t ctl_parser_pending(const ctl_parser_t *p) {
return p ? p->pending : 0;
}
size_t ctl_parser_pending_peak(const ctl_parser_t *p) {
return p ? p->pending_peak : 0;
}
static int starts_with(const char *s, const char *pfx) {
return strncmp(s, pfx, strlen(pfx)) == 0;
}
static int parse_call_line(ctl_parser_t *p, const char *line) {
/* call <id> <name> */
const char *s = line + 5;
while (*s == ' ' || *s == '\t')
s++;
if (!*s)
return -1;
const char *sp = strchr(s, ' ');
if (!sp)
return -1;
size_t idlen = (size_t)(sp - s);
if (idlen == 0 || idlen >= CTL_MAX_ID)
return -1;
while (*sp == ' ' || *sp == '\t')
sp++;
if (!*sp)
return -1;
size_t nlen = strlen(sp);
if (nlen == 0 || nlen >= CTL_MAX_NAME)
return -1;
for (size_t i = 0; i < nlen; i++) {
if (!(isalnum((unsigned char)sp[i]) || sp[i] == '_' || sp[i] == '-'))
return -1;
}
clear_call_args(&p->call);
memcpy(p->call.id, s, idlen);
p->call.id[idlen] = 0;
memcpy(p->call.name, sp, nlen);
p->call.name[nlen] = 0;
p->in_call = 1;
ctl_event_t ev = {0};
ev.type = CTL_EV_CALL_OPEN;
ev.call = &p->call;
emit(p, &ev);
return 0;
}
static int finish_arg(ctl_parser_t *p);
static int parse_arg_header(ctl_parser_t *p, const char *line) {
/* Forms:
* arg <key> <nbytes> → length-prefixed body follows
* arg <key> <value...> → value is rest of line (line-mode, confirmed now)
*/
const char *s = line + 4;
while (*s == ' ' || *s == '\t')
s++;
if (!*s)
return -1;
const char *sp = strchr(s, ' ');
if (!sp)
return -1;
size_t klen = (size_t)(sp - s);
if (klen == 0 || klen >= CTL_MAX_KEY)
return -1;
while (*sp == ' ' || *sp == '\t')
sp++;
if (!*sp)
return -1;
if (p->call.nargs >= CTL_MAX_ARGS)
return -1;
char *end = NULL;
unsigned long nbytes = strtoul(sp, &end, 10);
int length_prefixed = (end != sp && (*end == '\0' || *end == ' ' || *end == '\t'));
if (length_prefixed && end && *end != '\0') {
while (*end == ' ' || *end == '\t')
end++;
if (*end != '\0')
length_prefixed = 0; /* e.g. "9foo" or value starting with digits+text */
}
memcpy(p->arg_key, s, klen);
p->arg_key[klen] = 0;
if (!length_prefixed) {
/* line-mode: entire remainder is the value */
size_t vlen = strlen(sp);
if (vlen > CTL_MAX_ARG_BYTES)
return -1;
free(p->arg_buf);
p->arg_buf = malloc(vlen + 1);
if (!p->arg_buf)
return -2;
memcpy(p->arg_buf, sp, vlen);
p->arg_buf[vlen] = 0;
p->arg_need = vlen;
p->arg_got = vlen;
p->arg_cap = vlen;
finish_arg(p);
return 0;
}
if (nbytes > CTL_MAX_ARG_BYTES)
return -1;
p->arg_need = (size_t)nbytes;
p->arg_got = 0;
free(p->arg_buf);
p->arg_buf = NULL;
p->arg_cap = 0;
if (p->arg_need > 0) {
p->arg_buf = malloc(p->arg_need + 1);
if (!p->arg_buf)
return -2;
p->arg_cap = p->arg_need;
}
p->state = ST_ARG_BODY;
bump_pending(p, p->arg_need);
return 0;
}
static int finish_arg(ctl_parser_t *p) {
int i = p->call.nargs;
snprintf(p->call.args[i].key, CTL_MAX_KEY, "%s", p->arg_key);
p->call.args[i].value = p->arg_buf;
p->call.args[i].value_len = p->arg_got;
if (p->arg_buf)
p->arg_buf[p->arg_got] = 0;
p->arg_buf = NULL; /* ownership moved */
p->call.nargs++;
p->arg_need = 0;
p->arg_got = 0;
p->arg_key[0] = 0;
p->state = ST_LINE;
bump_pending(p, 0);
ctl_event_t ev = {0};
ev.type = CTL_EV_ARG;
ev.call = &p->call;
emit(p, &ev);
return 0;
}
static void emit_text_line(ctl_parser_t *p, const char *line, size_t len) {
/* include newline as confirmed text */
char tmp[CTL_MAX_LINE + 1];
if (len + 1 >= sizeof(tmp))
len = sizeof(tmp) - 2;
memcpy(tmp, line, len);
tmp[len] = '\n';
tmp[len + 1] = 0;
ctl_event_t ev = {0};
ev.type = CTL_EV_TEXT;
ev.text = tmp;
ev.text_len = len + 1;
emit(p, &ev);
}
static int is_fence_line(const char *line) {
if (strncmp(line, "```", 3) == 0)
return 1;
if (strncmp(line, "~~~", 3) == 0)
return 1;
return 0;
}
static int handle_line(ctl_parser_t *p, const char *line) {
/* Ignore markdown fences so models can wrap CTL in code blocks. */
if (is_fence_line(line)) {
bump_pending(p, 0);
return 0;
}
/* Ignore blank lines between CTL frames. */
if (line[0] == '\0') {
bump_pending(p, 0);
return 0;
}
if (!p->in_call) {
if (starts_with(line, "call ")) {
if (parse_call_line(p, line) != 0) {
fail(p, "invalid call line");
return -1;
}
bump_pending(p, 0);
return 0;
}
/* confirmed text line */
emit_text_line(p, line, strlen(line));
bump_pending(p, 0);
return 0;
}
/* in call */
if (starts_with(line, "arg ")) {
int rc = parse_arg_header(p, line);
if (rc == -2) {
fail(p, "oom");
return -1;
}
if (rc != 0) {
fail(p, "invalid arg header");
return -1;
}
if (p->arg_need == 0)
finish_arg(p);
return 0;
}
if (strcmp(line, "end") == 0) {
ctl_event_t ev = {0};
ev.type = CTL_EV_CALL_READY;
ev.call = &p->call;
emit(p, &ev);
clear_call_args(&p->call);
p->in_call = 0;
bump_pending(p, 0);
p->state = ST_LINE;
return 0;
}
fail(p, "unexpected line inside call");
return -1;
}
ctl_status_t ctl_parser_feed(ctl_parser_t *p, const char *data, size_t len) {
if (!p)
return CTL_ERR_CONTRACT;
if (p->state == ST_ERROR)
return CTL_ERR_CONTRACT;
size_t i = 0;
while (i < len) {
if (p->state == ST_ARG_BODY) {
size_t left = p->arg_need - p->arg_got;
size_t take = len - i;
if (take > left)
take = left;
if (take > 0) {
memcpy(p->arg_buf + p->arg_got, data + i, take);
p->arg_got += take;
i += take;
bump_pending(p, p->arg_need - p->arg_got);
}
if (p->arg_got == p->arg_need) {
finish_arg(p);
/* continue with remaining bytes in LINE mode */
}
continue;
}
/* line accumulation */
p->state = ST_LINE;
unsigned char c = (unsigned char)data[i++];
if (c == '\r')
continue; /* normalize CRLF */
if (c == '\n') {
p->line[p->line_len] = 0;
if (handle_line(p, p->line) != 0)
return CTL_ERR_CONTRACT;
p->line_len = 0;
if (p->state == ST_ARG_BODY && p->arg_need == 0) {
/* already finished empty arg */
}
continue;
}
if (p->line_len + 1 >= CTL_MAX_LINE) {
fail(p, "line too long");
return CTL_ERR_LIMIT;
}
p->line[p->line_len++] = (char)c;
bump_pending(p, p->line_len);
}
return CTL_OK;
}
ctl_status_t ctl_parser_finish(ctl_parser_t *p) {
if (!p)
return CTL_ERR_CONTRACT;
if (p->state == ST_ERROR)
return CTL_ERR_CONTRACT;
/* Mid-argument body cannot be confirmed at EOF. */
if (p->state == ST_ARG_BODY) {
fail(p, "incomplete arg body at EOF");
return CTL_ERR_CONTRACT;
}
/* Trailing text without final newline is still confirmed text. */
if (!p->in_call && p->line_len > 0) {
p->line[p->line_len] = 0;
emit_text_line(p, p->line, p->line_len);
p->line_len = 0;
}
/* EOF confirms an open call whose args are complete (implicit end).
* Matches paper EOF adequacy: stream end closes the frontier. */
if (p->in_call) {
if (p->line_len > 0) {
p->line[p->line_len] = 0;
/* leftover line must be end or fence/blank */
if (strcmp(p->line, "end") == 0 || is_fence_line(p->line) ||
p->line[0] == '\0') {
p->line_len = 0;
} else {
fail(p, "unexpected trailing line in call at EOF");
return CTL_ERR_CONTRACT;
}
}
ctl_event_t ev = {0};
ev.type = CTL_EV_CALL_READY;
ev.call = &p->call;
emit(p, &ev);
clear_call_args(&p->call);
p->in_call = 0;
}
bump_pending(p, 0);
return CTL_OK;
}