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:
+54
-1
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "cst_agent.h"
|
||||
#include "cst_attach.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
@@ -97,7 +98,11 @@ static void msg_free(cst_message_t *m) {
|
||||
free(m->content);
|
||||
free(m->tool_calls_json);
|
||||
free(m->meta_json);
|
||||
m->content = m->tool_calls_json = m->meta_json = NULL;
|
||||
free(m->reasoning);
|
||||
cst_attachments_free(m->attach, m->nattach);
|
||||
m->content = m->tool_calls_json = m->meta_json = m->reasoning = NULL;
|
||||
m->attach = NULL;
|
||||
m->nattach = 0;
|
||||
}
|
||||
|
||||
static int session_grow(cst_session_t *s) {
|
||||
@@ -127,6 +132,16 @@ static int session_push_mem(cst_session_t *s, const cst_message_t *src) {
|
||||
d->tool_calls_json = xstrdup(src->tool_calls_json);
|
||||
if (src->meta_json && src->meta_json[0])
|
||||
d->meta_json = xstrdup(src->meta_json);
|
||||
if (src->reasoning && src->reasoning[0])
|
||||
d->reasoning = xstrdup(src->reasoning);
|
||||
if (src->attach && src->nattach > 0) {
|
||||
d->attach = cst_attachments_dup(src->attach, src->nattach);
|
||||
if (!d->attach) {
|
||||
msg_free(d);
|
||||
return -1;
|
||||
}
|
||||
d->nattach = src->nattach;
|
||||
}
|
||||
s->nmsgs++;
|
||||
return 0;
|
||||
}
|
||||
@@ -174,9 +189,14 @@ static void load_existing(cst_session_t *s) {
|
||||
if (cJSON_IsObject(mo))
|
||||
meta = cJSON_PrintUnformatted(mo);
|
||||
m.meta_json = meta;
|
||||
m.reasoning = (char *)jstr(o, "reasoning");
|
||||
const cJSON *at = cJSON_GetObjectItemCaseSensitive(o, "attachments");
|
||||
if (cJSON_IsArray(at))
|
||||
m.attach = cst_attachments_from_json(at, &m.nattach);
|
||||
session_push_mem(s, &m);
|
||||
free(tcj);
|
||||
free(meta);
|
||||
cst_attachments_free(m.attach, m.nattach);
|
||||
cJSON_Delete(o);
|
||||
}
|
||||
free(line);
|
||||
@@ -249,6 +269,8 @@ static int write_line(FILE *fp, const cst_message_t *msg) {
|
||||
cJSON_AddStringToObject(o, "tool_calls_json", msg->tool_calls_json);
|
||||
}
|
||||
}
|
||||
if (msg->reasoning && msg->reasoning[0])
|
||||
cJSON_AddStringToObject(o, "reasoning", msg->reasoning);
|
||||
if (msg->meta_json && msg->meta_json[0]) {
|
||||
cJSON *mo = cJSON_Parse(msg->meta_json);
|
||||
if (cJSON_IsObject(mo))
|
||||
@@ -256,6 +278,28 @@ static int write_line(FILE *fp, const cst_message_t *msg) {
|
||||
else
|
||||
cJSON_Delete(mo);
|
||||
}
|
||||
if (msg->attach && msg->nattach > 0) {
|
||||
cJSON *arr = cJSON_CreateArray();
|
||||
for (int i = 0; i < msg->nattach; i++) {
|
||||
const cst_attachment_t *a = &msg->attach[i];
|
||||
cJSON *it = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(it, "id", a->id);
|
||||
cJSON_AddStringToObject(it, "name", a->name);
|
||||
cJSON_AddStringToObject(it, "mime", a->mime);
|
||||
cJSON_AddStringToObject(it, "rel", a->rel);
|
||||
cJSON_AddNumberToObject(it, "bytes", (double)a->bytes);
|
||||
if (a->width > 0 && a->height > 0) {
|
||||
cJSON_AddNumberToObject(it, "width", a->width);
|
||||
cJSON_AddNumberToObject(it, "height", a->height);
|
||||
}
|
||||
if (a->detail[0])
|
||||
cJSON_AddStringToObject(it, "detail", a->detail);
|
||||
if (a->origin[0])
|
||||
cJSON_AddStringToObject(it, "origin", a->origin);
|
||||
cJSON_AddItemToArray(arr, it);
|
||||
}
|
||||
cJSON_AddItemToObject(o, "attachments", arr);
|
||||
}
|
||||
char *txt = cJSON_PrintUnformatted(o);
|
||||
cJSON_Delete(o);
|
||||
if (!txt)
|
||||
@@ -310,6 +354,7 @@ int cst_session_clear(cst_session_t *s, int keep_system) {
|
||||
if (s->msgs[i].role == CST_ROLE_SYSTEM) {
|
||||
sys.role = CST_ROLE_SYSTEM;
|
||||
sys.content = xstrdup(s->msgs[i].content ? s->msgs[i].content : "");
|
||||
sys.meta_json = xstrdup(s->msgs[i].meta_json);
|
||||
have_sys = sys.content != NULL;
|
||||
break;
|
||||
}
|
||||
@@ -322,6 +367,7 @@ int cst_session_clear(cst_session_t *s, int keep_system) {
|
||||
write_line(s->fp, &sys);
|
||||
}
|
||||
free(sys.content);
|
||||
free(sys.meta_json);
|
||||
pthread_mutex_unlock(&s->mu);
|
||||
return rc;
|
||||
}
|
||||
@@ -339,6 +385,11 @@ int cst_session_rewrite(cst_session_t *s, const cst_message_t *msgs, int n) {
|
||||
copy[i].tool_calls_json =
|
||||
msgs[i].tool_calls_json ? strdup(msgs[i].tool_calls_json) : NULL;
|
||||
copy[i].meta_json = msgs[i].meta_json ? strdup(msgs[i].meta_json) : NULL;
|
||||
copy[i].reasoning = msgs[i].reasoning ? strdup(msgs[i].reasoning) : NULL;
|
||||
copy[i].attach = msgs[i].attach && msgs[i].nattach > 0
|
||||
? cst_attachments_dup(msgs[i].attach, msgs[i].nattach)
|
||||
: NULL;
|
||||
copy[i].nattach = copy[i].attach ? msgs[i].nattach : 0;
|
||||
}
|
||||
pthread_mutex_lock(&s->mu);
|
||||
free_all(s);
|
||||
@@ -352,6 +403,8 @@ int cst_session_rewrite(cst_session_t *s, const cst_message_t *msgs, int n) {
|
||||
free(copy[i].content);
|
||||
free(copy[i].tool_calls_json);
|
||||
free(copy[i].meta_json);
|
||||
free(copy[i].reasoning);
|
||||
cst_attachments_free(copy[i].attach, copy[i].nattach);
|
||||
}
|
||||
free(copy);
|
||||
return rc;
|
||||
|
||||
Reference in New Issue
Block a user