Commit Diff


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;