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
-2
@@ -5,6 +5,8 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "cst_agent.h"
|
||||
#include "cst_attach.h"
|
||||
#include "cst_mcp.h"
|
||||
#include "cst_tools.h"
|
||||
#include "os_compat.h"
|
||||
@@ -18,6 +20,7 @@
|
||||
|
||||
static int nfail;
|
||||
static char g_tmp[OS_PATH_MAX];
|
||||
static cst_agent_t *g_agent;
|
||||
|
||||
#define CHECK(cond, msg) \
|
||||
do { \
|
||||
@@ -99,6 +102,7 @@ static int server(void) {
|
||||
cJSON_AddItemToArray(tools, tool("mixed", "Mixed content."));
|
||||
cJSON_AddItemToArray(tools, tool("hang", "Never answers."));
|
||||
cJSON_AddItemToArray(tools, tool("crash", "Exits."));
|
||||
cJSON_AddItemToArray(tools, tool("pic", "Returns images."));
|
||||
}
|
||||
reply(id, r);
|
||||
} else if (strcmp(mt, "tools/call") == 0) {
|
||||
@@ -136,6 +140,21 @@ static int server(void) {
|
||||
cJSON_AddStringToObject(rr, "text", "resource text");
|
||||
cJSON_AddItemToArray(c, res);
|
||||
reply(id, r);
|
||||
} else if (strcmp(name, "pic") == 0) {
|
||||
cJSON *r = text_result("a chart", 0);
|
||||
cJSON *c = cJSON_GetObjectItemCaseSensitive(r, "content");
|
||||
const char *datas[] = {
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAC"
|
||||
"hwGA60e6kgAAAABJRU5ErkJggg==",
|
||||
"AAAA"};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
cJSON *img = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(img, "type", "image");
|
||||
cJSON_AddStringToObject(img, "mimeType", "image/png");
|
||||
cJSON_AddStringToObject(img, "data", datas[i]);
|
||||
cJSON_AddItemToArray(c, img);
|
||||
}
|
||||
reply(id, r);
|
||||
} else if (strcmp(name, "crash") == 0) {
|
||||
fprintf(stderr, "fatal: fake crash\n");
|
||||
exit(3);
|
||||
@@ -163,6 +182,7 @@ static int call(const char *tool, const char *k, const char *v,
|
||||
cst_tool_ctx_t ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.workspace_root = g_tmp;
|
||||
ctx.agent = g_agent;
|
||||
memset(r, 0, sizeof(*r));
|
||||
return cst_tool_run(&ctx, &c, r);
|
||||
}
|
||||
@@ -217,8 +237,8 @@ int main(int argc, char **argv) {
|
||||
cst_mcp_info_t mi[CST_MCP_MAX];
|
||||
int n = cst_mcp_info(mi, CST_MCP_MAX);
|
||||
const cst_mcp_info_t *f = info_of("fake", mi, n);
|
||||
CHECK(f && f->state == CST_MCP_READY && f->ntools == 6,
|
||||
"fake ready with 6 tools over two pages");
|
||||
CHECK(f && f->state == CST_MCP_READY && f->ntools == 7,
|
||||
"fake ready with 7 tools over two pages");
|
||||
const cst_mcp_info_t *rm = info_of("remote", mi, n);
|
||||
CHECK(rm && rm->state == CST_MCP_FAILED && strstr(rm->error, "stdio"),
|
||||
"url servers unsupported");
|
||||
@@ -255,8 +275,40 @@ int main(int argc, char **argv) {
|
||||
CHECK(r.exit_code == 0 && r.out && strstr(r.out, "first\n[image image/png") &&
|
||||
strstr(r.out, "resource text"),
|
||||
"content items mapped to text");
|
||||
CHECK(r.nattach == 0, "no session: images stay text");
|
||||
cst_tool_result_free(&r);
|
||||
|
||||
cst_agent_config_t acfg;
|
||||
memset(&acfg, 0, sizeof(acfg));
|
||||
snprintf(acfg.workspace_root, sizeof(acfg.workspace_root), "%s", g_tmp);
|
||||
char sdir[1024];
|
||||
snprintf(sdir, sizeof(sdir), "%.900s/cst-mcp-test-%d", g_tmp, (int)getpid());
|
||||
os_mkdir_p(sdir, 0755);
|
||||
snprintf(acfg.session_path, sizeof(acfg.session_path), "%s/s.jsonl", sdir);
|
||||
g_agent = cst_agent_create(&acfg);
|
||||
CHECK(g_agent != NULL, "agent for the session store");
|
||||
call("mcp__fake__pic", NULL, NULL, &r);
|
||||
CHECK(r.exit_code == 0 && r.nattach == 1 && r.attach &&
|
||||
strcmp(r.attach[0].mime, "image/png") == 0 &&
|
||||
strcmp(r.attach[0].origin, "mcp") == 0 &&
|
||||
r.attach[0].width == 1 && r.attach[0].height == 1,
|
||||
"image content stored and attached");
|
||||
CHECK(r.out && strstr(r.out, "a chart\n[image ") &&
|
||||
strstr(r.out, "shown to you as an image") &&
|
||||
strstr(r.out, "not shown:"),
|
||||
"one line per image, bad data falls back to text");
|
||||
if (r.nattach == 1) {
|
||||
char full[OS_PATH_MAX];
|
||||
CHECK(cst_attach_full_path(acfg.session_path, &r.attach[0], full,
|
||||
sizeof(full)) == 0 &&
|
||||
access(full, R_OK) == 0,
|
||||
"stored file exists");
|
||||
}
|
||||
cst_tool_result_free(&r);
|
||||
cst_agent_destroy(g_agent);
|
||||
g_agent = NULL;
|
||||
test_rmrf(sdir);
|
||||
|
||||
volatile int cancel = 1;
|
||||
os_set_cancel_flag(&cancel);
|
||||
double t0 = os_now_ms();
|
||||
|
||||
Reference in New Issue
Block a user