v0.7.0: image input, model registry, prefix-cache savings
Images - vision gate: text-only models get a note instead of image parts and no view_image tool; unknown models retry once without images on HTTP 400 - paste: Ctrl+V and /attach paste read the clipboard (wl-paste, xclip, pngpaste/osascript, Win32 PNG or CF_DIB converted to PNG); pasting image file paths attaches them - MCP image content is stored in the session and sent with the tool result - attach: rel path validation, base64 decode, /retry keeps images Model registry - built-in src/models_builtin.json embedded at build time, merged with <config>/models.json and cached GET /models metadata (context, vision, pricing); most specific rule wins per field - /model labels, /status source, --list-models [provider] Cache and cost - stable request prefix (frozen tools, notes as messages), DeepSeek prefix completion, OpenAI/Anthropic cache hints, /cost and hit rate in the status
This commit is contained in:
+97
-2
@@ -4,6 +4,7 @@
|
||||
#include "ctl.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -11,7 +12,6 @@ extern "C" {
|
||||
|
||||
#define CST_AGENT_MAX_MSG 256
|
||||
#define CST_AGENT_MAX_CONTENT (512 * 1024)
|
||||
#define CST_AGENT_CTX_WINDOW 80 /* max non-system msgs sent to LLM */
|
||||
#define CST_DEFAULT_MAX_STEPS 30
|
||||
#define CST_DEFAULT_TOOL_OUT_CAP (16 * 1024)
|
||||
|
||||
@@ -47,6 +47,19 @@ 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 image carried by a message (see cst_attach.h). The bytes live 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, image/jpeg, image/gif, image/webp, image/bmp */
|
||||
char rel[256]; /* file name inside the session's attachment directory */
|
||||
long long bytes; /* stored size */
|
||||
int width, height; /* pixels, 0 when the header could not be read */
|
||||
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;
|
||||
@@ -55,6 +68,10 @@ typedef struct {
|
||||
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 {
|
||||
@@ -149,6 +166,10 @@ 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);
|
||||
@@ -200,7 +221,9 @@ typedef struct {
|
||||
char finish_reason[32];
|
||||
long prompt_tokens;
|
||||
long completion_tokens;
|
||||
long cached_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) */
|
||||
@@ -222,8 +245,15 @@ typedef struct {
|
||||
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 */
|
||||
@@ -294,6 +324,25 @@ 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);
|
||||
/* 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. */
|
||||
@@ -311,6 +360,19 @@ 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);
|
||||
@@ -384,6 +446,39 @@ 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);
|
||||
|
||||
Reference in New Issue
Block a user