// retoor #include "context_manager.h" #include #include #include #define MIN_KEEP_CHARS 500 #define TRUNCATE_MARKER "\n\n[... content truncated for context management ...]\n\n" static const char *get_message_role(struct json_object *msg) { struct json_object *role_obj; if (json_object_object_get_ex(msg, "role", &role_obj)) { return json_object_get_string(role_obj); } // Tool results don't have a 'role' field in some implementations, // they have 'tool_call_id'. if (json_object_object_get_ex(msg, "tool_call_id", &role_obj)) { return "tool"; } return ""; } static bool has_tool_calls(struct json_object *msg) { struct json_object *tool_calls; if (json_object_object_get_ex(msg, "tool_calls", &tool_calls)) { return json_object_array_length(tool_calls) > 0; } return false; } 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) { 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); 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; size_t keep_each = target_len / 2; size_t marker_len = strlen(TRUNCATE_MARKER); char *new_content = malloc(keep_each * 2 + marker_len + 1); if (!new_content) return R_ERROR_OUT_OF_MEMORY; memcpy(new_content, content, keep_each); memcpy(new_content + keep_each, TRUNCATE_MARKER, marker_len); size_t offset = keep_each + marker_len; memcpy(new_content + offset, content + len - keep_each, keep_each); new_content[offset + keep_each] = '\0'; struct json_object *new_msg = json_tokener_parse(json_object_to_json_string(msg)); 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); if (count <= 2) return R_ERROR_API_ERROR; size_t initial_size = calculate_total_size(msgs); size_t target_size = (size_t)(initial_size * 0.5); if (target_size < 20000) target_size = 20000; fprintf(stderr, " \033[2m-> Context overflow (%zu chars). Middle-out shrinking to %zu...\033[0m\n", initial_size, target_size); // Strategy 1: Truncate very large messages first (safe, doesn't break sequence) for (int i = 0; i < messages_count(msgs); i++) { struct json_object *msg = messages_get_object(msgs, i); if (get_message_content_len(msg) > 50000) { perform_truncate(msgs, i, 0.2); } } // Strategy 2: Remove messages from the middle until size is within target // We keep: // - System message (usually index 0) // - Most recent 4 messages (usually current task context) while (calculate_total_size(msgs) > target_size && messages_count(msgs) > 6) { int middle_idx = 1; // Start after system struct json_object *msg = messages_get_object(msgs, middle_idx); const char *role = get_message_role(msg); int remove_count = 1; if (strcmp(role, "assistant") == 0 && has_tool_calls(msg)) { // Must also remove the following tool results to maintain sequence int search_idx = middle_idx + 1; while (search_idx < messages_count(msgs) - 4) { struct json_object *next_msg = messages_get_object(msgs, search_idx); if (strcmp(get_message_role(next_msg), "tool") == 0) { remove_count++; search_idx++; } else { break; } } } // Ensure we don't eat into the "recent" buffer if (middle_idx + remove_count > messages_count(msgs) - 4) { break; } messages_remove_range(msgs, middle_idx, remove_count); } size_t final_size = calculate_total_size(msgs); fprintf(stderr, " \033[2m-> Context shrunk to %zu chars. Remaining messages: %d\033[0m\n", final_size, messages_count(msgs)); return R_SUCCESS; }