commit - d8dc62cf90f48602dfdd56da2caf057b9fa09352
commit + 9e7c6a00ad3350f4729ca4fdd20a230b8961eb87
blob - 3ee3198e4b08ad901aaa426d5ab64bc68e96e2b4
blob + 02d88851f9889a8792817b47330d3e1529848d0d
--- regress/claude_subscription/claude_subscription_test.c
+++ regress/claude_subscription/claude_subscription_test.c
}
static void
+test_system_before_tool_result(void)
+{
+ struct claude_sub_req req;
+ struct msglist conv;
+ struct msg *m;
+ struct buf out;
+ struct json j;
+ char *role, *text;
+ size_t len;
+ int block, content, message, messages;
+
+ TAILQ_INIT(&conv);
+ m = msg_new(ROLE_ASSISTANT);
+ msg_add_tool_use(m, "tu_1", "read", "{}", 2);
+ TAILQ_INSERT_TAIL(&conv, m, entry);
+ m = msg_new(ROLE_USER);
+ msg_add_tool_result(m, "tu_1", "body", 4, 0);
+ TAILQ_INSERT_TAIL(&conv, m, entry);
+ memset(&req, 0, sizeof(req));
+ req.model = "claude-sonnet-4-6";
+ req.max_tokens = 4096;
+ req.system = "stored-system";
+ req.systemlen = strlen(req.system);
+ req.conv = &conv;
+ req.cache_tail_msg = -1;
+ buf_init(&out);
+ CHECK(claude_sub_build_request(&out, &req) == 0);
+ CHECK(json_parse(&j, out.data, out.len, 0) == 0);
+ messages = json_obj_get(&j, 0, "messages");
+ CHECK(json_arr_len(&j, messages) == 3);
+ message = json_arr_get(&j, messages, 0);
+ role = json_string(&j, message, "role", &len);
+ CHECK(role != NULL && len == 4 && memcmp(role, "user", 4) == 0);
+ free(role);
+ content = json_obj_get(&j, message, "content");
+ CHECK(json_arr_len(&j, content) == 1);
+ block = json_arr_get(&j, content, 0);
+ text = json_string(&j, block, "text", &len);
+ CHECK(text != NULL && len == req.systemlen &&
+ memcmp(text, req.system, len) == 0);
+ free(text);
+ message = json_arr_get(&j, messages, 1);
+ role = json_string(&j, message, "role", &len);
+ CHECK(role != NULL && len == 9 && memcmp(role, "assistant", 9) == 0);
+ free(role);
+ message = json_arr_get(&j, messages, 2);
+ role = json_string(&j, message, "role", &len);
+ CHECK(role != NULL && len == 4 && memcmp(role, "user", 4) == 0);
+ free(role);
+ CHECK(has(&out, "\"tool_use_id\":\"tu_1\""));
+ json_done(&j);
+ buf_free(&out);
+ msglist_free(&conv);
+}
+
+static void
test_headers(void)
{
char headers[8192];
test_tool_mapping();
test_duplicate_tools();
test_request_tools();
+ test_system_before_tool_result();
test_headers();
test_unicode_billing();
blob - 93cd88cd099465bc503179dd8e3792574bfa30d6
blob + 99546e3596e803a5ba3906839e1ea18f3a468428
--- src/common/claude_sub_req.c
+++ src/common/claude_sub_req.c
return (NULL);
}
+static const struct msg *
+first_message(const struct claude_sub_req *r)
+{
+ const struct msg *m = TAILQ_FIRST(r->conv);
+
+ return (m != NULL ? m : r->suffix);
+}
+
static void
digest_prefix(const void *data, size_t len, char *out, size_t n)
{
claude_sub_tool_map(mapped, sizeof(mapped),
b->tool_name) == -1)
return (-1);
- if (r->systemlen != 0 && first_user_text(r) == NULL)
+ /* System instructions need at least one canonical message to precede or
+ * carry them. A leading Assistant receives a wire-only User carrier;
+ * otherwise the first User content array remains the relocation target. */
+ if (r->systemlen != 0 && first_message(r) == NULL)
return (-1);
return (0);
}
json_obj_end(jo);
}
+static void
+emit_system_carrier(struct json_out *jo, const struct claude_sub_req *r)
+{
+ json_obj_begin(jo);
+ json_key(jo, "role");
+ json_cstr(jo, "user");
+ json_key(jo, "content");
+ json_arr_begin(jo);
+ json_obj_begin(jo);
+ json_key(jo, "type");
+ json_cstr(jo, "text");
+ json_key(jo, "text");
+ json_str(jo, r->system, r->systemlen);
+ if (r->cache)
+ emit_cache_control(jo);
+ json_obj_end(jo);
+ json_arr_end(jo);
+ json_obj_end(jo);
+}
+
int
claude_sub_build_request(struct buf *out, const struct claude_sub_req *r)
{
json_arr_begin(&jo);
mi = 0;
moved = 0;
+ m = first_message(r);
+ if (r->systemlen != 0 && m != NULL && m->role == ROLE_ASSISTANT) {
+ emit_system_carrier(&jo, r);
+ moved = 1;
+ }
TAILQ_FOREACH(m, r->conv, entry) {
int prepend;