commit - 83f2c983cb279c2381c039c3947997ebde1ce2e1
commit + 5ba13c5d7937caa643a04594bd9be7ec3f26a4bc
blob - 8b519d83481110bc2d8732230afc0ad6207089ef
blob + 2ff2caa01bbf79cd0afb7e9e32e39d4fd23b33a5
--- regress/compaction/compaction_test.c
+++ regress/compaction/compaction_test.c
msglist_free(&messages);
}
+static void
+test_file_collection_and_rendering(void)
+{
+ static const char expected[] =
+ "model continuation notes\n\n"
+ "<read-files>\n"
+ "a.c\n"
+ "</read-files>\n\n"
+ "<modified-files>\n"
+ "m.c\n"
+ "z.c\n"
+ "</modified-files>";
+ struct compaction_files *files;
+ struct msglist messages;
+ struct msg *message;
+ struct buf out;
+ const void *path;
+ size_t pathlen;
+
+ TAILQ_INIT(&messages);
+ message = msg_new(ROLE_ASSISTANT);
+ msg_add_tool_use(message, "1", "read", "{\"path\":\"z.c\"}",
+ strlen("{\"path\":\"z.c\"}"));
+ msg_add_tool_use(message, "2", "read", "{\"path\":\"a.c\"}",
+ strlen("{\"path\":\"a.c\"}"));
+ msg_add_tool_use(message, "3", "read", "{\"path\":\"z.c\"}",
+ strlen("{\"path\":\"z.c\"}"));
+ msg_add_tool_use(message, "4", "edit", "{\"path\":\"z.c\"}",
+ strlen("{\"path\":\"z.c\"}"));
+ msg_add_tool_use(message, "5", "write", "{\"path\":\"m.c\"}",
+ strlen("{\"path\":\"m.c\"}"));
+ msg_add_tool_use(message, "6", "multi_edit",
+ "{\"path\":\"m.c\"}", strlen("{\"path\":\"m.c\"}"));
+ msg_add_tool_use(message, "7", "shell",
+ "{\"path\":\"never.c\"}", strlen("{\"path\":\"never.c\"}"));
+ append_message(&messages, message);
+ message = msg_new(ROLE_USER);
+ msg_add_tool_result(message, "5", "write failed", 12, 1);
+ append_message(&messages, message);
+
+ files = compaction_files_new();
+ CHECK(compaction_files_collect(files, &messages) == 0);
+ CHECK(compaction_files_count(files, COMPACTION_FILE_READ) == 1);
+ CHECK(compaction_files_count(files, COMPACTION_FILE_MODIFIED) == 2);
+ CHECK(compaction_files_get(files, COMPACTION_FILE_READ, 0, &path,
+ &pathlen) == 0);
+ CHECK(pathlen == 3 && memcmp(path, "a.c", 3) == 0);
+ CHECK(compaction_files_get(files, COMPACTION_FILE_MODIFIED, 1, &path,
+ &pathlen) == 0);
+ CHECK(pathlen == 3 && memcmp(path, "z.c", 3) == 0);
+
+ buf_init(&out);
+ buf_addstr(&out, "model continuation notes");
+ CHECK(compaction_files_append(&out, files, SIZE_MAX) == 0);
+ CHECK(buf_is(&out, expected, sizeof(expected) - 1));
+
+ buf_free(&out);
+ compaction_files_free(files);
+ msglist_free(&messages);
+}
+
+static void
+test_file_cumulative_merge(void)
+{
+ static const char expected[] =
+ "<read-files>\n"
+ "new.c\n"
+ "old.c\n"
+ "</read-files>\n\n"
+ "<modified-files>\n"
+ "prior-mod.c\n"
+ "promote.c\n"
+ "</modified-files>";
+ struct compaction_files *current, *previous;
+ struct buf out;
+
+ previous = compaction_files_new();
+ CHECK(compaction_files_add(previous, COMPACTION_FILE_READ,
+ "old.c", 5) == 0);
+ CHECK(compaction_files_add(previous, COMPACTION_FILE_READ,
+ "promote.c", 9) == 0);
+ CHECK(compaction_files_add(previous, COMPACTION_FILE_MODIFIED,
+ "prior-mod.c", 11) == 0);
+ current = compaction_files_new();
+ CHECK(compaction_files_add(current, COMPACTION_FILE_READ,
+ "new.c", 5) == 0);
+ CHECK(compaction_files_add(current, COMPACTION_FILE_READ,
+ "prior-mod.c", 11) == 0);
+ CHECK(compaction_files_add(current, COMPACTION_FILE_MODIFIED,
+ "promote.c", 9) == 0);
+
+ CHECK(compaction_files_merge(current, previous) == 0);
+ CHECK(compaction_files_count(current, COMPACTION_FILE_READ) == 2);
+ CHECK(compaction_files_count(current, COMPACTION_FILE_MODIFIED) == 2);
+ /* Merge never mutates the prior fugu-generated details. */
+ CHECK(compaction_files_count(previous, COMPACTION_FILE_READ) == 2);
+ CHECK(compaction_files_count(previous, COMPACTION_FILE_MODIFIED) == 1);
+
+ buf_init(&out);
+ CHECK(compaction_files_append(&out, current, SIZE_MAX) == 0);
+ CHECK(buf_is(&out, expected, sizeof(expected) - 1));
+
+ buf_free(&out);
+ compaction_files_free(current);
+ compaction_files_free(previous);
+}
+
+static void
+test_file_safety_and_transactions(void)
+{
+ static const char expected[] =
+ "notes\n\n"
+ "<read-files>\n"
+ "keep.c\n"
+ "</read-files>\n\n"
+ "<modified-files>\n"
+ "a<&>.c\n"
+ "</modified-files>";
+ static const char empty_expected[] =
+ "<read-files>\n"
+ "</read-files>\n\n"
+ "<modified-files>\n"
+ "</modified-files>";
+ static const char malformed_utf8[] = {
+ '{', '"', 'p', 'a', 't', 'h', '"', ':', '"', 'x',
+ (char)0xc0, (char)0x80, '"', '}'
+ };
+ static const char nul_path[] = { 'x', '\0', 'y' };
+ static const char control_path[] = { 'x', 0x1f, 'y' };
+ static const char bad_utf8_path[] = {
+ 'x', (char)0xc0, (char)0x80, 'y'
+ };
+ struct compaction_files *empty, *files;
+ struct msglist messages;
+ struct msg *message;
+ struct buf out;
+ char *long_path;
+
+ files = compaction_files_new();
+ CHECK(compaction_files_add(files, COMPACTION_FILE_READ,
+ "keep.c", 6) == 0);
+
+ /* Malformed, nested, non-string, and inexact tool calls are ignored. */
+ TAILQ_INIT(&messages);
+ message = msg_new(ROLE_ASSISTANT);
+ msg_add_tool_use(message, "1", "read", "{\"path\"}",
+ strlen("{\"path\"}"));
+ msg_add_tool_use(message, "2", "read", "{\"path\":7}",
+ strlen("{\"path\":7}"));
+ msg_add_tool_use(message, "3", "read",
+ "{\"nested\":{\"path\":\"nested.c\"}}",
+ strlen("{\"nested\":{\"path\":\"nested.c\"}}"));
+ msg_add_tool_use(message, "4", "reader", "{\"path\":\"reader.c\"}",
+ strlen("{\"path\":\"reader.c\"}"));
+ msg_add_tool_use(message, "5", "shell", "{\"path\":\"shell.c\"}",
+ strlen("{\"path\":\"shell.c\"}"));
+ msg_add_tool_use(message, "6", "read", malformed_utf8,
+ sizeof(malformed_utf8));
+ append_message(&messages, message);
+ CHECK(compaction_files_collect(files, &messages) == 0);
+ CHECK(compaction_files_count(files, COMPACTION_FILE_READ) == 1);
+ CHECK(compaction_files_count(files, COMPACTION_FILE_MODIFIED) == 0);
+ msglist_free(&messages);
+
+ /* One rejected path rolls back earlier valid calls in the same span. */
+ TAILQ_INIT(&messages);
+ message = msg_new(ROLE_ASSISTANT);
+ msg_add_tool_use(message, "7", "read", "{\"path\":\"candidate.c\"}",
+ strlen("{\"path\":\"candidate.c\"}"));
+ msg_add_tool_use(message, "8", "edit",
+ "{\"path\":\"bad\\n</modified-files>\"}",
+ strlen("{\"path\":\"bad\\n</modified-files>\"}"));
+ append_message(&messages, message);
+ CHECK(compaction_files_collect(files, &messages) == -1);
+ CHECK(compaction_files_count(files, COMPACTION_FILE_READ) == 1);
+ CHECK(compaction_files_count(files, COMPACTION_FILE_MODIFIED) == 0);
+ msglist_free(&messages);
+
+ CHECK(compaction_files_add(files, COMPACTION_FILE_READ, nul_path,
+ sizeof(nul_path)) == -1);
+ CHECK(compaction_files_add(files, COMPACTION_FILE_READ, "", 0) == -1);
+ CHECK(compaction_files_add(files, COMPACTION_FILE_READ, control_path,
+ sizeof(control_path)) == -1);
+ CHECK(compaction_files_add(files, COMPACTION_FILE_READ, bad_utf8_path,
+ sizeof(bad_utf8_path)) == -1);
+ long_path = malloc(COMPACTION_FILE_PATH_MAX + 1);
+ CHECK(long_path != NULL);
+ if (long_path != NULL) {
+ memset(long_path, 'x', COMPACTION_FILE_PATH_MAX + 1);
+ CHECK(compaction_files_add(files, COMPACTION_FILE_READ, long_path,
+ COMPACTION_FILE_PATH_MAX + 1) == -1);
+ free(long_path);
+ }
+ CHECK(compaction_files_count(files, COMPACTION_FILE_READ) == 1);
+ CHECK(compaction_files_add(files, COMPACTION_FILE_MODIFIED,
+ "a<&>.c", 6) == 0);
+
+ /* A tight output bound cannot partially append generated structure. */
+ buf_init(&out);
+ buf_addstr(&out, "notes");
+ CHECK(compaction_files_append(&out, files, sizeof(expected) - 2) == -1);
+ CHECK(buf_is(&out, "notes", 5));
+ CHECK(compaction_files_append(&out, files, sizeof(expected) - 1) == 0);
+ CHECK(buf_is(&out, expected, sizeof(expected) - 1));
+ buf_free(&out);
+
+ /* Every generated summary gets both blocks, including empty sets. */
+ empty = compaction_files_new();
+ buf_init(&out);
+ CHECK(compaction_files_append(&out, empty,
+ sizeof(empty_expected) - 2) == -1);
+ CHECK(out.len == 0);
+ CHECK(compaction_files_append(&out, empty,
+ sizeof(empty_expected) - 1) == 0);
+ CHECK(buf_is(&out, empty_expected, sizeof(empty_expected) - 1));
+ buf_free(&out);
+ compaction_files_free(empty);
+ compaction_files_free(files);
+}
+
int
main(void)
{
test_result_boundaries();
test_previous_summary();
test_document_bound();
+ test_file_collection_and_rendering();
+ test_file_cumulative_merge();
+ test_file_safety_and_transactions();
REGRESS_END();
}
blob - 055f699049af7bf78409f053534a3cf087ad2171
blob + ef442a546e46c57af45ba8512f9ea51db50de6a0
--- src/common/compaction.c
+++ src/common/compaction.c
#include "json.h"
#include "msg.h"
#include "utf8.h"
+#include "xmalloc.h"
#include "compaction.h"
const char compaction_system_prompt[] =
int records;
};
+struct compaction_file {
+ u_char *path;
+ size_t len;
+};
+
+struct compaction_file_set {
+ struct compaction_file *files;
+ size_t len;
+ size_t cap;
+};
+
+struct compaction_files {
+ struct compaction_file_set read;
+ struct compaction_file_set modified;
+ size_t bytes;
+};
+
+static void
+file_set_free(struct compaction_file_set *set)
+{
+ size_t i;
+
+ for (i = 0; i < set->len; i++)
+ free(set->files[i].path);
+ free(set->files);
+ memset(set, 0, sizeof(*set));
+}
+
+struct compaction_files *
+compaction_files_new(void)
+{
+ return (xcalloc(1, sizeof(struct compaction_files)));
+}
+
+void
+compaction_files_free(struct compaction_files *files)
+{
+ if (files == NULL)
+ return;
+ file_set_free(&files->read);
+ file_set_free(&files->modified);
+ free(files);
+}
+
static int
+path_cmp(const void *ap, size_t alen, const void *bp, size_t blen)
+{
+ size_t len;
+ int cmp;
+
+ len = alen < blen ? alen : blen;
+ cmp = len == 0 ? 0 : memcmp(ap, bp, len);
+ if (cmp != 0)
+ return (cmp);
+ if (alen < blen)
+ return (-1);
+ if (alen > blen)
+ return (1);
+ return (0);
+}
+
+static int
+file_set_find(const struct compaction_file_set *set, const void *path,
+ size_t pathlen, size_t *at)
+{
+ size_t lo, hi, mid;
+ int cmp;
+
+ lo = 0;
+ hi = set->len;
+ while (lo < hi) {
+ mid = lo + (hi - lo) / 2;
+ cmp = path_cmp(path, pathlen, set->files[mid].path,
+ set->files[mid].len);
+ if (cmp == 0) {
+ *at = mid;
+ return (1);
+ }
+ if (cmp < 0)
+ hi = mid;
+ else
+ lo = mid + 1;
+ }
+ *at = lo;
+ return (0);
+}
+
+static void
+file_set_insert(struct compaction_file_set *set, size_t at,
+ const void *path, size_t pathlen)
+{
+ u_char *copy;
+ size_t cap;
+
+ copy = xmalloc(pathlen + 1);
+ if (pathlen > 0)
+ memcpy(copy, path, pathlen);
+ copy[pathlen] = '\0';
+ if (set->len == set->cap) {
+ cap = set->cap == 0 ? 16 : set->cap * 2;
+ if (cap > COMPACTION_FILE_COUNT_MAX)
+ cap = COMPACTION_FILE_COUNT_MAX;
+ set->files = xreallocarray(set->files, cap,
+ sizeof(*set->files));
+ set->cap = cap;
+ }
+ if (at < set->len)
+ memmove(&set->files[at + 1], &set->files[at],
+ (set->len - at) * sizeof(*set->files));
+ set->files[at].path = copy;
+ set->files[at].len = pathlen;
+ set->len++;
+}
+
+static void
+file_set_remove(struct compaction_file_set *set, size_t at)
+{
+ free(set->files[at].path);
+ if (at + 1 < set->len)
+ memmove(&set->files[at], &set->files[at + 1],
+ (set->len - at - 1) * sizeof(*set->files));
+ set->len--;
+}
+
+static int
+path_valid(const void *path, size_t pathlen)
+{
+ const u_char *p = path;
+ uint32_t cp;
+ size_t off, used;
+
+ if (pathlen == 0 || pathlen > COMPACTION_FILE_PATH_MAX || path == NULL)
+ return (0);
+ for (off = 0; off < pathlen; off += used) {
+ used = utf8_decode(p + off, pathlen - off, &cp);
+ if ((cp == UTF8_REPLACEMENT && used == 1) || cp < 0x20 ||
+ (cp >= 0x7f && cp <= 0x9f) || cp == 0x2028 ||
+ cp == 0x2029)
+ return (0);
+ }
+ return (1);
+}
+
+static size_t
+files_count(const struct compaction_files *files)
+{
+ return (files->read.len + files->modified.len);
+}
+
+static int
+files_add(struct compaction_files *files, enum compaction_file_kind kind,
+ const void *path, size_t pathlen)
+{
+ size_t at, read_at;
+
+ if (!path_valid(path, pathlen))
+ return (-1);
+ if (file_set_find(&files->modified, path, pathlen, &at))
+ return (0);
+ if (kind == COMPACTION_FILE_READ) {
+ if (file_set_find(&files->read, path, pathlen, &at))
+ return (0);
+ if (files_count(files) == COMPACTION_FILE_COUNT_MAX ||
+ pathlen > COMPACTION_FILE_BYTES_MAX - files->bytes)
+ return (-1);
+ file_set_insert(&files->read, at, path, pathlen);
+ files->bytes += pathlen;
+ return (0);
+ }
+ if (kind != COMPACTION_FILE_MODIFIED)
+ return (-1);
+ if (file_set_find(&files->read, path, pathlen, &read_at)) {
+ file_set_remove(&files->read, read_at);
+ file_set_find(&files->modified, path, pathlen, &at);
+ file_set_insert(&files->modified, at, path, pathlen);
+ return (0);
+ }
+ if (files_count(files) == COMPACTION_FILE_COUNT_MAX ||
+ pathlen > COMPACTION_FILE_BYTES_MAX - files->bytes)
+ return (-1);
+ file_set_insert(&files->modified, at, path, pathlen);
+ files->bytes += pathlen;
+ return (0);
+}
+
+static struct compaction_files *
+files_clone(const struct compaction_files *src)
+{
+ struct compaction_files *copy;
+ size_t i;
+
+ copy = compaction_files_new();
+ for (i = 0; i < src->read.len; i++)
+ file_set_insert(©->read, copy->read.len,
+ src->read.files[i].path, src->read.files[i].len);
+ for (i = 0; i < src->modified.len; i++)
+ file_set_insert(©->modified, copy->modified.len,
+ src->modified.files[i].path, src->modified.files[i].len);
+ copy->bytes = src->bytes;
+ return (copy);
+}
+
+static void
+files_publish(struct compaction_files *dst, struct compaction_files *src)
+{
+ struct compaction_files old;
+
+ old = *dst;
+ *dst = *src;
+ *src = old;
+ compaction_files_free(src);
+}
+
+int
+compaction_files_add(struct compaction_files *files,
+ enum compaction_file_kind kind, const void *path, size_t pathlen)
+{
+ struct compaction_files *candidate;
+
+ if (files == NULL)
+ return (-1);
+ candidate = files_clone(files);
+ if (files_add(candidate, kind, path, pathlen) == -1) {
+ compaction_files_free(candidate);
+ return (-1);
+ }
+ files_publish(files, candidate);
+ return (0);
+}
+
+static int
+tool_file_kind(const char *name, enum compaction_file_kind *kind)
+{
+ if (name == NULL)
+ return (0);
+ if (strcmp(name, "read") == 0) {
+ *kind = COMPACTION_FILE_READ;
+ return (1);
+ }
+ if (strcmp(name, "write") == 0 || strcmp(name, "edit") == 0 ||
+ strcmp(name, "multi_edit") == 0) {
+ *kind = COMPACTION_FILE_MODIFIED;
+ return (1);
+ }
+ return (0);
+}
+
+static int
+collect_block(struct compaction_files *files, const struct block *block)
+{
+ struct json json;
+ enum compaction_file_kind kind;
+ char *path;
+ size_t pathlen;
+ int path_token;
+
+ if (block->type != BLOCK_TOOL_USE ||
+ !tool_file_kind(block->tool_name, &kind))
+ return (0);
+ if ((block->tool_input_len > 0 && block->tool_input == NULL) ||
+ !json_valid_object(block->tool_input, block->tool_input_len) ||
+ json_parse(&json, block->tool_input, block->tool_input_len, 0) == -1)
+ return (0);
+ path_token = json_obj_get(&json, json_root(&json), "path");
+ if (path_token == -1 || !json_is_string(&json, path_token)) {
+ json_done(&json);
+ return (0);
+ }
+ path = json_get_str(&json, path_token, &pathlen);
+ json_done(&json);
+ if (path == NULL)
+ return (-1);
+ if (files_add(files, kind, path, pathlen) == -1) {
+ free(path);
+ return (-1);
+ }
+ free(path);
+ return (0);
+}
+
+int
+compaction_files_collect(struct compaction_files *files,
+ const struct msglist *messages)
+{
+ const struct msg *message;
+ const struct block *block;
+ struct compaction_files *candidate;
+
+ if (files == NULL || messages == NULL)
+ return (-1);
+ candidate = files_clone(files);
+ TAILQ_FOREACH(message, messages, entry) {
+ if (message->role != ROLE_ASSISTANT)
+ continue;
+ TAILQ_FOREACH(block, &message->blocks, entry) {
+ if (collect_block(candidate, block) == -1) {
+ compaction_files_free(candidate);
+ return (-1);
+ }
+ }
+ }
+ files_publish(files, candidate);
+ return (0);
+}
+
+int
+compaction_files_merge(struct compaction_files *files,
+ const struct compaction_files *previous)
+{
+ struct compaction_files *candidate;
+ size_t i;
+
+ if (files == NULL || previous == NULL)
+ return (-1);
+ candidate = files_clone(files);
+ for (i = 0; i < previous->read.len; i++) {
+ if (files_add(candidate, COMPACTION_FILE_READ,
+ previous->read.files[i].path,
+ previous->read.files[i].len) == -1)
+ goto fail;
+ }
+ for (i = 0; i < previous->modified.len; i++) {
+ if (files_add(candidate, COMPACTION_FILE_MODIFIED,
+ previous->modified.files[i].path,
+ previous->modified.files[i].len) == -1)
+ goto fail;
+ }
+ files_publish(files, candidate);
+ return (0);
+
+fail:
+ compaction_files_free(candidate);
+ return (-1);
+}
+
+size_t
+compaction_files_count(const struct compaction_files *files,
+ enum compaction_file_kind kind)
+{
+ if (files == NULL)
+ return (0);
+ if (kind == COMPACTION_FILE_READ)
+ return (files->read.len);
+ if (kind == COMPACTION_FILE_MODIFIED)
+ return (files->modified.len);
+ return (0);
+}
+
+int
+compaction_files_get(const struct compaction_files *files,
+ enum compaction_file_kind kind, size_t index, const void **path,
+ size_t *pathlen)
+{
+ const struct compaction_file_set *set;
+
+ if (files == NULL || path == NULL || pathlen == NULL)
+ return (-1);
+ if (kind == COMPACTION_FILE_READ)
+ set = &files->read;
+ else if (kind == COMPACTION_FILE_MODIFIED)
+ set = &files->modified;
+ else
+ return (-1);
+ if (index >= set->len)
+ return (-1);
+ *path = set->files[index].path;
+ *pathlen = set->files[index].len;
+ return (0);
+}
+
+static int
writer_add(struct compact_writer *w, const void *data, size_t len)
{
if (w->out->len > w->maxlen || len > w->maxlen - w->out->len)
}
static int
+file_path_add(struct compact_writer *writer,
+ const struct compaction_file *file)
+{
+ size_t i, start;
+
+ start = 0;
+ for (i = 0; i < file->len; i++) {
+ const char *escaped;
+
+ switch (file->path[i]) {
+ case '&':
+ escaped = "&";
+ break;
+ case '<':
+ escaped = "<";
+ break;
+ case '>':
+ escaped = ">";
+ break;
+ default:
+ continue;
+ }
+ if (writer_add(writer, file->path + start, i - start) == -1 ||
+ writer_addstr(writer, escaped) == -1)
+ return (-1);
+ start = i + 1;
+ }
+ return (writer_add(writer, file->path + start, file->len - start));
+}
+
+static int
+file_block_add(struct compact_writer *writer, const char *name,
+ const struct compaction_file_set *set)
+{
+ size_t i;
+
+ if (writer_addstr(writer, "<") == -1 ||
+ writer_addstr(writer, name) == -1 ||
+ writer_addstr(writer, ">\n") == -1)
+ return (-1);
+ for (i = 0; i < set->len; i++) {
+ if (file_path_add(writer, &set->files[i]) == -1 ||
+ writer_addstr(writer, "\n") == -1)
+ return (-1);
+ }
+ if (writer_addstr(writer, "</") == -1 ||
+ writer_addstr(writer, name) == -1 ||
+ writer_addstr(writer, ">") == -1)
+ return (-1);
+ return (0);
+}
+
+int
+compaction_files_append(struct buf *out, const struct compaction_files *files,
+ size_t maxlen)
+{
+ struct compact_writer writer;
+ struct buf rendered;
+
+ if (out == NULL || files == NULL || out->len > maxlen)
+ return (-1);
+ buf_init(&rendered);
+ memset(&writer, 0, sizeof(writer));
+ writer.out = &rendered;
+ writer.maxlen = SIZE_MAX;
+ if ((out->len > 0 && writer_addstr(&writer, "\n\n") == -1) ||
+ file_block_add(&writer, "read-files", &files->read) == -1 ||
+ writer_addstr(&writer, "\n\n") == -1 ||
+ file_block_add(&writer, "modified-files", &files->modified) == -1)
+ goto fail;
+ if (rendered.len > maxlen - out->len)
+ goto fail;
+ buf_add(out, rendered.data, rendered.len);
+ buf_free(&rendered);
+ return (0);
+
+fail:
+ buf_free(&rendered);
+ return (-1);
+}
+
+static int
tool_value_add(struct compact_writer *w, const struct json *json, int value)
{
const u_char *raw;
blob - b3ac9a218e357dcc1899f34c2a50993aadd2ee91
blob + 62c4147f7e26c32d7ff9b860a4bdabea7274fd7d
--- src/common/compaction.h
+++ src/common/compaction.h
#include <sys/types.h>
struct buf;
+struct compaction_files;
struct msglist;
#define COMPACTION_TOOL_RESULT_MAX 2000
+#define COMPACTION_FILE_PATH_MAX 4096
+#define COMPACTION_FILE_COUNT_MAX 4096
+#define COMPACTION_FILE_BYTES_MAX (1024 * 1024)
+enum compaction_file_kind {
+ COMPACTION_FILE_READ,
+ COMPACTION_FILE_MODIFIED
+};
+
extern const char compaction_system_prompt[];
/*
int compaction_serialize(struct buf *, const struct msglist *, const void *,
size_t, size_t);
+/*
+ * Structured file details carried across Compactions. Paths are retained
+ * with explicit lengths, in bytewise order. All mutating operations are
+ * transactional: validation or bound failure leaves the state unchanged.
+ */
+struct compaction_files *compaction_files_new(void);
+void compaction_files_free(struct compaction_files *);
+int compaction_files_add(struct compaction_files *,
+ enum compaction_file_kind, const void *, size_t);
+int compaction_files_collect(struct compaction_files *,
+ const struct msglist *);
+int compaction_files_merge(struct compaction_files *,
+ const struct compaction_files *);
+size_t compaction_files_count(const struct compaction_files *,
+ enum compaction_file_kind);
+int compaction_files_get(const struct compaction_files *,
+ enum compaction_file_kind, size_t, const void **, size_t *);
+
+/* Append both generated XML-like blocks within maxlen. */
+int compaction_files_append(struct buf *, const struct compaction_files *,
+ size_t);
+
#endif /* COMPACTION_H */