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); }); });