2026-01-29 00:38:21 +01:00
|
|
|
// retoor <retoor@molodetz.nl>
|
|
|
|
|
|
|
|
|
|
#include "context_manager.h"
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
#define MIN_KEEP_CHARS 500
|
2026-01-29 00:38:21 +01:00
|
|
|
#define TRUNCATE_MARKER "\n\n[... content truncated for context management ...]\n\n"
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
static const char *get_message_role(struct json_object *msg) {
|
2026-01-29 00:38:21 +01:00
|
|
|
struct json_object *role_obj;
|
|
|
|
|
if (json_object_object_get_ex(msg, "role", &role_obj)) {
|
2026-01-29 06:01:05 +01:00
|
|
|
return json_object_get_string(role_obj);
|
2026-01-29 00:38:21 +01:00
|
|
|
}
|
2026-01-29 06:01:05 +01:00
|
|
|
return "";
|
2026-01-29 00:38:21 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
static size_t get_message_content_len(struct json_object *msg) {
|
|
|
|
|
struct json_object *content_obj;
|
|
|
|
|
const char *content = NULL;
|
|
|
|
|
|
|
|
|
|
if (json_object_object_get_ex(msg, "content", &content_obj)) {
|
|
|
|
|
content = json_object_get_string(content_obj);
|
|
|
|
|
} else if (json_object_object_get_ex(msg, "tool_result", &content_obj)) {
|
|
|
|
|
content = json_object_get_string(content_obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return content ? strlen(content) : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t calculate_total_size(messages_handle msgs) {
|
|
|
|
|
size_t total = 0;
|
|
|
|
|
int count = messages_count(msgs);
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
|
total += get_message_content_len(messages_get_object(msgs, i));
|
|
|
|
|
}
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static r_status_t perform_truncate(messages_handle msgs, int index, double ratio) {
|
2026-01-29 00:38:21 +01:00
|
|
|
struct json_object *msg = messages_get_object(msgs, index);
|
|
|
|
|
if (!msg) return R_ERROR_NOT_FOUND;
|
|
|
|
|
|
|
|
|
|
struct json_object *content_obj;
|
|
|
|
|
const char *content = NULL;
|
|
|
|
|
bool is_tool_result = false;
|
|
|
|
|
|
|
|
|
|
if (json_object_object_get_ex(msg, "content", &content_obj)) {
|
|
|
|
|
content = json_object_get_string(content_obj);
|
|
|
|
|
} else if (json_object_object_get_ex(msg, "tool_result", &content_obj)) {
|
|
|
|
|
content = json_object_get_string(content_obj);
|
|
|
|
|
is_tool_result = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!content) return R_SUCCESS;
|
|
|
|
|
|
|
|
|
|
size_t len = strlen(content);
|
2026-01-29 06:01:05 +01:00
|
|
|
size_t target_len = (size_t)(len * ratio);
|
|
|
|
|
if (target_len < MIN_KEEP_CHARS * 2) target_len = MIN_KEEP_CHARS * 2;
|
|
|
|
|
if (target_len >= len) return R_SUCCESS;
|
2026-01-29 00:38:21 +01:00
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
size_t keep_each = target_len / 2;
|
|
|
|
|
|
|
|
|
|
char *new_content = malloc(keep_each * 2 + strlen(TRUNCATE_MARKER) + 1);
|
2026-01-29 00:38:21 +01:00
|
|
|
if (!new_content) return R_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
strncpy(new_content, content, keep_each);
|
|
|
|
|
new_content[keep_each] = '\0';
|
2026-01-29 00:38:21 +01:00
|
|
|
strcat(new_content, TRUNCATE_MARKER);
|
2026-01-29 06:01:05 +01:00
|
|
|
strcat(new_content, content + len - keep_each);
|
2026-01-29 00:38:21 +01:00
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
struct json_object *new_msg = json_object_get(msg);
|
2026-01-29 00:38:21 +01:00
|
|
|
if (is_tool_result) {
|
|
|
|
|
json_object_object_add(new_msg, "tool_result", json_object_new_string(new_content));
|
|
|
|
|
} else {
|
|
|
|
|
json_object_object_add(new_msg, "content", json_object_new_string(new_content));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(new_content);
|
|
|
|
|
return messages_replace_at(msgs, index, new_msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r_status_t context_manager_shrink(messages_handle msgs) {
|
|
|
|
|
if (!msgs) return R_ERROR_INVALID_ARG;
|
|
|
|
|
|
|
|
|
|
int count = messages_count(msgs);
|
2026-01-29 06:01:05 +01:00
|
|
|
if (count <= 1) return R_ERROR_API_ERROR;
|
|
|
|
|
|
|
|
|
|
size_t initial_size = calculate_total_size(msgs);
|
|
|
|
|
// Target 40% of initial size to be safe and avoid immediate re-overflow
|
|
|
|
|
size_t target_size = (size_t)(initial_size * 0.4);
|
|
|
|
|
if (target_size < 10000) target_size = 10000; // Don't shrink too much if it's already small
|
|
|
|
|
|
|
|
|
|
fprintf(stderr, " \033[2m-> Context overflow (approx %zu chars). Shrinking to %zu...\033[0m\n",
|
|
|
|
|
initial_size, target_size);
|
|
|
|
|
|
|
|
|
|
int iterations = 0;
|
|
|
|
|
while (calculate_total_size(msgs) > target_size && iterations < 50) {
|
|
|
|
|
iterations++;
|
|
|
|
|
count = messages_count(msgs);
|
|
|
|
|
|
|
|
|
|
// Strategy 1: Find largest non-system, non-last message and truncate it
|
|
|
|
|
int largest_idx = -1;
|
|
|
|
|
size_t largest_size = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < count - 1; i++) {
|
|
|
|
|
struct json_object *msg = messages_get_object(msgs, i);
|
|
|
|
|
if (strcmp(get_message_role(msg), "system") == 0) continue;
|
|
|
|
|
|
|
|
|
|
size_t s = get_message_content_len(msg);
|
|
|
|
|
if (s > largest_size) {
|
|
|
|
|
largest_size = s;
|
|
|
|
|
largest_idx = i;
|
|
|
|
|
}
|
2026-01-29 00:38:21 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
if (largest_idx != -1 && largest_size > 1000) {
|
|
|
|
|
perform_truncate(msgs, largest_idx, 0.3); // Cut to 30% of its size
|
|
|
|
|
continue;
|
2026-01-29 00:38:21 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
// Strategy 2: Remove oldest removable messages (keep sequence)
|
|
|
|
|
int first_removable = -1;
|
|
|
|
|
for (int i = 0; i < count - 1; i++) {
|
|
|
|
|
struct json_object *msg = messages_get_object(msgs, i);
|
|
|
|
|
if (strcmp(get_message_role(msg), "system") != 0) {
|
|
|
|
|
first_removable = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-29 00:38:21 +01:00
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
if (first_removable != -1 && first_removable < count - 1) {
|
|
|
|
|
// Remove 1 message at a time from the front
|
|
|
|
|
messages_remove_range(msgs, first_removable, 1);
|
|
|
|
|
} else {
|
|
|
|
|
// Nothing left to remove but system or last message
|
2026-01-29 00:38:21 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
// Last Resort: If still too big, truncate the last message
|
|
|
|
|
size_t final_size = calculate_total_size(msgs);
|
|
|
|
|
if (final_size > target_size) {
|
|
|
|
|
count = messages_count(msgs);
|
|
|
|
|
if (count > 0) {
|
|
|
|
|
perform_truncate(msgs, count - 1, 0.5);
|
|
|
|
|
}
|
2026-01-29 00:38:21 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
size_t shrunk_size = calculate_total_size(msgs);
|
|
|
|
|
fprintf(stderr, " \033[2m-> Context shrunk to approx %zu chars.\033[0m\n", shrunk_size);
|
|
|
|
|
|
|
|
|
|
return R_SUCCESS;
|
|
|
|
|
}
|