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:
@@ -0,0 +1,784 @@
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cst_agent.h"
|
||||
#include "os_compat.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
void cst_tool_result_free(cst_tool_result_t *r) {
|
||||
if (!r)
|
||||
return;
|
||||
free(r->out);
|
||||
r->out = NULL;
|
||||
r->out_len = 0;
|
||||
}
|
||||
|
||||
static const char *arg_get(const ctl_tool_call_t *call, const char *key) {
|
||||
for (int i = 0; i < call->nargs; i++) {
|
||||
if (strcmp(call->args[i].key, key) == 0)
|
||||
return call->args[i].value ? call->args[i].value : "";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int set_result(cst_tool_result_t *r, const char *s, int code) {
|
||||
cst_tool_result_free(r);
|
||||
size_t n = s ? strlen(s) : 0;
|
||||
r->out = malloc(n + 1);
|
||||
if (!r->out)
|
||||
return -1;
|
||||
if (n)
|
||||
memcpy(r->out, s, n);
|
||||
r->out[n] = 0;
|
||||
r->out_len = n;
|
||||
r->exit_code = code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read whole file into malloc'd buffer; *out_len set. Returns NULL if missing. */
|
||||
static char *slurp_file(const char *full, size_t *out_len) {
|
||||
*out_len = 0;
|
||||
FILE *fp = fopen(full, "rb");
|
||||
if (!fp)
|
||||
return NULL;
|
||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
long sz = ftell(fp);
|
||||
if (sz < 0) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
rewind(fp);
|
||||
char *buf = malloc((size_t)sz + 1);
|
||||
if (!buf) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
size_t n = fread(buf, 1, (size_t)sz, fp);
|
||||
fclose(fp);
|
||||
buf[n] = 0;
|
||||
*out_len = n;
|
||||
return buf;
|
||||
}
|
||||
|
||||
#define DIFF_OUT_CAP (48 * 1024)
|
||||
|
||||
/* OpenCode-style: always surface a unified diff after write/patch. */
|
||||
static char *make_unified_diff(const char *relpath, const char *oldc, size_t olen,
|
||||
const char *newc, size_t nlen) {
|
||||
char t0[OS_PATH_MAX], t1[OS_PATH_MAX];
|
||||
int fd0 = os_mkstemp(t0, sizeof(t0), "cstda");
|
||||
int fd1 = os_mkstemp(t1, sizeof(t1), "cstdb");
|
||||
if (fd0 < 0 || fd1 < 0) {
|
||||
if (fd0 >= 0) {
|
||||
#ifdef _WIN32
|
||||
_close(fd0);
|
||||
_unlink(t0);
|
||||
#else
|
||||
close(fd0);
|
||||
unlink(t0);
|
||||
#endif
|
||||
}
|
||||
if (fd1 >= 0) {
|
||||
#ifdef _WIN32
|
||||
_close(fd1);
|
||||
_unlink(t1);
|
||||
#else
|
||||
close(fd1);
|
||||
unlink(t1);
|
||||
#endif
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (olen) {
|
||||
#ifdef _WIN32
|
||||
(void)_write(fd0, oldc, (unsigned)olen);
|
||||
#else
|
||||
(void)write(fd0, oldc, olen);
|
||||
#endif
|
||||
}
|
||||
if (nlen) {
|
||||
#ifdef _WIN32
|
||||
(void)_write(fd1, newc, (unsigned)nlen);
|
||||
#else
|
||||
(void)write(fd1, newc, nlen);
|
||||
#endif
|
||||
}
|
||||
#ifdef _WIN32
|
||||
_close(fd0);
|
||||
_close(fd1);
|
||||
#else
|
||||
close(fd0);
|
||||
close(fd1);
|
||||
#endif
|
||||
|
||||
const char *label = relpath && relpath[0] ? relpath : "file";
|
||||
char cmd[OS_PATH_MAX * 2 + 256];
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"diff -u --label a/%s --label b/%s \"%s\" \"%s\"", label, label, t0,
|
||||
t1);
|
||||
|
||||
os_run_result_t rr = {0};
|
||||
int rc = os_run_shell_c(NULL, cmd, DIFF_OUT_CAP, &rr);
|
||||
#ifdef _WIN32
|
||||
_unlink(t0);
|
||||
_unlink(t1);
|
||||
#else
|
||||
unlink(t0);
|
||||
unlink(t1);
|
||||
#endif
|
||||
if (rc != 0) {
|
||||
os_run_result_free(&rr);
|
||||
return strdup("(no textual diff — diff unavailable)\n");
|
||||
}
|
||||
/* diff returns 1 when files differ — still success for our purposes */
|
||||
if (!rr.out || rr.out_len == 0) {
|
||||
os_run_result_free(&rr);
|
||||
return strdup("(no textual diff — identical or binary)\n");
|
||||
}
|
||||
char *out = rr.out;
|
||||
rr.out = NULL;
|
||||
size_t len = rr.out_len;
|
||||
os_run_result_free(&rr);
|
||||
if (len >= DIFF_OUT_CAP - 64) {
|
||||
snprintf(out + DIFF_OUT_CAP - 64, 64, "\n…(diff truncated)\n");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static int set_result_with_diff(cst_tool_result_t *r, const char *relpath,
|
||||
const char *oldc, size_t olen, const char *newc,
|
||||
size_t nlen, int code) {
|
||||
char *diff = make_unified_diff(relpath, oldc ? oldc : "", olen, newc ? newc : "",
|
||||
nlen);
|
||||
char hdr[256];
|
||||
snprintf(hdr, sizeof(hdr), "wrote %s (%zu bytes)\n",
|
||||
relpath ? relpath : "?", nlen);
|
||||
size_t hlen = strlen(hdr);
|
||||
size_t dlen = diff ? strlen(diff) : 0;
|
||||
char *buf = malloc(hlen + dlen + 1);
|
||||
if (!buf) {
|
||||
free(diff);
|
||||
return set_result(r, "ok", code);
|
||||
}
|
||||
memcpy(buf, hdr, hlen);
|
||||
if (diff && dlen)
|
||||
memcpy(buf + hlen, diff, dlen);
|
||||
buf[hlen + dlen] = 0;
|
||||
free(diff);
|
||||
cst_tool_result_free(r);
|
||||
r->out = buf;
|
||||
r->out_len = hlen + dlen;
|
||||
r->exit_code = code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_tool_read_file(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r) {
|
||||
const char *path = arg_get(call, "path");
|
||||
if (!path)
|
||||
return set_result(r, "error: missing path", 1);
|
||||
char full[4096];
|
||||
if (cst_sandbox_resolve(workspace_root, path, full, sizeof(full)) != 0)
|
||||
return set_result(r, "error: path outside workspace", 1);
|
||||
FILE *fp = fopen(full, "rb");
|
||||
if (!fp)
|
||||
return set_result(r, "error: cannot open file", 1);
|
||||
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||
fclose(fp);
|
||||
return set_result(r, "error: seek failed", 1);
|
||||
}
|
||||
long sz = ftell(fp);
|
||||
if (sz < 0 || sz > 2 * 1024 * 1024) {
|
||||
fclose(fp);
|
||||
return set_result(r, "error: file too large or empty seek", 1);
|
||||
}
|
||||
rewind(fp);
|
||||
char *buf = malloc((size_t)sz + 1);
|
||||
if (!buf) {
|
||||
fclose(fp);
|
||||
return set_result(r, "error: oom", 1);
|
||||
}
|
||||
size_t n = fread(buf, 1, (size_t)sz, fp);
|
||||
fclose(fp);
|
||||
buf[n] = 0;
|
||||
cst_tool_result_free(r);
|
||||
r->out = buf;
|
||||
r->out_len = n;
|
||||
r->exit_code = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_tool_write_file(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r) {
|
||||
const char *path = arg_get(call, "path");
|
||||
const char *content = arg_get(call, "content");
|
||||
if (!path || !content)
|
||||
return set_result(r, "error: missing path or content", 1);
|
||||
char full[4096];
|
||||
if (cst_sandbox_resolve(workspace_root, path, full, sizeof(full)) != 0)
|
||||
return set_result(r, "error: path outside workspace", 1);
|
||||
|
||||
size_t olen = 0;
|
||||
char *oldc = slurp_file(full, &olen);
|
||||
|
||||
FILE *fp = fopen(full, "wb");
|
||||
if (!fp) {
|
||||
free(oldc);
|
||||
return set_result(r, "error: cannot write file", 1);
|
||||
}
|
||||
size_t n = strlen(content);
|
||||
if (fwrite(content, 1, n, fp) != n) {
|
||||
fclose(fp);
|
||||
free(oldc);
|
||||
return set_result(r, "error: write failed", 1);
|
||||
}
|
||||
fclose(fp);
|
||||
int rc = set_result_with_diff(r, path, oldc, olen, content, n, 0);
|
||||
free(oldc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int cst_tool_run_shell(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r) {
|
||||
const char *cmd = arg_get(call, "cmd");
|
||||
if (!cmd)
|
||||
cmd = arg_get(call, "command");
|
||||
if (!cmd)
|
||||
return set_result(r, "error: missing cmd", 1);
|
||||
|
||||
char root_real[OS_PATH_MAX];
|
||||
if (os_realpath(workspace_root, root_real, sizeof(root_real)) != 0)
|
||||
return set_result(r, "error: bad workspace", 1);
|
||||
|
||||
os_run_result_t rr = {0};
|
||||
if (os_run_shell_c(root_real, cmd, 512 * 1024, &rr) != 0) {
|
||||
os_run_result_free(&rr);
|
||||
return set_result(r, "error: shell exec failed", 1);
|
||||
}
|
||||
cst_tool_result_free(r);
|
||||
r->out = rr.out;
|
||||
r->out_len = rr.out_len;
|
||||
r->exit_code = rr.exit_code;
|
||||
rr.out = NULL;
|
||||
os_run_result_free(&rr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Minimal unified diff apply: only support *** Update File: / write via
|
||||
* apply_patch tool with arg path + content for MVP simplicity is write_file.
|
||||
* For apply_patch: expect arg "patch" with a simple format:
|
||||
* *** Update File: <relpath>
|
||||
* <full new file content>
|
||||
* Or classic unified diff for single file with ---/+++ and @@ hunks.
|
||||
*/
|
||||
|
||||
static int apply_full_replace(const char *workspace_root, const char *path,
|
||||
const char *content, cst_tool_result_t *r) {
|
||||
ctl_tool_call_t fake = {0};
|
||||
snprintf(fake.name, sizeof(fake.name), "write_file");
|
||||
fake.nargs = 2;
|
||||
snprintf(fake.args[0].key, sizeof(fake.args[0].key), "path");
|
||||
fake.args[0].value = (char *)path;
|
||||
fake.args[0].value_len = strlen(path);
|
||||
snprintf(fake.args[1].key, sizeof(fake.args[1].key), "content");
|
||||
fake.args[1].value = (char *)content;
|
||||
fake.args[1].value_len = strlen(content);
|
||||
return cst_tool_write_file(workspace_root, &fake, r);
|
||||
}
|
||||
|
||||
/* Apply one complete unified-diff hunk to a malloc'd file buffer.
|
||||
* Returns 0 on success. Very small subset: context/add/remove lines. */
|
||||
static int apply_hunk(char **file, size_t *flen, const char *hunk,
|
||||
size_t hunk_len) {
|
||||
/* Find @@ line */
|
||||
const char *p = hunk;
|
||||
const char *end = hunk + hunk_len;
|
||||
while (p < end && !(*p == '@' && p + 1 < end && p[1] == '@')) {
|
||||
while (p < end && *p != '\n')
|
||||
p++;
|
||||
if (p < end)
|
||||
p++;
|
||||
}
|
||||
if (p >= end)
|
||||
return -1;
|
||||
while (p < end && *p != '\n')
|
||||
p++;
|
||||
if (p < end)
|
||||
p++;
|
||||
|
||||
/* Parse old file into lines */
|
||||
size_t nlines = 0;
|
||||
for (size_t i = 0; i < *flen; i++)
|
||||
if ((*file)[i] == '\n')
|
||||
nlines++;
|
||||
if (*flen > 0 && (*file)[*flen - 1] != '\n')
|
||||
nlines++;
|
||||
char **lines = calloc(nlines + 64, sizeof(char *));
|
||||
size_t *llens = calloc(nlines + 64, sizeof(size_t));
|
||||
if (!lines || !llens) {
|
||||
free(lines);
|
||||
free(llens);
|
||||
return -1;
|
||||
}
|
||||
size_t li = 0, start = 0;
|
||||
for (size_t i = 0; i <= *flen; i++) {
|
||||
if (i == *flen || (*file)[i] == '\n') {
|
||||
size_t L = i - start;
|
||||
lines[li] = malloc(L + 1);
|
||||
if (!lines[li]) {
|
||||
for (size_t k = 0; k < li; k++)
|
||||
free(lines[k]);
|
||||
free(lines);
|
||||
free(llens);
|
||||
return -1;
|
||||
}
|
||||
memcpy(lines[li], *file + start, L);
|
||||
lines[li][L] = 0;
|
||||
llens[li] = L;
|
||||
li++;
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
size_t cur = 0;
|
||||
/* Walk hunk body */
|
||||
while (p < end) {
|
||||
char tag = *p;
|
||||
const char *ls = p + 1;
|
||||
const char *le = ls;
|
||||
while (le < end && *le != '\n')
|
||||
le++;
|
||||
size_t L = (size_t)(le - ls);
|
||||
if (tag == ' ' || tag == '-') {
|
||||
if (cur >= li || llens[cur] != L || memcmp(lines[cur], ls, L) != 0) {
|
||||
for (size_t k = 0; k < li; k++)
|
||||
free(lines[k]);
|
||||
free(lines);
|
||||
free(llens);
|
||||
return -1;
|
||||
}
|
||||
if (tag == '-') {
|
||||
free(lines[cur]);
|
||||
memmove(&lines[cur], &lines[cur + 1], (li - cur - 1) * sizeof(char *));
|
||||
memmove(&llens[cur], &llens[cur + 1], (li - cur - 1) * sizeof(size_t));
|
||||
li--;
|
||||
} else {
|
||||
cur++;
|
||||
}
|
||||
} else if (tag == '+') {
|
||||
char *nl = malloc(L + 1);
|
||||
if (!nl) {
|
||||
for (size_t k = 0; k < li; k++)
|
||||
free(lines[k]);
|
||||
free(lines);
|
||||
free(llens);
|
||||
return -1;
|
||||
}
|
||||
memcpy(nl, ls, L);
|
||||
nl[L] = 0;
|
||||
lines = realloc(lines, (li + 2) * sizeof(char *));
|
||||
llens = realloc(llens, (li + 2) * sizeof(size_t));
|
||||
memmove(&lines[cur + 1], &lines[cur], (li - cur) * sizeof(char *));
|
||||
memmove(&llens[cur + 1], &llens[cur], (li - cur) * sizeof(size_t));
|
||||
lines[cur] = nl;
|
||||
llens[cur] = L;
|
||||
li++;
|
||||
cur++;
|
||||
} else if (tag == '\\') {
|
||||
/* "\ No newline at end of file" — ignore */
|
||||
} else {
|
||||
for (size_t k = 0; k < li; k++)
|
||||
free(lines[k]);
|
||||
free(lines);
|
||||
free(llens);
|
||||
return -1;
|
||||
}
|
||||
p = (le < end) ? le + 1 : end;
|
||||
}
|
||||
/* rebuild */
|
||||
size_t total = 0;
|
||||
for (size_t k = 0; k < li; k++)
|
||||
total += llens[k] + 1;
|
||||
char *out = malloc(total + 1);
|
||||
if (!out) {
|
||||
for (size_t k = 0; k < li; k++)
|
||||
free(lines[k]);
|
||||
free(lines);
|
||||
free(llens);
|
||||
return -1;
|
||||
}
|
||||
size_t o = 0;
|
||||
for (size_t k = 0; k < li; k++) {
|
||||
memcpy(out + o, lines[k], llens[k]);
|
||||
o += llens[k];
|
||||
out[o++] = '\n';
|
||||
free(lines[k]);
|
||||
}
|
||||
free(lines);
|
||||
free(llens);
|
||||
free(*file);
|
||||
*file = out;
|
||||
*flen = o;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_tool_apply_patch(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r) {
|
||||
const char *patch = arg_get(call, "patch");
|
||||
if (!patch)
|
||||
return set_result(r, "error: missing patch", 1);
|
||||
|
||||
/* Full-file replace shorthand */
|
||||
if (strncmp(patch, "*** Update File: ", 17) == 0) {
|
||||
const char *nl = strchr(patch, '\n');
|
||||
if (!nl)
|
||||
return set_result(r, "error: incomplete Update File header", 1);
|
||||
char path[1024];
|
||||
size_t plen = (size_t)(nl - (patch + 17));
|
||||
if (plen >= sizeof(path))
|
||||
return set_result(r, "error: path too long", 1);
|
||||
memcpy(path, patch + 17, plen);
|
||||
path[plen] = 0;
|
||||
char full[4096];
|
||||
if (cst_sandbox_resolve(workspace_root, path, full, sizeof(full)) != 0)
|
||||
return set_result(r, "error: path outside workspace", 1);
|
||||
size_t olen = 0;
|
||||
char *oldc = slurp_file(full, &olen);
|
||||
const char *neu = nl + 1;
|
||||
size_t nlen = strlen(neu);
|
||||
ctl_tool_call_t fake = {0};
|
||||
snprintf(fake.name, sizeof(fake.name), "write_file");
|
||||
fake.nargs = 2;
|
||||
snprintf(fake.args[0].key, sizeof(fake.args[0].key), "path");
|
||||
fake.args[0].value = path;
|
||||
fake.args[0].value_len = strlen(path);
|
||||
snprintf(fake.args[1].key, sizeof(fake.args[1].key), "content");
|
||||
fake.args[1].value = (char *)neu;
|
||||
fake.args[1].value_len = nlen;
|
||||
/* write_file already emits diff; free unused oldc */
|
||||
free(oldc);
|
||||
return cst_tool_write_file(workspace_root, &fake, r);
|
||||
}
|
||||
|
||||
/* Unified diff: require complete hunks (each @@ ... until next @@ or EOF) */
|
||||
const char *p = patch;
|
||||
char *rel = NULL;
|
||||
/* find +++ b/path */
|
||||
const char *plus = strstr(p, "\n+++ ");
|
||||
if (!plus)
|
||||
plus = (strncmp(p, "+++ ", 4) == 0) ? p : NULL;
|
||||
if (!plus)
|
||||
return set_result(r, "error: missing +++ line (incomplete patch)", 1);
|
||||
const char *ps = plus + (plus[0] == '\n' ? 5 : 4);
|
||||
if (ps[0] == 'b' && ps[1] == '/')
|
||||
ps += 2;
|
||||
const char *pe = strchr(ps, '\n');
|
||||
if (!pe)
|
||||
return set_result(r, "error: incomplete +++ line", 1);
|
||||
size_t rlen = (size_t)(pe - ps);
|
||||
rel = malloc(rlen + 1);
|
||||
if (!rel)
|
||||
return set_result(r, "error: oom", 1);
|
||||
memcpy(rel, ps, rlen);
|
||||
rel[rlen] = 0;
|
||||
|
||||
char full[4096];
|
||||
if (cst_sandbox_resolve(workspace_root, rel, full, sizeof(full)) != 0) {
|
||||
free(rel);
|
||||
return set_result(r, "error: path outside workspace", 1);
|
||||
}
|
||||
char *relpath_for_diff = rel; /* owned until return */
|
||||
|
||||
FILE *fp = fopen(full, "rb");
|
||||
char *file = NULL;
|
||||
size_t flen = 0;
|
||||
if (fp) {
|
||||
fseek(fp, 0, SEEK_END);
|
||||
long sz = ftell(fp);
|
||||
rewind(fp);
|
||||
if (sz < 0) {
|
||||
fclose(fp);
|
||||
free(relpath_for_diff);
|
||||
return set_result(r, "error: ftell", 1);
|
||||
}
|
||||
file = malloc((size_t)sz + 1);
|
||||
if (!file) {
|
||||
fclose(fp);
|
||||
free(relpath_for_diff);
|
||||
return set_result(r, "error: oom", 1);
|
||||
}
|
||||
flen = fread(file, 1, (size_t)sz, fp);
|
||||
fclose(fp);
|
||||
file[flen] = 0;
|
||||
} else {
|
||||
file = calloc(1, 1);
|
||||
flen = 0;
|
||||
}
|
||||
|
||||
/* Snapshot before mutating hunks. */
|
||||
char *old_snapshot = NULL;
|
||||
size_t old_len = flen;
|
||||
if (flen) {
|
||||
old_snapshot = malloc(flen + 1);
|
||||
if (old_snapshot) {
|
||||
memcpy(old_snapshot, file, flen);
|
||||
old_snapshot[flen] = 0;
|
||||
}
|
||||
} else {
|
||||
old_snapshot = calloc(1, 1);
|
||||
old_len = 0;
|
||||
}
|
||||
|
||||
/* Split into hunks starting at @@ */
|
||||
const char *h = strstr(pe, "\n@@");
|
||||
if (!h)
|
||||
h = strstr(pe, "@@");
|
||||
if (!h) {
|
||||
free(file);
|
||||
free(old_snapshot);
|
||||
free(relpath_for_diff);
|
||||
return set_result(r, "error: no complete hunk", 1);
|
||||
}
|
||||
if (*h == '\n')
|
||||
h++;
|
||||
while (h && *h == '@') {
|
||||
const char *next = strstr(h + 2, "\n@@");
|
||||
size_t hlen;
|
||||
if (next) {
|
||||
hlen = (size_t)(next + 1 - h);
|
||||
} else {
|
||||
hlen = strlen(h);
|
||||
}
|
||||
const char *hnl = strchr(h, '\n');
|
||||
if (!hnl || hnl >= h + hlen - 1) {
|
||||
free(file);
|
||||
free(old_snapshot);
|
||||
free(relpath_for_diff);
|
||||
return set_result(r, "error: incomplete hunk (not confirmed)", 1);
|
||||
}
|
||||
if (apply_hunk(&file, &flen, h, hlen) != 0) {
|
||||
free(file);
|
||||
free(old_snapshot);
|
||||
free(relpath_for_diff);
|
||||
return set_result(r, "error: hunk apply failed", 1);
|
||||
}
|
||||
h = next ? next + 1 : NULL;
|
||||
}
|
||||
|
||||
fp = fopen(full, "wb");
|
||||
if (!fp) {
|
||||
free(file);
|
||||
free(old_snapshot);
|
||||
free(relpath_for_diff);
|
||||
return set_result(r, "error: cannot write", 1);
|
||||
}
|
||||
if (fwrite(file, 1, flen, fp) != flen) {
|
||||
fclose(fp);
|
||||
free(file);
|
||||
free(old_snapshot);
|
||||
free(relpath_for_diff);
|
||||
return set_result(r, "error: write failed", 1);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
/* Patch text is already a diff — show it when short. */
|
||||
size_t plen = strlen(patch);
|
||||
if (plen > 0 && plen < DIFF_OUT_CAP) {
|
||||
char hdr[256];
|
||||
snprintf(hdr, sizeof(hdr), "applied patch → %s\n", relpath_for_diff);
|
||||
size_t hlen = strlen(hdr);
|
||||
char *buf = malloc(hlen + plen + 1);
|
||||
if (buf) {
|
||||
memcpy(buf, hdr, hlen);
|
||||
memcpy(buf + hlen, patch, plen);
|
||||
buf[hlen + plen] = 0;
|
||||
cst_tool_result_free(r);
|
||||
r->out = buf;
|
||||
r->out_len = hlen + plen;
|
||||
r->exit_code = 0;
|
||||
free(file);
|
||||
free(old_snapshot);
|
||||
free(relpath_for_diff);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int rc = set_result_with_diff(r, relpath_for_diff, old_snapshot, old_len, file,
|
||||
flen, 0);
|
||||
free(file);
|
||||
free(old_snapshot);
|
||||
free(relpath_for_diff);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int cst_tool_dispatch(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r) {
|
||||
if (!call || !r)
|
||||
return -1;
|
||||
memset(r, 0, sizeof(*r));
|
||||
if (strcmp(call->name, "read_file") == 0)
|
||||
return cst_tool_read_file(workspace_root, call, r);
|
||||
if (strcmp(call->name, "run_shell") == 0)
|
||||
return cst_tool_run_shell(workspace_root, call, r);
|
||||
if (strcmp(call->name, "apply_patch") == 0)
|
||||
return cst_tool_apply_patch(workspace_root, call, r);
|
||||
if (strcmp(call->name, "write_file") == 0)
|
||||
return cst_tool_write_file(workspace_root, call, r);
|
||||
return set_result(r, "error: unknown tool", 1);
|
||||
}
|
||||
|
||||
/* Parse JSON string at *pp (must point at '"'); advances *pp past closing quote.
|
||||
* Returns malloc'd decoded value. */
|
||||
static char *parse_json_string(const char **pp, size_t *out_len) {
|
||||
const char *p = *pp;
|
||||
if (!p || *p != '"')
|
||||
return NULL;
|
||||
p++;
|
||||
size_t cap = 64, len = 0;
|
||||
char *out = malloc(cap);
|
||||
if (!out)
|
||||
return NULL;
|
||||
while (*p) {
|
||||
char ch;
|
||||
if (*p == '\\' && p[1]) {
|
||||
p++;
|
||||
if (*p == 'n')
|
||||
ch = '\n';
|
||||
else if (*p == 'r')
|
||||
ch = '\r';
|
||||
else if (*p == 't')
|
||||
ch = '\t';
|
||||
else
|
||||
ch = *p;
|
||||
p++;
|
||||
} else if (*p == '"') {
|
||||
p++;
|
||||
out[len] = 0;
|
||||
*pp = p;
|
||||
if (out_len)
|
||||
*out_len = len;
|
||||
return out;
|
||||
} else {
|
||||
ch = *p++;
|
||||
}
|
||||
if (len + 2 > cap) {
|
||||
cap *= 2;
|
||||
char *nb = realloc(out, cap);
|
||||
if (!nb) {
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
out = nb;
|
||||
}
|
||||
out[len++] = ch;
|
||||
}
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cst_tool_call_from_native(ctl_tool_call_t *out, const char *id,
|
||||
const char *name, const char *args_json) {
|
||||
if (!out || !name || !name[0])
|
||||
return -1;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (id)
|
||||
snprintf(out->id, sizeof(out->id), "%s", id);
|
||||
snprintf(out->name, sizeof(out->name), "%s", name);
|
||||
if (!args_json)
|
||||
return 0;
|
||||
|
||||
const char *p = args_json;
|
||||
while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')
|
||||
p++;
|
||||
if (*p != '{')
|
||||
return 0;
|
||||
p++;
|
||||
|
||||
while (*p) {
|
||||
while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t' || *p == ',')
|
||||
p++;
|
||||
if (*p == '}' || !*p)
|
||||
break;
|
||||
if (*p != '"')
|
||||
break;
|
||||
|
||||
size_t klen = 0;
|
||||
char *key = parse_json_string(&p, &klen);
|
||||
if (!key)
|
||||
break;
|
||||
while (*p == ' ' || *p == '\t')
|
||||
p++;
|
||||
if (*p != ':') {
|
||||
free(key);
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
|
||||
p++;
|
||||
|
||||
if (*p == '"') {
|
||||
size_t vlen = 0;
|
||||
char *val = parse_json_string(&p, &vlen);
|
||||
if (!val) {
|
||||
free(key);
|
||||
break;
|
||||
}
|
||||
if (out->nargs < CTL_MAX_ARGS && klen < CTL_MAX_KEY) {
|
||||
snprintf(out->args[out->nargs].key, CTL_MAX_KEY, "%s", key);
|
||||
out->args[out->nargs].value = val;
|
||||
out->args[out->nargs].value_len = vlen;
|
||||
out->nargs++;
|
||||
} else {
|
||||
free(val);
|
||||
}
|
||||
free(key);
|
||||
} else if (*p == '{' || *p == '[') {
|
||||
free(key);
|
||||
char open = *p;
|
||||
char close = (open == '{') ? '}' : ']';
|
||||
int depth = 0;
|
||||
int in_str = 0;
|
||||
for (; *p; p++) {
|
||||
if (in_str) {
|
||||
if (*p == '\\' && p[1]) {
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
if (*p == '"')
|
||||
in_str = 0;
|
||||
continue;
|
||||
}
|
||||
if (*p == '"') {
|
||||
in_str = 1;
|
||||
continue;
|
||||
}
|
||||
if (*p == open)
|
||||
depth++;
|
||||
else if (*p == close) {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
free(key);
|
||||
while (*p && *p != ',' && *p != '}')
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user