mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat(compression): reconcile Phase 2 with Phase 1 API surface
- Use 'standard' mode (not 'caveman') in CompressionMode type - Align CompressionConfig with Phase 1 shape (enabled, defaultMode, autoTriggerTokens, cacheMinutes, preserveSystemPrompt, comboOverrides) - Extend CompressionStats with techniquesUsed + rulesApplied + durationMs - Make CompressionResult.stats nullable (Phase 1 compat) - Add Phase 1 functions: estimateCompressionTokens, createCompressionStats, trackCompressionStats, selectCompressionStrategy, applyCompression, checkComboOverride, shouldAutoTrigger, getEffectiveMode - Add lite.ts stub for Phase 1 'lite' compression mode - Add index.ts barrel file for full module export - Fix DB compression.ts to return Phase 1 CompressionConfig shape - Fix chatCore.ts to use unified compression pipeline (selectCompressionStrategy + applyCompression) - Add backwards-compatible estimateTokensForStats alias - Renumber migration 028 → 030 (Phase 1 uses 028) - Update all tests for reconciled API (67/67 pass)
This commit is contained in:
@@ -106,9 +106,12 @@ import {
|
||||
} from "../services/modelFamilyFallback.ts";
|
||||
import { computeRequestHash, deduplicate, shouldDeduplicate } from "../services/requestDedup.ts";
|
||||
import { compressContext, estimateTokens, getTokenLimit } from "../services/contextManager.ts";
|
||||
import { cavemanCompress } from "../services/compression/caveman.ts";
|
||||
import {
|
||||
selectCompressionStrategy,
|
||||
applyCompression,
|
||||
} from "../services/compression/strategySelector.ts";
|
||||
import { estimateCompressionTokens } from "../services/compression/stats.ts";
|
||||
import { getCompressionSettings } from "../../src/lib/db/compression.ts";
|
||||
import type { CavemanConfig } from "../services/compression/types.ts";
|
||||
import {
|
||||
getBackgroundTaskReason,
|
||||
getDegradedModel,
|
||||
@@ -1265,21 +1268,29 @@ export async function handleChatCore({
|
||||
`Checking compression: ${estimatedTokens} tokens vs ${threshold} threshold (${contextLimit} limit, ${reservedTokens} reserved)`
|
||||
);
|
||||
|
||||
// Caveman compression (Phase 2) — runs before context compression
|
||||
// Prompt compression pipeline — Phase 1 (lite) + Phase 2 (standard/caveman)
|
||||
try {
|
||||
const compressionSettings = getCompressionSettings();
|
||||
if (compressionSettings.cavemanConfig?.enabled) {
|
||||
const cavemanResult = cavemanCompress(body, compressionSettings.cavemanConfig);
|
||||
if (cavemanResult.compressed) {
|
||||
body = cavemanResult.body as typeof body;
|
||||
const compressionConfig = getCompressionSettings();
|
||||
const estimatedTokens = estimateCompressionTokens(body);
|
||||
const mode = selectCompressionStrategy(compressionConfig, comboName ?? null, estimatedTokens);
|
||||
if (mode !== "off") {
|
||||
const compressionResult = applyCompression(body as Record<string, unknown>, mode, {
|
||||
model: effectiveModel,
|
||||
config: compressionConfig,
|
||||
});
|
||||
if (compressionResult.compressed && compressionResult.stats) {
|
||||
body = compressionResult.body as typeof body;
|
||||
const s = compressionResult.stats;
|
||||
log?.info?.(
|
||||
"CAVEMAN",
|
||||
`Caveman compression: ${cavemanResult.stats.originalTokens} → ${cavemanResult.stats.compressedTokens} tokens (${cavemanResult.stats.savingsPercent}% savings, ${cavemanResult.stats.durationMs}ms, rules: ${cavemanResult.stats.rulesApplied?.join(", ")})`
|
||||
"COMPRESSION",
|
||||
`Prompt compression (${s.mode}): ${s.originalTokens} → ${s.compressedTokens} tokens (${s.savingsPercent}% savings, ${s.durationMs ?? 0}ms${
|
||||
s.rulesApplied?.length ? `, rules: ${s.rulesApplied.join(", ")}` : ""
|
||||
})`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log?.warn?.("CAVEMAN", "Caveman compression failed (non-fatal): " + err);
|
||||
log?.warn?.("COMPRESSION", "Prompt compression failed (non-fatal): " + err);
|
||||
}
|
||||
|
||||
if (estimatedTokens > threshold) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { CavemanConfig, CavemanRule, CompressionResult, CompressionStats } from "./types.ts";
|
||||
import type { CavemanConfig, CavemanRule, CompressionResult, CompressionMode } 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, estimateTokensForStats, trackCompressionStats } from "./stats.ts";
|
||||
|
||||
const CHARS_PER_TOKEN = 4;
|
||||
import {
|
||||
createCompressionStats,
|
||||
estimateCompressionTokens,
|
||||
trackCompressionStats,
|
||||
} from "./stats.ts";
|
||||
|
||||
interface ChatMessage {
|
||||
role: string;
|
||||
@@ -25,13 +27,15 @@ export function applyRulesToText(
|
||||
|
||||
for (const rule of rules) {
|
||||
const before = result;
|
||||
if (typeof rule.replacement === "function") {
|
||||
result = result.replace(rule.pattern, (...args) => {
|
||||
const { pattern, replacement } = rule;
|
||||
if (typeof replacement === "function") {
|
||||
const fn = replacement;
|
||||
result = result.replace(pattern, (...args) => {
|
||||
const match = args[0];
|
||||
return rule.replacement(match, ...args.slice(1, -2));
|
||||
return fn(match, ...args.slice(1, -2));
|
||||
});
|
||||
} else {
|
||||
result = result.replace(rule.pattern, rule.replacement);
|
||||
result = result.replace(pattern, replacement);
|
||||
}
|
||||
if (result !== before) {
|
||||
appliedRules.push(rule.name);
|
||||
@@ -58,20 +62,23 @@ export function cavemanCompress(
|
||||
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 {
|
||||
body,
|
||||
compressed: false,
|
||||
stats: createCompressionStats(0, 0, "caveman"),
|
||||
};
|
||||
return emptyResult();
|
||||
}
|
||||
|
||||
if (!body.messages || !Array.isArray(body.messages) || body.messages.length === 0) {
|
||||
return {
|
||||
body,
|
||||
compressed: false,
|
||||
stats: createCompressionStats(0, 0, "caveman"),
|
||||
};
|
||||
return emptyResult();
|
||||
}
|
||||
|
||||
let totalOriginalTokens = 0;
|
||||
@@ -89,35 +96,31 @@ export function cavemanCompress(
|
||||
.join("\n")
|
||||
: "";
|
||||
|
||||
totalOriginalTokens += estimateTokensForStats(contentStr);
|
||||
totalOriginalTokens += estimateCompressionTokens(contentStr);
|
||||
|
||||
if (!contentStr || contentStr.length < config.minMessageLength) {
|
||||
totalCompressedTokens += estimateTokensForStats(contentStr);
|
||||
totalCompressedTokens += estimateCompressionTokens(contentStr);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (!config.compressRoles.includes(msg.role as "user" | "assistant" | "system")) {
|
||||
totalCompressedTokens += estimateTokensForStats(contentStr);
|
||||
totalCompressedTokens += estimateCompressionTokens(contentStr);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Step 1: Extract preserved blocks
|
||||
const { text: extractedText, blocks } = extractPreservedBlocks(contentStr);
|
||||
|
||||
// Step 2: Apply rules by context
|
||||
const rules = getRulesForContext(msg.role).filter(
|
||||
(rule) => !config.skipRules.includes(rule.name)
|
||||
);
|
||||
const { text: rulesApplied, appliedRules } = applyRulesToText(extractedText, rules);
|
||||
allAppliedRules.push(...appliedRules);
|
||||
|
||||
// Step 3: Restore preserved blocks
|
||||
const restored = restorePreservedBlocks(rulesApplied, blocks);
|
||||
|
||||
// Step 4: Cleanup artifacts
|
||||
const cleaned = cleanupArtifacts(restored);
|
||||
|
||||
totalCompressedTokens += estimateTokensForStats(cleaned);
|
||||
totalCompressedTokens += estimateCompressionTokens(cleaned);
|
||||
|
||||
const newContent =
|
||||
typeof msg.content === "string"
|
||||
@@ -132,24 +135,20 @@ export function cavemanCompress(
|
||||
});
|
||||
|
||||
const durationMs = performance.now() - startMs;
|
||||
const savingsPercent =
|
||||
totalOriginalTokens > 0
|
||||
? Math.round(((totalOriginalTokens - totalCompressedTokens) / totalOriginalTokens) * 100)
|
||||
: 0;
|
||||
|
||||
const stats: CompressionStats = {
|
||||
mode: "caveman",
|
||||
originalTokens: totalOriginalTokens,
|
||||
compressedTokens: totalCompressedTokens,
|
||||
savingsPercent,
|
||||
durationMs: Math.round(durationMs * 100) / 100,
|
||||
rulesApplied: [...new Set(allAppliedRules)],
|
||||
};
|
||||
const uniqueRules = [...new Set(allAppliedRules)];
|
||||
const stats = createCompressionStats(
|
||||
body as unknown as Record<string, unknown>,
|
||||
{ ...body, messages: compressedMessages } as unknown as Record<string, unknown>,
|
||||
"standard" as CompressionMode,
|
||||
uniqueRules.length > 0 ? ["caveman-rules"] : [],
|
||||
uniqueRules.length > 0 ? uniqueRules : undefined,
|
||||
Math.round(durationMs * 100) / 100
|
||||
);
|
||||
|
||||
const compressed = totalCompressedTokens < totalOriginalTokens;
|
||||
|
||||
const result: CompressionResult = {
|
||||
body: { ...body, messages: compressedMessages },
|
||||
body: { ...body, messages: compressedMessages } as unknown as Record<string, unknown>,
|
||||
compressed,
|
||||
stats,
|
||||
};
|
||||
|
||||
33
open-sse/services/compression/index.ts
Normal file
33
open-sse/services/compression/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export type {
|
||||
CompressionMode,
|
||||
CompressionConfig,
|
||||
CompressionStats,
|
||||
CompressionResult,
|
||||
CavemanConfig,
|
||||
CavemanRule,
|
||||
} from "./types.ts";
|
||||
|
||||
export { DEFAULT_COMPRESSION_CONFIG, DEFAULT_CAVEMAN_CONFIG } from "./types.ts";
|
||||
|
||||
export { cavemanCompress, applyRulesToText } from "./caveman.ts";
|
||||
|
||||
export { getRulesForContext, CAVEMAN_RULES } from "./cavemanRules.ts";
|
||||
|
||||
export { extractPreservedBlocks, restorePreservedBlocks } from "./preservation.ts";
|
||||
|
||||
export {
|
||||
estimateCompressionTokens,
|
||||
createCompressionStats,
|
||||
trackCompressionStats,
|
||||
getDefaultCompressionConfig,
|
||||
} from "./stats.ts";
|
||||
|
||||
export {
|
||||
selectCompressionStrategy,
|
||||
applyCompression,
|
||||
getEffectiveMode,
|
||||
checkComboOverride,
|
||||
shouldAutoTrigger,
|
||||
} from "./strategySelector.ts";
|
||||
|
||||
export { applyLiteCompression } from "./lite.ts";
|
||||
48
open-sse/services/compression/lite.ts
Normal file
48
open-sse/services/compression/lite.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Lite compression — minimal prompt compression for Phase 1.
|
||||
* Strips trailing whitespace, collapses multiple blank lines,
|
||||
* and trims system prompts when auto-triggered.
|
||||
*/
|
||||
import type { CompressionConfig, CompressionMode, CompressionResult } from "./types.ts";
|
||||
import { createCompressionStats, estimateCompressionTokens } from "./stats.ts";
|
||||
|
||||
export function applyLiteCompression(
|
||||
body: Record<string, unknown>,
|
||||
_options?: { model?: string; config?: CompressionConfig }
|
||||
): CompressionResult {
|
||||
if (!body.messages || !Array.isArray(body.messages)) {
|
||||
return { body, compressed: false, stats: null };
|
||||
}
|
||||
|
||||
let modified = false;
|
||||
const messages = body.messages as Array<{ role?: string; content?: string | unknown }>;
|
||||
|
||||
const compressedMessages = messages.map((msg) => {
|
||||
if (typeof msg.content !== "string" || !msg.content) return msg;
|
||||
|
||||
let content = msg.content;
|
||||
|
||||
const trimmed = content
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.replace(/ +$/gm, "")
|
||||
.trimEnd();
|
||||
if (trimmed !== content) {
|
||||
modified = true;
|
||||
content = trimmed;
|
||||
}
|
||||
|
||||
return { ...msg, content };
|
||||
});
|
||||
|
||||
if (!modified) {
|
||||
return { body, compressed: false, stats: null };
|
||||
}
|
||||
|
||||
const compressedBody = { ...body, messages: compressedMessages };
|
||||
const stats = createCompressionStats(body, compressedBody, "lite" as CompressionMode, [
|
||||
"whitespace-collapse",
|
||||
"blank-line-reduction",
|
||||
]);
|
||||
|
||||
return { body: compressedBody, compressed: true, stats };
|
||||
}
|
||||
@@ -2,48 +2,59 @@ import {
|
||||
type CompressionMode,
|
||||
type CompressionStats,
|
||||
type CompressionConfig,
|
||||
type CompressionResult,
|
||||
DEFAULT_COMPRESSION_CONFIG,
|
||||
DEFAULT_CAVEMAN_CONFIG,
|
||||
} from "./types.ts";
|
||||
|
||||
const CHARS_PER_TOKEN = 4;
|
||||
|
||||
export function estimateTokensForStats(text: string | object | null | undefined): number {
|
||||
export function estimateCompressionTokens(text: string | object | null | undefined): number {
|
||||
if (!text) return 0;
|
||||
const str = typeof text === "string" ? text : JSON.stringify(text);
|
||||
return Math.ceil(str.length / CHARS_PER_TOKEN);
|
||||
}
|
||||
|
||||
/** @deprecated Use estimateCompressionTokens instead */
|
||||
export const estimateTokensForStats = estimateCompressionTokens;
|
||||
|
||||
export function createCompressionStats(
|
||||
originalTokens: number,
|
||||
compressedTokens: number,
|
||||
mode: string,
|
||||
rulesApplied?: string[]
|
||||
originalBody: Record<string, unknown>,
|
||||
compressedBody: Record<string, unknown>,
|
||||
mode: CompressionMode,
|
||||
techniquesUsed: string[],
|
||||
rulesApplied?: string[],
|
||||
durationMs?: number
|
||||
): CompressionStats {
|
||||
const originalTokens = estimateCompressionTokens(originalBody);
|
||||
const compressedTokens = estimateCompressionTokens(compressedBody);
|
||||
const savingsPercent =
|
||||
originalTokens > 0
|
||||
? Math.round(((originalTokens - compressedTokens) / originalTokens) * 100)
|
||||
? Math.round(((originalTokens - compressedTokens) / originalTokens) * 10000) / 100
|
||||
: 0;
|
||||
return {
|
||||
mode,
|
||||
originalTokens,
|
||||
compressedTokens,
|
||||
savingsPercent,
|
||||
durationMs: 0,
|
||||
...(rulesApplied ? { rulesApplied } : {}),
|
||||
techniquesUsed,
|
||||
mode,
|
||||
timestamp: Date.now(),
|
||||
...(rulesApplied && rulesApplied.length > 0 ? { rulesApplied } : {}),
|
||||
...(durationMs !== undefined ? { durationMs } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function trackCompressionStats(stats: CompressionStats): void {
|
||||
if (process.env.COMPRESSION_DEBUG === "1") {
|
||||
console.log("[compression]", JSON.stringify(stats));
|
||||
}
|
||||
if (stats.originalTokens <= 0) return;
|
||||
const rulesInfo = stats.rulesApplied?.length ? ` rules=${stats.rulesApplied.join(",")}` : "";
|
||||
const durationInfo = stats.durationMs !== undefined ? ` ${stats.durationMs}ms` : "";
|
||||
console.log(
|
||||
`[COMPRESSION] mode=${stats.mode} tokens=${stats.originalTokens}->${stats.compressedTokens} savings=${stats.savingsPercent}% techniques=${stats.techniquesUsed.join(",")}${rulesInfo}${durationInfo}`
|
||||
);
|
||||
}
|
||||
|
||||
export function getDefaultCompressionConfig(): CompressionConfig {
|
||||
return {
|
||||
mode: "off",
|
||||
enabled: false,
|
||||
...DEFAULT_COMPRESSION_CONFIG,
|
||||
cavemanConfig: { ...DEFAULT_CAVEMAN_CONFIG },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,42 +1,59 @@
|
||||
import { type CompressionMode, type CompressionConfig, type CompressionResult } from "./types.ts";
|
||||
import { createCompressionStats } from "./stats.ts";
|
||||
import type { CompressionConfig, CompressionMode, CompressionResult } from "./types.ts";
|
||||
import { applyLiteCompression } from "./lite.ts";
|
||||
import { cavemanCompress } from "./caveman.ts";
|
||||
|
||||
export function selectCompressionStrategy(
|
||||
config: CompressionConfig | null,
|
||||
_body: unknown,
|
||||
_tokenCount: number,
|
||||
_provider: string
|
||||
): CompressionMode {
|
||||
if (!config || !config.enabled) return "off";
|
||||
return config.mode || "caveman";
|
||||
export function checkComboOverride(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null
|
||||
): CompressionMode | null {
|
||||
if (!comboId || !config.comboOverrides) return null;
|
||||
return config.comboOverrides[comboId] ?? null;
|
||||
}
|
||||
|
||||
export function applyCompression(
|
||||
body: unknown,
|
||||
mode: CompressionMode,
|
||||
config: CompressionConfig | null
|
||||
): CompressionResult {
|
||||
if (mode === "off" || !config?.enabled) {
|
||||
return { body, compressed: false, stats: createCompressionStats(0, 0, "off") };
|
||||
}
|
||||
if (mode === "caveman" && config.cavemanConfig) {
|
||||
return cavemanCompress(body as Parameters<typeof cavemanCompress>[0], config.cavemanConfig);
|
||||
}
|
||||
return { body, compressed: false, stats: createCompressionStats(0, 0, mode) };
|
||||
export function shouldAutoTrigger(config: CompressionConfig, estimatedTokens: number): boolean {
|
||||
return config.autoTriggerTokens > 0 && estimatedTokens >= config.autoTriggerTokens;
|
||||
}
|
||||
|
||||
export function getEffectiveMode(
|
||||
comboOverride: string | null,
|
||||
_autoTrigger: boolean,
|
||||
defaultMode: string
|
||||
config: CompressionConfig,
|
||||
comboId: string | null,
|
||||
estimatedTokens: number
|
||||
): CompressionMode {
|
||||
if (comboOverride && ["off", "lite", "caveman", "aggressive", "ultra"].includes(comboOverride)) {
|
||||
return comboOverride as CompressionMode;
|
||||
if (!config.enabled) return "off";
|
||||
|
||||
const comboMode = checkComboOverride(config, comboId);
|
||||
if (comboMode) return comboMode;
|
||||
|
||||
if (shouldAutoTrigger(config, estimatedTokens)) return "lite";
|
||||
|
||||
return config.defaultMode;
|
||||
}
|
||||
|
||||
export function selectCompressionStrategy(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null,
|
||||
estimatedTokens: number
|
||||
): CompressionMode {
|
||||
return getEffectiveMode(config, comboId, estimatedTokens);
|
||||
}
|
||||
|
||||
export function applyCompression(
|
||||
body: Record<string, unknown>,
|
||||
mode: CompressionMode,
|
||||
options?: { model?: string; config?: CompressionConfig }
|
||||
): CompressionResult {
|
||||
if (mode === "off") {
|
||||
return { body, compressed: false, stats: null };
|
||||
}
|
||||
if (defaultMode === "standard") return "caveman";
|
||||
if (["off", "lite", "caveman", "aggressive", "ultra"].includes(defaultMode)) {
|
||||
return defaultMode as CompressionMode;
|
||||
if (mode === "lite") {
|
||||
return applyLiteCompression(body, options);
|
||||
}
|
||||
return "off";
|
||||
if (mode === "standard") {
|
||||
const cavemanConfig = options?.config?.cavemanConfig;
|
||||
if (cavemanConfig) {
|
||||
return cavemanCompress(body as Parameters<typeof cavemanCompress>[0], cavemanConfig);
|
||||
}
|
||||
return { body, compressed: false, stats: null };
|
||||
}
|
||||
return { body, compressed: false, stats: null };
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/**
|
||||
* Compression Types — Phase 1 + Phase 2 (Caveman)
|
||||
* Compression Pipeline Types — Phase 1 (Lite) + Phase 2 (Standard/Caveman)
|
||||
*
|
||||
* Shared type definitions for the compression pipeline.
|
||||
* No implementation logic — types only.
|
||||
* Phase 1: 'off' and 'lite' modes.
|
||||
* Phase 2: 'standard' mode (caveman engine).
|
||||
*/
|
||||
|
||||
/** Supported compression modes */
|
||||
export type CompressionMode = "off" | "lite" | "caveman" | "aggressive" | "ultra";
|
||||
/** Compression mode levels */
|
||||
export type CompressionMode = "off" | "lite" | "standard" | "aggressive" | "ultra";
|
||||
|
||||
/** A single caveman compression rule */
|
||||
/** A single caveman compression rule (Phase 2) */
|
||||
export interface CavemanRule {
|
||||
name: string;
|
||||
pattern: RegExp;
|
||||
@@ -17,7 +18,7 @@ export interface CavemanRule {
|
||||
preservePatterns?: RegExp[];
|
||||
}
|
||||
|
||||
/** Configuration for the caveman compression engine */
|
||||
/** Configuration for the caveman compression engine (Phase 2) */
|
||||
export interface CavemanConfig {
|
||||
enabled: boolean;
|
||||
compressRoles: ("user" | "assistant" | "system")[];
|
||||
@@ -26,31 +27,47 @@ export interface CavemanConfig {
|
||||
preservePatterns: string[];
|
||||
}
|
||||
|
||||
/** Statistics for a single compression operation */
|
||||
/** Per-request compression statistics */
|
||||
export interface CompressionStats {
|
||||
mode: string;
|
||||
originalTokens: number;
|
||||
compressedTokens: number;
|
||||
savingsPercent: number;
|
||||
durationMs: number;
|
||||
techniquesUsed: string[];
|
||||
mode: CompressionMode;
|
||||
timestamp: number;
|
||||
rulesApplied?: string[];
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
/** Result of a compression operation */
|
||||
export interface CompressionResult {
|
||||
body: unknown;
|
||||
body: Record<string, unknown>;
|
||||
compressed: boolean;
|
||||
stats: CompressionStats;
|
||||
stats: CompressionStats | null;
|
||||
}
|
||||
|
||||
/** Top-level compression configuration */
|
||||
/** Compression configuration stored in DB */
|
||||
export interface CompressionConfig {
|
||||
mode: CompressionMode;
|
||||
enabled: boolean;
|
||||
defaultMode: CompressionMode;
|
||||
autoTriggerTokens: number;
|
||||
cacheMinutes: number;
|
||||
preserveSystemPrompt: boolean;
|
||||
comboOverrides: Record<string, CompressionMode>;
|
||||
cavemanConfig?: CavemanConfig;
|
||||
}
|
||||
|
||||
/** Default caveman configuration */
|
||||
/** Default compression config values */
|
||||
export const DEFAULT_COMPRESSION_CONFIG: CompressionConfig = {
|
||||
enabled: false,
|
||||
defaultMode: "off",
|
||||
autoTriggerTokens: 0,
|
||||
cacheMinutes: 5,
|
||||
preserveSystemPrompt: true,
|
||||
comboOverrides: {},
|
||||
};
|
||||
|
||||
/** Default caveman configuration (Phase 2) */
|
||||
export const DEFAULT_CAVEMAN_CONFIG: CavemanConfig = {
|
||||
enabled: true,
|
||||
compressRoles: ["user"],
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { getDbInstance } from "./core.ts";
|
||||
import { invalidateDbCache } from "./readCache.ts";
|
||||
import { DEFAULT_CAVEMAN_CONFIG } from "../../../open-sse/services/compression/types.ts";
|
||||
import type { CavemanConfig } from "../../../open-sse/services/compression/types.ts";
|
||||
import {
|
||||
DEFAULT_COMPRESSION_CONFIG,
|
||||
DEFAULT_CAVEMAN_CONFIG,
|
||||
} from "../../../open-sse/services/compression/types.ts";
|
||||
import type {
|
||||
CompressionConfig,
|
||||
CavemanConfig,
|
||||
CompressionMode,
|
||||
} from "../../../open-sse/services/compression/types.ts";
|
||||
|
||||
const NAMESPACE = "compression";
|
||||
|
||||
@@ -11,19 +18,21 @@ function toRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === "object" ? (value as JsonRecord) : {};
|
||||
}
|
||||
|
||||
export interface CompressionSettings {
|
||||
mode: string;
|
||||
enabled: boolean;
|
||||
cavemanConfig: CavemanConfig;
|
||||
function parseJsonSafe(raw: string | null): unknown {
|
||||
if (!raw) return undefined;
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCompressionSettings(): CompressionSettings {
|
||||
export function getCompressionSettings(): CompressionConfig {
|
||||
const db = getDbInstance();
|
||||
const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = ?").all(NAMESPACE);
|
||||
|
||||
const defaults: CompressionSettings = {
|
||||
mode: "off",
|
||||
enabled: false,
|
||||
const config: CompressionConfig = {
|
||||
...DEFAULT_COMPRESSION_CONFIG,
|
||||
cavemanConfig: { ...DEFAULT_CAVEMAN_CONFIG },
|
||||
};
|
||||
|
||||
@@ -32,18 +41,44 @@ export function getCompressionSettings(): CompressionSettings {
|
||||
const key = typeof record.key === "string" ? record.key : null;
|
||||
const rawValue = typeof record.value === "string" ? record.value : null;
|
||||
if (!key || !rawValue) continue;
|
||||
try {
|
||||
const parsed = JSON.parse(rawValue);
|
||||
if (key === "mode") defaults.mode = parsed;
|
||||
else if (key === "enabled") defaults.enabled = parsed;
|
||||
else if (key === "cavemanConfig")
|
||||
defaults.cavemanConfig = { ...DEFAULT_CAVEMAN_CONFIG, ...parsed };
|
||||
} catch {
|
||||
// skip malformed JSON
|
||||
const parsed = parseJsonSafe(rawValue);
|
||||
if (parsed === undefined) continue;
|
||||
|
||||
switch (key) {
|
||||
case "enabled":
|
||||
config.enabled = !!parsed;
|
||||
break;
|
||||
case "defaultMode":
|
||||
if (["off", "lite", "standard", "aggressive", "ultra"].includes(parsed as string)) {
|
||||
config.defaultMode = parsed as CompressionMode;
|
||||
}
|
||||
break;
|
||||
case "autoTriggerTokens":
|
||||
config.autoTriggerTokens = typeof parsed === "number" ? parsed : 0;
|
||||
break;
|
||||
case "cacheMinutes":
|
||||
config.cacheMinutes = typeof parsed === "number" ? parsed : 5;
|
||||
break;
|
||||
case "preserveSystemPrompt":
|
||||
config.preserveSystemPrompt = !!parsed;
|
||||
break;
|
||||
case "comboOverrides":
|
||||
if (typeof parsed === "object" && parsed !== null) {
|
||||
config.comboOverrides = parsed as Record<string, CompressionMode>;
|
||||
}
|
||||
break;
|
||||
case "cavemanConfig":
|
||||
if (typeof parsed === "object" && parsed !== null) {
|
||||
config.cavemanConfig = {
|
||||
...DEFAULT_CAVEMAN_CONFIG,
|
||||
...(parsed as Partial<CavemanConfig>),
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return defaults;
|
||||
return config;
|
||||
}
|
||||
|
||||
export function updateCompressionSettings(settings: Record<string, unknown>): void {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
-- 028: Acknowledge caveman compression test suite
|
||||
-- 030: Acknowledge caveman compression test suite
|
||||
-- No schema changes required. Tests live in tests/unit/compression/ and tests/golden-set/.
|
||||
SELECT 1;
|
||||
@@ -4,7 +4,7 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { cavemanCompress } from "../../open-sse/services/compression/caveman.ts";
|
||||
import { estimateTokensForStats } from "../../open-sse/services/compression/stats.ts";
|
||||
import { estimateCompressionTokens } from "../../open-sse/services/compression/stats.ts";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -19,7 +19,7 @@ function compressText(text: string): {
|
||||
originalTokens: number;
|
||||
compressedTokens: number;
|
||||
} {
|
||||
const originalTokens = estimateTokensForStats(text);
|
||||
const originalTokens = estimateCompressionTokens(text);
|
||||
const result = cavemanCompress(
|
||||
{ messages: [{ role: "user", content: text }] },
|
||||
{ enabled: true, compressRoles: ["user"] }
|
||||
@@ -29,7 +29,7 @@ function compressText(text: string): {
|
||||
const messages = (result.body as { messages?: { content?: string }[] }).messages;
|
||||
compressed = messages?.[0]?.content ?? text;
|
||||
}
|
||||
const compressedTokens = estimateTokensForStats(compressed);
|
||||
const compressedTokens = estimateCompressionTokens(compressed);
|
||||
return { compressed, originalTokens, compressedTokens };
|
||||
}
|
||||
|
||||
|
||||
@@ -9,31 +9,33 @@ import type { CavemanConfig } from "../../../open-sse/services/compression/types
|
||||
|
||||
describe("compression DB module", () => {
|
||||
beforeEach(() => {
|
||||
// Clean up compression namespace before each test
|
||||
const db = getDbInstance();
|
||||
db.prepare("DELETE FROM key_value WHERE namespace = ?").run("compression");
|
||||
});
|
||||
|
||||
it("should return default settings", () => {
|
||||
const settings = getCompressionSettings();
|
||||
assert.equal(settings.mode, "off");
|
||||
assert.equal(settings.enabled, false);
|
||||
assert.ok(settings.cavemanConfig);
|
||||
assert.equal(settings.cavemanConfig.enabled, true);
|
||||
assert.deepEqual(settings.cavemanConfig.compressRoles, ["user"]);
|
||||
assert.equal(settings.cavemanConfig.minMessageLength, 50);
|
||||
it("should return default config", () => {
|
||||
const config = getCompressionSettings();
|
||||
assert.equal(config.defaultMode, "off");
|
||||
assert.equal(config.enabled, false);
|
||||
assert.equal(config.autoTriggerTokens, 0);
|
||||
assert.equal(config.cacheMinutes, 5);
|
||||
assert.equal(config.preserveSystemPrompt, true);
|
||||
assert.ok(config.cavemanConfig);
|
||||
assert.equal(config.cavemanConfig.enabled, true);
|
||||
assert.deepEqual(config.cavemanConfig.compressRoles, ["user"]);
|
||||
assert.equal(config.cavemanConfig.minMessageLength, 50);
|
||||
});
|
||||
|
||||
it("should update and retrieve settings", () => {
|
||||
updateCompressionSettings({ enabled: true, mode: "caveman" });
|
||||
const settings = getCompressionSettings();
|
||||
assert.equal(settings.enabled, true);
|
||||
assert.equal(settings.mode, "caveman");
|
||||
updateCompressionSettings({ enabled: true, defaultMode: "standard" });
|
||||
const config = getCompressionSettings();
|
||||
assert.equal(config.enabled, true);
|
||||
assert.equal(config.defaultMode, "standard");
|
||||
|
||||
updateCompressionSettings({ enabled: false, mode: "off" });
|
||||
updateCompressionSettings({ enabled: false, defaultMode: "off" });
|
||||
const reset = getCompressionSettings();
|
||||
assert.equal(reset.enabled, false);
|
||||
assert.equal(reset.mode, "off");
|
||||
assert.equal(reset.defaultMode, "off");
|
||||
});
|
||||
|
||||
it("should update cavemanConfig", () => {
|
||||
@@ -43,8 +45,8 @@ describe("compression DB module", () => {
|
||||
minMessageLength: 100,
|
||||
};
|
||||
updateCompressionSettings({ cavemanConfig: customConfig });
|
||||
const settings = getCompressionSettings();
|
||||
assert.deepEqual(settings.cavemanConfig.compressRoles, ["user", "system"]);
|
||||
assert.equal(settings.cavemanConfig.minMessageLength, 100);
|
||||
const config = getCompressionSettings();
|
||||
assert.deepEqual(config.cavemanConfig.compressRoles, ["user", "system"]);
|
||||
assert.equal(config.cavemanConfig.minMessageLength, 100);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user