v0.7.5: arrow keys, image request fix, deepseek-flash vision, musl CI
TUI keys: register both CSI and SS3 forms of the cursor/Home/End keys and
the xterm modifier sequences when terminfo lacks them, so an arrow sent in
the "other" form no longer turns into Esc + a typed letter. Up/Down move by
visual (soft-wrapped) row, and the draft survives a trip through history.
scripts/tui_keys drives the TUI in a pty to check all of this.
Images: text inside a multimodal content array is now a {"type":"text"}
part; DeepSeek rejected the bare string with HTTP 422. deepseek-flash
(and its deepseek-chat / deepseek-reasoner aliases) read images, so the
registry marks them vision; deepseek-v4-pro stays text only.
CI: models.c lost _DEFAULT_SOURCE in 0.7.0 and gmtime_r broke the musl
build; tests sleep with poll() instead of usleep for the same reason.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#include "cJSON.h"
|
||||
#include "cst_agent.h"
|
||||
#include "cst_attach.h"
|
||||
#include "cst_test_util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -325,8 +327,88 @@ static void test_images_off(void) {
|
||||
cJSON_Delete(j);
|
||||
}
|
||||
|
||||
/* Every element of every content array must be a typed part object. */
|
||||
static int parts_typed(const cJSON *root) {
|
||||
const cJSON *m;
|
||||
cJSON_ArrayForEach(m, cJSON_GetObjectItemCaseSensitive(root, "messages")) {
|
||||
const cJSON *c = cJSON_GetObjectItemCaseSensitive(m, "content");
|
||||
if (!cJSON_IsArray(c))
|
||||
continue;
|
||||
const cJSON *p;
|
||||
cJSON_ArrayForEach(p, c) {
|
||||
const cJSON *t = cJSON_GetObjectItemCaseSensitive(p, "type");
|
||||
if (!cJSON_IsObject(p) || !cJSON_IsString(t))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void test_images_on(void) {
|
||||
char dir[1024], sess[1100], err[256];
|
||||
if (test_tmpdir(dir, sizeof(dir), "cst-body") != 0) {
|
||||
EXPECT(0, "scratch dir");
|
||||
return;
|
||||
}
|
||||
snprintf(sess, sizeof(sess), "%s/s.jsonl", dir);
|
||||
static const unsigned char png[32] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A,
|
||||
'\n', 0, 0, 0, 13, 'I', 'H', 'D', 'R',
|
||||
0, 0, 0, 2, 0, 0, 0, 2};
|
||||
cst_attachment_t *img = cst_attach_import_bytes(sess, "shot.png",
|
||||
(const char *)png,
|
||||
sizeof(png), err, sizeof(err));
|
||||
EXPECT(img != NULL, "image stored");
|
||||
if (!img)
|
||||
return;
|
||||
cst_attachment_t gone = *img;
|
||||
snprintf(gone.rel, sizeof(gone.rel), "missing.png");
|
||||
cst_message_t m[5];
|
||||
m[0] = msg(CST_ROLE_USER, "what is this");
|
||||
m[0].attach = img;
|
||||
m[0].nattach = 1;
|
||||
m[1] = msg(CST_ROLE_ASSISTANT, "");
|
||||
m[1].tool_calls_json = "[{\"id\":\"c1\",\"type\":\"function\",\"function\":"
|
||||
"{\"name\":\"view_image\",\"arguments\":\"{}\"}}]";
|
||||
m[2] = msg(CST_ROLE_TOOL, "view_image shot.png");
|
||||
snprintf(m[2].tool_call_id, sizeof(m[2].tool_call_id), "c1");
|
||||
m[2].attach = img;
|
||||
m[2].nattach = 1;
|
||||
m[3] = msg(CST_ROLE_USER, "and now?");
|
||||
m[4] = msg(CST_ROLE_USER, "");
|
||||
m[4].attach = &gone;
|
||||
m[4].nattach = 1;
|
||||
cst_llm_request_t r;
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.model = "deepseek-flash";
|
||||
r.msgs = m;
|
||||
r.nmsgs = 5;
|
||||
r.session_path = sess;
|
||||
cJSON *j = build_req(&r);
|
||||
EXPECT(parts_typed(j), "content arrays hold only typed parts");
|
||||
const cJSON *p0 = cJSON_GetObjectItemCaseSensitive(
|
||||
cJSON_GetArrayItem(cJSON_GetObjectItemCaseSensitive(j, "messages"), 0),
|
||||
"content");
|
||||
const cJSON *t0 = cJSON_GetObjectItemCaseSensitive(
|
||||
cJSON_GetArrayItem(p0, 0), "text");
|
||||
const cJSON *u1 = cJSON_GetObjectItemCaseSensitive(
|
||||
cJSON_GetObjectItemCaseSensitive(cJSON_GetArrayItem(p0, 1), "image_url"),
|
||||
"url");
|
||||
EXPECT(cJSON_IsString(t0) && strcmp(t0->valuestring, "what is this") == 0,
|
||||
"user text is a text part");
|
||||
EXPECT(cJSON_IsString(u1) &&
|
||||
strncmp(u1->valuestring, "data:image/png;base64,", 22) == 0,
|
||||
"image is a data URL part");
|
||||
char *s = cJSON_PrintUnformatted(j);
|
||||
EXPECT(s && strstr(s, "and now?") && strstr(s, "not sent"),
|
||||
"follow-up text and the missing-image note are parts too");
|
||||
free(s);
|
||||
cJSON_Delete(j);
|
||||
cst_attach_free(img);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_images_off();
|
||||
test_images_on();
|
||||
test_reasoning_and_prefix();
|
||||
test_cache_hints();
|
||||
test_tools_snapshot();
|
||||
|
||||
Reference in New Issue
Block a user