mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 12:22:14 +03:00
Implements the aggressive compression pipeline (issue #1588) with three core stages: tool-result compression, progressive aging, and rule-based summarization. Includes downgrade chain to caveman/lite when savings are insufficient. Core modules: - summarizer.ts: RuleBasedSummarizer with intent/file/error/decision extraction, code fence trimming, skip-already-compressed guard - toolResultCompressor.ts: 5 auto-detected strategies (fileContent, grepSearch, shellOutput, json, errorMessage) with toggle support - progressiveAging.ts: 4-tier degradation (verbatim→light→moderate→ fullSummary) with [COMPRESSED:aging:<tier>] markers - aggressive.ts: Orchestrator with 3-step pipeline and downgrade chain (aggressive→caveman→lite→as-is) Integration: - strategySelector.ts: applyCompression() now dispatches mode='aggressive' - compression.ts: aggressiveConfig CRUD with deep merge of nested objects - API route: Zod schema for aggressiveConfig thresholds/strategies - CompressionSettingsTab: aggressive mode card, thresholds, toggles, i18n - Migration 031: SELECT 1 no-op (config stored as kv key) Tests: 109 across 8 files (types, summarizer, toolResultCompressor, progressiveAging, aggressive, integration, golden eval, caveman regression) All pass. Typecheck clean. Lint clean (0 errors). Build succeeds.
175 lines
6.3 KiB
TypeScript
175 lines
6.3 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { applyAging } from "../../../open-sse/services/compression/progressiveAging.ts";
|
|
import type { AgingThresholds, Summarizer } from "../../../open-sse/services/compression/types.ts";
|
|
|
|
function makeMessages(
|
|
count: number,
|
|
role: string = "user",
|
|
prefix: string = "Message"
|
|
): Array<{ role: string; content: string }> {
|
|
return Array.from({ length: count }, (_, i) => ({
|
|
role: i % 2 === 0 ? "user" : "assistant",
|
|
content: `${prefix} ${i}: ${"x".repeat(100)}`,
|
|
}));
|
|
}
|
|
|
|
describe("applyAging", () => {
|
|
const defaultThresholds: AgingThresholds = { fullSummary: 5, moderate: 3, light: 2, verbatim: 2 };
|
|
|
|
it("returns empty array for empty input", () => {
|
|
const result = applyAging([]);
|
|
assert.deepEqual(result.messages, []);
|
|
assert.equal(result.saved, 0);
|
|
});
|
|
|
|
it("returns single message unchanged (verbatim tier)", () => {
|
|
const msgs = [{ role: "user", content: "Hello world" }];
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
assert.equal(result.messages.length, 1);
|
|
assert.equal((result.messages[0] as { content: string }).content, "Hello world");
|
|
});
|
|
|
|
it("keeps last 2 messages as verbatim", () => {
|
|
const msgs = makeMessages(10);
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
const last2 = result.messages.slice(-2);
|
|
for (const msg of last2) {
|
|
const content =
|
|
typeof (msg as { content: unknown }).content === "string"
|
|
? (msg as { content: string }).content
|
|
: "";
|
|
assert.ok(
|
|
!content.startsWith("[COMPRESSED:aging:"),
|
|
`Last message should be verbatim, got: ${content.slice(0, 40)}`
|
|
);
|
|
}
|
|
});
|
|
|
|
it("applies fullSummary tier to oldest messages", () => {
|
|
const msgs = makeMessages(10);
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
const first = result.messages[0] as { content: string };
|
|
const content = typeof first.content === "string" ? first.content : "";
|
|
assert.ok(
|
|
content.startsWith("[COMPRESSED:aging:fullSummary]"),
|
|
`Expected fullSummary marker, got: ${content.slice(0, 60)}`
|
|
);
|
|
});
|
|
|
|
it("skips messages with [COMPRESSED: prefix (idempotent)", () => {
|
|
const msgs: Array<{ role: string; content: string }> = [
|
|
{ role: "assistant", content: "[COMPRESSED:aging:fullSummary] prior summary" },
|
|
{ role: "user", content: "New message" },
|
|
];
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
const first = result.messages[0] as { content: string };
|
|
const content = typeof first.content === "string" ? first.content : "";
|
|
assert.ok(content.startsWith("[COMPRESSED:aging:fullSummary]"));
|
|
assert.ok(!content.includes("[COMPRESSED:aging:fullSummary][COMPRESSED:"));
|
|
});
|
|
|
|
it("uses custom thresholds", () => {
|
|
const customThresholds: AgingThresholds = {
|
|
fullSummary: 1,
|
|
moderate: 1,
|
|
light: 1,
|
|
verbatim: 1,
|
|
};
|
|
const msgs = makeMessages(6);
|
|
const result = applyAging(msgs, customThresholds);
|
|
assert.ok(result.messages.length > 0);
|
|
});
|
|
|
|
it("accepts custom summarizer", () => {
|
|
const customSummarizer: Summarizer = {
|
|
summarize: () => "[CUSTOM SUMMARY]",
|
|
};
|
|
const msgs = makeMessages(10);
|
|
const result = applyAging(msgs, defaultThresholds, customSummarizer);
|
|
const fullSummaryMsgs = result.messages.filter((m) => {
|
|
const content =
|
|
typeof (m as { content: unknown }).content === "string"
|
|
? (m as { content: string }).content
|
|
: "";
|
|
return content.includes("[CUSTOM SUMMARY]");
|
|
});
|
|
assert.ok(fullSummaryMsgs.length > 0, "Expected at least one message with custom summary");
|
|
});
|
|
|
|
it("computes saved tokens", () => {
|
|
const msgs = makeMessages(10);
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
assert.ok(typeof result.saved === "number");
|
|
});
|
|
|
|
it("handles 2-message conversation (all verbatim)", () => {
|
|
const msgs = [
|
|
{ role: "user", content: "Hello" },
|
|
{ role: "assistant", content: "Hi there" },
|
|
];
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
assert.equal(result.messages.length, 2);
|
|
for (const msg of result.messages) {
|
|
const content =
|
|
typeof (msg as { content: unknown }).content === "string"
|
|
? (msg as { content: string }).content
|
|
: "";
|
|
assert.ok(!content.startsWith("[COMPRESSED:"));
|
|
}
|
|
});
|
|
|
|
it("handles 5-message conversation with light tier", () => {
|
|
const msgs = makeMessages(5);
|
|
const thresholds: AgingThresholds = { fullSummary: 10, moderate: 10, light: 3, verbatim: 1 };
|
|
const result = applyAging(msgs, thresholds);
|
|
assert.ok(result.messages.length > 0);
|
|
});
|
|
|
|
it("handles messages with array content", () => {
|
|
const msgs = [{ role: "user", content: [{ type: "text", text: "Hello from array" }] }];
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
assert.equal(result.messages.length, 1);
|
|
});
|
|
|
|
it("does not mutate original messages array", () => {
|
|
const original = makeMessages(5);
|
|
const originalCopy = JSON.parse(JSON.stringify(original));
|
|
applyAging(original, defaultThresholds);
|
|
assert.deepEqual(original, originalCopy);
|
|
});
|
|
|
|
it("handles all-assistant messages", () => {
|
|
const msgs = Array.from({ length: 8 }, (_, i) => ({
|
|
role: "assistant" as const,
|
|
content: `Response ${i}: ${"y".repeat(100)}`,
|
|
}));
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
assert.ok(result.messages.length > 0);
|
|
});
|
|
|
|
it("handles mixed user/assistant/tool messages", () => {
|
|
const msgs = [
|
|
{ role: "user", content: "Fix the bug" },
|
|
{ role: "assistant", content: "I'll fix it" },
|
|
{ role: "tool", content: "Result: fixed" },
|
|
{ role: "assistant", content: "Done" },
|
|
];
|
|
const result = applyAging(msgs, defaultThresholds);
|
|
assert.ok(result.messages.length > 0);
|
|
});
|
|
|
|
it("idempotent — running twice produces same output", () => {
|
|
const msgs = makeMessages(10);
|
|
const first = applyAging(msgs, defaultThresholds);
|
|
const second = applyAging(first.messages, defaultThresholds);
|
|
const firstContent = JSON.stringify(
|
|
first.messages.map((m) => (m as { content: string }).content)
|
|
);
|
|
const secondContent = JSON.stringify(
|
|
second.messages.map((m) => (m as { content: string }).content)
|
|
);
|
|
assert.equal(firstContent, secondContent);
|
|
});
|
|
});
|