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:
+82
-4
@@ -1,5 +1,6 @@
|
||||
#include "cst_agent.h"
|
||||
#include "cst_ext.h"
|
||||
#include "cst_models.h"
|
||||
#include "cst_tools.h"
|
||||
#include "cst_version.h"
|
||||
#include "os_compat.h"
|
||||
@@ -29,6 +30,8 @@ static void usage(const char *argv0) {
|
||||
" -N, --new start a new session\n"
|
||||
" -R, --resume ID open a session by id or unique prefix\n"
|
||||
" -L, --list-sessions list sessions and exit\n"
|
||||
" --list-models [P] what is known about each provider's models\n"
|
||||
" (built-in, <config>/models.json, GET /models)\n"
|
||||
" -A, --approval MODE ask | auto (default: edits run, shell in\n"
|
||||
" the sandbox or by safe rules) | yolo\n"
|
||||
" -y, --yolo same as --approval yolo (this run only)\n"
|
||||
@@ -81,6 +84,62 @@ static int parse_confirm(const char *s, cst_agent_config_t *cfg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void list_one_model(const char *provider, const char *id,
|
||||
const char *where) {
|
||||
cst_model_info_t mi;
|
||||
cst_model_info(provider, id, &mi);
|
||||
char tag[48], price[128] = "";
|
||||
cst_model_info_label(&mi, tag, sizeof(tag));
|
||||
if (mi.has_price)
|
||||
snprintf(price, sizeof(price), " %s%g/%g/%g per 1M (miss/hit/out)",
|
||||
mi.price.currency, mi.price.miss, mi.price.hit, mi.price.out);
|
||||
printf(" %-40s %-18s %-8s%s [%s]\n", id, tag, where, price, mi.source);
|
||||
}
|
||||
|
||||
/* --list-models [provider]: the merged registry, to check models.json. */
|
||||
static int list_models(const char *only) {
|
||||
char user[1100] = "";
|
||||
cst_models_user_path(user, sizeof(user));
|
||||
FILE *uf = user[0] ? fopen(user, "rb") : NULL;
|
||||
printf("user file: %s%s\n", user[0] ? user : "(no config dir)",
|
||||
uf ? "" : " (none)");
|
||||
if (uf)
|
||||
fclose(uf);
|
||||
if (cst_models_load_error()[0])
|
||||
printf("warning: %s\n", cst_models_load_error());
|
||||
int shown = 0;
|
||||
static cst_model_meta_t cached[1024];
|
||||
for (int i = 0; i < cst_provider_count(); i++) {
|
||||
const cst_provider_t *p = cst_provider_at(i);
|
||||
if (only && only[0] && strcmp(p->name, only) != 0)
|
||||
continue;
|
||||
shown++;
|
||||
printf("\n%s %s (%s; key %s)\n", p->name, p->api_base,
|
||||
cst_provider_source(p), p->key_env[0] ? p->key_env : "-");
|
||||
char listed[16][128];
|
||||
int nl = cst_provider_models(p, listed, 16);
|
||||
for (int k = 0; k < nl; k++)
|
||||
list_one_model(p->name, listed[k],
|
||||
strcmp(listed[k], p->model) == 0 ? "default" : "listed");
|
||||
long age = 0;
|
||||
int nc = cst_models_cache_load(p->name, cached, 1024, &age);
|
||||
if (nc > 0) {
|
||||
printf(" -- %d from GET /models, fetched %ldh ago%s\n", nc,
|
||||
age / 3600, age >= CST_MODELS_CACHE_TTL ? " (stale)" : "");
|
||||
for (int k = 0; k < nc; k++)
|
||||
list_one_model(p->name, cached[k].id, "api");
|
||||
} else if (!nl) {
|
||||
printf(" (no models listed; the first one GET /models returns is "
|
||||
"used)\n");
|
||||
}
|
||||
}
|
||||
if (only && only[0] && !shown) {
|
||||
fprintf(stderr, "unknown provider: %s\n", only);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int list_sessions(const char *ws) {
|
||||
static char ids[200][128];
|
||||
int n = cst_agent_store_list(ws, ids, 200);
|
||||
@@ -286,6 +345,7 @@ int main(int argc, char **argv) {
|
||||
{"effort", required_argument, 0, 'e'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"version", no_argument, 0, 'V'},
|
||||
{"list-models", optional_argument, 0, 0x100},
|
||||
{0, 0, 0, 0}};
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "w:s:b:k:m:P:r:n:T:Hp:dahVNcR:LyA:C:e:", opts, NULL)) !=
|
||||
@@ -385,6 +445,14 @@ int main(int argc, char **argv) {
|
||||
usage(argv[0]);
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
case 0x100: {
|
||||
const char *only = optarg;
|
||||
if (!only && optind < argc && argv[optind][0] != '-')
|
||||
only = argv[optind++];
|
||||
int rc = list_models(only);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
case 'V':
|
||||
printf("cst-agent %s\n", CST_VERSION);
|
||||
curl_global_cleanup();
|
||||
@@ -543,10 +611,20 @@ int main(int argc, char **argv) {
|
||||
/* Router presets have no fixed model: take the first one the API lists
|
||||
* (the TUI does the same after its /models probe). */
|
||||
if (!cfg.model[0] && cfg.headless) {
|
||||
char ids[1][128], err[256] = "";
|
||||
if (cst_llm_list_models(cfg.api_base, cfg.api_key, ids, 1, err,
|
||||
sizeof(err)) > 0) {
|
||||
snprintf(cfg.model, sizeof(cfg.model), "%s", ids[0]);
|
||||
static cst_model_meta_t list[512];
|
||||
char err[256] = "";
|
||||
long age = 0;
|
||||
int n = cst_models_cache_load(cfg.provider, list, 1, &age);
|
||||
if (n <= 0 || age >= CST_MODELS_CACHE_TTL) {
|
||||
int r = cst_llm_list_models_meta(cfg.api_base, cfg.api_key, list, 512,
|
||||
err, sizeof(err));
|
||||
if (r >= 0)
|
||||
cst_models_cache_save(cfg.provider, cfg.api_base, list, r);
|
||||
if (r >= 0 || n <= 0)
|
||||
n = r;
|
||||
}
|
||||
if (n > 0) {
|
||||
snprintf(cfg.model, sizeof(cfg.model), "%s", list[0].id);
|
||||
} else {
|
||||
fprintf(stderr, "no model for %s: pass --model (%s)\n", cfg.provider,
|
||||
err[0] ? err : "the API listed none");
|
||||
|
||||
Reference in New Issue
Block a user