feat(compression): expose rule intensities and track usd savings

Add estimated USD savings to compression analytics so saved tokens can
be reported in cost terms alongside existing token metrics.

Expose caveman rule intensity metadata for settings consumers and add a
settings API route alias for rule lookup. Also preserve system prompts
when aggressive compression falls back to lite mode.
This commit is contained in:
diegosouzapw
2026-05-02 12:57:37 -03:00
parent 0f35c148ea
commit aa40bc34f5
8 changed files with 70 additions and 7 deletions

View File

@@ -1689,6 +1689,18 @@ export async function handleChatCore({
try {
const { insertCompressionAnalyticsRow } =
await import("../../src/lib/db/compressionAnalytics.ts");
const { calculateCost } = await import("../../src/lib/usage/costCalculator.ts");
const tokensSaved = Math.max(
0,
result.stats.originalTokens - result.stats.compressedTokens
);
const estimatedUsdSaved = await calculateCost(
provider ?? "",
effectiveModel ?? "",
{
input: tokensSaved,
}
);
insertCompressionAnalyticsRow({
timestamp: new Date().toISOString(),
combo_id: comboName ?? null,
@@ -1696,12 +1708,10 @@ export async function handleChatCore({
mode,
original_tokens: result.stats.originalTokens,
compressed_tokens: result.stats.compressedTokens,
tokens_saved: Math.max(
0,
result.stats.originalTokens - result.stats.compressedTokens
),
tokens_saved: tokensSaved,
duration_ms: result.stats.durationMs ?? null,
request_id: skillRequestId,
estimated_usd_saved: estimatedUsdSaved || null,
validation_fallback: result.stats.fallbackApplied ? 1 : 0,
output_mode: cavemanOutputModeApplied ? cavemanOutputModeIntensity : null,
});

View File

@@ -144,7 +144,10 @@ export function compressAggressive(
}
try {
const liteResult = applyLiteCompression({ messages: currentMessages });
const liteResult = applyLiteCompression(
{ messages: currentMessages },
{ preserveSystemPrompt: cfg.preserveSystemPrompt !== false }
);
if (liteResult?.compressed && liteResult.stats) {
const liteSavings = liteResult.stats.savingsPercent ?? 0;
if (liteSavings > resultStats.savingsPercent) {

View File

@@ -406,11 +406,15 @@ export function getRuleByName(name: string): CavemanRule | undefined {
}
export function getCavemanRuleMetadata() {
const intensities = ["lite", "full", "ultra"] as const;
return CAVEMAN_RULES.map((rule) => ({
name: rule.name,
context: rule.context,
category: rule.category ?? "terse",
minIntensity: rule.minIntensity ?? "lite",
intensities: intensities.filter(
(intensity) => INTENSITY_RANK[intensity] >= INTENSITY_RANK[rule.minIntensity ?? "lite"]
),
description: rule.description ?? rule.name.replace(/_/g, " "),
}));
}

View File

@@ -70,6 +70,7 @@ interface RuleMetadata {
category: string;
context: string;
minIntensity: CavemanIntensity;
intensities?: CavemanIntensity[];
description: string;
}
@@ -498,7 +499,7 @@ export default function CompressionSettingsTab() {
<button
key={rule.name}
onClick={() => toggleCavemanRule(rule.name)}
title={`${rule.category} · ${rule.context} · ${rule.minIntensity}`}
title={`${rule.category} · ${rule.context} · ${(rule.intensities ?? [rule.minIntensity]).join("/")}`}
className={`px-2 py-1 rounded text-xs border transition-all ${
config.cavemanConfig!.skipRules.includes(rule.name)
? "border-red-500/50 bg-red-500/10 text-red-400 line-through"

View File

@@ -0,0 +1 @@
export { GET } from "@/app/api/compression/rules/route";

View File

@@ -1,7 +1,10 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { cavemanCompress } from "../../../open-sse/services/compression/caveman.ts";
import { getRuleByName } from "../../../open-sse/services/compression/cavemanRules.ts";
import {
getCavemanRuleMetadata,
getRuleByName,
} from "../../../open-sse/services/compression/cavemanRules.ts";
import { applyCompression } from "../../../open-sse/services/compression/strategySelector.ts";
function compress(content: string, options = {}) {
@@ -132,6 +135,7 @@ describe("Caveman v3.7.9 rule parity", () => {
});
it("exposes metadata for new rule names", () => {
const metadata = getCavemanRuleMetadata();
for (const ruleName of [
"articles",
"pleasantries",
@@ -140,6 +144,9 @@ describe("Caveman v3.7.9 rule parity", () => {
"ultra_abbreviations",
]) {
assert.ok(getRuleByName(ruleName), `missing rule ${ruleName}`);
const rule = metadata.find((entry) => entry.name === ruleName);
assert.ok(rule?.category, `missing metadata category for ${ruleName}`);
assert.ok(rule?.intensities?.length, `missing metadata intensities for ${ruleName}`);
}
});
});

View File

@@ -148,6 +148,26 @@ describe("Integration: strategySelector → aggressive pipeline", () => {
assert.equal(systemMsg!.content, "You are a helpful coding assistant.");
});
it("compressAggressive preserves duplicate system prompts during lite fallback", () => {
const messages = [
{ role: "system", content: "Policy exact. Keep exact." },
{ role: "system", content: "Policy exact. Keep exact." },
{ role: "user", content: "Short" },
];
const result = compressAggressive(messages, {
preserveSystemPrompt: true,
minSavingsThreshold: 0.9,
summarizerEnabled: false,
maxTokensPerMessage: 999999,
});
assert.deepEqual(
result.messages.filter((message) => message.role === "system"),
messages.filter((message) => message.role === "system")
);
});
it("compressAggressive downgrade chain works when aggressive fails", () => {
// Empty messages should trigger downgrade gracefully
const result = compressAggressive([]);

View File

@@ -293,6 +293,23 @@ describe("compressionAnalytics", () => {
assert.equal(summary.realUsage.bySource.provider, 1);
});
it("aggregates estimated USD savings separately from token estimates", () => {
insertCompressionAnalyticsRow({
timestamp: new Date().toISOString(),
mode: "standard",
original_tokens: 1000,
compressed_tokens: 700,
tokens_saved: 300,
request_id: "req-usd",
estimated_usd_saved: 0.0015,
});
attachCompressionUsageReceipt("req-usd", { prompt_tokens: 700, total_tokens: 700 }, "provider");
const summary = getCompressionAnalyticsSummary();
assert.equal(summary.realUsage.requestsWithReceipts, 1);
assert.equal(summary.realUsage.estimatedUsdSaved, 0.0015);
});
it("summarizes validation fallback and output mode rows", () => {
insertCompressionAnalyticsRow({
timestamp: new Date().toISOString(),