forked from WHC/Hermit
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,174 @@
|
||||
#ifndef CST_AGENT_H
|
||||
#define CST_AGENT_H
|
||||
|
||||
#include "ctl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CST_AGENT_MAX_MSG 256
|
||||
#define CST_AGENT_MAX_CONTENT (512 * 1024)
|
||||
#define CST_AGENT_CTX_WINDOW 40 /* max non-system msgs sent to LLM */
|
||||
|
||||
typedef enum {
|
||||
CST_ROLE_SYSTEM = 0,
|
||||
CST_ROLE_USER,
|
||||
CST_ROLE_ASSISTANT,
|
||||
CST_ROLE_TOOL
|
||||
} cst_role_t;
|
||||
|
||||
typedef struct {
|
||||
cst_role_t role;
|
||||
char *content;
|
||||
char tool_call_id[CTL_MAX_ID]; /* for tool results */
|
||||
char tool_name[CTL_MAX_NAME];
|
||||
char *tool_calls_json; /* assistant: OpenAI tool_calls array JSON, or NULL */
|
||||
} cst_message_t;
|
||||
|
||||
typedef struct {
|
||||
char workspace_root[4096];
|
||||
char session_path[4096];
|
||||
char api_base[512];
|
||||
char api_key[512];
|
||||
char model[128];
|
||||
int headless;
|
||||
int confirm_shell; /* confirm run_shell */
|
||||
int confirm_write; /* confirm write_file / apply_patch */
|
||||
int auto_approve; /* headless / CST_AUTO_APPROVE=1 */
|
||||
} cst_agent_config_t;
|
||||
|
||||
typedef struct cst_session cst_session_t;
|
||||
typedef struct cst_agent cst_agent_t;
|
||||
|
||||
/* --- sandbox --- */
|
||||
int cst_sandbox_resolve(const char *workspace_root, const char *rel_or_abs,
|
||||
char *out, size_t out_sz);
|
||||
|
||||
/* --- session (append-only JSONL) --- */
|
||||
cst_session_t *cst_session_open(const char *path);
|
||||
void cst_session_close(cst_session_t *s);
|
||||
int cst_session_append(cst_session_t *s, const cst_message_t *msg);
|
||||
int cst_session_count(const cst_session_t *s);
|
||||
const cst_message_t *cst_session_get(const cst_session_t *s, int i);
|
||||
const char *cst_session_path(const cst_session_t *s);
|
||||
int cst_session_clear(cst_session_t *s, int keep_system);
|
||||
int cst_session_rewrite(cst_session_t *s, const cst_message_t *msgs, int n);
|
||||
|
||||
/* --- workspace .agent/sessions store (OpenCode-like) --- */
|
||||
int cst_agent_store_ensure(const char *workspace);
|
||||
int cst_agent_store_dir(const char *workspace, char *out, size_t out_sz);
|
||||
int cst_agent_store_new_id(char *id_out, size_t id_sz);
|
||||
int cst_agent_store_path_for_id(const char *workspace, const char *id,
|
||||
char *out, size_t out_sz);
|
||||
int cst_agent_store_set_current(const char *workspace, const char *id);
|
||||
int cst_agent_store_get_current(const char *workspace, char *id_out,
|
||||
size_t id_sz);
|
||||
int cst_agent_store_resolve_default(const char *workspace, char *path_out,
|
||||
size_t path_sz, char *id_out,
|
||||
size_t id_sz);
|
||||
int cst_agent_store_list(const char *workspace, char ids[][128], int max_ids);
|
||||
|
||||
/* --- tools --- */
|
||||
typedef struct {
|
||||
char *out;
|
||||
size_t out_len;
|
||||
int exit_code;
|
||||
} cst_tool_result_t;
|
||||
|
||||
void cst_tool_result_free(cst_tool_result_t *r);
|
||||
|
||||
int cst_tool_read_file(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r);
|
||||
int cst_tool_run_shell(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r);
|
||||
int cst_tool_apply_patch(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r);
|
||||
int cst_tool_write_file(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r);
|
||||
int cst_tool_dispatch(const char *workspace_root, const ctl_tool_call_t *call,
|
||||
cst_tool_result_t *r);
|
||||
|
||||
/* Build ctl_tool_call from native OpenAI tool_call fields. */
|
||||
int cst_tool_call_from_native(ctl_tool_call_t *out, const char *id,
|
||||
const char *name, const char *args_json);
|
||||
|
||||
/* --- LLM --- */
|
||||
typedef void (*cst_llm_on_delta_fn)(const char *data, size_t len, void *ud);
|
||||
typedef void (*cst_llm_on_native_tool_fn)(const char *id, const char *name,
|
||||
const char *args_json, void *ud);
|
||||
|
||||
typedef struct {
|
||||
const char *api_base;
|
||||
const char *api_key;
|
||||
const char *model;
|
||||
const cst_message_t *msgs;
|
||||
int nmsgs;
|
||||
cst_llm_on_delta_fn on_delta;
|
||||
cst_llm_on_native_tool_fn on_native_tool; /* fired once per completed call */
|
||||
void *userdata;
|
||||
int enable_tools; /* send tools[] schema */
|
||||
int tool_choice_none; /* "tool_choice":"none" */
|
||||
volatile int *cancel_flag; /* non-zero → abort curl */
|
||||
} cst_llm_request_t;
|
||||
|
||||
int cst_llm_chat_stream(const cst_llm_request_t *req, char *err, size_t err_sz);
|
||||
|
||||
/* Live UI / observability hooks for a turn */
|
||||
typedef struct {
|
||||
void (*on_status)(const char *msg, void *ud);
|
||||
void (*on_assistant_delta)(const char *text, size_t len, void *ud);
|
||||
void (*on_tool_event)(const char *phase, const ctl_tool_call_t *call,
|
||||
const char *detail, void *ud);
|
||||
void (*on_think_delta)(const char *text, size_t len, void *ud);
|
||||
/* Return 1=allow, 0=deny. NULL → use config auto_approve / defaults. */
|
||||
int (*on_permission)(const char *tool_name, const char *detail, void *ud);
|
||||
void *userdata;
|
||||
} cst_agent_hooks_t;
|
||||
|
||||
/* --- agent loop --- */
|
||||
cst_agent_t *cst_agent_create(const cst_agent_config_t *cfg);
|
||||
void cst_agent_destroy(cst_agent_t *a);
|
||||
int cst_agent_run_turn(cst_agent_t *a, const char *user_text, char *err,
|
||||
size_t err_sz);
|
||||
int cst_agent_run_turn_ex(cst_agent_t *a, const char *user_text,
|
||||
const cst_agent_hooks_t *hooks, char *err,
|
||||
size_t err_sz);
|
||||
cst_session_t *cst_agent_session(cst_agent_t *a);
|
||||
const cst_agent_config_t *cst_agent_config(const cst_agent_t *a);
|
||||
cst_agent_config_t *cst_agent_config_mut(cst_agent_t *a);
|
||||
void cst_agent_request_cancel(cst_agent_t *a);
|
||||
int cst_agent_new_session(cst_agent_t *a, char *id_out, size_t id_sz);
|
||||
int cst_agent_switch_session(cst_agent_t *a, const char *id);
|
||||
int cst_agent_compact(cst_agent_t *a, char *err, size_t err_sz);
|
||||
|
||||
const char *cst_agent_system_prompt(void);
|
||||
|
||||
/* Credentials: zenity + ~/.config/cst-agent/credentials (0600) */
|
||||
int cst_creds_load_api_key(char *out, size_t out_sz);
|
||||
int cst_creds_save_api_key(const char *key);
|
||||
int cst_creds_prompt_api_key(char *out, size_t out_sz);
|
||||
int cst_creds_ensure_api_key(char *out, size_t out_sz);
|
||||
|
||||
/* JSON baseline parser for experiments (buffer until valid object). */
|
||||
typedef struct {
|
||||
int ok;
|
||||
char name[CTL_MAX_NAME];
|
||||
char arguments_json[65536];
|
||||
} cst_json_tool_t;
|
||||
|
||||
int cst_json_try_parse_tool(const char *buf, size_t len, cst_json_tool_t *out);
|
||||
|
||||
/* Built-in / CST_MODELS model picker list. Writes up to max_out names.
|
||||
* Always includes `current` if non-empty and not already listed. */
|
||||
int cst_models_list(const char *current, char out[][128], int max_out);
|
||||
|
||||
int cst_tui_run(cst_agent_t *agent);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CST_AGENT_H */
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef CTL_H
|
||||
#define CTL_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef CTL_MAX_LINE
|
||||
#define CTL_MAX_LINE 4096
|
||||
#endif
|
||||
|
||||
#ifndef CTL_MAX_ARGS
|
||||
#define CTL_MAX_ARGS 16
|
||||
#endif
|
||||
|
||||
#ifndef CTL_MAX_ARG_BYTES
|
||||
#define CTL_MAX_ARG_BYTES (1024 * 1024)
|
||||
#endif
|
||||
|
||||
#ifndef CTL_MAX_ID
|
||||
#define CTL_MAX_ID 64
|
||||
#endif
|
||||
|
||||
#ifndef CTL_MAX_NAME
|
||||
#define CTL_MAX_NAME 64
|
||||
#endif
|
||||
|
||||
#ifndef CTL_MAX_KEY
|
||||
#define CTL_MAX_KEY 64
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
CTL_OK = 0,
|
||||
CTL_ERR_CONTRACT = 1, /* left L0_tool */
|
||||
CTL_ERR_NOMEM = 2,
|
||||
CTL_ERR_LIMIT = 3
|
||||
} ctl_status_t;
|
||||
|
||||
typedef enum {
|
||||
CTL_EV_NONE = 0,
|
||||
CTL_EV_CALL_OPEN, /* id+name confirmed */
|
||||
CTL_EV_ARG, /* one arg fully confirmed */
|
||||
CTL_EV_CALL_READY, /* end confirmed — safe to execute */
|
||||
CTL_EV_TEXT, /* non-CTL confirmed text (outside call) */
|
||||
CTL_EV_ERROR
|
||||
} ctl_event_type_t;
|
||||
|
||||
typedef struct {
|
||||
char key[CTL_MAX_KEY];
|
||||
char *value;
|
||||
size_t value_len;
|
||||
} ctl_arg_t;
|
||||
|
||||
typedef struct {
|
||||
char id[CTL_MAX_ID];
|
||||
char name[CTL_MAX_NAME];
|
||||
ctl_arg_t args[CTL_MAX_ARGS];
|
||||
int nargs;
|
||||
} ctl_tool_call_t;
|
||||
|
||||
typedef struct {
|
||||
ctl_event_type_t type;
|
||||
const ctl_tool_call_t *call; /* valid for CALL_* / ARG */
|
||||
const char *text; /* for TEXT */
|
||||
size_t text_len;
|
||||
const char *error; /* for ERROR */
|
||||
} ctl_event_t;
|
||||
|
||||
typedef void (*ctl_on_event_fn)(const ctl_event_t *ev, void *userdata);
|
||||
|
||||
typedef struct ctl_parser ctl_parser_t;
|
||||
|
||||
ctl_parser_t *ctl_parser_create(ctl_on_event_fn on_event, void *userdata);
|
||||
void ctl_parser_destroy(ctl_parser_t *p);
|
||||
|
||||
/* Feed UTF-8 bytes. Emits only confirmed events. */
|
||||
ctl_status_t ctl_parser_feed(ctl_parser_t *p, const char *data, size_t len);
|
||||
|
||||
/* EOF: flush; incomplete call => ERROR (no execute). */
|
||||
ctl_status_t ctl_parser_finish(ctl_parser_t *p);
|
||||
|
||||
/* Bytes currently pending (unconfirmed). */
|
||||
size_t ctl_parser_pending(const ctl_parser_t *p);
|
||||
|
||||
/* Peak pending observed (for experiments). */
|
||||
size_t ctl_parser_pending_peak(const ctl_parser_t *p);
|
||||
|
||||
/* Reset for a new assistant turn. */
|
||||
void ctl_parser_reset(ctl_parser_t *p);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CTL_H */
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef MM_TOOL_H
|
||||
#define MM_TOOL_H
|
||||
|
||||
#include "ctl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Streaming CST scanner for MiniMax native tool XML:
|
||||
*
|
||||
* <minimax:tool_call>
|
||||
* <invoke name="read_file">
|
||||
* <parameter name="path">README.md</parameter>
|
||||
* </invoke>
|
||||
* </minimax:tool_call>
|
||||
*
|
||||
* Also accepts the dialect seen in the wild:
|
||||
* <minimax:tool_call>
|
||||
* invoke read_file
|
||||
* <arg name="path">./README.md</arg>
|
||||
* </minimax:tool_call>
|
||||
*
|
||||
* Confirmation: nothing executes until </minimax:tool_call>.
|
||||
*/
|
||||
|
||||
typedef void (*mm_on_event_fn)(const ctl_event_t *ev, void *userdata);
|
||||
|
||||
typedef struct mm_scan mm_scan_t;
|
||||
|
||||
mm_scan_t *mm_scan_create(mm_on_event_fn on_event, void *userdata);
|
||||
void mm_scan_destroy(mm_scan_t *s);
|
||||
void mm_scan_reset(mm_scan_t *s);
|
||||
|
||||
/* Feed bytes. Emits TEXT for confirmed non-tool regions and CALL_* for
|
||||
* completed tool blocks. Incomplete tool blocks stay pending. */
|
||||
int mm_scan_feed(mm_scan_t *s, const char *data, size_t len);
|
||||
|
||||
/* Flush: incomplete open tool block => emit remaining as TEXT (no execute). */
|
||||
int mm_scan_finish(mm_scan_t *s);
|
||||
|
||||
size_t mm_scan_pending(const mm_scan_t *s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef CST_OS_COMPAT_H
|
||||
#define CST_OS_COMPAT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef OS_PATH_MAX
|
||||
#define OS_PATH_MAX 4096
|
||||
#endif
|
||||
|
||||
/* --- path --- */
|
||||
int os_path_is_abs(const char *p);
|
||||
/* Join with native separator; out always uses '/' on Unix and '\\' on Win for
|
||||
* the joiner, but os_path_norm_slashes converts to '/' for comparisons. */
|
||||
int os_path_join(char *out, size_t out_sz, const char *a, const char *b);
|
||||
void os_path_norm_slashes(char *p);
|
||||
int os_realpath(const char *path, char *out, size_t out_sz);
|
||||
/* True if path is under root (after realpath/norm). Trailing slash rules apply. */
|
||||
int os_path_under(const char *root, const char *path);
|
||||
|
||||
/* --- dirs / config --- */
|
||||
int os_mkdir(const char *path, int mode);
|
||||
int os_config_dir(char *out, size_t out_sz);
|
||||
int os_temp_dir(char *out, size_t out_sz);
|
||||
/* Create unique temp file; writes path into out; returns fd or -1. Caller closes. */
|
||||
int os_mkstemp(char *out, size_t out_sz, const char *prefix);
|
||||
|
||||
/* Directory containing the running executable (for bundled busybox).
|
||||
* Call os_set_prog_path(argv[0]) early on Unix/Termux as fallback. */
|
||||
void os_set_prog_path(const char *argv0);
|
||||
int os_exe_dir(char *out, size_t out_sz);
|
||||
|
||||
/* Resolve shell interpreter path into out.
|
||||
* Order: CST_SHELL → <exe_dir>/busybox[.exe] → /bin/sh (Unix). */
|
||||
int os_resolve_shell(char *out, size_t out_sz);
|
||||
|
||||
/* --- process --- */
|
||||
typedef struct {
|
||||
char *out;
|
||||
size_t out_len;
|
||||
int exit_code;
|
||||
} os_run_result_t;
|
||||
|
||||
void os_run_result_free(os_run_result_t *r);
|
||||
|
||||
/* Run argv[0..] (NULL-terminated) with cwd; capture stdout+stderr.
|
||||
* max_out caps captured bytes (0 = default 512KiB). */
|
||||
int os_run_capture(const char *cwd, const char *const argv[], size_t max_out,
|
||||
os_run_result_t *r);
|
||||
|
||||
/* Convenience: shell -c cmd via os_resolve_shell. */
|
||||
int os_run_shell_c(const char *cwd, const char *cmd, size_t max_out,
|
||||
os_run_result_t *r);
|
||||
|
||||
/* --- misc --- */
|
||||
void os_localtime(const time_t *t, struct tm *out);
|
||||
int os_getpid(void);
|
||||
int os_chmod_private(const char *path); /* best-effort 0600; no-op/ok on Win */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CST_OS_COMPAT_H */
|
||||
Reference in New Issue
Block a user