From a7233d1bf1a6478e044945035d0e706e2d1d037d Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sun, 3 May 2026 12:38:21 -0300 Subject: [PATCH] feat(compression): unify config validation and persist MCP savings Centralize compression config schemas across settings, preview, RTK, and combo APIs to enforce consistent validation for stacked pipelines and engine-specific options. Expand caveman and stacked compression behavior by applying default combos at runtime, surfacing validation and fallback metadata, and exposing aggressive and ultra adapter schemas for configuration UI and tests. Persist MCP description compression snapshots into analytics without counting them as provider usage, and extend the dashboard with the new RTK controls and localized labels. --- open-sse/handlers/chatCore.ts | 172 +++++++++++------ open-sse/mcp-server/descriptionCompressor.ts | 71 +++++++ open-sse/mcp-server/tools/compressionTools.ts | 11 +- .../compression/engines/cavemanAdapter.ts | 174 +++++++++++++++++- .../services/compression/strategySelector.ts | 15 ++ .../caveman/CavemanContextPageClient.tsx | 2 +- .../context/rtk/RtkContextPageClient.tsx | 52 +++++- src/app/api/compression/preview/route.ts | 50 +---- src/app/api/context/combos/[id]/route.ts | 14 +- src/app/api/context/combos/route.ts | 14 +- src/app/api/context/rtk/config/route.ts | 21 +-- src/app/api/context/rtk/test/route.ts | 3 +- src/app/api/settings/compression/route.ts | 126 +------------ src/i18n/messages/en.json | 9 + src/i18n/messages/pt-BR.json | 9 + src/lib/db/compressionAnalytics.ts | 17 ++ .../validation/compressionConfigSchemas.ts | 165 +++++++++++++++++ .../chatcore-compression-integration.test.ts | 94 +++++++++- .../compression-preview-api.test.ts | 12 ++ .../compression/compressionAnalytics.test.ts | 22 +++ .../compression/compressionMcpTools.test.ts | 22 +++ .../context-compression-api.test.ts | 14 ++ .../unit/compression/engine-registry.test.ts | 14 +- 23 files changed, 825 insertions(+), 278 deletions(-) create mode 100644 src/shared/validation/compressionConfigSchemas.ts diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 99564b41f7..fac0c99448 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1596,6 +1596,86 @@ export async function handleChatCore({ log?.debug?.("COMPRESSION", "Prompt compression disabled or unavailable"); } let compressionComboKey = comboName ?? null; + let compressionComboApplied = false; + type RuntimeCompressionCombo = { + id: string; + pipeline: NonNullable; + languagePacks: string[]; + outputMode: boolean; + outputModeIntensity: string; + }; + const isBuiltinStackedPipeline = ( + pipeline: CompressionConfig["stackedPipeline"] | undefined + ): boolean => { + if (!Array.isArray(pipeline) || pipeline.length !== 2) return false; + const [first, second] = pipeline; + return ( + first?.engine === "rtk" && + (first.intensity === undefined || first.intensity === "standard") && + !first.config && + second?.engine === "caveman" && + (second.intensity === undefined || second.intensity === "full") && + !second.config + ); + }; + const applyCompressionComboConfig = ( + compressionCombo: RuntimeCompressionCombo | null, + routingOverrideIds: string[] = [] + ): boolean => { + if (!compressionCombo || compressionCombo.pipeline.length === 0) return false; + const comboLanguagePacks = [ + ...new Set( + compressionCombo.languagePacks + .map((pack) => pack.trim()) + .filter((pack) => pack.length > 0) + ), + ]; + const comboOutputIntensity = ( + ["lite", "full", "ultra"].includes(compressionCombo.outputModeIntensity) + ? compressionCombo.outputModeIntensity + : (config.cavemanOutputMode?.intensity ?? "full") + ) as "lite" | "full" | "ultra"; + const comboDefaultLanguage = + comboLanguagePacks.find((pack) => pack === config.languageConfig?.defaultLanguage) ?? + comboLanguagePacks[0] ?? + config.languageConfig?.defaultLanguage ?? + "en"; + const comboOverrides = { ...(config.comboOverrides ?? {}) }; + for (const id of routingOverrideIds) { + if (id) comboOverrides[id] = "stacked"; + } + config = { + ...config, + compressionComboId: compressionCombo.id, + stackedPipeline: compressionCombo.pipeline, + languageConfig: { + ...(config.languageConfig ?? { + enabled: false, + defaultLanguage: "en", + autoDetect: true, + enabledPacks: ["en"], + }), + enabled: true, + defaultLanguage: comboDefaultLanguage, + enabledPacks: + comboLanguagePacks.length > 0 + ? comboLanguagePacks + : (config.languageConfig?.enabledPacks ?? ["en"]), + }, + cavemanOutputMode: { + ...(config.cavemanOutputMode ?? { + enabled: false, + intensity: "full", + autoClarity: true, + }), + enabled: compressionCombo.outputMode, + intensity: comboOutputIntensity, + }, + comboOverrides, + }; + compressionComboApplied = true; + return true; + }; if (isCombo && comboName) { try { const { getComboByName } = await import("../../src/lib/localDb"); @@ -1644,59 +1724,12 @@ export async function handleChatCore({ routingComboIds .map((id) => getCompressionComboForRoutingCombo(id)) .find((combo) => combo !== null) ?? null; - if (assignedCompressionCombo && assignedCompressionCombo.pipeline.length > 0) { - const comboLanguagePacks = [ - ...new Set( - assignedCompressionCombo.languagePacks - .map((pack) => pack.trim()) - .filter((pack) => pack.length > 0) - ), - ]; - const comboOutputIntensity = ( - ["lite", "full", "ultra"].includes(assignedCompressionCombo.outputModeIntensity) - ? assignedCompressionCombo.outputModeIntensity - : (config.cavemanOutputMode?.intensity ?? "full") - ) as "lite" | "full" | "ultra"; - const comboDefaultLanguage = - comboLanguagePacks.find( - (pack) => pack === config.languageConfig?.defaultLanguage - ) ?? - comboLanguagePacks[0] ?? - config.languageConfig?.defaultLanguage ?? - "en"; - config = { - ...config, - compressionComboId: assignedCompressionCombo.id, - stackedPipeline: assignedCompressionCombo.pipeline, - languageConfig: { - ...(config.languageConfig ?? { - enabled: false, - defaultLanguage: "en", - autoDetect: true, - enabledPacks: ["en"], - }), - enabled: true, - defaultLanguage: comboDefaultLanguage, - enabledPacks: - comboLanguagePacks.length > 0 - ? comboLanguagePacks - : (config.languageConfig?.enabledPacks ?? ["en"]), - }, - cavemanOutputMode: { - ...(config.cavemanOutputMode ?? { - enabled: false, - intensity: "full", - autoClarity: true, - }), - enabled: assignedCompressionCombo.outputMode, - intensity: comboOutputIntensity, - }, - comboOverrides: { - ...(config.comboOverrides ?? {}), - ...(comboName ? { [comboName]: "stacked" as const } : {}), - ...(comboConfig?.id ? { [comboConfig.id]: "stacked" as const } : {}), - }, - }; + if ( + applyCompressionComboConfig( + assignedCompressionCombo as RuntimeCompressionCombo | null, + routingComboIds + ) + ) { compressionComboKey = comboName; } } @@ -1708,6 +1741,39 @@ export async function handleChatCore({ ); } } + const modeBeforeOutputTransform = selectCompressionStrategy( + config, + compressionComboKey, + estimatedTokens, + body as Record, + { provider, targetFormat, model: effectiveModel } + ); + if ( + modeBeforeOutputTransform === "stacked" && + !compressionComboApplied && + !config.compressionComboId && + isBuiltinStackedPipeline(config.stackedPipeline) + ) { + try { + const { getDefaultCompressionCombo } = + await import("../../src/lib/db/compressionCombos.ts"); + const defaultCompressionCombo = getDefaultCompressionCombo(); + if ( + applyCompressionComboConfig(defaultCompressionCombo as RuntimeCompressionCombo | null) + ) { + log?.debug?.( + "COMPRESSION", + `Default compression combo applied: ${defaultCompressionCombo?.id}` + ); + } + } catch (err) { + log?.debug?.( + "COMPRESSION", + "Default compression combo lookup skipped: " + + (err instanceof Error ? err.message : String(err)) + ); + } + } if (config.cavemanOutputMode?.enabled) { try { const { applyCavemanOutputMode } = await import("../services/compression/outputMode.ts"); diff --git a/open-sse/mcp-server/descriptionCompressor.ts b/open-sse/mcp-server/descriptionCompressor.ts index e8ad2e173e..b73a5d1e22 100644 --- a/open-sse/mcp-server/descriptionCompressor.ts +++ b/open-sse/mcp-server/descriptionCompressor.ts @@ -32,6 +32,14 @@ const descriptionCompressionStats: McpDescriptionCompressionStats = { estimatedTokensSaved: 0, }; +const persistedDescriptionCompressionStats: McpDescriptionCompressionStats = { + descriptionsCompressed: 0, + charsBefore: 0, + charsAfter: 0, + charsSaved: 0, + estimatedTokensSaved: 0, +}; + const MCP_LIST_CONTAINER_KEYS = new Set(["tools", "prompts", "resources", "resourceTemplates"]); const MCP_METADATA_DESCRIPTION_FIELDS = ["description"]; @@ -163,10 +171,73 @@ export function getMcpDescriptionCompressionStats(): McpDescriptionCompressionSt return { ...descriptionCompressionStats }; } +function getUnpersistedMcpDescriptionCompressionStats(): McpDescriptionCompressionStats { + return { + descriptionsCompressed: + descriptionCompressionStats.descriptionsCompressed - + persistedDescriptionCompressionStats.descriptionsCompressed, + charsBefore: + descriptionCompressionStats.charsBefore - persistedDescriptionCompressionStats.charsBefore, + charsAfter: + descriptionCompressionStats.charsAfter - persistedDescriptionCompressionStats.charsAfter, + charsSaved: + descriptionCompressionStats.charsSaved - persistedDescriptionCompressionStats.charsSaved, + estimatedTokensSaved: + descriptionCompressionStats.estimatedTokensSaved - + persistedDescriptionCompressionStats.estimatedTokensSaved, + }; +} + +export async function snapshotMcpDescriptionCompressionStats(): Promise { + const delta = getUnpersistedMcpDescriptionCompressionStats(); + if ( + delta.descriptionsCompressed <= 0 || + delta.charsSaved <= 0 || + delta.estimatedTokensSaved <= 0 + ) { + return { + descriptionsCompressed: 0, + charsBefore: 0, + charsAfter: 0, + charsSaved: 0, + estimatedTokensSaved: 0, + }; + } + + const originalTokens = Math.max(delta.estimatedTokensSaved, Math.ceil(delta.charsBefore / 4)); + const compressedTokens = Math.max(0, originalTokens - delta.estimatedTokensSaved); + const { insertCompressionAnalyticsRow } = + await import("../../src/lib/db/compressionAnalytics.ts"); + insertCompressionAnalyticsRow({ + timestamp: new Date().toISOString(), + mode: "mcp-description", + engine: "mcp-description", + original_tokens: originalTokens, + compressed_tokens: compressedTokens, + tokens_saved: delta.estimatedTokensSaved, + mcp_description_tokens_saved: delta.estimatedTokensSaved, + }); + + persistedDescriptionCompressionStats.descriptionsCompressed = + descriptionCompressionStats.descriptionsCompressed; + persistedDescriptionCompressionStats.charsBefore = descriptionCompressionStats.charsBefore; + persistedDescriptionCompressionStats.charsAfter = descriptionCompressionStats.charsAfter; + persistedDescriptionCompressionStats.charsSaved = descriptionCompressionStats.charsSaved; + persistedDescriptionCompressionStats.estimatedTokensSaved = + descriptionCompressionStats.estimatedTokensSaved; + + return delta; +} + export function resetMcpDescriptionCompressionStats(): void { descriptionCompressionStats.descriptionsCompressed = 0; descriptionCompressionStats.charsBefore = 0; descriptionCompressionStats.charsAfter = 0; descriptionCompressionStats.charsSaved = 0; descriptionCompressionStats.estimatedTokensSaved = 0; + persistedDescriptionCompressionStats.descriptionsCompressed = 0; + persistedDescriptionCompressionStats.charsBefore = 0; + persistedDescriptionCompressionStats.charsAfter = 0; + persistedDescriptionCompressionStats.charsSaved = 0; + persistedDescriptionCompressionStats.estimatedTokensSaved = 0; } diff --git a/open-sse/mcp-server/tools/compressionTools.ts b/open-sse/mcp-server/tools/compressionTools.ts index 087e78a119..382a977634 100644 --- a/open-sse/mcp-server/tools/compressionTools.ts +++ b/open-sse/mcp-server/tools/compressionTools.ts @@ -15,7 +15,10 @@ import { getCompressionAnalyticsSummary } from "../../../src/lib/db/compressionA import { getCacheStatsSummary } from "../../../src/lib/db/compressionCacheStats.ts"; import { listCompressionCombos } from "../../../src/lib/db/compressionCombos.ts"; import type { McpToolExtraLike } from "../scopeEnforcement.ts"; -import { getMcpDescriptionCompressionStats } from "../descriptionCompressor.ts"; +import { + getMcpDescriptionCompressionStats, + snapshotMcpDescriptionCompressionStats, +} from "../descriptionCompressor.ts"; /** * Handle compression_status tool: return current compression config, analytics, and cache stats @@ -59,6 +62,8 @@ export async function handleCompressionStatus( charsAfter: number; charsSaved: number; estimatedTokensSaved: number; + persistedEstimatedTokensSaved: number; + persistedSnapshots: number; source: "mcp_metadata_estimate"; notProviderUsage: true; }; @@ -73,6 +78,7 @@ export async function handleCompressionStatus( const start = Date.now(); try { const settings = await getCompressionSettings(); + await snapshotMcpDescriptionCompressionStats(); const analyticsSummary = getCompressionAnalyticsSummary(); const mcpDescriptionStats = getMcpDescriptionCompressionStats(); const cacheStats = getCacheStatsSummary(); @@ -107,6 +113,9 @@ export async function handleCompressionStatus( charsAfter: mcpDescriptionStats.charsAfter, charsSaved: mcpDescriptionStats.charsSaved, estimatedTokensSaved: mcpDescriptionStats.estimatedTokensSaved, + persistedEstimatedTokensSaved: + analyticsSummary.mcpDescriptionCompression.estimatedTokensSaved, + persistedSnapshots: analyticsSummary.mcpDescriptionCompression.snapshots, source: "mcp_metadata_estimate" as const, notProviderUsage: true as const, }, diff --git a/open-sse/services/compression/engines/cavemanAdapter.ts b/open-sse/services/compression/engines/cavemanAdapter.ts index 27bb44d87d..51ae2b6548 100644 --- a/open-sse/services/compression/engines/cavemanAdapter.ts +++ b/open-sse/services/compression/engines/cavemanAdapter.ts @@ -3,7 +3,11 @@ import { cavemanCompress } from "../caveman.ts"; import { compressAggressive } from "../aggressive.ts"; import { ultraCompress } from "../ultra.ts"; import { createCompressionStats } from "../stats.ts"; -import type { CavemanIntensity } from "../types.ts"; +import { + DEFAULT_AGGRESSIVE_CONFIG, + DEFAULT_ULTRA_CONFIG, + type CavemanIntensity, +} from "../types.ts"; import type { CompressionEngine, EngineConfigField, EngineValidationResult } from "./types.ts"; const CAVEMAN_INTENSITIES: CavemanIntensity[] = ["lite", "full", "ultra"]; @@ -32,10 +36,116 @@ const CAVEMAN_SCHEMA: EngineConfigField[] = [ }, ]; +const AGGRESSIVE_SCHEMA: EngineConfigField[] = [ + { + key: "summarizerEnabled", + type: "boolean", + label: "Summarizer enabled", + defaultValue: DEFAULT_AGGRESSIVE_CONFIG.summarizerEnabled, + }, + { + key: "maxTokensPerMessage", + type: "number", + label: "Max tokens per message", + defaultValue: DEFAULT_AGGRESSIVE_CONFIG.maxTokensPerMessage, + min: 256, + max: 32768, + }, + { + key: "minSavingsThreshold", + type: "number", + label: "Minimum savings threshold", + defaultValue: DEFAULT_AGGRESSIVE_CONFIG.minSavingsThreshold, + min: 0, + max: 1, + }, + { + key: "preserveSystemPrompt", + type: "boolean", + label: "Preserve system prompt", + defaultValue: true, + }, +]; + +const ULTRA_SCHEMA: EngineConfigField[] = [ + { + key: "enabled", + type: "boolean", + label: "Enabled", + defaultValue: DEFAULT_ULTRA_CONFIG.enabled, + }, + { + key: "compressionRate", + type: "number", + label: "Compression rate", + defaultValue: DEFAULT_ULTRA_CONFIG.compressionRate, + min: 0, + max: 1, + }, + { + key: "minScoreThreshold", + type: "number", + label: "Minimum score threshold", + defaultValue: DEFAULT_ULTRA_CONFIG.minScoreThreshold, + min: 0, + max: 1, + }, + { + key: "slmFallbackToAggressive", + type: "boolean", + label: "Fallback to aggressive", + defaultValue: DEFAULT_ULTRA_CONFIG.slmFallbackToAggressive, + }, + { + key: "modelPath", + type: "string", + label: "Model path", + defaultValue: "", + }, + { + key: "maxTokensPerMessage", + type: "number", + label: "Max tokens per message", + defaultValue: DEFAULT_ULTRA_CONFIG.maxTokensPerMessage, + min: 0, + max: 32768, + }, + { + key: "preserveSystemPrompt", + type: "boolean", + label: "Preserve system prompt", + defaultValue: true, + }, +]; + function ok(): EngineValidationResult { return { valid: true, errors: [] }; } +function isRecord(value: unknown): value is Record { + return !!value && typeof value === "object" && !Array.isArray(value); +} + +function validateBoolean(config: Record, key: string, errors: string[]): void { + if (config[key] !== undefined && typeof config[key] !== "boolean") { + errors.push(`${key} must be a boolean`); + } +} + +function validateNumberRange( + config: Record, + key: string, + min: number, + max: number, + errors: string[] +): void { + const value = config[key]; + if (value === undefined) return; + if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) { + errors.push(`${key} must be a number between ${min} and ${max}`); + } +} + function validateCavemanLikeConfig(config: Record): EngineValidationResult { const errors: string[] = []; if ( @@ -56,6 +166,50 @@ function validateCavemanLikeConfig(config: Record): EngineValid return { valid: errors.length === 0, errors }; } +function validateAggressiveConfig(config: Record): EngineValidationResult { + const errors: string[] = []; + validateBoolean(config, "summarizerEnabled", errors); + validateBoolean(config, "preserveSystemPrompt", errors); + validateNumberRange(config, "maxTokensPerMessage", 256, 32768, errors); + validateNumberRange(config, "minSavingsThreshold", 0, 1, errors); + + if (config.thresholds !== undefined) { + if (!isRecord(config.thresholds)) { + errors.push("thresholds must be an object"); + } else { + for (const key of ["fullSummary", "moderate", "light", "verbatim"]) { + validateNumberRange(config.thresholds, key, 1, 100, errors); + } + } + } + + if (config.toolStrategies !== undefined) { + if (!isRecord(config.toolStrategies)) { + errors.push("toolStrategies must be an object"); + } else { + for (const key of ["fileContent", "grepSearch", "shellOutput", "json", "errorMessage"]) { + validateBoolean(config.toolStrategies, key, errors); + } + } + } + + return { valid: errors.length === 0, errors }; +} + +function validateUltraConfig(config: Record): EngineValidationResult { + const errors: string[] = []; + validateBoolean(config, "enabled", errors); + validateBoolean(config, "slmFallbackToAggressive", errors); + validateBoolean(config, "preserveSystemPrompt", errors); + validateNumberRange(config, "compressionRate", 0, 1, errors); + validateNumberRange(config, "minScoreThreshold", 0, 1, errors); + validateNumberRange(config, "maxTokensPerMessage", 0, 32768, errors); + if (config.modelPath !== undefined && typeof config.modelPath !== "string") { + errors.push("modelPath must be a string"); + } + return { valid: errors.length === 0, errors }; +} + export const liteEngine: CompressionEngine = { id: "lite", name: "Lite", @@ -83,10 +237,10 @@ export const liteEngine: CompressionEngine = { return this.apply(body, { stepConfig: config }); }, getConfigSchema() { - return []; + return AGGRESSIVE_SCHEMA; }, - validateConfig() { - return ok(); + validateConfig(config) { + return validateAggressiveConfig(config); }, }; @@ -189,10 +343,10 @@ export const aggressiveEngine: CompressionEngine = { return this.apply(body, { stepConfig: config }); }, getConfigSchema() { - return []; + return AGGRESSIVE_SCHEMA; }, - validateConfig() { - return ok(); + validateConfig(config) { + return validateAggressiveConfig(config); }, }; @@ -246,9 +400,9 @@ export const ultraEngine: CompressionEngine = { return this.apply(body, { stepConfig: config }); }, getConfigSchema() { - return []; + return ULTRA_SCHEMA; }, - validateConfig() { - return ok(); + validateConfig(config) { + return validateUltraConfig(config); }, }; diff --git a/open-sse/services/compression/strategySelector.ts b/open-sse/services/compression/strategySelector.ts index 6478d66ad8..37bc9782ce 100644 --- a/open-sse/services/compression/strategySelector.ts +++ b/open-sse/services/compression/strategySelector.ts @@ -199,6 +199,9 @@ export function applyStackedCompression( const rules = new Set(); const breakdown: NonNullable = []; const rtkRawOutputPointers: NonNullable = []; + const validationWarnings = new Set(); + const validationErrors = new Set(); + let fallbackApplied = false; const start = performance.now(); for (const step of steps) { @@ -218,6 +221,9 @@ export function applyStackedCompression( result.stats.rtkRawOutputPointers?.forEach((pointer) => { rtkRawOutputPointers.push(pointer); }); + result.stats.validationWarnings?.forEach((warning) => validationWarnings.add(warning)); + result.stats.validationErrors?.forEach((error) => validationErrors.add(error)); + fallbackApplied = fallbackApplied || result.stats.fallbackApplied === true; breakdown.push({ engine: step.engine, originalTokens: result.stats.originalTokens, @@ -246,6 +252,15 @@ export function applyStackedCompression( stats.compressionComboId = options?.compressionComboId ?? options?.config?.compressionComboId ?? null; stats.engineBreakdown = breakdown; + if (validationWarnings.size > 0) { + stats.validationWarnings = Array.from(validationWarnings); + } + if (validationErrors.size > 0) { + stats.validationErrors = Array.from(validationErrors); + } + if (fallbackApplied) { + stats.fallbackApplied = true; + } if (rtkRawOutputPointers.length > 0) { const seenPointers = new Set(); stats.rtkRawOutputPointers = rtkRawOutputPointers.filter((pointer) => { diff --git a/src/app/(dashboard)/dashboard/context/caveman/CavemanContextPageClient.tsx b/src/app/(dashboard)/dashboard/context/caveman/CavemanContextPageClient.tsx index cfac49fa4c..e879548954 100644 --- a/src/app/(dashboard)/dashboard/context/caveman/CavemanContextPageClient.tsx +++ b/src/app/(dashboard)/dashboard/context/caveman/CavemanContextPageClient.tsx @@ -251,7 +251,7 @@ export default function CavemanContextPageClient() { {previewPrompt}

- {t("bypassConditions")}: security, irreversible, clarification, order-sensitive + {t("bypassConditions")}: {t("bypassConditionsList")}

diff --git a/src/app/(dashboard)/dashboard/context/rtk/RtkContextPageClient.tsx b/src/app/(dashboard)/dashboard/context/rtk/RtkContextPageClient.tsx index a7b148be8e..e42b098115 100644 --- a/src/app/(dashboard)/dashboard/context/rtk/RtkContextPageClient.tsx +++ b/src/app/(dashboard)/dashboard/context/rtk/RtkContextPageClient.tsx @@ -23,6 +23,10 @@ type RtkConfig = { maxLinesPerResult: number; maxCharsPerResult: number; deduplicateThreshold: number; + customFiltersEnabled: boolean; + trustProjectFilters: boolean; + rawOutputRetention: "never" | "failures" | "always"; + rawOutputMaxBytes: number; }; type AnalyticsSummary = { @@ -156,7 +160,7 @@ export default function RtkContextPageClient() { {config && (
-
+
+ +
{[ ["applyToToolResults", t("toolResults")], ["applyToAssistantMessages", t("assistantMessages")], ["applyToCodeBlocks", t("codeBlocks")], + ["customFiltersEnabled", t("customFilters")], + ["trustProjectFilters", t("trustProjectFilters")], ].map(([key, label]) => (
+
+ +
)} diff --git a/src/app/api/compression/preview/route.ts b/src/app/api/compression/preview/route.ts index 398180e224..6d072b544a 100644 --- a/src/app/api/compression/preview/route.ts +++ b/src/app/api/compression/preview/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { enforceApiKeyPolicy } from "@/shared/utils/apiKeyPolicy"; +import { compressionPreviewConfigSchema } from "@/shared/validation/compressionConfigSchemas"; import { applyCompression } from "@omniroute/open-sse/services/compression/strategySelector"; import type { CompressionConfig, @@ -8,54 +9,7 @@ import type { } from "@omniroute/open-sse/services/compression/types"; import { buildCompressionPreviewDiff } from "@omniroute/open-sse/services/compression/diffHelper"; -const previewRtkConfigSchema = z - .object({ - enabled: z.boolean().optional(), - intensity: z.enum(["minimal", "standard", "aggressive"]).optional(), - applyToToolResults: z.boolean().optional(), - applyToCodeBlocks: z.boolean().optional(), - applyToAssistantMessages: z.boolean().optional(), - enabledFilters: z.array(z.string()).optional(), - disabledFilters: z.array(z.string()).optional(), - maxLinesPerResult: z.number().int().min(0).max(100000).optional(), - maxCharsPerResult: z.number().int().min(0).max(1000000).optional(), - deduplicateThreshold: z.number().int().min(2).max(100).optional(), - customFiltersEnabled: z.boolean().optional(), - trustProjectFilters: z.boolean().optional(), - rawOutputRetention: z.enum(["never", "failures", "always"]).optional(), - rawOutputMaxBytes: z.number().int().min(1024).max(10_000_000).optional(), - }) - .strict(); - -const previewPipelineStepSchema = z - .object({ - engine: z.enum(["lite", "caveman", "aggressive", "ultra", "rtk"]), - intensity: z.string().optional(), - config: z.record(z.string(), z.unknown()).optional(), - }) - .strict(); - -export const PreviewCompressionConfigSchema = z - .object({ - enabled: z.boolean().optional(), - defaultMode: z - .enum(["off", "lite", "standard", "aggressive", "ultra", "rtk", "stacked"]) - .optional(), - preserveSystemPrompt: z.boolean().optional(), - compressionComboId: z.string().nullable().optional(), - rtkConfig: previewRtkConfigSchema.optional(), - stackedPipeline: z.array(previewPipelineStepSchema).optional(), - languageConfig: z - .object({ - enabled: z.boolean().optional(), - defaultLanguage: z.string().min(1).optional(), - autoDetect: z.boolean().optional(), - enabledPacks: z.array(z.string().min(1)).optional(), - }) - .strict() - .optional(), - }) - .strict(); +export const PreviewCompressionConfigSchema = compressionPreviewConfigSchema; export const PreviewRequestSchema = z.object({ messages: z diff --git a/src/app/api/context/combos/[id]/route.ts b/src/app/api/context/combos/[id]/route.ts index 4b76cee303..83285c8229 100644 --- a/src/app/api/context/combos/[id]/route.ts +++ b/src/app/api/context/combos/[id]/route.ts @@ -8,14 +8,12 @@ import { } from "@/lib/db/compressionCombos"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; +import { + cavemanIntensitySchema, + stackedPipelineStepSchema, +} from "@/shared/validation/compressionConfigSchemas"; -export const pipelineStepSchema = z - .object({ - engine: z.enum(["lite", "caveman", "aggressive", "ultra", "rtk"]), - intensity: z.string().optional(), - config: z.record(z.string(), z.unknown()).optional(), - }) - .strict(); +export const pipelineStepSchema = stackedPipelineStepSchema; export const compressionComboUpdateSchema = z .object({ @@ -24,7 +22,7 @@ export const compressionComboUpdateSchema = z pipeline: z.array(pipelineStepSchema).min(1).optional(), languagePacks: z.array(z.string().trim().min(1)).optional(), outputMode: z.boolean().optional(), - outputModeIntensity: z.string().optional(), + outputModeIntensity: cavemanIntensitySchema.optional(), isDefault: z.boolean().optional(), }) .strict(); diff --git a/src/app/api/context/combos/route.ts b/src/app/api/context/combos/route.ts index 12c42a08dd..2590af0940 100644 --- a/src/app/api/context/combos/route.ts +++ b/src/app/api/context/combos/route.ts @@ -3,14 +3,12 @@ import { z } from "zod"; import { createCompressionCombo, listCompressionCombos } from "@/lib/db/compressionCombos"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; +import { + cavemanIntensitySchema, + stackedPipelineStepSchema, +} from "@/shared/validation/compressionConfigSchemas"; -export const pipelineStepSchema = z - .object({ - engine: z.enum(["lite", "caveman", "aggressive", "ultra", "rtk"]), - intensity: z.string().optional(), - config: z.record(z.string(), z.unknown()).optional(), - }) - .strict(); +export const pipelineStepSchema = stackedPipelineStepSchema; export const compressionComboCreateSchema = z .object({ @@ -20,7 +18,7 @@ export const compressionComboCreateSchema = z pipeline: z.array(pipelineStepSchema).min(1).optional(), languagePacks: z.array(z.string().trim().min(1)).optional(), outputMode: z.boolean().optional(), - outputModeIntensity: z.string().optional(), + outputModeIntensity: cavemanIntensitySchema.optional(), isDefault: z.boolean().optional(), }) .strict(); diff --git a/src/app/api/context/rtk/config/route.ts b/src/app/api/context/rtk/config/route.ts index 2ea97102ab..2d97058bb7 100644 --- a/src/app/api/context/rtk/config/route.ts +++ b/src/app/api/context/rtk/config/route.ts @@ -1,27 +1,10 @@ import { NextResponse } from "next/server"; -import { z } from "zod"; import { getCompressionSettings, updateCompressionSettings } from "@/lib/db/compression"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; +import { rtkConfigSchema } from "@/shared/validation/compressionConfigSchemas"; -export const rtkConfigSchema = z - .object({ - enabled: z.boolean().optional(), - intensity: z.enum(["minimal", "standard", "aggressive"]).optional(), - applyToToolResults: z.boolean().optional(), - applyToCodeBlocks: z.boolean().optional(), - applyToAssistantMessages: z.boolean().optional(), - enabledFilters: z.array(z.string()).optional(), - disabledFilters: z.array(z.string()).optional(), - maxLinesPerResult: z.number().int().min(0).max(100000).optional(), - maxCharsPerResult: z.number().int().min(0).max(1000000).optional(), - deduplicateThreshold: z.number().int().min(2).max(100).optional(), - customFiltersEnabled: z.boolean().optional(), - trustProjectFilters: z.boolean().optional(), - rawOutputRetention: z.enum(["never", "failures", "always"]).optional(), - rawOutputMaxBytes: z.number().int().min(1024).max(10_000_000).optional(), - }) - .strict(); +export { rtkConfigSchema }; export async function GET(request: Request) { const authError = await requireManagementAuth(request); diff --git a/src/app/api/context/rtk/test/route.ts b/src/app/api/context/rtk/test/route.ts index 498b5c7f6c..4307198e2c 100644 --- a/src/app/api/context/rtk/test/route.ts +++ b/src/app/api/context/rtk/test/route.ts @@ -6,12 +6,13 @@ import { } from "@omniroute/open-sse/services/compression/engines/rtk"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; +import { rtkConfigSchema } from "@/shared/validation/compressionConfigSchemas"; export const rtkTestSchema = z .object({ text: z.string().min(1), command: z.string().optional(), - config: z.record(z.string(), z.unknown()).optional(), + config: rtkConfigSchema.optional(), }) .strict(); diff --git a/src/app/api/settings/compression/route.ts b/src/app/api/settings/compression/route.ts index 62eb52287b..97f1e3aeef 100644 --- a/src/app/api/settings/compression/route.ts +++ b/src/app/api/settings/compression/route.ts @@ -1,132 +1,8 @@ import { NextRequest, NextResponse } from "next/server"; -import { z } from "zod"; import { getCompressionSettings, updateCompressionSettings } from "@/lib/db/compression"; import { isAuthenticated } from "@/shared/utils/apiAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; - -const compressionModeSchema = z.enum([ - "off", - "lite", - "standard", - "aggressive", - "ultra", - "rtk", - "stacked", -]); -const cavemanIntensitySchema = z.enum(["lite", "full", "ultra"]); -const rtkIntensitySchema = z.enum(["minimal", "standard", "aggressive"]); - -const cavemanConfigSchema = z - .object({ - enabled: z.boolean().optional(), - compressRoles: z.array(z.enum(["user", "assistant", "system"])).optional(), - skipRules: z.array(z.string()).optional(), - minMessageLength: z.number().int().min(0).optional(), - preservePatterns: z.array(z.string()).optional(), - intensity: cavemanIntensitySchema.optional(), - }) - .strict(); - -const cavemanOutputModeSchema = z - .object({ - enabled: z.boolean().optional(), - intensity: cavemanIntensitySchema.optional(), - autoClarity: z.boolean().optional(), - }) - .strict(); - -const rtkConfigSchema = z - .object({ - enabled: z.boolean().optional(), - intensity: rtkIntensitySchema.optional(), - applyToToolResults: z.boolean().optional(), - applyToCodeBlocks: z.boolean().optional(), - applyToAssistantMessages: z.boolean().optional(), - enabledFilters: z.array(z.string()).optional(), - disabledFilters: z.array(z.string()).optional(), - maxLinesPerResult: z.number().int().min(0).max(100000).optional(), - maxCharsPerResult: z.number().int().min(0).max(1000000).optional(), - deduplicateThreshold: z.number().int().min(2).max(100).optional(), - customFiltersEnabled: z.boolean().optional(), - trustProjectFilters: z.boolean().optional(), - rawOutputRetention: z.enum(["never", "failures", "always"]).optional(), - rawOutputMaxBytes: z.number().int().min(1024).max(10_000_000).optional(), - }) - .strict(); - -const languageConfigSchema = z - .object({ - enabled: z.boolean().optional(), - defaultLanguage: z.string().trim().min(1).optional(), - autoDetect: z.boolean().optional(), - enabledPacks: z.array(z.string().trim().min(1)).optional(), - }) - .strict(); - -const stackedPipelineStepSchema = z - .object({ - engine: z.enum(["lite", "caveman", "aggressive", "ultra", "rtk"]), - intensity: z.string().optional(), - config: z.record(z.string(), z.unknown()).optional(), - }) - .strict(); - -const aggressiveConfigSchema = z - .object({ - thresholds: z - .object({ - fullSummary: z.number().int().min(1).max(100).optional(), - moderate: z.number().int().min(1).max(100).optional(), - light: z.number().int().min(1).max(100).optional(), - verbatim: z.number().int().min(1).max(100).optional(), - }) - .optional(), - toolStrategies: z - .object({ - fileContent: z.boolean().optional(), - grepSearch: z.boolean().optional(), - shellOutput: z.boolean().optional(), - json: z.boolean().optional(), - errorMessage: z.boolean().optional(), - }) - .optional(), - summarizerEnabled: z.boolean().optional(), - maxTokensPerMessage: z.number().int().min(256).max(32768).optional(), - minSavingsThreshold: z.number().min(0).max(1).optional(), - }) - .strict(); - -const ultraConfigSchema = z - .object({ - enabled: z.boolean().optional(), - compressionRate: z.number().min(0).max(1).optional(), - minScoreThreshold: z.number().min(0).max(1).optional(), - slmFallbackToAggressive: z.boolean().optional(), - modelPath: z.string().trim().min(1).optional(), - maxTokensPerMessage: z.number().int().min(0).max(32768).optional(), - }) - .strict(); - -const compressionSettingsUpdateSchema = z - .object({ - enabled: z.boolean().optional(), - defaultMode: compressionModeSchema.optional(), - autoTriggerMode: compressionModeSchema.optional(), - autoTriggerTokens: z.number().int().min(0).optional(), - cacheMinutes: z.number().int().min(1).max(60).optional(), - preserveSystemPrompt: z.boolean().optional(), - mcpDescriptionCompressionEnabled: z.boolean().optional(), - comboOverrides: z.record(z.string(), compressionModeSchema).optional(), - compressionComboId: z.string().trim().min(1).nullable().optional(), - stackedPipeline: z.array(stackedPipelineStepSchema).optional(), - cavemanConfig: cavemanConfigSchema.optional(), - cavemanOutputMode: cavemanOutputModeSchema.optional(), - rtkConfig: rtkConfigSchema.optional(), - languageConfig: languageConfigSchema.optional(), - aggressive: aggressiveConfigSchema.optional(), - ultra: ultraConfigSchema.optional(), - }) - .strict(); +import { compressionSettingsUpdateSchema } from "@/shared/validation/compressionConfigSchemas"; export async function GET(request: NextRequest) { if (!(await isAuthenticated(request))) { diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 0cd65be5bc..2960f222ce 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -3719,6 +3719,14 @@ "codeBlocks": "Code blocks", "maxLines": "Max lines", "maxChars": "Max chars", + "deduplicateThreshold": "Deduplicate threshold", + "customFilters": "Custom filters", + "trustProjectFilters": "Trust project filters", + "rawOutputRetention": "Raw output retention", + "rawOutputNever": "Never", + "rawOutputFailures": "Failures", + "rawOutputAlways": "Always", + "rawOutputMaxBytes": "Raw output max bytes", "filterTesting": "Filter Testing", "pasteOutput": "Paste tool output to test filtering...", "run": "Run", @@ -3770,6 +3778,7 @@ "outputModeDesc": "Instructs the LLM to respond in a terse, compact format.", "autoClarity": "Auto-Clarity Bypass", "bypassConditions": "Bypass conditions", + "bypassConditionsList": "security, irreversible, clarification, order-sensitive", "preview": { "lite": "Respond concise. Preserve technical terms, code, errors, URLs, and identifiers.", "full": "Respond terse and compact. Preserve all technical substance.", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 521e35bd53..ba9f01aa77 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -3421,6 +3421,14 @@ "codeBlocks": "Blocos de codigo", "maxLines": "Max linhas", "maxChars": "Max caracteres", + "deduplicateThreshold": "Limite de deduplicacao", + "customFilters": "Filtros customizados", + "trustProjectFilters": "Confiar em filtros do projeto", + "rawOutputRetention": "Retencao de raw output", + "rawOutputNever": "Nunca", + "rawOutputFailures": "Falhas", + "rawOutputAlways": "Sempre", + "rawOutputMaxBytes": "Max bytes de raw output", "filterTesting": "Teste de filtros", "pasteOutput": "Cole output de ferramenta para testar...", "run": "Executar", @@ -3472,6 +3480,7 @@ "outputModeDesc": "Instrui o LLM a responder em formato curto e compacto.", "autoClarity": "Bypass Auto-Clarity", "bypassConditions": "Condicoes de bypass", + "bypassConditionsList": "seguranca, irreversivel, clarificacao, sensivel a ordem", "preview": { "lite": "Responda conciso. Preserve termos tecnicos, codigo, erros, URLs e identificadores.", "full": "Responda seco e compacto. Preserve todo conteudo tecnico.", diff --git a/src/lib/db/compressionAnalytics.ts b/src/lib/db/compressionAnalytics.ts index 2d956ab1d0..16a5b51415 100644 --- a/src/lib/db/compressionAnalytics.ts +++ b/src/lib/db/compressionAnalytics.ts @@ -49,6 +49,10 @@ export interface CompressionAnalyticsSummary { estimatedUsdSaved: number; bySource: Record; }; + mcpDescriptionCompression: { + snapshots: number; + estimatedTokensSaved: number; + }; } let columnsEnsuredForDb: unknown = null; @@ -413,6 +417,15 @@ export function getCompressionAnalyticsSummary(since?: string): CompressionAnaly ) .get(...params) as { cnt: number } | undefined; + const mcpDescriptionRow = db + .prepare( + ` + SELECT COUNT(*) as cnt, COALESCE(SUM(mcp_description_tokens_saved), 0) as saved + FROM compression_analytics ${appendCondition(whereClause, "mcp_description_tokens_saved > 0")} + ` + ) + .get(...params) as { cnt: number; saved: number } | undefined; + return { totalRequests: scalar?.total ?? 0, totalTokensSaved: scalar?.totalSaved ?? 0, @@ -425,5 +438,9 @@ export function getCompressionAnalyticsSummary(since?: string): CompressionAnaly last24h, validationFallbacks: fallbackRow?.cnt ?? 0, realUsage, + mcpDescriptionCompression: { + snapshots: mcpDescriptionRow?.cnt ?? 0, + estimatedTokensSaved: mcpDescriptionRow?.saved ?? 0, + }, }; } diff --git a/src/shared/validation/compressionConfigSchemas.ts b/src/shared/validation/compressionConfigSchemas.ts new file mode 100644 index 0000000000..6357fd0b43 --- /dev/null +++ b/src/shared/validation/compressionConfigSchemas.ts @@ -0,0 +1,165 @@ +import { z } from "zod"; + +export const compressionModeSchema = z.enum([ + "off", + "lite", + "standard", + "aggressive", + "ultra", + "rtk", + "stacked", +]); + +export const cavemanIntensitySchema = z.enum(["lite", "full", "ultra"]); +export const rtkIntensitySchema = z.enum(["minimal", "standard", "aggressive"]); +export const rtkRawOutputRetentionSchema = z.enum(["never", "failures", "always"]); + +export const cavemanConfigSchema = z + .object({ + enabled: z.boolean().optional(), + compressRoles: z.array(z.enum(["user", "assistant", "system"])).optional(), + skipRules: z.array(z.string()).optional(), + minMessageLength: z.number().int().min(0).optional(), + preservePatterns: z.array(z.string()).optional(), + intensity: cavemanIntensitySchema.optional(), + }) + .strict(); + +export const cavemanOutputModeSchema = z + .object({ + enabled: z.boolean().optional(), + intensity: cavemanIntensitySchema.optional(), + autoClarity: z.boolean().optional(), + }) + .strict(); + +export const rtkConfigSchema = z + .object({ + enabled: z.boolean().optional(), + intensity: rtkIntensitySchema.optional(), + applyToToolResults: z.boolean().optional(), + applyToCodeBlocks: z.boolean().optional(), + applyToAssistantMessages: z.boolean().optional(), + enabledFilters: z.array(z.string()).optional(), + disabledFilters: z.array(z.string()).optional(), + maxLinesPerResult: z.number().int().min(0).max(100000).optional(), + maxCharsPerResult: z.number().int().min(0).max(1000000).optional(), + deduplicateThreshold: z.number().int().min(2).max(100).optional(), + customFiltersEnabled: z.boolean().optional(), + trustProjectFilters: z.boolean().optional(), + rawOutputRetention: rtkRawOutputRetentionSchema.optional(), + rawOutputMaxBytes: z.number().int().min(1024).max(10_000_000).optional(), + }) + .strict(); + +export const languageConfigSchema = z + .object({ + enabled: z.boolean().optional(), + defaultLanguage: z.string().trim().min(1).optional(), + autoDetect: z.boolean().optional(), + enabledPacks: z.array(z.string().trim().min(1)).optional(), + }) + .strict(); + +export const aggressiveConfigSchema = z + .object({ + thresholds: z + .object({ + fullSummary: z.number().int().min(1).max(100).optional(), + moderate: z.number().int().min(1).max(100).optional(), + light: z.number().int().min(1).max(100).optional(), + verbatim: z.number().int().min(1).max(100).optional(), + }) + .strict() + .optional(), + toolStrategies: z + .object({ + fileContent: z.boolean().optional(), + grepSearch: z.boolean().optional(), + shellOutput: z.boolean().optional(), + json: z.boolean().optional(), + errorMessage: z.boolean().optional(), + }) + .strict() + .optional(), + summarizerEnabled: z.boolean().optional(), + maxTokensPerMessage: z.number().int().min(256).max(32768).optional(), + minSavingsThreshold: z.number().min(0).max(1).optional(), + preserveSystemPrompt: z.boolean().optional(), + }) + .strict(); + +export const ultraConfigSchema = z + .object({ + enabled: z.boolean().optional(), + compressionRate: z.number().min(0).max(1).optional(), + minScoreThreshold: z.number().min(0).max(1).optional(), + slmFallbackToAggressive: z.boolean().optional(), + modelPath: z.string().trim().min(1).optional(), + maxTokensPerMessage: z.number().int().min(0).max(32768).optional(), + preserveSystemPrompt: z.boolean().optional(), + }) + .strict(); + +const noConfigSchema = z.object({}).strict(); + +export const stackedPipelineStepSchema = z.discriminatedUnion("engine", [ + z + .object({ + engine: z.literal("lite"), + intensity: z.literal("lite").optional(), + config: noConfigSchema.optional(), + }) + .strict(), + z + .object({ + engine: z.literal("caveman"), + intensity: cavemanIntensitySchema.optional(), + config: cavemanConfigSchema.optional(), + }) + .strict(), + z + .object({ + engine: z.literal("aggressive"), + intensity: z.literal("standard").optional(), + config: aggressiveConfigSchema.optional(), + }) + .strict(), + z + .object({ + engine: z.literal("ultra"), + intensity: z.literal("ultra").optional(), + config: ultraConfigSchema.optional(), + }) + .strict(), + z + .object({ + engine: z.literal("rtk"), + intensity: rtkIntensitySchema.optional(), + config: rtkConfigSchema.optional(), + }) + .strict(), +]); + +export const compressionSettingsUpdateSchema = z + .object({ + enabled: z.boolean().optional(), + defaultMode: compressionModeSchema.optional(), + autoTriggerMode: compressionModeSchema.optional(), + autoTriggerTokens: z.number().int().min(0).optional(), + cacheMinutes: z.number().int().min(1).max(60).optional(), + preserveSystemPrompt: z.boolean().optional(), + mcpDescriptionCompressionEnabled: z.boolean().optional(), + comboOverrides: z.record(z.string(), compressionModeSchema).optional(), + compressionComboId: z.string().trim().min(1).nullable().optional(), + stackedPipeline: z.array(stackedPipelineStepSchema).optional(), + cavemanConfig: cavemanConfigSchema.optional(), + cavemanOutputMode: cavemanOutputModeSchema.optional(), + rtkConfig: rtkConfigSchema.optional(), + languageConfig: languageConfigSchema.optional(), + aggressive: aggressiveConfigSchema.optional(), + ultra: ultraConfigSchema.optional(), + }) + .strict(); + +export const compressionPreviewConfigSchema = compressionSettingsUpdateSchema; diff --git a/tests/integration/chatcore-compression-integration.test.ts b/tests/integration/chatcore-compression-integration.test.ts index 0afa60edf5..62b730625d 100644 --- a/tests/integration/chatcore-compression-integration.test.ts +++ b/tests/integration/chatcore-compression-integration.test.ts @@ -180,10 +180,10 @@ test("chatCore integration: disabled prompt compression leaves combo override re "Test body should exceed proactive compression threshold" ); - let capturedBody: any = null; + let capturedBody: { messages?: Array<{ role?: string; content?: string }> } | null = null; globalThis.fetch = async (_url: string | URL | Request, init?: RequestInit) => { if (init?.body) { - capturedBody = JSON.parse(init.body as string); + capturedBody = JSON.parse(init.body as string) as typeof capturedBody; } return new Response( JSON.stringify({ @@ -660,6 +660,96 @@ test("chatCore integration: assigned compression combo applies language packs an } }); +test("chatCore integration: default compression combo applies for unassigned stacked requests", async () => { + const provider = "openai"; + const model = "gpt-4"; + + await compressionDb.updateCompressionSettings({ + enabled: true, + defaultMode: "stacked", + autoTriggerTokens: 0, + cavemanOutputMode: { + enabled: false, + intensity: "full", + autoClarity: true, + }, + languageConfig: { + enabled: false, + defaultLanguage: "en", + autoDetect: true, + enabledPacks: ["en"], + }, + }); + + const compressionCombo = compressionCombosDb.createCompressionCombo({ + name: "Default PT Output Mode", + pipeline: [{ engine: "caveman", intensity: "lite" }], + languagePacks: ["pt-BR"], + outputMode: true, + outputModeIntensity: "lite", + isDefault: true, + }); + + const connection = await providersDb.createProviderConnection({ + provider, + apiKey: "test-key", + isActive: true, + }); + + let capturedBody: { messages?: Array<{ role?: string; content?: string }> } | null = null; + globalThis.fetch = async (_url: string | URL | Request, init?: RequestInit) => { + if (init?.body) { + capturedBody = JSON.parse(init.body as string) as typeof capturedBody; + } + return new Response( + JSON.stringify({ + choices: [{ message: { role: "assistant", content: "ok" } }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }), + { + status: 200, + headers: { "content-type": "application/json" }, + } + ); + }; + + try { + const result = await handleChatCore({ + body: { + model, + stream: false, + messages: [{ role: "user", content: "Resuma esta implementacao." }], + }, + modelInfo: { provider, model }, + credentials: { apiKey: "test-key" }, + log: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }, + clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Map() }, + connectionId: connection.id, + }); + + assert.ok(result.success, "Request should succeed"); + assert.ok(capturedBody, "Fetch should receive the request body"); + const firstMessage = capturedBody.messages?.[0]; + assert.equal(firstMessage?.role, "system"); + assert.match(firstMessage?.content ?? "", /OmniRoute Caveman Output Mode/); + assert.match(firstMessage?.content ?? "", /Responda conciso/); + + let summary = compressionAnalyticsDb.getCompressionAnalyticsSummary(); + for ( + let attempt = 0; + attempt < 100 && !summary.byCompressionCombo[compressionCombo.id]; + attempt += 1 + ) { + await new Promise((resolve) => setTimeout(resolve, 20)); + summary = compressionAnalyticsDb.getCompressionAnalyticsSummary(); + } + + assert.equal(summary.byCompressionCombo[compressionCombo.id].count, 1); + } finally { + globalThis.fetch = originalFetch; + } +}); + test("chatCore integration: modular compression records analytics row best-effort", async () => { const provider = "openai"; const model = "gpt-4"; diff --git a/tests/unit/compression/compression-preview-api.test.ts b/tests/unit/compression/compression-preview-api.test.ts index 4d14c0da3f..9a34952d15 100644 --- a/tests/unit/compression/compression-preview-api.test.ts +++ b/tests/unit/compression/compression-preview-api.test.ts @@ -49,5 +49,17 @@ describe("compression preview API contract", () => { }).success, false ); + assert.equal( + PreviewCompressionConfigSchema.safeParse({ + stackedPipeline: [{ engine: "rtk", intensity: "bogus" }], + }).success, + false + ); + assert.equal( + PreviewCompressionConfigSchema.safeParse({ + stackedPipeline: [{ engine: "rtk", config: { maxLinesPerResult: -1 } }], + }).success, + false + ); }); }); diff --git a/tests/unit/compression/compressionAnalytics.test.ts b/tests/unit/compression/compressionAnalytics.test.ts index c2ee8f4dbf..12d1f55443 100644 --- a/tests/unit/compression/compressionAnalytics.test.ts +++ b/tests/unit/compression/compressionAnalytics.test.ts @@ -68,6 +68,10 @@ describe("compressionAnalytics", () => { estimatedUsdSaved: 0, bySource: {}, }, + mcpDescriptionCompression: { + snapshots: 0, + estimatedTokensSaved: 0, + }, }); assert.equal(summary.last24h.length, 24); }); @@ -335,4 +339,22 @@ describe("compressionAnalytics", () => { assert.equal(summary.realUsage.requestsWithReceipts, 1); assert.equal(summary.realUsage.totalTokens, 1020); }); + + it("summarizes MCP description estimates without counting them as provider receipts", () => { + insertCompressionAnalyticsRow({ + timestamp: new Date().toISOString(), + mode: "mcp-description", + engine: "mcp-description", + original_tokens: 20, + compressed_tokens: 12, + tokens_saved: 8, + mcp_description_tokens_saved: 8, + }); + + const summary = getCompressionAnalyticsSummary(); + assert.equal(summary.mcpDescriptionCompression.snapshots, 1); + assert.equal(summary.mcpDescriptionCompression.estimatedTokensSaved, 8); + assert.equal(summary.realUsage.requestsWithReceipts, 0); + assert.equal(summary.realUsage.bySource.mcp_metadata_estimate, undefined); + }); }); diff --git a/tests/unit/compression/compressionMcpTools.test.ts b/tests/unit/compression/compressionMcpTools.test.ts index 4adeb9a1e5..09282bec51 100644 --- a/tests/unit/compression/compressionMcpTools.test.ts +++ b/tests/unit/compression/compressionMcpTools.test.ts @@ -21,6 +21,8 @@ const { listCompressionCombosTool, compressionComboStatsTool, } = await import("../../../open-sse/mcp-server/schemas/tools.ts"); +const { maybeCompressMcpDescription, resetMcpDescriptionCompressionStats } = + await import("../../../open-sse/mcp-server/descriptionCompressor.ts"); describe("compression MCP tool schemas", () => { it("uses canonical read/write compression scopes", () => { @@ -83,6 +85,26 @@ describe("handleCompressionStatus", () => { assert.equal(typeof result.analytics.mcpDescriptionCompression.charsBefore, "number"); assert.equal(typeof result.analytics.mcpDescriptionCompression.charsAfter, "number"); }); + + it("snapshots MCP description savings into analytics separately", async () => { + resetMcpDescriptionCompressionStats(); + const compressed = maybeCompressMcpDescription( + "The function returns the current weather for a city and the detailed forecast summary.", + { enabled: true } + ); + assert.match(compressed, /weather/i); + + const result = await handleCompressionStatus({}); + assert.ok(result.analytics.mcpDescriptionCompression.estimatedTokensSaved > 0); + assert.ok(result.analytics.mcpDescriptionCompression.persistedEstimatedTokensSaved > 0); + assert.ok(result.analytics.mcpDescriptionCompression.persistedSnapshots > 0); + + const stats = await handleCompressionComboStats({ since: "all" }); + const mcp = stats.mcpDescriptionCompression as { estimatedTokensSaved?: number }; + const realUsage = stats.realUsage as { bySource?: Record }; + assert.ok((mcp.estimatedTokensSaved ?? 0) > 0); + assert.equal(realUsage.bySource?.mcp_metadata_estimate, undefined); + }); }); describe("handleCompressionConfigure", () => { diff --git a/tests/unit/compression/context-compression-api.test.ts b/tests/unit/compression/context-compression-api.test.ts index c591301ad5..6cdb5719df 100644 --- a/tests/unit/compression/context-compression-api.test.ts +++ b/tests/unit/compression/context-compression-api.test.ts @@ -23,6 +23,13 @@ describe("context compression API schemas", () => { assert.equal(rtkConfigSchema.safeParse({ rawOutputRetention: "plaintext" }).success, false); assert.equal(rtkTestSchema.safeParse({ text: "" }).success, false); assert.equal(rtkTestSchema.safeParse({ text: "ok", extra: true }).success, false); + assert.equal( + rtkTestSchema.safeParse({ + text: "ok", + config: { maxLinesPerResult: -1, madeUp: true }, + }).success, + false + ); }); it("rejects invalid compression combo payloads", () => { @@ -38,5 +45,12 @@ describe("context compression API schemas", () => { assert.equal(compressionComboUpdateSchema.safeParse({ pipeline: [] }).success, false); assert.equal(assignmentsUpdateSchema.safeParse({ routingComboIds: ["combo-a"] }).success, true); assert.equal(assignmentsUpdateSchema.safeParse({ routingComboIds: [""] }).success, false); + assert.equal( + compressionComboCreateSchema.safeParse({ + name: "Bad", + pipeline: [{ engine: "rtk", intensity: "bogus" }], + }).success, + false + ); }); }); diff --git a/tests/unit/compression/engine-registry.test.ts b/tests/unit/compression/engine-registry.test.ts index 2d7889bfef..f57422e1cb 100644 --- a/tests/unit/compression/engine-registry.test.ts +++ b/tests/unit/compression/engine-registry.test.ts @@ -1,7 +1,11 @@ import { describe, it, beforeEach } from "node:test"; import assert from "node:assert/strict"; -import { cavemanEngine } from "../../../open-sse/services/compression/engines/cavemanAdapter.ts"; +import { + aggressiveEngine, + cavemanEngine, + ultraEngine, +} from "../../../open-sse/services/compression/engines/cavemanAdapter.ts"; import { rtkEngine as realRtkEngine } from "../../../open-sse/services/compression/engines/rtk/index.ts"; import { clearCompressionEngineRegistry, @@ -63,11 +67,19 @@ describe("compression engine registry contract", () => { it("exposes schema and validation for built-in adapters", () => { const cavemanSchema = cavemanEngine.getConfigSchema(); const rtkSchema = realRtkEngine.getConfigSchema(); + const aggressiveSchema = aggressiveEngine.getConfigSchema(); + const ultraSchema = ultraEngine.getConfigSchema(); assert.ok(cavemanSchema.some((field) => field.key === "intensity")); assert.ok(rtkSchema.some((field) => field.key === "applyToCodeBlocks")); + assert.ok(aggressiveSchema.some((field) => field.key === "maxTokensPerMessage")); + assert.ok(ultraSchema.some((field) => field.key === "compressionRate")); assert.equal(cavemanEngine.validateConfig({ intensity: "full" }).valid, true); assert.equal(cavemanEngine.validateConfig({ intensity: "bad" }).valid, false); assert.equal(realRtkEngine.validateConfig({ maxLinesPerResult: 20 }).valid, true); + assert.equal(aggressiveEngine.validateConfig({ maxTokensPerMessage: 2048 }).valid, true); + assert.equal(aggressiveEngine.validateConfig({ maxTokensPerMessage: 10 }).valid, false); + assert.equal(ultraEngine.validateConfig({ compressionRate: 0.4 }).valid, true); + assert.equal(ultraEngine.validateConfig({ compressionRate: 4 }).valid, false); }); });