A turn could write a report and say "here it is" -- and there was nothing to
open. The engine had no way to hand a finished file to the person, and the
front end had no way to show one. This release adds the whole path: a tool to
deliver, a store that keeps the copy, a card in the transcript, and open /
share through the platform. It also fixes two bugs the phone exposed while it
was being tried there.
deliver_file (engine)
- cst_attachment_t.for_user marks a copy that belongs to the person, not to
the model; cst_attach_import_artifact() stores any bytes up to
CST_ARTIFACT_MAX_BYTES (64 MB) with the mime taken from the name's
extension. Attachments are unchanged (content decides the type, 16 MB).
- It is a read-policy engine tool: path resolves inside the workspace, a
directory is refused, title (optional) is the display name.
- The wire is what keeps it out of the conversation: parts_add_attachments()
skips for_user entries without even a "not sent" note, and a turn that only
delivered a file no longer invents an empty user message. tests/test_artifacts.c
pins that down end to end, including a session-file round trip.
The card (Qt front end)
- filesText() replaced imagesText(): one line per file -- kind, stored name,
name, origin, mime, bytes, pixels -- decoded by ImageStrip.qml. A for_user
entry is a file card (extension badge, name, type and size, Open, Share);
a picture stays a thumbnail and opens on tap. Both shapes are read, so
sessions written by an older version still draw.
- AgentCore::openArtifact() / shareArtifact() accept a name only when one of
the session's own messages carries it, and hand the display name to the
platform layer for the status line.
- Android opens through the app's FileProvider (content:// + ACTION_VIEW,
resolveActivity() checked, read permission granted) and shares through the
share sheet; a desktop uses QDesktopServices / the clipboard.
CST_APP_NO_OPEN=1 makes --uitest run without opening anything.
Two bugs the device found
- The session writer had its own copy of the attachment JSON and forgot
`for_user`: the file survived the file, but came back as an attachment --
no card, and put back in front of the model. It now uses the one writer
(cst_attachments_to_json()), with the round trip covered by a test.
- engineToolImages() looked the card up with findTool(), which only finds a
card that is still running -- and the engine reports the result *before*
the files, so the card was already complete and the files were dropped.
A live turn therefore showed no card while a replay of the same session
did. It now matches on the call id.
- A tap that could not work said nothing: ImageStrip.qml's errorText was
never drawn. The card now carries the reason ("no app on the phone opens
report.md").
Also
- The saved provider key reaches the engine on every start: AgentCore::open()
was handed an empty key and the engine never resolves credentials itself
(only the CLI did), so every start asked for it again. It resolves the same
order main.c uses (saved key, provider environment, CST_API_KEY) and wipes
the copy it made; --selftest checks it against a scratch credentials file.
- Android: notification_send reads the Java exception text into its result,
and keeps failing on the NX789J -- the build() returns null and Qt clears
the exception before C++ can read it, so the call has to be wrapped in Java
next (docs/android.md says so).
- --uitest grew the delivered-file case (card, Open, Share, a forged name
refused), the live order of result-then-files, and the startup row count.
Version 0.9.2.
574 lines
26 KiB
C
574 lines
26 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) */
|
|
/* A delivered file (deliver_file): the front end shows it as a card the
|
|
* person can open or share, and no request ever carries it. The stored copy
|
|
* is the file as it was, not a text reduction -- see cst_attach.h. */
|
|
int for_user;
|
|
} 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 */
|
|
/* What the model is asked to do (Chat Completions fields). 0 = the field
|
|
* is not sent at all and the provider's own default stands; that is what a
|
|
* config that was not told otherwise has, so a CLI run is unchanged.
|
|
* temperature and top_p are doubles, max_tokens the completion cap. */
|
|
double temperature;
|
|
double top_p;
|
|
int max_tokens;
|
|
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 */
|
|
double temperature; /* 0 = not sent */
|
|
double top_p; /* 0 = not sent */
|
|
int max_tokens; /* 0 = not sent */
|
|
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);
|
|
/* Images a tool produced (view_image, MCP image content), fired right after
|
|
* the call ran and before the result is persisted. Only a call that made at
|
|
* least one image is reported, so a front end can show it at once instead of
|
|
* reading the store. */
|
|
void (*on_tool_images)(const ctl_tool_call_t *call,
|
|
const cst_attachment_t *list, int n, 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);
|
|
/* The stored name of pending attachment i ("img-1a2b3c4d.png"), or NULL when
|
|
* i is out of range. The file is already in the session store, so a front end
|
|
* can show the image before the message is sent. */
|
|
const char *cst_agent_pending_rel(const cst_agent_t *a, int i);
|
|
/* 1 when pending attachment i is an image, 0 otherwise (or out of range). */
|
|
int cst_agent_pending_is_image(const cst_agent_t *a, int i);
|
|
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
|
|
#define CST_SETTINGS_MAX_PARAMS 16
|
|
/* Generation parameters remembered per provider ("params" in settings.json):
|
|
* what the user set for that provider's models. 0 = not set, the field is
|
|
* not sent. */
|
|
typedef struct {
|
|
char provider[32];
|
|
double temperature;
|
|
double top_p;
|
|
int max_tokens;
|
|
} cst_gen_params_t;
|
|
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];
|
|
int nparams;
|
|
cst_gen_params_t params[CST_SETTINGS_MAX_PARAMS];
|
|
} 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);
|
|
/* The remembered parameters of a provider, or NULL when it has none. */
|
|
const cst_gen_params_t *cst_settings_params(const cst_settings_t *s,
|
|
const char *provider);
|
|
/* Remember parameters for a provider; all-zero (or NULL) forgets them. */
|
|
int cst_settings_remember_params(const char *provider,
|
|
const cst_gen_params_t *p);
|
|
/* 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 */
|