mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
Replace regex-based compression artifact cleanup with linear helpers to avoid pathological backtracking and normalize whitespace safely. Tighten request and response parsing in assess, Gemini translation, and executor telemetry to avoid unsafe property access and invalid category filtering. Also add managed database backup support for legacy encrypted connection migration, improve SQLite load error handling, and cover the regressions with unit tests.
597 lines
18 KiB
TypeScript
597 lines
18 KiB
TypeScript
import type {
|
|
CavemanConfig,
|
|
CavemanRule,
|
|
CompressionResult,
|
|
CompressionMode,
|
|
CompressionStats,
|
|
} from "./types.ts";
|
|
import { DEFAULT_CAVEMAN_CONFIG } from "./types.ts";
|
|
import { CAVEMAN_RULES, getRulesForContext } from "./cavemanRules.ts";
|
|
import { extractPreservedBlocks, restorePreservedBlocks } from "./preservation.ts";
|
|
import { createCompressionStats, estimateCompressionTokens } from "./stats.ts";
|
|
import { validateCompression } from "./validation.ts";
|
|
import { mapTextContent } from "./messageContent.ts";
|
|
import { detectCompressionLanguage } from "./languageDetector.ts";
|
|
|
|
interface ChatMessage {
|
|
role: string;
|
|
content?: string | Array<{ type: string; text?: string }>;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface ChatRequestBody {
|
|
messages?: ChatMessage[];
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
const RULE_KEYWORDS: Record<string, string[]> = {
|
|
redundant_phrasing: ["make sure", "be sure"],
|
|
redundant_because: ["due to the fact", "the reason is because"],
|
|
redundant_directive: ["it is important", "you should", "remember to"],
|
|
pleasantries: [
|
|
"sure",
|
|
"certainly",
|
|
"of course",
|
|
"happy to",
|
|
"thanks",
|
|
"thank you",
|
|
"glad to help",
|
|
"glad to",
|
|
"no problem",
|
|
"you're welcome",
|
|
"youre welcome",
|
|
"absolutely",
|
|
],
|
|
polite_framing: [
|
|
"please",
|
|
"kindly",
|
|
"could you please",
|
|
"would you please",
|
|
"can you please",
|
|
"i would like you",
|
|
"i want you",
|
|
"i need you",
|
|
],
|
|
hedging: [
|
|
"it seems like",
|
|
"it appears that",
|
|
"i think that",
|
|
"i believe that",
|
|
"probably",
|
|
"possibly",
|
|
"maybe it",
|
|
],
|
|
verbose_instructions: [
|
|
"provide a detailed",
|
|
"give me a comprehensive",
|
|
"write an in-depth",
|
|
"create a thorough",
|
|
"explain in detail",
|
|
],
|
|
filler_adverbs: ["basically", "essentially", "actually", "literally", "simply", "currently"],
|
|
filler_phrases: ["i want to", "i need to", "i'd like to", "i'm looking for"],
|
|
redundant_openers: ["hi there", "hello", "good morning", "hey"],
|
|
verbose_requests: ["i was wondering", "would it be possible"],
|
|
leader_phrases: [
|
|
"i'll",
|
|
"i will",
|
|
"i can",
|
|
"i'd",
|
|
"let me",
|
|
"you can",
|
|
"we will",
|
|
"we can",
|
|
"let's",
|
|
],
|
|
self_reference: ["i am trying to", "i am working on", "i have been"],
|
|
excessive_gratitude: ["thank you so much", "thanks in advance", "i really appreciate"],
|
|
qualifier_removal: ["a bit", "a little", "somewhat", "kind of", "sort of"],
|
|
softeners: ["if possible", "when you get a chance", "at your convenience", "just wondering"],
|
|
uncertainty_fillers: ["i guess", "i suppose", "more or less", "in a way"],
|
|
assistant_fillers: ["here's", "below is", "this is"],
|
|
compound_collapse: ["and any potential"],
|
|
explanatory_prefix: [
|
|
"the function appears to be handling",
|
|
"the code seems to",
|
|
"the class is",
|
|
"this module is",
|
|
],
|
|
question_to_directive: [
|
|
"can you explain why",
|
|
"could you show me how",
|
|
"would you tell me",
|
|
"can you tell me",
|
|
],
|
|
context_setup: ["i have the following code", "here is my code", "below is the code"],
|
|
intent_clarification: [
|
|
"what i'm trying to do",
|
|
"my objective is to",
|
|
"what i need is",
|
|
"i'm aiming to",
|
|
],
|
|
background_removal: ["as you may know", "as we discussed earlier"],
|
|
meta_commentary: ["note that", "keep in mind", "remember that"],
|
|
purpose_statement: ["for the purpose of", "with the goal of", "in an effort to", "for every"],
|
|
list_conjunction: ["and also", "as well as"],
|
|
purpose_phrases: ["in order to", "so as to"],
|
|
redundant_quantifiers: ["each and every", "any and all"],
|
|
all_quantifier: ["any and all"],
|
|
verbose_connectors: ["furthermore", "additionally", "moreover", "in addition"],
|
|
transition_removal: ["on the other hand", "in contrast", "however"],
|
|
emphasis_removal: ["very", "really", "extremely", "highly", "quite"],
|
|
passive_voice: [
|
|
"is being used",
|
|
"is being called",
|
|
"is being generated",
|
|
"was created",
|
|
"was generated",
|
|
"was implemented",
|
|
],
|
|
repeated_context: [
|
|
"as we discussed earlier",
|
|
"as mentioned before",
|
|
"as previously stated",
|
|
"as i said before",
|
|
],
|
|
repeated_question: [
|
|
"same question as before",
|
|
"i asked this earlier",
|
|
"this is the same question",
|
|
],
|
|
reestablished_context: ["going back to the code above", "referring back to", "returning to"],
|
|
summary_replacement: ["to summarize", "in summary of our conversation", "to recap"],
|
|
ultra_abbreviations: ["database"],
|
|
ultra_config_abbreviation: ["configuration"],
|
|
ultra_function_abbreviation: ["function"],
|
|
ultra_request_abbreviation: ["request"],
|
|
ultra_response_abbreviation: ["response"],
|
|
ultra_implementation_abbreviation: ["implementation"],
|
|
ultra_authentication_abbreviation: ["authentication"],
|
|
ultra_authorization_abbreviation: ["authorization"],
|
|
ultra_application_abbreviation: ["application"],
|
|
ultra_dependency_abbreviation: ["dependency", "dependencies"],
|
|
ultra_common_abbreviations: [
|
|
"implementation",
|
|
"authentication",
|
|
"authorization",
|
|
"application",
|
|
"dependency",
|
|
"dependencies",
|
|
],
|
|
};
|
|
|
|
const ARTICLE_HINT_RE = /\b(?:a|an|the)\b/;
|
|
|
|
function shouldAttemptRule(ruleName: string, lowerText: string): boolean {
|
|
if (ruleName === "articles") {
|
|
ARTICLE_HINT_RE.lastIndex = 0;
|
|
return ARTICLE_HINT_RE.test(lowerText);
|
|
}
|
|
|
|
const keywords = RULE_KEYWORDS[ruleName];
|
|
return !keywords || keywords.some((keyword) => lowerText.includes(keyword));
|
|
}
|
|
|
|
export function applyRulesToText(
|
|
text: string,
|
|
rules: CavemanRule[]
|
|
): { text: string; appliedRules: string[] } {
|
|
let result = text;
|
|
const lowerResult = text.toLowerCase();
|
|
const appliedRules: string[] = [];
|
|
|
|
for (const rule of rules) {
|
|
if (!shouldAttemptRule(rule.name, lowerResult)) continue;
|
|
|
|
const before = result;
|
|
const { pattern, replacement } = rule;
|
|
if (typeof replacement === "function") {
|
|
const fn = replacement;
|
|
result = result.replace(pattern, (...args) => {
|
|
const match = args[0];
|
|
return fn(match, ...args.slice(1, -2));
|
|
});
|
|
} else {
|
|
result = result.replace(pattern, replacement);
|
|
}
|
|
if (result !== before) {
|
|
appliedRules.push(rule.name);
|
|
}
|
|
}
|
|
|
|
return { text: result, appliedRules };
|
|
}
|
|
|
|
function cleanupArtifacts(text: string): string {
|
|
let result = text;
|
|
if (hasRepeatedHorizontalWhitespace(result)) {
|
|
result = collapseHorizontalWhitespaceRuns(result);
|
|
}
|
|
result = removeHorizontalWhitespaceBeforePunctuation(result);
|
|
result = collapseRepeatedSentencePunctuation(result);
|
|
if (result.includes(" \n") || result.includes("\t\n")) {
|
|
result = stripLineTrailingHorizontalWhitespace(result);
|
|
}
|
|
if (result.endsWith(" ") || result.endsWith("\t")) result = result.trimEnd();
|
|
if (result.includes("\n\n\n")) result = collapseExcessNewlines(result);
|
|
if (result.startsWith("\n")) result = trimLeadingNewlines(result);
|
|
if (result.endsWith("\n")) result = trimTrailingNewlines(result);
|
|
return result;
|
|
}
|
|
|
|
function isHorizontalWhitespace(char: string): boolean {
|
|
return char === " " || char === "\t";
|
|
}
|
|
|
|
function isSentencePunctuation(char: string): boolean {
|
|
return char === "." || char === "!" || char === "?";
|
|
}
|
|
|
|
function isCleanupPunctuation(char: string): boolean {
|
|
return (
|
|
char === "," || char === "." || char === ";" || char === ":" || char === "!" || char === "?"
|
|
);
|
|
}
|
|
|
|
function hasRepeatedHorizontalWhitespace(text: string): boolean {
|
|
let previousWasWhitespace = false;
|
|
for (const char of text) {
|
|
const currentIsWhitespace = isHorizontalWhitespace(char);
|
|
if (currentIsWhitespace && previousWasWhitespace) return true;
|
|
previousWasWhitespace = currentIsWhitespace;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function collapseHorizontalWhitespaceRuns(text: string): string {
|
|
let output = "";
|
|
let changed = false;
|
|
|
|
for (let index = 0; index < text.length; index++) {
|
|
const char = text[index];
|
|
if (!isHorizontalWhitespace(char)) {
|
|
output += char;
|
|
continue;
|
|
}
|
|
|
|
const start = index;
|
|
while (index + 1 < text.length && isHorizontalWhitespace(text[index + 1])) {
|
|
index++;
|
|
}
|
|
|
|
if (index > start) {
|
|
output += " ";
|
|
changed = true;
|
|
} else {
|
|
output += char;
|
|
}
|
|
}
|
|
|
|
return changed ? output : text;
|
|
}
|
|
|
|
function removeHorizontalWhitespaceBeforePunctuation(text: string): string {
|
|
let output = "";
|
|
let changed = false;
|
|
|
|
for (let index = 0; index < text.length; index++) {
|
|
const char = text[index];
|
|
if (!isHorizontalWhitespace(char)) {
|
|
output += char;
|
|
continue;
|
|
}
|
|
|
|
const start = index;
|
|
while (index + 1 < text.length && isHorizontalWhitespace(text[index + 1])) {
|
|
index++;
|
|
}
|
|
|
|
const nextChar = text[index + 1];
|
|
if (nextChar && isCleanupPunctuation(nextChar)) {
|
|
changed = true;
|
|
continue;
|
|
}
|
|
|
|
output += text.slice(start, index + 1);
|
|
}
|
|
|
|
return changed ? output : text;
|
|
}
|
|
|
|
function collapseRepeatedSentencePunctuation(text: string): string {
|
|
let output = "";
|
|
let changed = false;
|
|
|
|
for (let index = 0; index < text.length; index++) {
|
|
const char = text[index];
|
|
if (!isSentencePunctuation(char)) {
|
|
output += char;
|
|
continue;
|
|
}
|
|
|
|
let lastPunctuation = char;
|
|
const start = index;
|
|
while (index + 1 < text.length && isSentencePunctuation(text[index + 1])) {
|
|
index++;
|
|
lastPunctuation = text[index];
|
|
}
|
|
|
|
if (index > start) changed = true;
|
|
output += lastPunctuation;
|
|
}
|
|
|
|
return changed ? output : text;
|
|
}
|
|
|
|
function trimEndHorizontalWhitespace(text: string): string {
|
|
let end = text.length;
|
|
while (end > 0 && isHorizontalWhitespace(text[end - 1])) {
|
|
end--;
|
|
}
|
|
return end === text.length ? text : text.slice(0, end);
|
|
}
|
|
|
|
function stripLineTrailingHorizontalWhitespace(text: string): string {
|
|
const lines = text.split("\n");
|
|
let changed = false;
|
|
const cleanedLines = lines.map((line) => {
|
|
const cleaned = trimEndHorizontalWhitespace(line);
|
|
if (cleaned !== line) changed = true;
|
|
return cleaned;
|
|
});
|
|
return changed ? cleanedLines.join("\n") : text;
|
|
}
|
|
|
|
function collapseExcessNewlines(text: string): string {
|
|
let output = "";
|
|
let changed = false;
|
|
|
|
for (let index = 0; index < text.length; index++) {
|
|
const char = text[index];
|
|
if (char !== "\n") {
|
|
output += char;
|
|
continue;
|
|
}
|
|
|
|
const start = index;
|
|
while (index + 1 < text.length && text[index + 1] === "\n") {
|
|
index++;
|
|
}
|
|
|
|
const newlineCount = index - start + 1;
|
|
if (newlineCount > 2) {
|
|
output += "\n\n";
|
|
changed = true;
|
|
} else {
|
|
output += text.slice(start, index + 1);
|
|
}
|
|
}
|
|
|
|
return changed ? output : text;
|
|
}
|
|
|
|
function trimLeadingNewlines(text: string): string {
|
|
let start = 0;
|
|
while (start < text.length && text[start] === "\n") {
|
|
start++;
|
|
}
|
|
return start === 0 ? text : text.slice(start);
|
|
}
|
|
|
|
function trimTrailingNewlines(text: string): string {
|
|
let end = text.length;
|
|
while (end > 0 && text[end - 1] === "\n") {
|
|
end--;
|
|
}
|
|
return end === text.length ? text : text.slice(0, end);
|
|
}
|
|
|
|
function recapitalizeSentences(text: string): string {
|
|
return text.replace(/(^|[.!?][ \t]|\n[ \t]*)([a-z])/g, (_match, prefix: string, char: string) => {
|
|
return `${prefix}${char.toUpperCase()}`;
|
|
});
|
|
}
|
|
|
|
function createCavemanStats(
|
|
originalTokens: number,
|
|
compressedTokens: number,
|
|
techniquesUsed: string[],
|
|
rulesApplied: string[] | undefined,
|
|
durationMs: number
|
|
): CompressionStats {
|
|
const savingsPercent =
|
|
originalTokens > 0
|
|
? Math.round(((originalTokens - compressedTokens) / originalTokens) * 10000) / 100
|
|
: 0;
|
|
return {
|
|
originalTokens,
|
|
compressedTokens,
|
|
savingsPercent,
|
|
techniquesUsed,
|
|
mode: "standard",
|
|
timestamp: Date.now(),
|
|
...(rulesApplied && rulesApplied.length > 0 ? { rulesApplied } : {}),
|
|
durationMs,
|
|
};
|
|
}
|
|
|
|
function compileUserPreservePatterns(patterns: string[]): {
|
|
patterns: RegExp[];
|
|
warnings: string[];
|
|
} {
|
|
const compiled: RegExp[] = [];
|
|
const warnings: string[] = [];
|
|
for (const pattern of patterns) {
|
|
try {
|
|
compiled.push(new RegExp(pattern, "g"));
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
warnings.push(`Invalid preservePatterns regex ignored: ${pattern} (${message})`);
|
|
}
|
|
}
|
|
return { patterns: compiled, warnings };
|
|
}
|
|
|
|
const PROTECTED_STRUCTURE_RE =
|
|
/```|~~~|`|https?:\/\/|\[[^\]\n]{1,1000}\]\([^)[ \t\n]{1,2000}(?:[ \t]+"[^"]{0,1000}")?\)|^#{1,6}\s+|^[ \t]*\|(?:[^|\n]{0,1000}\|){1,100}[ \t]*$|\$\$|\\\[|\\begin\{|^\s*#(?:set|show|let|import|include)\b|\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\b|\bprocess\.env\.[A-Za-z_][A-Za-z0-9_]*\b|\$[A-Z_][A-Z0-9_]*\b|\b\d+(?:\.\d+){1,3}(?:[-+][A-Za-z0-9.-]+)?\b|\b[a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*)+\(\)?|\b[A-Za-z_$][\w$]*[ \t]*\([^()\n]{0,1000}\)|(?:^|\s)(?:\.{0,2}\/[A-Za-z0-9_@./-]+|[A-Za-z]:\\[A-Za-z0-9_.\\/-]+)|\b(?:TypeError|ReferenceError|SyntaxError|RangeError|URIError|EvalError|Error|Exception):[^\n]{0,1000}/im;
|
|
const PROTECTED_STRUCTURE_PREFILTER_RE = /[`~\[\]\|$#\\/:_()0-9]/;
|
|
|
|
function hasProtectedStructure(text: string): boolean {
|
|
if (!PROTECTED_STRUCTURE_PREFILTER_RE.test(text)) return false;
|
|
PROTECTED_STRUCTURE_RE.lastIndex = 0;
|
|
return PROTECTED_STRUCTURE_RE.test(text);
|
|
}
|
|
|
|
export function cavemanCompress(
|
|
body: ChatRequestBody,
|
|
options?: Partial<CavemanConfig>
|
|
): CompressionResult {
|
|
const startMs = performance.now();
|
|
const config: CavemanConfig = { ...DEFAULT_CAVEMAN_CONFIG, ...options };
|
|
|
|
const emptyResult = (): CompressionResult => ({
|
|
body: body as unknown as Record<string, unknown>,
|
|
compressed: false,
|
|
stats: createCompressionStats(
|
|
body as unknown as Record<string, unknown>,
|
|
body as unknown as Record<string, unknown>,
|
|
"standard" as CompressionMode,
|
|
[]
|
|
),
|
|
});
|
|
|
|
if (!config.enabled) {
|
|
return emptyResult();
|
|
}
|
|
|
|
if (!body.messages || !Array.isArray(body.messages) || body.messages.length === 0) {
|
|
return emptyResult();
|
|
}
|
|
|
|
let totalOriginalTokens = 0;
|
|
let totalCompressedTokens = 0;
|
|
const allAppliedRules: string[] = [];
|
|
const validationWarnings: string[] = [];
|
|
const validationErrors: string[] = [];
|
|
let fallbackApplied = false;
|
|
let preservedBlockCount = 0;
|
|
const customPreservation = compileUserPreservePatterns(config.preservePatterns ?? []);
|
|
validationWarnings.push(...customPreservation.warnings);
|
|
|
|
const compressedMessages = body.messages.map((msg): ChatMessage => {
|
|
if (typeof msg.content !== "string" && !Array.isArray(msg.content)) {
|
|
return msg;
|
|
}
|
|
|
|
const contentStr =
|
|
typeof msg.content === "string"
|
|
? msg.content
|
|
: msg.content
|
|
.map((part) =>
|
|
part && typeof part === "object" && "text" in part && typeof part.text === "string"
|
|
? part.text
|
|
: ""
|
|
)
|
|
.filter(Boolean)
|
|
.join("\n");
|
|
totalOriginalTokens += estimateCompressionTokens(contentStr);
|
|
|
|
if (!contentStr || contentStr.length < config.minMessageLength) {
|
|
totalCompressedTokens += estimateCompressionTokens(contentStr);
|
|
return msg;
|
|
}
|
|
|
|
if (!config.compressRoles.includes(msg.role as "user" | "assistant" | "system")) {
|
|
totalCompressedTokens += estimateCompressionTokens(contentStr);
|
|
return msg;
|
|
}
|
|
|
|
const compressTextPart = (textPart: string): string => {
|
|
if (!textPart || textPart.length < config.minMessageLength) return textPart;
|
|
|
|
const shouldPreserve =
|
|
customPreservation.patterns.length > 0 || hasProtectedStructure(textPart);
|
|
const { text: extractedText, blocks } = shouldPreserve
|
|
? extractPreservedBlocks(textPart, {
|
|
preservePatterns: customPreservation.patterns,
|
|
})
|
|
: { text: textPart, blocks: [] };
|
|
preservedBlockCount += blocks.length;
|
|
|
|
const detectedLanguage = config.autoDetectLanguage
|
|
? detectCompressionLanguage(textPart)
|
|
: (config.language ?? "en");
|
|
const enabledPacks = config.enabledLanguagePacks ?? ["en", detectedLanguage];
|
|
const language = enabledPacks.includes(detectedLanguage)
|
|
? detectedLanguage
|
|
: enabledPacks.includes("en")
|
|
? "en"
|
|
: detectedLanguage;
|
|
const rules = getRulesForContext(msg.role, config.intensity, language).filter(
|
|
(rule) => !config.skipRules.includes(rule.name)
|
|
);
|
|
const { text: rulesApplied, appliedRules } = applyRulesToText(extractedText, rules);
|
|
allAppliedRules.push(...appliedRules);
|
|
|
|
const normalized = recapitalizeSentences(cleanupArtifacts(rulesApplied));
|
|
const cleaned =
|
|
blocks.length > 0
|
|
? cleanupArtifacts(restorePreservedBlocks(normalized, blocks))
|
|
: normalized;
|
|
if (shouldPreserve || blocks.length > 0) {
|
|
const validation = validateCompression(textPart, cleaned);
|
|
validationWarnings.push(...validation.warnings);
|
|
if (!validation.valid) {
|
|
validationErrors.push(...validation.errors);
|
|
fallbackApplied = true;
|
|
return textPart;
|
|
}
|
|
}
|
|
|
|
return cleaned;
|
|
};
|
|
|
|
const compressedMessage = mapTextContent(msg, compressTextPart) as ChatMessage;
|
|
const cleaned =
|
|
typeof compressedMessage.content === "string"
|
|
? compressedMessage.content
|
|
: Array.isArray(compressedMessage.content)
|
|
? compressedMessage.content
|
|
.map((part) =>
|
|
part && typeof part === "object" && "text" in part && typeof part.text === "string"
|
|
? part.text
|
|
: ""
|
|
)
|
|
.filter(Boolean)
|
|
.join("\n")
|
|
: contentStr;
|
|
totalCompressedTokens += estimateCompressionTokens(cleaned);
|
|
|
|
return compressedMessage;
|
|
});
|
|
|
|
const durationMs = performance.now() - startMs;
|
|
const uniqueRules = [...new Set(allAppliedRules)];
|
|
const stats = createCavemanStats(
|
|
totalOriginalTokens,
|
|
totalCompressedTokens,
|
|
uniqueRules.length > 0 ? ["caveman-rules"] : [],
|
|
uniqueRules.length > 0 ? uniqueRules : undefined,
|
|
Math.round(durationMs * 100) / 100
|
|
);
|
|
if (validationWarnings.length > 0) stats.validationWarnings = [...new Set(validationWarnings)];
|
|
if (validationErrors.length > 0) stats.validationErrors = [...new Set(validationErrors)];
|
|
if (fallbackApplied) stats.fallbackApplied = true;
|
|
if (preservedBlockCount > 0) stats.preservedBlockCount = preservedBlockCount;
|
|
|
|
const compressed = totalCompressedTokens < totalOriginalTokens;
|
|
|
|
const result: CompressionResult = {
|
|
body: { ...body, messages: compressedMessages } as unknown as Record<string, unknown>,
|
|
compressed,
|
|
stats,
|
|
};
|
|
|
|
return result;
|
|
}
|