Working mode: --mode coding|general (CST_MODE, settings.json, /mode, WebUI run settings). general swaps the default framing for a general assistant one -- workflow, safety notes, tool wording (run_shell, the subagents) -- and /status shows it; minimal (the paper profile) stays byte-frozen. The prompt state carries the mode so a switch leaves a system note in history. Clipboard: clipboard_read / clipboard_write tools (16 MiB cap read, default 64 KiB window; writing is a WRITE permission with a preview) on pbcopy / wl-copy / xclip / pbpaste / wl-paste / xclip and the Win32 clipboard. In the TUI, /attach paste falls back to clipboard text when there is no image on it. PDF: a dependency-free text extractor (src/pdf_text.c): inflate, Flate/ASCIIHex/ASCII85, PNG/TIFF predictors, layout-independent object scan, object streams, page tree walk with inherited resources, content lexer (Tj/TJ/'/" and the Td/TD/T*/Tm spacing heuristics), per-font ToUnicode CMaps and inline images skipped. Encrypted files and pages with no text layer are refused with the reason. Attachments: /attach (a path or files dropped on the terminal) now take images, PDFs and text. A PDF is stored as its text layer with the mime still saying application/pdf and its page count; anything else has to be UTF-8 text. Documents travel inline as text parts, so CST_NO_IMAGES and text-only models no longer drop them, while images keep their old path. Tests: test_pdf_text (plain, bare and zlib-framed Flate, inline image, CID + ToUnicode, object stream, error paths), documents in test_attach, inlined documents in test_llm_body, mode in test_config.
530 lines
24 KiB
C
530 lines
24 KiB
C
#ifndef CST_AGENT_H
|
|
#define CST_AGENT_H
|
|
|
|
#include "ctl.h"
|
|
|
|
#include <stddef.h>
|
|
#include <time.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define CST_AGENT_MAX_MSG 256
|
|
#define CST_AGENT_MAX_CONTENT (512 * 1024)
|
|
#define CST_DEFAULT_MAX_STEPS 30
|
|
#define CST_DEFAULT_TOOL_OUT_CAP (16 * 1024)
|
|
|
|
typedef enum {
|
|
CST_ROLE_SYSTEM = 0,
|
|
CST_ROLE_USER,
|
|
CST_ROLE_ASSISTANT,
|
|
CST_ROLE_TOOL
|
|
} cst_role_t;
|
|
|
|
/* How tool calls travel between model and runtime.
|
|
* native-eos : OpenAI tools[]; execute after the stream ends (baseline).
|
|
* native-inc : OpenAI tools[]; execute as soon as one call's arguments are
|
|
* complete (next index started / finish_reason).
|
|
* ctl : CTL frames in plain text, length-prefixed args; execute on
|
|
* confirmed `end` while the stream is still running.
|
|
* ctl-heredoc: same, prompt teaches `arg <key> <<TAG ... TAG` bodies.
|
|
* xml : MiniMax <minimax:tool_call> text; execute after the stream. */
|
|
typedef enum {
|
|
CST_PROTO_NATIVE_EOS = 0,
|
|
CST_PROTO_NATIVE_INC,
|
|
CST_PROTO_CTL,
|
|
CST_PROTO_CTL_HEREDOC,
|
|
CST_PROTO_XML
|
|
} cst_protocol_t;
|
|
|
|
const char *cst_protocol_name(cst_protocol_t p);
|
|
int cst_protocol_parse(const char *s, cst_protocol_t *out);
|
|
int cst_protocol_is_native(cst_protocol_t p);
|
|
int cst_protocol_is_ctl(cst_protocol_t p);
|
|
/* Text protocols carry calls/results in message text (no tools[]). */
|
|
int cst_protocol_is_text(cst_protocol_t p);
|
|
/* Eager protocols execute a call during the stream, at confirmation time. */
|
|
int cst_protocol_is_eager(cst_protocol_t p);
|
|
|
|
/* One file carried by a message (see cst_attach.h): an image, or a document
|
|
* whose stored payload is its text. The payload lives beside the session;
|
|
* this is the metadata plus the name of the stored copy. */
|
|
typedef struct {
|
|
char id[32]; /* unique inside the session ("img-1a2b3c4d") */
|
|
char name[256]; /* display name (original file name) */
|
|
char mime[32]; /* image/png, ...; application/pdf for a PDF whose text
|
|
* layer the stored file holds; text/plain for text */
|
|
char rel[256]; /* file name inside the session's attachment directory */
|
|
long long bytes; /* stored size (the text, for documents) */
|
|
int width, height; /* pixels, 0 when the header could not be read */
|
|
int pages; /* PDF page count, 0 for anything else */
|
|
char detail[8]; /* auto | low | high (wire hint, "" = auto) */
|
|
char origin[32]; /* user | view_image | mcp (for display only) */
|
|
} cst_attachment_t;
|
|
|
|
typedef struct {
|
|
cst_role_t role;
|
|
char *content;
|
|
char tool_call_id[CTL_MAX_ID]; /* tool results (role=tool, or text-protocol
|
|
result frames sent as role=user) */
|
|
char tool_name[CTL_MAX_NAME];
|
|
char *tool_calls_json; /* assistant: OpenAI tool_calls array JSON, or NULL */
|
|
char *meta_json; /* optional JSON object (kind, protocol, ...) */
|
|
cst_attachment_t *attach; /* images shown with this message, or NULL */
|
|
int nattach;
|
|
char *reasoning; /* assistant: reasoning_content as streamed, or NULL */
|
|
int prefix; /* wire only: assistant prefix the model continues */
|
|
} cst_message_t;
|
|
|
|
typedef struct {
|
|
char workspace_root[4096];
|
|
char session_path[4096];
|
|
char api_base[512];
|
|
char api_key[512];
|
|
char model[128];
|
|
char provider[32];
|
|
char effort[16]; /* low | medium | high | max */
|
|
char mode[16]; /* coding | general (CST_DEFAULT_MODE) */
|
|
cst_protocol_t protocol;
|
|
int max_steps; /* model calls per user turn */
|
|
size_t tool_out_cap; /* bytes of one tool output sent to the model */
|
|
int headless;
|
|
int confirm_shell; /* confirm run_shell */
|
|
int confirm_write; /* confirm file-changing tools */
|
|
int confirm_net; /* confirm web_fetch */
|
|
int auto_approve; /* headless / CST_AUTO_APPROVE=1 / --yolo */
|
|
int approval; /* cst_approval_t (interactive sessions) */
|
|
int plan_mode; /* read-only tools + planning note */
|
|
int tool_profile; /* cst_tool_profile_t (CST_TOOLS) */
|
|
char tools_off[1024]; /* ",name,name," tools switched off (/tools off) */
|
|
/* Sub-agents (task tool). */
|
|
int depth; /* 0 = top-level; sub-agents run at 1 */
|
|
int read_only; /* no write / shell tools */
|
|
char tools_only[1024]; /* ",a,b," allowlist; empty = every tool */
|
|
const char *role_prompt; /* role instructions (not owned) */
|
|
char undo_sid[128]; /* snapshot into this session's undo... */
|
|
char undo_tag[64]; /* ...under this turn tag (the parent's) */
|
|
} cst_agent_config_t;
|
|
|
|
/* ask: confirm per tool; auto: sandbox/rules decide, ask on doubt; yolo: never
|
|
* ask (same as auto_approve). */
|
|
typedef enum {
|
|
CST_APPROVAL_ASK = 0,
|
|
CST_APPROVAL_AUTO,
|
|
CST_APPROVAL_YOLO
|
|
} cst_approval_t;
|
|
#define CST_DEFAULT_APPROVAL CST_APPROVAL_AUTO
|
|
const char *cst_approval_name(int a);
|
|
int cst_approval_parse(const char *s, int *out);
|
|
|
|
/* Working mode: how the system prompt frames the agent. "coding" (default)
|
|
* is code framed; "general" is for tasks that are not about a codebase
|
|
* (documents, data, research, everyday files). Orthogonal to plan mode. */
|
|
#define CST_DEFAULT_MODE "coding"
|
|
int cst_mode_valid(const char *m);
|
|
/* Canonical name of m, or the default when m is unknown / empty. */
|
|
const char *cst_mode_name(const char *m);
|
|
|
|
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);
|
|
/* Tool output: role=tool, or a text-protocol user message answering a call. */
|
|
int cst_message_is_tool_result(const cst_message_t *m);
|
|
/* The transcript as Markdown, malloc'd (NULL on OOM). Tool output and call
|
|
* arguments are cut at 4000 bytes. Takes the session lock. */
|
|
char *cst_session_export_md(cst_session_t *s, const char *title,
|
|
const char *sid, const char *model);
|
|
/* Readers on another thread must hold the lock while using pointers returned
|
|
* by cst_session_get (append may reallocate). Append locks internally. */
|
|
void cst_session_lock(cst_session_t *s);
|
|
void cst_session_unlock(cst_session_t *s);
|
|
|
|
/* --- 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);
|
|
typedef struct {
|
|
char id[128];
|
|
long long mtime;
|
|
int messages;
|
|
char title[128]; /* set via /rename; empty when none */
|
|
char first_user[128]; /* first user message, one line */
|
|
} cst_session_info_t;
|
|
int cst_agent_store_info(const char *workspace, const char *id,
|
|
cst_session_info_t *info);
|
|
int cst_agent_store_title_set(const char *workspace, const char *id,
|
|
const char *title);
|
|
int cst_agent_store_delete(const char *workspace, const char *id);
|
|
|
|
/* --- tools --- */
|
|
typedef struct {
|
|
char *out;
|
|
size_t out_len;
|
|
int exit_code;
|
|
/* Images the tool produced for the model (view_image, image-returning MCP
|
|
* tools). cst_tool_result_free releases them. */
|
|
cst_attachment_t *attach;
|
|
int nattach;
|
|
} cst_tool_result_t;
|
|
|
|
void cst_tool_result_free(cst_tool_result_t *r);
|
|
|
|
int cst_tool_known(const char *name);
|
|
int cst_tool_is_shell(const char *name);
|
|
int cst_tool_is_write(const char *name);
|
|
/* OpenAI tools[] array for every tool (cJSON*, caller frees). */
|
|
void *cst_tools_schema(void);
|
|
|
|
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);
|
|
/* sandboxed: confine the command with the OS sandbox (cst_sandbox.h). */
|
|
int cst_tool_run_shell_ex(const char *workspace_root,
|
|
const ctl_tool_call_t *call, cst_tool_result_t *r,
|
|
int sandboxed);
|
|
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. Returns -1 when
|
|
* args_json is not a JSON object (call still gets id/name). */
|
|
int cst_tool_call_from_native(ctl_tool_call_t *out, const char *id,
|
|
const char *name, const char *args_json);
|
|
void cst_tool_call_clear(ctl_tool_call_t *c);
|
|
|
|
/* Apply unified-diff hunks (text after the ---/+++ header) to one buffer.
|
|
* All hunks must be complete and locate unambiguously, else nothing changes.
|
|
* On success *out is malloc'd. */
|
|
int cst_patch_apply_buffer(const char *orig, size_t olen, const char *hunks,
|
|
size_t hlen, char **out, size_t *out_len,
|
|
int *nhunks, char *err, size_t err_sz);
|
|
|
|
/* --- LLM --- */
|
|
typedef void (*cst_llm_on_delta_fn)(const char *data, size_t len, void *ud);
|
|
/* args_json == NULL means the arguments were not valid JSON / too large. */
|
|
typedef void (*cst_llm_on_native_tool_fn)(const char *id, const char *name,
|
|
const char *args_json, void *ud);
|
|
typedef void (*cst_llm_on_native_open_fn)(int index, const char *id,
|
|
const char *name, void *ud);
|
|
|
|
typedef struct {
|
|
long http_status;
|
|
char finish_reason[32];
|
|
long prompt_tokens;
|
|
long completion_tokens;
|
|
long cached_tokens; /* prompt tokens read from the provider's cache */
|
|
long cache_miss_tokens; /* prompt tokens billed at the full input price */
|
|
long cache_write_tokens; /* prompt tokens written to the cache (Anthropic) */
|
|
double t_first_byte_ms; /* relative to request start; -1 if none */
|
|
double t_end_ms;
|
|
int client_stopped; /* stream ended by stop_flag (not an error) */
|
|
int retries;
|
|
char error[512]; /* in-stream or HTTP error message */
|
|
} cst_llm_stats_t;
|
|
|
|
typedef struct {
|
|
const char *api_base;
|
|
const char *api_key;
|
|
const char *model;
|
|
const char *reasoning_effort; /* NULL = not sent */
|
|
const cst_message_t *msgs;
|
|
int nmsgs;
|
|
cst_llm_on_delta_fn on_delta;
|
|
/* Fired once per call, as soon as that call's arguments are complete. */
|
|
cst_llm_on_native_tool_fn on_native_tool;
|
|
cst_llm_on_native_open_fn on_native_open;
|
|
void *userdata;
|
|
int enable_tools; /* send tools[] schema */
|
|
const cst_agent_config_t *tool_cfg; /* active tool set; NULL = all */
|
|
const char *tools_json; /* tools[] sent verbatim (frozen); NULL = tool_cfg */
|
|
int tool_choice_none; /* "tool_choice":"none" */
|
|
int send_reasoning; /* assistant reasoning goes back as reasoning_content */
|
|
int cache_style; /* CST_CACHE_* hints to add */
|
|
const char *cache_key; /* prompt_cache_key (CST_CACHE_OPENAI) */
|
|
const char *cache_ttl; /* cache_control ttl (CST_CACHE_ANTHROPIC); NULL = 5m */
|
|
int images_off; /* the model does not take images: send a note instead */
|
|
size_t tool_out_cap; /* wire cap for role=tool content; 0 = default */
|
|
const char *session_path; /* where stored images live; NULL = none */
|
|
volatile int *cancel_flag; /* non-zero → abort (error "cancelled") */
|
|
volatile int *stop_flag; /* non-zero → stop reading, not an error */
|
|
cst_llm_stats_t *stats; /* optional */
|
|
} cst_llm_request_t;
|
|
|
|
int cst_llm_chat_stream(const cst_llm_request_t *req, char *err, size_t err_sz);
|
|
|
|
/* GET {api_base}/models (10 s timeout). Returns the number of model ids
|
|
* written, CST_MODELS_AUTH when the key is rejected (401/403), or -1 on
|
|
* network / format errors. */
|
|
#define CST_MODELS_AUTH (-2)
|
|
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);
|
|
/* JSON request body (malloc'd) exactly as sent on the wire. */
|
|
char *cst_llm_build_body(const cst_llm_request_t *req);
|
|
/* Reset the CST_RECORD_SSE / CST_REPLAY_SSE sequence counter. */
|
|
void cst_llm_trace_reset(void);
|
|
/* Set CURLOPT_CAINFO on a CURL * when a CA bundle file is known. */
|
|
void cst_curl_apply_ca(void *curl);
|
|
|
|
/* on_permission answers */
|
|
#define CST_PERM_DENY 0
|
|
#define CST_PERM_ONCE 1
|
|
#define CST_PERM_ALWAYS_TOOL 2 /* saved to .agent/permissions.json */
|
|
#define CST_PERM_ALWAYS_PREFIX 3 /* run_shell: allow this command prefix */
|
|
/* on_permission detail prefix when a sandboxed run_shell looked blocked and
|
|
* the question is whether to rerun it outside the sandbox. The rest of the
|
|
* detail is the tail of the sandboxed output. */
|
|
#define CST_PERM_SANDBOX "[sandbox] "
|
|
|
|
/* 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 a CST_PERM_* answer. NULL → deny unless auto_approve. preview is
|
|
* the diff a write call would make, the full command for run_shell, or
|
|
* NULL. */
|
|
int (*on_permission)(const char *tool_name, const char *detail,
|
|
const char *preview, void *ud);
|
|
/* ask_user tool. Fill ans and return 0, or -1 when dismissed. NULL means
|
|
* nobody can answer (headless). */
|
|
int (*on_ask_user)(const char *question, const char *options, char *ans,
|
|
size_t ans_sz, void *ud);
|
|
/* todo_write replaced the task list (one item per line). */
|
|
void (*on_todo)(const char *text, void *ud);
|
|
/* Full tool output, fired right after execution (before it is persisted). */
|
|
void (*on_tool_result)(const ctl_tool_call_t *call, const char *out,
|
|
size_t len, int exit_code, 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);
|
|
/* Re-root the agent at another directory. Per-workspace state (permission
|
|
* rules, metrics, project notes) follows, the system prompt is rebuilt and the
|
|
* token budget is re-measured. The session is not touched: the caller decides
|
|
* whether to keep it or start a new one. */
|
|
int cst_agent_set_workspace(cst_agent_t *a, const char *path, char *err,
|
|
size_t err_sz);
|
|
int cst_agent_compact(cst_agent_t *a, char *err, size_t err_sz);
|
|
/* Bare id of the current session (file name without .jsonl). */
|
|
void cst_agent_session_id(const cst_agent_t *a, char *out, size_t out_sz);
|
|
/* Transcript path of the current session (images are stored beside it). */
|
|
const char *cst_agent_session_path(const cst_agent_t *a);
|
|
/* Images picked for the next user message (/attach). They ride along with the
|
|
* next cst_agent_run_turn*() call and are then part of the session.
|
|
* cst_agent_attach_file copies the file into the session store; *err gets a
|
|
* short reason on failure (-1). */
|
|
int cst_agent_attach_file(cst_agent_t *a, const char *path, char *err,
|
|
size_t err_sz);
|
|
/* Same for bytes already in memory (clipboard); name is display-only. */
|
|
int cst_agent_attach_bytes(cst_agent_t *a, const char *name, const char *data,
|
|
size_t n, char *err, size_t err_sz);
|
|
/* Whether the current model is shown images: 1 yes, -1 unknown (tried; a
|
|
* 400 turns sending off), 0 no (images go as text notes). */
|
|
int cst_agent_sees_images(const cst_agent_t *a);
|
|
int cst_agent_pending_count(const cst_agent_t *a);
|
|
/* One line "<name> (<mime>, 12 KB)"; -1 when i is out of range. */
|
|
int cst_agent_pending_describe(const cst_agent_t *a, int i, char *out,
|
|
size_t out_sz);
|
|
void cst_agent_pending_clear(cst_agent_t *a);
|
|
/* Latest todo_write list of this session, or NULL. */
|
|
const char *cst_agent_todo(const cst_agent_t *a);
|
|
/* AGENTS.md (or CST.md) text injected into the system prompt, or NULL. */
|
|
const char *cst_agent_notes(const cst_agent_t *a);
|
|
void cst_agent_reload_notes(cst_agent_t *a);
|
|
/* Roll back the file changes of the newest undoable turn. */
|
|
int cst_agent_undo(cst_agent_t *a, char *report, size_t report_sz);
|
|
int cst_agent_undo_available(const cst_agent_t *a);
|
|
/* Persistent permission rules (see cst_perms.h). */
|
|
struct cst_perms *cst_agent_perms(cst_agent_t *a);
|
|
/* /retry: remove the last user prompt and its replies; caller frees. */
|
|
char *cst_agent_rewind_last_turn(cst_agent_t *a);
|
|
/* Session id and turn tag that /undo snapshots of the running turn use. */
|
|
void cst_agent_undo_key(const cst_agent_t *a, char *sid, size_t sid_sz,
|
|
char *tag, size_t tag_sz);
|
|
/* Last prompt_tokens reported by the model (0 if unknown). */
|
|
long cst_agent_last_prompt_tokens(const cst_agent_t *a);
|
|
/* Token use since the session was opened, and of the latest turn. */
|
|
typedef struct {
|
|
int requests;
|
|
long prompt, cached, miss, write, out;
|
|
double cost; /* in currency; valid when priced */
|
|
int priced; /* every request had a known price */
|
|
char currency[8];
|
|
} cst_usage_t;
|
|
void cst_agent_usage(const cst_agent_t *a, cst_usage_t *session,
|
|
cst_usage_t *turn);
|
|
/* "[cst] ..." notes the agent appends when Plan mode, approval, branch,
|
|
* date or AGENTS.md change mid-session (the system prompt stays frozen). */
|
|
int cst_message_is_note(const cst_message_t *m);
|
|
|
|
const char *cst_agent_system_prompt(void);
|
|
const char *cst_agent_system_prompt_for(cst_protocol_t p);
|
|
/* Prompt for this config's tool set and protocol, plus optional project
|
|
* instructions (AGENTS.md). malloc'd. */
|
|
char *cst_agent_build_prompt(const cst_agent_config_t *cfg, cst_protocol_t p,
|
|
const char *project_notes);
|
|
|
|
/* Per-provider API keys in <config>/credentials (mode 0600), one
|
|
* "<provider>.api_key=..." line each. */
|
|
int cst_creds_load(const char *provider, char *out, size_t out_sz);
|
|
int cst_creds_save(const char *provider, const char *key);
|
|
/* Saved key, then the provider's env variable. Returns 0 when found;
|
|
* *from (optional) gets "saved" or the variable name. */
|
|
int cst_creds_resolve(const char *provider, char *out, size_t out_sz,
|
|
const char **from);
|
|
/* Hidden console prompt (no echo). */
|
|
int cst_creds_prompt_console(const char *provider, char *out, size_t out_sz);
|
|
|
|
/* Last used provider, model per provider, effort and working mode, in
|
|
* <config>/settings.json. */
|
|
#define CST_SETTINGS_MAX_MODELS 16
|
|
#define CST_SETTINGS_MAX_TRUSTED 32
|
|
typedef struct {
|
|
char provider[32];
|
|
char effort[16];
|
|
char mode[16]; /* coding | general, "" = not chosen yet */
|
|
char approval[8]; /* "" = not chosen yet */
|
|
int show_thinking; /* TUI: Thought blocks start expanded */
|
|
/* workspaces whose .agent/plugins and .agent/mcp.json may run */
|
|
int ntrusted;
|
|
char trusted[CST_SETTINGS_MAX_TRUSTED][512];
|
|
int nmodels;
|
|
struct {
|
|
char provider[32];
|
|
char model[128];
|
|
} models[CST_SETTINGS_MAX_MODELS];
|
|
} cst_settings_t;
|
|
int cst_settings_load(cst_settings_t *s);
|
|
int cst_settings_save(const cst_settings_t *s);
|
|
const char *cst_settings_model(const cst_settings_t *s, const char *provider);
|
|
/* Record cfg's provider, model, effort and mode as the last used ones. */
|
|
int cst_settings_remember(const cst_agent_config_t *cfg);
|
|
/* Record an approval mode the user picked (CST_APPROVAL_*). */
|
|
int cst_settings_remember_approval(int approval);
|
|
int cst_settings_remember_thinking(int show_thinking);
|
|
/* Record the working mode the user picked (coding | general). */
|
|
int cst_settings_remember_mode(const char *mode);
|
|
/* Workspace trust for project plugins / MCP servers (by real path). */
|
|
int cst_settings_trusted(const char *workspace);
|
|
int cst_settings_trust(const char *workspace, int on);
|
|
|
|
/* 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);
|
|
|
|
/* Provider presets (base URL + default model). */
|
|
typedef struct {
|
|
const char *name;
|
|
const char *api_base;
|
|
const char *model;
|
|
const char *key_env; /* provider-specific key variable, e.g. DEEPSEEK_API_KEY */
|
|
} cst_provider_t;
|
|
|
|
const cst_provider_t *cst_provider_find(const char *name);
|
|
const cst_provider_t *cst_provider_default(void);
|
|
int cst_provider_count(void);
|
|
const cst_provider_t *cst_provider_at(int i);
|
|
/* Context window of a model in tokens (CST_CTX_TOKENS overrides). */
|
|
long cst_model_context_tokens(const char *model);
|
|
|
|
/* How a provider caches prompt prefixes.
|
|
* auto : implicit prefix cache, nothing to send (DeepSeek, MiniMax...)
|
|
* openai : implicit, routed by "prompt_cache_key"
|
|
* anthropic : explicit "cache_control" breakpoints on content parts */
|
|
typedef enum {
|
|
CST_CACHE_AUTO = 0,
|
|
CST_CACHE_OPENAI,
|
|
CST_CACHE_ANTHROPIC
|
|
} cst_cache_style_t;
|
|
typedef struct {
|
|
int cache_style; /* cst_cache_style_t */
|
|
int reasoning_passback; /* history keeps reasoning_content (DeepSeek) */
|
|
int prefix_completion; /* assistant "prefix": true via <base>/beta */
|
|
int vision; /* 1 takes images, 0 text only, -1 unknown */
|
|
} cst_model_caps_t;
|
|
/* CST_NO_CACHE_HINTS=1 forces CST_CACHE_AUTO. */
|
|
void cst_model_caps(const char *provider, const char *api_base,
|
|
const char *model, cst_model_caps_t *out);
|
|
|
|
/* Prices per million tokens. CST_PRICE="miss,hit,out[,write[,currency]]"
|
|
* overrides the built-in table. */
|
|
typedef struct {
|
|
char currency[8]; /* "¥", "$" */
|
|
double miss, hit, write, out;
|
|
int offpeak_half; /* DeepSeek: half price outside Beijing peak hours */
|
|
} cst_price_t;
|
|
/* 0 when a price is known for model, -1 otherwise. */
|
|
int cst_model_price(const char *model, cst_price_t *out);
|
|
/* DeepSeek peak hours: Beijing time Mon-Fri 9:00-12:00 and 14:00-18:00. */
|
|
int cst_price_is_peak(time_t when);
|
|
double cst_price_cost(const cst_price_t *p, long miss, long hit, long write,
|
|
long out, time_t when);
|
|
|
|
/* Reasoning effort: low | medium | high | max. */
|
|
#define CST_DEFAULT_EFFORT "medium"
|
|
int cst_effort_valid(const char *effort);
|
|
/* Value for "reasoning_effort", or NULL when this provider/model does not
|
|
* take the parameter. */
|
|
const char *cst_effort_wire(const char *provider, const char *model,
|
|
const char *effort);
|
|
|
|
/* 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 */
|