commit 9e7c6a00ad3350f4729ca4fdd20a230b8961eb87 from: Isaac Meerleo date: Fri Jul 17 22:46:42 2026 UTC Preserve System order for Assistant suffixes commit - d8dc62cf90f48602dfdd56da2caf057b9fa09352 commit + 9e7c6a00ad3350f4729ca4fdd20a230b8961eb87 blob - 3ee3198e4b08ad901aaa426d5ab64bc68e96e2b4 blob + 02d88851f9889a8792817b47330d3e1529848d0d --- regress/claude_subscription/claude_subscription_test.c +++ regress/claude_subscription/claude_subscription_test.c @@ -325,6 +325,62 @@ test_request_tools(void) } 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]; @@ -398,6 +454,7 @@ main(void) 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 @@ -69,6 +69,14 @@ first_user_text(const struct claude_sub_req *r) 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) { @@ -226,7 +234,10 @@ validate_request(const struct claude_sub_req *r) 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); } @@ -306,6 +317,26 @@ emit_message(struct json_out *jo, const struct msg *m, 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) { @@ -369,6 +400,11 @@ claude_sub_build_request(struct buf *out, const struct 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;