mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
feat(compression): Output Styles registry + D0 telemetry (Phase 4A) (#4694)
Phase 4A: Output Styles registry + D0 telemetry. Integrated into release/v3.8.35.
This commit is contained in:
committed by
GitHub
parent
44f81eaa60
commit
23ca0ca5c7
@@ -1120,29 +1120,44 @@ export async function handleChatCore({
|
||||
);
|
||||
}
|
||||
}
|
||||
if (config.enabled && config.cavemanOutputMode?.enabled) {
|
||||
// Phase 4A: unified output styles (supersedes cavemanOutputMode via the back-compat shim).
|
||||
let outputStyleResult: import("../services/compression/outputStyles/apply.ts").OutputStylesResult | null =
|
||||
null;
|
||||
if (config.enabled) {
|
||||
try {
|
||||
const { applyCavemanOutputMode } = await import("../services/compression/outputMode.ts");
|
||||
const outputModeLanguage =
|
||||
config.languageConfig?.enabled === true ? config.languageConfig.defaultLanguage : "en";
|
||||
const outputMode = applyCavemanOutputMode(
|
||||
body as Parameters<typeof applyCavemanOutputMode>[0],
|
||||
config.cavemanOutputMode,
|
||||
outputModeLanguage
|
||||
const { resolveOutputStyleSelection } = await import(
|
||||
"../services/compression/outputStyles/backCompat.ts"
|
||||
);
|
||||
if (outputMode.applied) {
|
||||
body = outputMode.body as typeof body;
|
||||
cavemanOutputModeApplied = true;
|
||||
cavemanOutputModeIntensity = config.cavemanOutputMode.intensity;
|
||||
estimatedTokens = estimateTokens(body?.messages ?? body?.input ?? []);
|
||||
log?.debug?.("COMPRESSION", "Caveman output mode instruction applied");
|
||||
} else if (outputMode.skippedReason && outputMode.skippedReason !== "disabled") {
|
||||
log?.debug?.("COMPRESSION", `Caveman output mode skipped: ${outputMode.skippedReason}`);
|
||||
const selection = resolveOutputStyleSelection(config);
|
||||
if (selection.length > 0) {
|
||||
const { applyOutputStyles } = await import(
|
||||
"../services/compression/outputStyles/apply.ts"
|
||||
);
|
||||
const outputStyleLanguage =
|
||||
config.languageConfig?.enabled === true ? config.languageConfig.defaultLanguage : "en";
|
||||
outputStyleResult = applyOutputStyles(
|
||||
body as Parameters<typeof applyOutputStyles>[0],
|
||||
selection,
|
||||
outputStyleLanguage
|
||||
);
|
||||
if (outputStyleResult.applied) {
|
||||
body = outputStyleResult.body as typeof body;
|
||||
cavemanOutputModeApplied = true;
|
||||
cavemanOutputModeIntensity =
|
||||
outputStyleResult.appliedStyles?.map((s) => `${s.id}:${s.level}`).join(",") ?? null;
|
||||
estimatedTokens = estimateTokens(body?.messages ?? body?.input ?? []);
|
||||
log?.debug?.("COMPRESSION", "Output styles applied");
|
||||
} else if (
|
||||
outputStyleResult.skippedReason &&
|
||||
outputStyleResult.skippedReason !== "no_styles"
|
||||
) {
|
||||
log?.debug?.("COMPRESSION", `Output styles skipped: ${outputStyleResult.skippedReason}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log?.debug?.(
|
||||
"COMPRESSION",
|
||||
"Caveman output mode skipped: " + (err instanceof Error ? err.message : String(err))
|
||||
"Output styles skipped: " + (err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1396,6 +1411,35 @@ export async function handleChatCore({
|
||||
}
|
||||
})();
|
||||
}
|
||||
if (outputStyleResult) {
|
||||
void (async () => {
|
||||
try {
|
||||
const { buildOutputStyleTelemetry } = await import(
|
||||
"../services/compression/outputStyles/telemetry.ts"
|
||||
);
|
||||
const { insertCompressionRunTelemetryRow } = await import(
|
||||
"../../src/lib/db/compressionRunTelemetry.ts"
|
||||
);
|
||||
const record = buildOutputStyleTelemetry({
|
||||
requestId: skillRequestId ?? traceId ?? "",
|
||||
model: effectiveModel ?? "",
|
||||
provider: provider ?? "",
|
||||
source: config.compressionComboId ? "active-profile" : "default",
|
||||
tokensBefore: estimatedTokens,
|
||||
tokensAfter: estimatedTokens,
|
||||
applied: outputStyleResult.applied,
|
||||
appliedStyles: outputStyleResult.appliedStyles,
|
||||
skippedReason: outputStyleResult.skippedReason,
|
||||
});
|
||||
insertCompressionRunTelemetryRow(record);
|
||||
} catch (err) {
|
||||
log?.debug?.(
|
||||
"COMPRESSION",
|
||||
"Run-telemetry emit skipped: " + (err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
})();
|
||||
}
|
||||
} catch (err) {
|
||||
log?.warn?.(
|
||||
"COMPRESSION",
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface CavemanOutputModeResult {
|
||||
export const SHARED_BOUNDARIES =
|
||||
"Code blocks, file paths, commands, errors, URLs: keep exact. Security warnings, irreversible action confirmations, multi-step ordered sequences: write normal. Resume terse style after. Active every response until user asks for normal mode.";
|
||||
|
||||
const CAVEMAN_INSTRUCTION_BY_LANGUAGE = {
|
||||
export const CAVEMAN_INSTRUCTION_BY_LANGUAGE = {
|
||||
en: {
|
||||
lite: `Respond concise. Drop filler, pleasantries, hedging. Keep full sentences, technical terms, code, errors, URLs, and identifiers exact. ${SHARED_BOUNDARIES}`,
|
||||
full: `Respond terse like smart caveman. Drop articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries, hedging. Fragments OK. Short synonyms (big not extensive, fix not implement). Keep all technical substance, code, errors, URLs, identifiers exact. ${SHARED_BOUNDARIES}`,
|
||||
|
||||
135
open-sse/services/compression/outputStyles/apply.ts
Normal file
135
open-sse/services/compression/outputStyles/apply.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { SHARED_BOUNDARIES, shouldBypassCavemanOutputMode } from "../outputMode.ts";
|
||||
import { OUTPUT_STYLE_IDS, outputStyleMeta } from "./catalog.ts";
|
||||
|
||||
export type OutputStyleLevel = "lite" | "full" | "ultra";
|
||||
|
||||
export interface OutputStyleSelectionEntry {
|
||||
id: string;
|
||||
level: OutputStyleLevel;
|
||||
}
|
||||
|
||||
interface ChatMessage {
|
||||
role: string;
|
||||
content?: string | unknown[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ChatRequestBody {
|
||||
messages?: ChatMessage[];
|
||||
instructions?: string;
|
||||
input?: unknown;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface OutputStylesResult {
|
||||
body: ChatRequestBody;
|
||||
applied: boolean;
|
||||
skippedReason?: string;
|
||||
/** The styles actually injected (after unknown/locale filtering), in catalog order. */
|
||||
appliedStyles?: OutputStyleSelectionEntry[];
|
||||
}
|
||||
|
||||
/** Single idempotency marker guarding the unified injection (D-A: one marker for all styles). */
|
||||
export const OUTPUT_STYLE_MARKER = "[OmniRoute Output Styles]";
|
||||
|
||||
/**
|
||||
* Resolve the selection into the ordered, locale-gated, known styles in catalog order.
|
||||
* Pure: drops unknown ids and locale-mismatched styles; never throws (D-A6 forward-compat).
|
||||
*/
|
||||
function resolveStyles(
|
||||
selection: OutputStyleSelectionEntry[],
|
||||
language: string
|
||||
): OutputStyleSelectionEntry[] {
|
||||
const byId = new Map(selection.map((entry) => [entry.id, entry]));
|
||||
const resolved: OutputStyleSelectionEntry[] = [];
|
||||
for (const id of OUTPUT_STYLE_IDS) {
|
||||
const entry = byId.get(id);
|
||||
if (!entry) continue;
|
||||
const meta = outputStyleMeta(id);
|
||||
if (!meta) continue;
|
||||
if (meta.locale && meta.locale !== language) continue;
|
||||
resolved.push({ id, level: entry.level });
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/** Build the combined instruction body (no marker, no trailing boundary). Pure / deterministic. */
|
||||
function buildStyleInstructions(
|
||||
resolved: OutputStyleSelectionEntry[],
|
||||
language: string
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
for (const { id, level } of resolved) {
|
||||
const meta = outputStyleMeta(id);
|
||||
const localized = meta.i18n?.[language];
|
||||
const levels = localized ?? meta.levels;
|
||||
// Strip the per-style boundary so SHARED_BOUNDARIES is appended exactly once below.
|
||||
parts.push(levels[level].replace(SHARED_BOUNDARIES, "").trim());
|
||||
}
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject one or more output styles deterministically and front-loaded into the system prompt.
|
||||
* - Selection resolved in catalog order; unknown/locale-mismatched styles dropped.
|
||||
* - SHARED_BOUNDARIES applied once at the end (not per style).
|
||||
* - Single idempotency marker; re-applying is a no-op.
|
||||
* - Content bypass runs once across the whole turn (all-or-nothing); reason recorded.
|
||||
*/
|
||||
export function applyOutputStyles(
|
||||
body: ChatRequestBody,
|
||||
selection: OutputStyleSelectionEntry[],
|
||||
language = "en"
|
||||
): OutputStylesResult {
|
||||
const resolved = resolveStyles(selection ?? [], language);
|
||||
if (resolved.length === 0) {
|
||||
return { body, applied: false, skippedReason: "no_styles" };
|
||||
}
|
||||
|
||||
// Single space before the shared boundary so a legacy single-style (terse-prose)
|
||||
// injection stays byte-identical to the old caveman output mode (D-A5 back-compat).
|
||||
const combined = `${buildStyleInstructions(resolved, language)} ${SHARED_BOUNDARIES}`;
|
||||
const instruction = `${OUTPUT_STYLE_MARKER}\n${combined}`;
|
||||
|
||||
const messages = Array.isArray(body.messages) ? body.messages : null;
|
||||
if (!messages || messages.length === 0) {
|
||||
if (typeof body.instructions === "string") {
|
||||
if (body.instructions.includes(OUTPUT_STYLE_MARKER)) {
|
||||
return { body, applied: false, skippedReason: "already_applied" };
|
||||
}
|
||||
return {
|
||||
body: { ...body, instructions: `${body.instructions.trim()}\n\n${instruction}` },
|
||||
applied: true,
|
||||
appliedStyles: resolved,
|
||||
};
|
||||
}
|
||||
if (typeof body.input === "string" || Array.isArray(body.input)) {
|
||||
return { body: { ...body, instructions: instruction }, applied: true, appliedStyles: resolved };
|
||||
}
|
||||
return { body, applied: false, skippedReason: "no_messages" };
|
||||
}
|
||||
|
||||
// Idempotency before bypass so an already-injected marker (which contains
|
||||
// SHARED_BOUNDARIES keywords) cannot trigger a false-positive bypass.
|
||||
const alreadyApplied = messages.some(
|
||||
(message) =>
|
||||
message.role === "system" &&
|
||||
typeof message.content === "string" &&
|
||||
message.content.includes(OUTPUT_STYLE_MARKER)
|
||||
);
|
||||
if (alreadyApplied) return { body, applied: false, skippedReason: "already_applied" };
|
||||
|
||||
// Content bypass (all-or-nothing for the turn): reuse the existing rules verbatim.
|
||||
const bypass = shouldBypassCavemanOutputMode(messages);
|
||||
if (bypass) return { body, applied: false, skippedReason: bypass };
|
||||
|
||||
const nextMessages = [...messages];
|
||||
const first = nextMessages[0];
|
||||
if (first?.role === "system" && typeof first.content === "string") {
|
||||
nextMessages[0] = { ...first, content: `${first.content.trim()}\n\n${instruction}` };
|
||||
} else {
|
||||
nextMessages.unshift({ role: "system", content: instruction });
|
||||
}
|
||||
|
||||
return { body: { ...body, messages: nextMessages }, applied: true, appliedStyles: resolved };
|
||||
}
|
||||
29
open-sse/services/compression/outputStyles/backCompat.ts
Normal file
29
open-sse/services/compression/outputStyles/backCompat.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { OutputStyleSelectionEntry } from "./apply.ts";
|
||||
|
||||
interface LegacyOutputModeConfig {
|
||||
enabled?: boolean;
|
||||
intensity?: "lite" | "full" | "ultra";
|
||||
}
|
||||
|
||||
interface ConfigSlice {
|
||||
outputStyles?: OutputStyleSelectionEntry[];
|
||||
cavemanOutputMode?: LegacyOutputModeConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the effective output-style selection (D-A5 back-compat).
|
||||
* Precedence: an explicit non-empty `outputStyles` wins; otherwise a stored
|
||||
* `cavemanOutputMode` (when enabled) maps to `[{ terse-prose, <intensity> }]`,
|
||||
* keeping existing installs byte-identical until they opt into other styles.
|
||||
* Pure; never throws.
|
||||
*/
|
||||
export function resolveOutputStyleSelection(config: ConfigSlice): OutputStyleSelectionEntry[] {
|
||||
if (Array.isArray(config.outputStyles) && config.outputStyles.length > 0) {
|
||||
return config.outputStyles;
|
||||
}
|
||||
const legacy = config.cavemanOutputMode;
|
||||
if (legacy?.enabled) {
|
||||
return [{ id: "terse-prose", level: legacy.intensity ?? "full" }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
76
open-sse/services/compression/outputStyles/catalog.ts
Normal file
76
open-sse/services/compression/outputStyles/catalog.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { SHARED_BOUNDARIES, CAVEMAN_INSTRUCTION_BY_LANGUAGE } from "../outputMode.ts";
|
||||
|
||||
/**
|
||||
* A single output-steering style. Instruction text MUST be static per
|
||||
* `(id, level, language)` — no timestamps, no per-request interpolation — so the
|
||||
* injected system prefix stays prompt-cache-stable (D-A4). The registry contract
|
||||
* forbids non-deterministic instruction text.
|
||||
*/
|
||||
export interface OutputStyle {
|
||||
/** Stable id, e.g. "terse-prose" | "less-code" | "terse-cjk". */
|
||||
id: string;
|
||||
/** Human label for the settings panel. */
|
||||
label: string;
|
||||
/** Short panel description (i18n-independent English). */
|
||||
description?: string;
|
||||
/** Instruction text per intensity. Static / deterministic. */
|
||||
levels: { lite: string; full: string; ultra: string };
|
||||
/** Optional per-style boundary clause; when absent the SHARED_BOUNDARIES is used. */
|
||||
boundaries?: string;
|
||||
/** Locale gate: when set, the style is only offered/honored under this language code. */
|
||||
locale?: string;
|
||||
/** Optional localized `levels`, keyed by language code. */
|
||||
i18n?: Record<string, { lite: string; full: string; ultra: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Output Style registry. Adding a style = one entry here; the injector and the
|
||||
* settings panel both enumerate this object, so no other file needs to change (D-A1).
|
||||
* Declaration order is the deterministic concatenation order used by the injector.
|
||||
*/
|
||||
export const OUTPUT_STYLE_CATALOG: Record<string, OutputStyle> = {
|
||||
"terse-prose": {
|
||||
id: "terse-prose",
|
||||
label: "Terse prose",
|
||||
description: "Drop filler/articles/hedging; keep technical substance exact.",
|
||||
// Migrated verbatim from the caveman output mode (outputMode.ts) — referenced (not
|
||||
// re-typed) so the back-compat injection stays byte-identical across ALL languages,
|
||||
// not just English (the legacy mode localized to en/pt-BR/ja/id).
|
||||
levels: CAVEMAN_INSTRUCTION_BY_LANGUAGE.en,
|
||||
i18n: {
|
||||
"pt-BR": CAVEMAN_INSTRUCTION_BY_LANGUAGE["pt-BR"],
|
||||
ja: CAVEMAN_INSTRUCTION_BY_LANGUAGE.ja,
|
||||
id: CAVEMAN_INSTRUCTION_BY_LANGUAGE.id,
|
||||
},
|
||||
},
|
||||
"less-code": {
|
||||
id: "less-code",
|
||||
label: "Less code",
|
||||
description: "YAGNI ladder: smallest working change, no unrequested abstractions.",
|
||||
// Ported from 9router ponytail (ponytailPrompt.js); attribution preserved.
|
||||
levels: {
|
||||
lite: `Write the smallest change that satisfies the request. Skip speculative abstractions. ${SHARED_BOUNDARIES}`,
|
||||
full: `Act like a lazy senior dev applying YAGNI. Smallest working change only. No unrequested abstractions, no premature generalization, no extra layers, no defensive scaffolding the request did not ask for. Reuse existing code over adding new code. ${SHARED_BOUNDARIES}`,
|
||||
ultra: `Minimal diff discipline. Touch the fewest lines that make it work. Zero new files, classes, or config unless strictly required. Inline over abstract. No "while we're here" extras. ${SHARED_BOUNDARIES}`,
|
||||
},
|
||||
},
|
||||
"terse-cjk": {
|
||||
id: "terse-cjk",
|
||||
label: "Terse CJK (文言)",
|
||||
description: "Classical-Chinese ultra-terse style (locale-gated to zh).",
|
||||
// Ported from 9router wenyan (cavemanPrompts.js); the worked extensibility example.
|
||||
locale: "zh",
|
||||
levels: {
|
||||
lite: `回答从简,去虚词、寒暄、修饰。代码、路径、命令、错误、URL、标识符一律照原样保留。${SHARED_BOUNDARIES}`,
|
||||
full: `以文言简体回答,惜字如金,去赘语虚词。代码、路径、命令、错误、URL、标识符照原样保留,不得改写。${SHARED_BOUNDARIES}`,
|
||||
ultra: `以极简文言回答,字字千金。仅留要义。代码、API名、错误串、URL、标识符照原样保留,绝不省略或改写。${SHARED_BOUNDARIES}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Catalog ids in declaration order (the deterministic concat order). */
|
||||
export const OUTPUT_STYLE_IDS: string[] = Object.keys(OUTPUT_STYLE_CATALOG);
|
||||
|
||||
export function outputStyleMeta(id: string): OutputStyle {
|
||||
return OUTPUT_STYLE_CATALOG[id];
|
||||
}
|
||||
50
open-sse/services/compression/outputStyles/telemetry.ts
Normal file
50
open-sse/services/compression/outputStyles/telemetry.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { OutputStyleSelectionEntry } from "./apply.ts";
|
||||
|
||||
/** The CompressionRunTelemetry fields this sub-project (A) fills. Clock-free / pure. */
|
||||
export interface OutputStyleTelemetryRecord {
|
||||
requestId: string;
|
||||
model: string;
|
||||
provider: string;
|
||||
source: string;
|
||||
tokensBefore: number;
|
||||
tokensAfter: number;
|
||||
ratio: number;
|
||||
outputStyles?: OutputStyleSelectionEntry[];
|
||||
outputStyleBypass?: string;
|
||||
outputTokens?: number;
|
||||
}
|
||||
|
||||
// Benign (non-content-bypass) skips that must NOT be recorded as a bypass reason.
|
||||
const BENIGN_SKIPS = new Set(["disabled", "no_styles", "no_messages", "already_applied"]);
|
||||
|
||||
export function buildOutputStyleTelemetry(input: {
|
||||
requestId: string;
|
||||
model: string;
|
||||
provider: string;
|
||||
source: string;
|
||||
tokensBefore: number;
|
||||
tokensAfter: number;
|
||||
applied: boolean;
|
||||
appliedStyles?: OutputStyleSelectionEntry[];
|
||||
skippedReason?: string;
|
||||
outputTokens?: number;
|
||||
}): OutputStyleTelemetryRecord {
|
||||
const ratio = input.tokensBefore > 0 ? input.tokensAfter / input.tokensBefore : 0;
|
||||
const record: OutputStyleTelemetryRecord = {
|
||||
requestId: input.requestId,
|
||||
model: input.model,
|
||||
provider: input.provider,
|
||||
source: input.source,
|
||||
tokensBefore: input.tokensBefore,
|
||||
tokensAfter: input.tokensAfter,
|
||||
ratio,
|
||||
};
|
||||
if (input.applied && input.appliedStyles && input.appliedStyles.length > 0) {
|
||||
record.outputStyles = input.appliedStyles;
|
||||
}
|
||||
if (!input.applied && input.skippedReason && !BENIGN_SKIPS.has(input.skippedReason)) {
|
||||
record.outputStyleBypass = input.skippedReason;
|
||||
}
|
||||
if (typeof input.outputTokens === "number") record.outputTokens = input.outputTokens;
|
||||
return record;
|
||||
}
|
||||
@@ -69,6 +69,13 @@ export interface CavemanOutputModeConfig {
|
||||
autoClarity: boolean;
|
||||
}
|
||||
|
||||
export type OutputStyleLevel = "lite" | "full" | "ultra";
|
||||
|
||||
export interface OutputStyleSelectionEntry {
|
||||
id: string;
|
||||
level: OutputStyleLevel;
|
||||
}
|
||||
|
||||
export interface RtkConfig {
|
||||
enabled: boolean;
|
||||
intensity: RtkIntensity;
|
||||
@@ -135,6 +142,8 @@ export interface CompressionConfig {
|
||||
stackedPipeline?: CompressionPipelineStep[];
|
||||
cavemanConfig?: CavemanConfig;
|
||||
cavemanOutputMode?: CavemanOutputModeConfig;
|
||||
/** Phase 4A: selected output styles (supersedes cavemanOutputMode via a back-compat shim). */
|
||||
outputStyles?: OutputStyleSelectionEntry[];
|
||||
rtkConfig?: RtkConfig;
|
||||
languageConfig?: CompressionLanguageConfig;
|
||||
aggressive?: AggressiveConfig;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface Summary {
|
||||
totalRuns: number;
|
||||
totalTokensSaved: number;
|
||||
runsWithStyles: number;
|
||||
bypassCount: number;
|
||||
totalOutputTokens: number;
|
||||
appliedStyleCounts: Record<string, number>;
|
||||
}
|
||||
|
||||
const EMPTY: Summary = {
|
||||
totalRuns: 0,
|
||||
totalTokensSaved: 0,
|
||||
runsWithStyles: 0,
|
||||
bypassCount: 0,
|
||||
totalOutputTokens: 0,
|
||||
appliedStyleCounts: {},
|
||||
};
|
||||
|
||||
export default function CompressionStylesTile() {
|
||||
const t = useTranslations("settings");
|
||||
const [summary, setSummary] = useState<Summary>(EMPTY);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/settings/compression/run-telemetry")
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((data: Summary | null) => {
|
||||
if (data) setSummary(data);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const styles = Object.entries(summary.appliedStyleCounts);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="compression-styles-tile"
|
||||
className="rounded-lg border border-border/40 bg-surface p-4"
|
||||
>
|
||||
<p className="text-sm font-medium text-text-main">{t("compressionStylesTileTitle")}</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-text-main">
|
||||
{summary.totalTokensSaved.toLocaleString("en-US", { useGrouping: false })}
|
||||
</p>
|
||||
<p className="text-xs text-text-muted">tokens saved · {summary.runsWithStyles} runs styled</p>
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{styles.length === 0 ? (
|
||||
<span className="text-xs text-text-muted">No styled runs yet.</span>
|
||||
) : (
|
||||
styles.map(([id, count]) => (
|
||||
<span
|
||||
key={id}
|
||||
className="rounded bg-border/30 px-2 py-0.5 text-xs text-text-main"
|
||||
>
|
||||
{id} · {count}
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
// Import Card/Toggle from their direct module paths rather than the @/shared/components
|
||||
// barrel: the barrel transitively pulls a heavy/Node-only module that hangs the
|
||||
// vitest/jsdom component test. Direct imports resolve identically under Next.js.
|
||||
@@ -23,6 +23,10 @@ import {
|
||||
ENGINE_IDS,
|
||||
engineMeta,
|
||||
} from "../../../../../../open-sse/services/compression/engineCatalog.ts";
|
||||
import {
|
||||
OUTPUT_STYLE_IDS,
|
||||
outputStyleMeta,
|
||||
} from "../../../../../../open-sse/services/compression/outputStyles/catalog.ts";
|
||||
import { deriveDefaultPlan } from "../../../../../../open-sse/services/compression/deriveDefaultPlan.ts";
|
||||
|
||||
type CavemanIntensity = "lite" | "full" | "ultra";
|
||||
@@ -45,6 +49,7 @@ interface CompressionConfig {
|
||||
engines: Record<string, EngineToggle>;
|
||||
activeComboId: string | null;
|
||||
cavemanOutputMode?: CavemanOutputModeConfig;
|
||||
outputStyles?: Array<{ id: string; level: CavemanIntensity }>;
|
||||
}
|
||||
|
||||
const CAVEMAN_OUTPUT_LEVELS: CavemanIntensity[] = ["lite", "full", "ultra"];
|
||||
@@ -56,6 +61,7 @@ const DEFAULT_CONFIG: CompressionConfig = {
|
||||
engines: {},
|
||||
activeComboId: null,
|
||||
cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: true },
|
||||
outputStyles: [],
|
||||
};
|
||||
|
||||
function normalizeEngines(raw: unknown): Record<string, EngineToggle> {
|
||||
@@ -70,6 +76,9 @@ function normalizeEngines(raw: unknown): Record<string, EngineToggle> {
|
||||
|
||||
export default function CompressionPanel() {
|
||||
const t = useTranslations("settings");
|
||||
// D-A6/§7: locale-gated styles (e.g. terse-cjk → zh) are only OFFERED under their locale.
|
||||
// Compare the UI language base ("zh-CN" → "zh") against the style's `locale`.
|
||||
const uiLang = (useLocale() || "en").split("-")[0];
|
||||
const [config, setConfig] = useState<CompressionConfig>(DEFAULT_CONFIG);
|
||||
const [mcpAccessibility, setMcpAccessibility] = useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -86,6 +95,7 @@ export default function CompressionPanel() {
|
||||
...data,
|
||||
engines: normalizeEngines(data.engines),
|
||||
cavemanOutputMode: data.cavemanOutputMode ?? DEFAULT_CONFIG.cavemanOutputMode,
|
||||
outputStyles: data.outputStyles ?? DEFAULT_CONFIG.outputStyles,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -135,12 +145,24 @@ export default function CompressionPanel() {
|
||||
save({ engines });
|
||||
};
|
||||
|
||||
const setCavemanOutput = (patch: Partial<CavemanOutputModeConfig>) => {
|
||||
const cavemanOutputMode: CavemanOutputModeConfig = {
|
||||
...(config.cavemanOutputMode ?? DEFAULT_CONFIG.cavemanOutputMode!),
|
||||
...patch,
|
||||
};
|
||||
save({ cavemanOutputMode });
|
||||
const setOutputStyle = (id: string, patch: { enabled?: boolean; level?: CavemanIntensity }) => {
|
||||
const current = config.outputStyles ?? [];
|
||||
const existing = current.find((s) => s.id === id);
|
||||
let next = current;
|
||||
if (patch.enabled === false) {
|
||||
next = current.filter((s) => s.id !== id);
|
||||
} else {
|
||||
const level = patch.level ?? existing?.level ?? "full";
|
||||
next = existing
|
||||
? current.map((s) => (s.id === id ? { id, level } : s))
|
||||
: [...current, { id, level }];
|
||||
}
|
||||
// Persist in catalog order so injection order is stable.
|
||||
const ordered = OUTPUT_STYLE_IDS.flatMap((sid) => {
|
||||
const hit = next.find((s) => s.id === sid);
|
||||
return hit ? [hit] : [];
|
||||
});
|
||||
save({ outputStyles: ordered });
|
||||
};
|
||||
|
||||
const toggleMcpAccessibility = async (enabled: boolean) => {
|
||||
@@ -273,40 +295,63 @@ export default function CompressionPanel() {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* cavemanOutput — response-output instruction injection (separate from the input engine) */}
|
||||
<div className="mt-2 flex flex-col gap-2 border-t border-border/30 py-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
{/* Output Styles — response-output instruction injection (Phase 4A, catalog-driven) */}
|
||||
<div className="mt-2 flex flex-col gap-3 border-t border-border/30 py-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-text-main">
|
||||
{t("compressionSettingsCavemanOutputMode")}
|
||||
{t("compressionSettingsOutputStyles")}
|
||||
</p>
|
||||
<p className="mt-0.5 text-xs text-text-muted">
|
||||
Injects terse response instructions without rewriting provider output.
|
||||
Inject response-shaping instructions without rewriting provider output. Combine freely.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<select
|
||||
data-testid="caveman-output-intensity"
|
||||
value={config.cavemanOutputMode?.intensity ?? "full"}
|
||||
onChange={(e) => setCavemanOutput({ intensity: e.target.value as CavemanIntensity })}
|
||||
disabled={!config.cavemanOutputMode?.enabled || saving}
|
||||
className="w-28 rounded border border-border bg-surface px-2 py-1 text-xs text-text-main"
|
||||
>
|
||||
{CAVEMAN_OUTPUT_LEVELS.map((lvl) => (
|
||||
<option key={lvl} value={lvl}>
|
||||
{lvl}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span data-testid="caveman-output-toggle">
|
||||
<Toggle
|
||||
size="sm"
|
||||
checked={config.cavemanOutputMode?.enabled ?? false}
|
||||
onChange={(enabled) => setCavemanOutput({ enabled })}
|
||||
disabled={saving}
|
||||
ariaLabel={t("compressionSettingsCavemanOutputMode")}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
{OUTPUT_STYLE_IDS.filter((id) => {
|
||||
const m = outputStyleMeta(id);
|
||||
return !m?.locale || m.locale === uiLang;
|
||||
}).map((id) => {
|
||||
const meta = outputStyleMeta(id);
|
||||
const sel = config.outputStyles?.find((s) => s.id === id);
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
data-testid={`output-style-row-${id}`}
|
||||
className="flex items-center justify-between gap-2"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm text-text-main">{meta.label}</p>
|
||||
{meta.description && (
|
||||
<p className="text-xs text-text-muted">{meta.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<select
|
||||
data-testid={`output-style-level-${id}`}
|
||||
value={sel?.level ?? "full"}
|
||||
onChange={(e) =>
|
||||
setOutputStyle(id, { level: e.target.value as CavemanIntensity })
|
||||
}
|
||||
disabled={!sel || saving}
|
||||
className="w-28 rounded border border-border bg-surface px-2 py-1 text-xs text-text-main"
|
||||
>
|
||||
{CAVEMAN_OUTPUT_LEVELS.map((lvl) => (
|
||||
<option key={lvl} value={lvl}>
|
||||
{lvl}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span data-testid={`output-style-toggle-${id}`}>
|
||||
<Toggle
|
||||
size="sm"
|
||||
checked={Boolean(sel)}
|
||||
onChange={(enabled) => setOutputStyle(id, { enabled })}
|
||||
disabled={saving}
|
||||
ariaLabel={meta.label}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* mcpAccessibility — writes its own endpoint / separate store */}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import CompressionPanel from "./CompressionPanel";
|
||||
import CompressionStylesTile from "../CompressionStylesTile";
|
||||
|
||||
export default function CompressionSettingsPage() {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<CompressionPanel />
|
||||
{/* D0: read-only telemetry tile (output-token savings + applied styles) */}
|
||||
<CompressionStylesTile />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
23
src/app/api/settings/compression/run-telemetry/route.ts
Normal file
23
src/app/api/settings/compression/run-telemetry/route.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getCompressionRunTelemetrySummary } from "@/lib/db/compressionRunTelemetry";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const summary = getCompressionRunTelemetrySummary();
|
||||
return NextResponse.json(summary);
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{
|
||||
totalRuns: 0,
|
||||
totalTokensSaved: 0,
|
||||
runsWithStyles: 0,
|
||||
bypassCount: 0,
|
||||
totalOutputTokens: 0,
|
||||
appliedStyleCounts: {},
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5819,6 +5819,8 @@
|
||||
"mcpAccessibilityTitle": "MCP accessibility output",
|
||||
"compressionSettingsCavemanIntensity": "Caveman intensity",
|
||||
"compressionSettingsCavemanOutputMode": "Caveman output mode",
|
||||
"compressionSettingsOutputStyles": "Output styles",
|
||||
"compressionStylesTileTitle": "Output styles",
|
||||
"compressionSettingsOutputIntensity": "Output intensity",
|
||||
"compressionSettingsAutoClarityBypass": "Auto clarity bypass",
|
||||
"resilienceWaitForCooldown": "Wait for Cooldown",
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type AggressiveConfig,
|
||||
type CavemanConfig,
|
||||
type CavemanOutputModeConfig,
|
||||
type OutputStyleSelectionEntry,
|
||||
type CompressionLanguageConfig,
|
||||
type CompressionPipelineStep,
|
||||
type CompressionConfig,
|
||||
@@ -108,6 +109,21 @@ function normalizeCavemanOutputModeConfig(value: unknown): CavemanOutputModeConf
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeOutputStyleSelection(value: unknown): OutputStyleSelectionEntry[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
const out: OutputStyleSelectionEntry[] = [];
|
||||
for (const raw of value) {
|
||||
const record = toRecord(raw);
|
||||
const id = typeof record.id === "string" ? record.id.trim() : "";
|
||||
const level =
|
||||
record.level === "lite" || record.level === "full" || record.level === "ultra"
|
||||
? record.level
|
||||
: null;
|
||||
if (id && level) out.push({ id, level });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function normalizeRtkConfig(value: unknown): RtkConfig {
|
||||
const record = toRecord(value);
|
||||
return {
|
||||
@@ -512,6 +528,7 @@ export async function getCompressionSettings(): Promise<CompressionConfig> {
|
||||
...DEFAULT_COMPRESSION_CONFIG,
|
||||
cavemanConfig: { ...DEFAULT_CAVEMAN_CONFIG },
|
||||
cavemanOutputMode: { ...DEFAULT_CAVEMAN_OUTPUT_MODE_CONFIG },
|
||||
outputStyles: [],
|
||||
rtkConfig: { ...DEFAULT_RTK_CONFIG },
|
||||
languageConfig: { ...DEFAULT_COMPRESSION_LANGUAGE_CONFIG },
|
||||
stackedPipeline: normalizeStackedPipeline(undefined),
|
||||
@@ -590,6 +607,9 @@ export async function getCompressionSettings(): Promise<CompressionConfig> {
|
||||
case "cavemanOutputMode":
|
||||
config.cavemanOutputMode = normalizeCavemanOutputModeConfig(parsed);
|
||||
break;
|
||||
case "outputStyles":
|
||||
config.outputStyles = normalizeOutputStyleSelection(parsed);
|
||||
break;
|
||||
case "rtkConfig":
|
||||
config.rtkConfig = normalizeRtkConfig(parsed);
|
||||
break;
|
||||
|
||||
126
src/lib/db/compressionRunTelemetry.ts
Normal file
126
src/lib/db/compressionRunTelemetry.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { getDbInstance } from "./core";
|
||||
|
||||
export interface CompressionRunTelemetryInput {
|
||||
requestId: string;
|
||||
model: string;
|
||||
provider: string;
|
||||
source: string;
|
||||
tokensBefore: number;
|
||||
tokensAfter: number;
|
||||
ratio: number;
|
||||
costDelta?: number;
|
||||
outputStyles?: Array<{ id: string; level: "lite" | "full" | "ultra" }>;
|
||||
outputStyleBypass?: string;
|
||||
outputTokens?: number;
|
||||
}
|
||||
|
||||
export interface CompressionRunTelemetrySummary {
|
||||
totalRuns: number;
|
||||
totalTokensSaved: number;
|
||||
runsWithStyles: number;
|
||||
bypassCount: number;
|
||||
totalOutputTokens: number;
|
||||
appliedStyleCounts: Record<string, number>;
|
||||
}
|
||||
|
||||
function ensureCompressionRunTelemetryTable(): void {
|
||||
const db = getDbInstance();
|
||||
// `CREATE TABLE IF NOT EXISTS` is idempotent and cheap; run it unconditionally so the
|
||||
// table self-heals if it was dropped (e.g. test isolation) under the same db handle.
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS compression_run_telemetry (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp INTEGER NOT NULL,
|
||||
request_id TEXT,
|
||||
model TEXT,
|
||||
provider TEXT,
|
||||
source TEXT,
|
||||
tokens_before INTEGER NOT NULL,
|
||||
tokens_after INTEGER NOT NULL,
|
||||
ratio REAL,
|
||||
cost_delta REAL,
|
||||
output_styles TEXT,
|
||||
output_style_bypass TEXT,
|
||||
output_tokens INTEGER
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist one CompressionRunTelemetry record (D0). Best-effort and off the hot path:
|
||||
* the `timestamp` is stamped here (never inside the pure resolvers). Mirrors the
|
||||
* compression-stats / compressionAnalytics recording discipline — never throws into a request.
|
||||
*/
|
||||
export function insertCompressionRunTelemetryRow(row: CompressionRunTelemetryInput): void {
|
||||
try {
|
||||
const db = getDbInstance();
|
||||
ensureCompressionRunTelemetryTable();
|
||||
db.prepare(
|
||||
`INSERT INTO compression_run_telemetry (
|
||||
timestamp, request_id, model, provider, source,
|
||||
tokens_before, tokens_after, ratio, cost_delta,
|
||||
output_styles, output_style_bypass, output_tokens
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
Date.now(),
|
||||
row.requestId ?? null,
|
||||
row.model ?? null,
|
||||
row.provider ?? null,
|
||||
row.source ?? null,
|
||||
row.tokensBefore,
|
||||
row.tokensAfter,
|
||||
row.ratio,
|
||||
row.costDelta ?? null,
|
||||
row.outputStyles && row.outputStyles.length > 0 ? JSON.stringify(row.outputStyles) : null,
|
||||
row.outputStyleBypass ?? null,
|
||||
row.outputTokens ?? null
|
||||
);
|
||||
} catch {
|
||||
// best-effort telemetry — a write failure never affects a request
|
||||
}
|
||||
}
|
||||
|
||||
export function getCompressionRunTelemetrySummary(): CompressionRunTelemetrySummary {
|
||||
const db = getDbInstance();
|
||||
ensureCompressionRunTelemetryTable();
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT tokens_before, tokens_after, output_styles, output_style_bypass, output_tokens
|
||||
FROM compression_run_telemetry`
|
||||
)
|
||||
.all() as Array<{
|
||||
tokens_before: number;
|
||||
tokens_after: number;
|
||||
output_styles: string | null;
|
||||
output_style_bypass: string | null;
|
||||
output_tokens: number | null;
|
||||
}>;
|
||||
|
||||
const summary: CompressionRunTelemetrySummary = {
|
||||
totalRuns: rows.length,
|
||||
totalTokensSaved: 0,
|
||||
runsWithStyles: 0,
|
||||
bypassCount: 0,
|
||||
totalOutputTokens: 0,
|
||||
appliedStyleCounts: {},
|
||||
};
|
||||
|
||||
for (const row of rows) {
|
||||
summary.totalTokensSaved += Math.max(0, row.tokens_before - row.tokens_after);
|
||||
summary.totalOutputTokens += row.output_tokens ?? 0;
|
||||
if (row.output_style_bypass) summary.bypassCount += 1;
|
||||
if (row.output_styles) {
|
||||
summary.runsWithStyles += 1;
|
||||
try {
|
||||
const styles = JSON.parse(row.output_styles) as Array<{ id: string }>;
|
||||
for (const style of styles) {
|
||||
summary.appliedStyleCounts[style.id] =
|
||||
(summary.appliedStyleCounts[style.id] ?? 0) + 1;
|
||||
}
|
||||
} catch {
|
||||
// ignore a corrupt JSON cell
|
||||
}
|
||||
}
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
@@ -33,6 +33,13 @@ export const cavemanOutputModeSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const outputStyleSelectionSchema = z
|
||||
.object({
|
||||
id: z.string().trim().min(1),
|
||||
level: cavemanIntensitySchema,
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const rtkConfigSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
@@ -189,6 +196,7 @@ export const compressionSettingsUpdateSchema = z
|
||||
stackedPipeline: z.array(stackedPipelineStepSchema).optional(),
|
||||
cavemanConfig: cavemanConfigSchema.optional(),
|
||||
cavemanOutputMode: cavemanOutputModeSchema.optional(),
|
||||
outputStyles: z.array(outputStyleSelectionSchema).optional(),
|
||||
rtkConfig: rtkConfigSchema.optional(),
|
||||
languageConfig: languageConfigSchema.optional(),
|
||||
aggressive: aggressiveConfigSchema.optional(),
|
||||
|
||||
@@ -687,7 +687,7 @@ test("chatCore integration: assigned compression combo applies language packs an
|
||||
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 ?? "", /OmniRoute Output Styles/);
|
||||
assert.match(firstMessage?.content ?? "", /Responda conciso/);
|
||||
|
||||
for (
|
||||
@@ -782,7 +782,7 @@ test("chatCore integration: default stacked compression combo applies for unassi
|
||||
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 ?? "", /OmniRoute Output Styles/);
|
||||
assert.match(firstMessage?.content ?? "", /Responda conciso/);
|
||||
|
||||
let summary = compressionAnalyticsDb.getCompressionAnalyticsSummary();
|
||||
@@ -1037,7 +1037,7 @@ test("chatCore integration: caveman output mode skipped when compression is glob
|
||||
"user",
|
||||
"No system message should be injected when compression is disabled"
|
||||
);
|
||||
assert.doesNotMatch(capturedBody.messages[0].content ?? "", /Caveman Output Mode/);
|
||||
assert.doesNotMatch(capturedBody.messages[0].content ?? "", /Output Styles/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
@@ -1103,7 +1103,7 @@ test("chatCore integration: caveman output mode injected when both compression a
|
||||
|
||||
assert.ok(result.success, "Request should succeed");
|
||||
assert.equal(capturedBody.messages[0].role, "system");
|
||||
assert.match(capturedBody.messages[0].content ?? "", /Caveman Output Mode/);
|
||||
assert.match(capturedBody.messages[0].content ?? "", /Output Styles/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
155
tests/unit/compression/output-styles-apply.test.ts
Normal file
155
tests/unit/compression/output-styles-apply.test.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
applyOutputStyles,
|
||||
OUTPUT_STYLE_MARKER,
|
||||
type OutputStyleSelectionEntry,
|
||||
} from "../../../open-sse/services/compression/outputStyles/apply.ts";
|
||||
|
||||
const sel = (
|
||||
...entries: Array<[string, "lite" | "full" | "ultra"]>
|
||||
): OutputStyleSelectionEntry[] => entries.map(([id, level]) => ({ id, level }));
|
||||
|
||||
test("injects a system instruction with the unified marker", () => {
|
||||
const r = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "Summarize this API response." }] },
|
||||
sel(["terse-prose", "full"])
|
||||
);
|
||||
assert.equal(r.applied, true);
|
||||
assert.equal(r.body.messages?.[0]?.role, "system");
|
||||
assert.match(String(r.body.messages?.[0]?.content), new RegExp(escapeRe(OUTPUT_STYLE_MARKER)));
|
||||
assert.match(String(r.body.messages?.[0]?.content), /Respond terse/);
|
||||
assert.deepEqual(r.appliedStyles, [{ id: "terse-prose", level: "full" }]);
|
||||
});
|
||||
|
||||
test("combines two styles in catalog order with a single shared boundary", () => {
|
||||
const r = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "Refactor this module." }] },
|
||||
sel(["less-code", "full"], ["terse-prose", "full"]) // requested out of order
|
||||
);
|
||||
const text = String(r.body.messages?.[0]?.content);
|
||||
// catalog order is terse-prose before less-code
|
||||
const proseAt = text.indexOf("Respond terse");
|
||||
const codeAt = text.indexOf("lazy senior dev");
|
||||
assert.ok(proseAt >= 0 && codeAt >= 0 && proseAt < codeAt, "catalog order");
|
||||
// SHARED_BOUNDARIES appears exactly once (appended once, not per style)
|
||||
const boundaryCount = (text.match(/Resume terse style after\./g) ?? []).length;
|
||||
assert.equal(boundaryCount, 1);
|
||||
assert.deepEqual(
|
||||
r.appliedStyles?.map((s) => s.id),
|
||||
["terse-prose", "less-code"]
|
||||
);
|
||||
});
|
||||
|
||||
test("appends to an existing system prompt", () => {
|
||||
const r = applyOutputStyles(
|
||||
{
|
||||
messages: [
|
||||
{ role: "system", content: "Follow tenant policy." },
|
||||
{ role: "user", content: "Summarize logs." },
|
||||
],
|
||||
},
|
||||
sel(["terse-prose", "lite"])
|
||||
);
|
||||
assert.match(String(r.body.messages?.[0]?.content), /Follow tenant policy/);
|
||||
assert.match(String(r.body.messages?.[0]?.content), /Drop filler/);
|
||||
});
|
||||
|
||||
test("idempotent: re-applying is a no-op", () => {
|
||||
const body = { messages: [{ role: "user", content: "Summarize logs." }] };
|
||||
const once = applyOutputStyles(body, sel(["terse-prose", "full"])).body;
|
||||
const twice = applyOutputStyles(once, sel(["terse-prose", "full"]));
|
||||
assert.equal(twice.applied, false);
|
||||
assert.equal(twice.skippedReason, "already_applied");
|
||||
const markerCount = (String(twice.body.messages?.[0]?.content).match(
|
||||
new RegExp(escapeRe(OUTPUT_STYLE_MARKER), "g")
|
||||
) ?? []).length;
|
||||
assert.equal(markerCount, 1);
|
||||
});
|
||||
|
||||
test("content bypass is all-or-nothing across every selected style", () => {
|
||||
const r = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "Explain this security vulnerability in detail." }] },
|
||||
sel(["terse-prose", "full"], ["less-code", "full"])
|
||||
);
|
||||
assert.equal(r.applied, false);
|
||||
assert.equal(r.skippedReason, "security_warning");
|
||||
assert.equal(r.body.messages?.[0]?.role, "user"); // untouched
|
||||
});
|
||||
|
||||
test("no styles selected → body untouched", () => {
|
||||
const body = { messages: [{ role: "user", content: "Tell me a joke." }] };
|
||||
const r = applyOutputStyles(body, []);
|
||||
assert.equal(r.applied, false);
|
||||
assert.equal(r.skippedReason, "no_styles");
|
||||
assert.equal(r.body.messages?.[0]?.content, "Tell me a joke.");
|
||||
});
|
||||
|
||||
test("unknown style id is skipped, never throws", () => {
|
||||
const r = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "hi" }] },
|
||||
sel(["__nope__", "full"], ["terse-prose", "full"])
|
||||
);
|
||||
assert.equal(r.applied, true);
|
||||
assert.deepEqual(r.appliedStyles?.map((s) => s.id), ["terse-prose"]);
|
||||
});
|
||||
|
||||
test("locale gate: terse-cjk only honored under zh", () => {
|
||||
const enOnly = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "hi" }] },
|
||||
sel(["terse-cjk", "full"]),
|
||||
"en"
|
||||
);
|
||||
assert.equal(enOnly.applied, false);
|
||||
assert.equal(enOnly.skippedReason, "no_styles");
|
||||
|
||||
const zh = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "hi" }] },
|
||||
sel(["terse-cjk", "full"]),
|
||||
"zh"
|
||||
);
|
||||
assert.equal(zh.applied, true);
|
||||
assert.match(String(zh.body.messages?.[0]?.content), /文言/);
|
||||
});
|
||||
|
||||
test("determinism: same (selection, language) yields byte-identical injected text", () => {
|
||||
const make = () =>
|
||||
applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "do a thing" }] },
|
||||
sel(["terse-prose", "full"], ["less-code", "lite"])
|
||||
).body.messages?.[0]?.content;
|
||||
assert.equal(make(), make());
|
||||
});
|
||||
|
||||
test("Responses input (no messages) uses instructions field", () => {
|
||||
const r = applyOutputStyles(
|
||||
{ input: [{ type: "message", role: "user", content: "Summarize logs." }] },
|
||||
sel(["terse-prose", "full"])
|
||||
);
|
||||
assert.equal(r.applied, true);
|
||||
assert.match(String(r.body.instructions), new RegExp(escapeRe(OUTPUT_STYLE_MARKER)));
|
||||
assert.ok(!("messages" in r.body));
|
||||
});
|
||||
|
||||
test("terse-prose localizes per language (back-compat with the legacy caveman packs)", () => {
|
||||
// Regression guard: the legacy caveman output mode localized to en/pt-BR/ja/id; the
|
||||
// migrated terse-prose style must inject the SAME localized text, not fall back to English.
|
||||
const ptBR = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "Resuma os logs." }] },
|
||||
sel(["terse-prose", "lite"]),
|
||||
"pt-BR"
|
||||
);
|
||||
assert.match(String(ptBR.body.messages?.[0]?.content), /Responda conciso/);
|
||||
assert.doesNotMatch(String(ptBR.body.messages?.[0]?.content), /Respond concise/);
|
||||
|
||||
const en = applyOutputStyles(
|
||||
{ messages: [{ role: "user", content: "Summarize logs." }] },
|
||||
sel(["terse-prose", "lite"]),
|
||||
"en"
|
||||
);
|
||||
assert.match(String(en.body.messages?.[0]?.content), /Respond concise/);
|
||||
});
|
||||
|
||||
function escapeRe(s: string): string {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
49
tests/unit/compression/output-styles-backcompat.test.ts
Normal file
49
tests/unit/compression/output-styles-backcompat.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveOutputStyleSelection } from "../../../open-sse/services/compression/outputStyles/backCompat.ts";
|
||||
import { applyOutputStyles } from "../../../open-sse/services/compression/outputStyles/apply.ts";
|
||||
import { applyCavemanOutputMode } from "../../../open-sse/services/compression/outputMode.ts";
|
||||
|
||||
test("explicit outputStyles win when present", () => {
|
||||
const sel = resolveOutputStyleSelection({
|
||||
outputStyles: [{ id: "less-code", level: "ultra" }],
|
||||
cavemanOutputMode: { enabled: true, intensity: "lite", autoClarity: true },
|
||||
});
|
||||
assert.deepEqual(sel, [{ id: "less-code", level: "ultra" }]);
|
||||
});
|
||||
|
||||
test("legacy cavemanOutputMode maps to terse-prose at the same intensity", () => {
|
||||
const sel = resolveOutputStyleSelection({
|
||||
cavemanOutputMode: { enabled: true, intensity: "full", autoClarity: true },
|
||||
});
|
||||
assert.deepEqual(sel, [{ id: "terse-prose", level: "full" }]);
|
||||
});
|
||||
|
||||
test("disabled legacy mode and no styles → empty selection", () => {
|
||||
assert.deepEqual(
|
||||
resolveOutputStyleSelection({
|
||||
cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: true },
|
||||
}),
|
||||
[]
|
||||
);
|
||||
assert.deepEqual(resolveOutputStyleSelection({}), []);
|
||||
});
|
||||
|
||||
test("golden: legacy config injects the same prose instruction as the old injector", () => {
|
||||
const body = { messages: [{ role: "user", content: "Summarize this API response." }] };
|
||||
const legacy = applyCavemanOutputMode(structuredClone(body), {
|
||||
enabled: true,
|
||||
intensity: "full",
|
||||
autoClarity: true,
|
||||
});
|
||||
const sel = resolveOutputStyleSelection({
|
||||
cavemanOutputMode: { enabled: true, intensity: "full", autoClarity: true },
|
||||
});
|
||||
const next = applyOutputStyles(structuredClone(body), sel);
|
||||
|
||||
const legacyInstr = String(legacy.body.messages?.[0]?.content);
|
||||
const nextInstr = String(next.body.messages?.[0]?.content);
|
||||
// The prose instruction text (minus the marker line) must be byte-identical.
|
||||
const strip = (s: string) => s.split("\n").slice(1).join("\n");
|
||||
assert.equal(strip(nextInstr), strip(legacyInstr));
|
||||
});
|
||||
51
tests/unit/compression/output-styles-catalog.test.ts
Normal file
51
tests/unit/compression/output-styles-catalog.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
OUTPUT_STYLE_CATALOG,
|
||||
OUTPUT_STYLE_IDS,
|
||||
outputStyleMeta,
|
||||
type OutputStyle,
|
||||
} from "../../../open-sse/services/compression/outputStyles/catalog.ts";
|
||||
|
||||
test("catalog seeds terse-prose, less-code, terse-cjk with all three levels", () => {
|
||||
for (const id of ["terse-prose", "less-code", "terse-cjk"]) {
|
||||
const meta = outputStyleMeta(id);
|
||||
assert.ok(meta, `${id} present`);
|
||||
assert.equal(typeof meta.label, "string");
|
||||
for (const level of ["lite", "full", "ultra"] as const) {
|
||||
assert.equal(typeof meta.levels[level], "string");
|
||||
assert.ok(meta.levels[level].length > 0, `${id}.${level} non-empty`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("OUTPUT_STYLE_IDS lists every catalog id in catalog (declaration) order", () => {
|
||||
assert.deepEqual(OUTPUT_STYLE_IDS, Object.keys(OUTPUT_STYLE_CATALOG));
|
||||
});
|
||||
|
||||
test("terse-cjk carries a locale gate of zh", () => {
|
||||
assert.equal(outputStyleMeta("terse-cjk").locale, "zh");
|
||||
assert.equal(outputStyleMeta("terse-prose").locale, undefined);
|
||||
});
|
||||
|
||||
test("extensibility: one entry added to the catalog is enumerated with no other change", () => {
|
||||
const probe: OutputStyle = {
|
||||
id: "__probe__",
|
||||
label: "Probe",
|
||||
levels: { lite: "L", full: "F", ultra: "U" },
|
||||
};
|
||||
const extended = { ...OUTPUT_STYLE_CATALOG, [probe.id]: probe };
|
||||
const ids = Object.keys(extended);
|
||||
assert.ok(ids.includes("__probe__"));
|
||||
// Adding a style adds exactly one id; no plumbing edited.
|
||||
assert.equal(ids.length, OUTPUT_STYLE_IDS.length + 1);
|
||||
});
|
||||
|
||||
test("every level instruction is deterministic (no Date/Math.random tokens)", () => {
|
||||
for (const id of OUTPUT_STYLE_IDS) {
|
||||
const meta = outputStyleMeta(id);
|
||||
for (const level of ["lite", "full", "ultra"] as const) {
|
||||
assert.doesNotMatch(meta.levels[level], /Date\.now|Math\.random|\$\{/);
|
||||
}
|
||||
}
|
||||
});
|
||||
29
tests/unit/compression/output-styles-config.test.ts
Normal file
29
tests/unit/compression/output-styles-config.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { compressionSettingsUpdateSchema } from "../../../src/shared/validation/compressionConfigSchemas.ts";
|
||||
import type { CompressionConfig } from "../../../open-sse/services/compression/types.ts";
|
||||
|
||||
test("schema accepts a valid outputStyles selection", () => {
|
||||
const parsed = compressionSettingsUpdateSchema.parse({
|
||||
outputStyles: [
|
||||
{ id: "terse-prose", level: "full" },
|
||||
{ id: "less-code", level: "lite" },
|
||||
],
|
||||
});
|
||||
assert.equal(parsed.outputStyles?.length, 2);
|
||||
});
|
||||
|
||||
test("schema rejects an invalid level", () => {
|
||||
assert.throws(() =>
|
||||
compressionSettingsUpdateSchema.parse({
|
||||
outputStyles: [{ id: "terse-prose", level: "extreme" }],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("CompressionConfig type carries outputStyles", () => {
|
||||
const cfg: Pick<CompressionConfig, "outputStyles"> = {
|
||||
outputStyles: [{ id: "terse-prose", level: "full" }],
|
||||
};
|
||||
assert.equal(cfg.outputStyles?.[0]?.id, "terse-prose");
|
||||
});
|
||||
49
tests/unit/compression/output-styles-wiring.test.ts
Normal file
49
tests/unit/compression/output-styles-wiring.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { buildOutputStyleTelemetry } from "../../../open-sse/services/compression/outputStyles/telemetry.ts";
|
||||
|
||||
test("builds a telemetry record from an applied result", () => {
|
||||
const rec = buildOutputStyleTelemetry({
|
||||
requestId: "req-1",
|
||||
model: "gpt-4o",
|
||||
provider: "openai",
|
||||
source: "active-profile",
|
||||
tokensBefore: 1000,
|
||||
tokensAfter: 1000,
|
||||
applied: true,
|
||||
appliedStyles: [{ id: "terse-prose", level: "full" }],
|
||||
});
|
||||
assert.equal(rec.requestId, "req-1");
|
||||
assert.equal(rec.ratio, 1);
|
||||
assert.deepEqual(rec.outputStyles, [{ id: "terse-prose", level: "full" }]);
|
||||
assert.equal(rec.outputStyleBypass, undefined);
|
||||
});
|
||||
|
||||
test("records the bypass reason and omits styles when bypassed", () => {
|
||||
const rec = buildOutputStyleTelemetry({
|
||||
requestId: "req-2",
|
||||
model: "m",
|
||||
provider: "p",
|
||||
source: "default",
|
||||
tokensBefore: 500,
|
||||
tokensAfter: 500,
|
||||
applied: false,
|
||||
skippedReason: "security_warning",
|
||||
});
|
||||
assert.equal(rec.outputStyleBypass, "security_warning");
|
||||
assert.equal(rec.outputStyles, undefined);
|
||||
});
|
||||
|
||||
test("does not treat a benign skip (disabled/no_styles) as a bypass", () => {
|
||||
const rec = buildOutputStyleTelemetry({
|
||||
requestId: "req-3",
|
||||
model: "m",
|
||||
provider: "p",
|
||||
source: "off",
|
||||
tokensBefore: 0,
|
||||
tokensAfter: 0,
|
||||
applied: false,
|
||||
skippedReason: "no_styles",
|
||||
});
|
||||
assert.equal(rec.outputStyleBypass, undefined);
|
||||
});
|
||||
69
tests/unit/db/compressionRunTelemetry.test.ts
Normal file
69
tests/unit/db/compressionRunTelemetry.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { describe, it, beforeEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const tmpDir = mkdtempSync(join(tmpdir(), "omniroute-rt-"));
|
||||
process.env.DATA_DIR = tmpDir;
|
||||
|
||||
const core = await import("../../../src/lib/db/core.ts");
|
||||
core.resetDbInstance();
|
||||
const {
|
||||
insertCompressionRunTelemetryRow,
|
||||
getCompressionRunTelemetrySummary,
|
||||
} = await import("../../../src/lib/db/compressionRunTelemetry.ts");
|
||||
const { getDbInstance } = core;
|
||||
|
||||
describe("compressionRunTelemetry", () => {
|
||||
beforeEach(() => {
|
||||
const db = getDbInstance();
|
||||
db.exec("DROP TABLE IF EXISTS compression_run_telemetry");
|
||||
});
|
||||
|
||||
it("persists a run record and summarizes savings + applied styles", () => {
|
||||
insertCompressionRunTelemetryRow({
|
||||
requestId: "req-1",
|
||||
model: "gpt-4o",
|
||||
provider: "openai",
|
||||
source: "active-profile",
|
||||
tokensBefore: 1000,
|
||||
tokensAfter: 700,
|
||||
ratio: 0.7,
|
||||
outputStyles: [{ id: "terse-prose", level: "full" }],
|
||||
outputTokens: 320,
|
||||
});
|
||||
insertCompressionRunTelemetryRow({
|
||||
requestId: "req-2",
|
||||
model: "gpt-4o",
|
||||
provider: "openai",
|
||||
source: "default",
|
||||
tokensBefore: 500,
|
||||
tokensAfter: 500,
|
||||
ratio: 1,
|
||||
outputStyleBypass: "security_warning",
|
||||
});
|
||||
|
||||
const summary = getCompressionRunTelemetrySummary();
|
||||
assert.equal(summary.totalRuns, 2);
|
||||
assert.equal(summary.totalTokensSaved, 300); // (1000-700) + (500-500)
|
||||
assert.equal(summary.runsWithStyles, 1);
|
||||
assert.equal(summary.bypassCount, 1);
|
||||
assert.deepEqual(summary.appliedStyleCounts, { "terse-prose": 1 });
|
||||
});
|
||||
|
||||
it("never throws on a malformed row; outputStyles is optional", () => {
|
||||
assert.doesNotThrow(() =>
|
||||
insertCompressionRunTelemetryRow({
|
||||
requestId: "req-3",
|
||||
model: "m",
|
||||
provider: "p",
|
||||
source: "off",
|
||||
tokensBefore: 0,
|
||||
tokensAfter: 0,
|
||||
ratio: 0,
|
||||
})
|
||||
);
|
||||
assert.equal(getCompressionRunTelemetrySummary().totalRuns, 1);
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
// ── Mock next-intl (CompressionSettingsTab calls useTranslations) ──────────
|
||||
vi.mock("next-intl", () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => "en",
|
||||
}));
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
@@ -55,6 +56,21 @@ afterEach(async () => {
|
||||
function setupFetchMock() {
|
||||
vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => {
|
||||
const url = input.toString();
|
||||
// D0 tile reads this; it must resolve to a valid Summary BEFORE the generic
|
||||
// /api/settings/compression match (the run-telemetry URL contains that prefix).
|
||||
if (url.includes("/run-telemetry")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
totalRuns: 0,
|
||||
totalTokensSaved: 0,
|
||||
runsWithStyles: 0,
|
||||
bypassCount: 0,
|
||||
totalOutputTokens: 0,
|
||||
appliedStyleCounts: {},
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
if (url.includes("/api/settings/compression")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
@@ -101,6 +117,8 @@ describe("CompressionSettingsPage", () => {
|
||||
|
||||
expect(container).toBeTruthy();
|
||||
expect(container.children.length).toBeGreaterThan(0);
|
||||
// D0: the read-only telemetry tile is mounted alongside the panel.
|
||||
expect(container.querySelector('[data-testid="compression-styles-tile"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not crash when fetch calls fail (fail-soft)", async () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
// labels/descriptions, engine ids, data-testid hooks, and the PUT request body.
|
||||
vi.mock("next-intl", () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => "en",
|
||||
}));
|
||||
|
||||
// ── Harness ─────────────────────────────────────────────────────────────────
|
||||
|
||||
158
tests/unit/ui/compressionStylesPanel.test.tsx
Normal file
158
tests/unit/ui/compressionStylesPanel.test.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
// @vitest-environment jsdom
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import {
|
||||
OUTPUT_STYLE_IDS,
|
||||
outputStyleMeta,
|
||||
} from "../../../open-sse/services/compression/outputStyles/catalog.ts";
|
||||
|
||||
// Locale is mutable per-test so we can exercise the locale gate (terse-cjk → zh only).
|
||||
const intl = vi.hoisted(() => ({ locale: "en" }));
|
||||
vi.mock("next-intl", () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => intl.locale,
|
||||
}));
|
||||
|
||||
const containers: HTMLElement[] = [];
|
||||
const roots: Array<{ unmount: () => void }> = [];
|
||||
|
||||
function mount(ui: React.ReactElement): HTMLElement {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
containers.push(container);
|
||||
const root = createRoot(container);
|
||||
roots.push(root);
|
||||
act(() => root.render(ui));
|
||||
return container;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
||||
true;
|
||||
intl.locale = "en";
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
await act(async () => {
|
||||
while (roots.length > 0) roots.pop()?.unmount();
|
||||
});
|
||||
while (containers.length > 0) containers.pop()?.remove();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
async function flush() {
|
||||
await act(async () => {
|
||||
for (let i = 0; i < 10; i++) await Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function setupFetchMock() {
|
||||
const puts: Array<{ url: string; body: Record<string, unknown> }> = [];
|
||||
const json = (body: unknown, status = 200) =>
|
||||
new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
|
||||
const initial = {
|
||||
enabled: true,
|
||||
autoTriggerTokens: 0,
|
||||
preserveSystemPrompt: true,
|
||||
engines: {},
|
||||
activeComboId: null,
|
||||
outputStyles: [],
|
||||
cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: true },
|
||||
};
|
||||
vi.spyOn(globalThis, "fetch").mockImplementation(
|
||||
async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = input.toString();
|
||||
const method = (init?.method ?? "GET").toUpperCase();
|
||||
if (url.includes("/api/settings/compression/mcp-accessibility")) return json({ enabled: true });
|
||||
if (url.includes("/api/settings/compression")) {
|
||||
if (method === "PUT") {
|
||||
const body = JSON.parse(String(init?.body ?? "{}"));
|
||||
puts.push({ url, body });
|
||||
return json({ ...initial, ...body });
|
||||
}
|
||||
return json(initial);
|
||||
}
|
||||
return json({}, 404);
|
||||
}
|
||||
);
|
||||
return { puts };
|
||||
}
|
||||
|
||||
describe("CompressionPanel output styles", () => {
|
||||
it("renders one row per catalog style", async () => {
|
||||
setupFetchMock();
|
||||
intl.locale = "zh-CN"; // a locale that matches every gated style, so all rows render
|
||||
const { default: CompressionPanel } = await import(
|
||||
"../../../src/app/(dashboard)/dashboard/context/settings/CompressionPanel"
|
||||
);
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionPanel />);
|
||||
});
|
||||
await flush();
|
||||
for (const id of OUTPUT_STYLE_IDS) {
|
||||
const row = container.querySelector(`[data-testid="output-style-row-${id}"]`);
|
||||
expect(row, `expected a row for style "${id}"`).toBeTruthy();
|
||||
expect(container.textContent).toContain(outputStyleMeta(id).label);
|
||||
}
|
||||
});
|
||||
|
||||
it("locale-gates terse-cjk: hidden under a non-zh locale", async () => {
|
||||
setupFetchMock();
|
||||
intl.locale = "en";
|
||||
const { default: CompressionPanel } = await import(
|
||||
"../../../src/app/(dashboard)/dashboard/context/settings/CompressionPanel"
|
||||
);
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionPanel />);
|
||||
});
|
||||
await flush();
|
||||
// terse-cjk (locale "zh") must NOT be offered under "en"…
|
||||
expect(container.querySelector(`[data-testid="output-style-row-terse-cjk"]`)).toBeFalsy();
|
||||
// …while the non-gated styles still render.
|
||||
expect(container.querySelector(`[data-testid="output-style-row-terse-prose"]`)).toBeTruthy();
|
||||
expect(container.querySelector(`[data-testid="output-style-row-less-code"]`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("locale-gates terse-cjk: offered under a zh locale (zh-CN base matches)", async () => {
|
||||
setupFetchMock();
|
||||
intl.locale = "zh-CN";
|
||||
const { default: CompressionPanel } = await import(
|
||||
"../../../src/app/(dashboard)/dashboard/context/settings/CompressionPanel"
|
||||
);
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionPanel />);
|
||||
});
|
||||
await flush();
|
||||
expect(container.querySelector(`[data-testid="output-style-row-terse-cjk"]`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("toggling a style PUTs an outputStyles selection", async () => {
|
||||
const { puts } = setupFetchMock();
|
||||
const { default: CompressionPanel } = await import(
|
||||
"../../../src/app/(dashboard)/dashboard/context/settings/CompressionPanel"
|
||||
);
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionPanel />);
|
||||
});
|
||||
await flush();
|
||||
const toggle = container.querySelector(
|
||||
`[data-testid="output-style-toggle-terse-prose"] button, [data-testid="output-style-toggle-terse-prose"] input`
|
||||
) as HTMLElement | null;
|
||||
expect(toggle).toBeTruthy();
|
||||
await act(async () => {
|
||||
toggle!.click();
|
||||
});
|
||||
await flush();
|
||||
const put = puts.find((p) => "outputStyles" in p.body);
|
||||
expect(put, "a PUT carrying outputStyles").toBeTruthy();
|
||||
expect(
|
||||
(put!.body.outputStyles as Array<{ id: string }>).some((s) => s.id === "terse-prose")
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
68
tests/unit/ui/compressionStylesTile.test.tsx
Normal file
68
tests/unit/ui/compressionStylesTile.test.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
// @vitest-environment jsdom
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
|
||||
vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key }));
|
||||
|
||||
const containers: HTMLElement[] = [];
|
||||
const roots: Array<{ unmount: () => void }> = [];
|
||||
|
||||
function mount(ui: React.ReactElement): HTMLElement {
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
containers.push(container);
|
||||
const root = createRoot(container);
|
||||
roots.push(root);
|
||||
act(() => root.render(ui));
|
||||
return container;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
||||
true;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
await act(async () => {
|
||||
while (roots.length > 0) roots.pop()?.unmount();
|
||||
});
|
||||
while (containers.length > 0) containers.pop()?.remove();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
async function flush() {
|
||||
await act(async () => {
|
||||
for (let i = 0; i < 10; i++) await Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
describe("CompressionStylesTile", () => {
|
||||
it("renders total savings and applied style ids from the summary endpoint", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
totalRuns: 4,
|
||||
totalTokensSaved: 1234,
|
||||
runsWithStyles: 3,
|
||||
bypassCount: 1,
|
||||
totalOutputTokens: 900,
|
||||
appliedStyleCounts: { "terse-prose": 3, "less-code": 1 },
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
);
|
||||
const { default: CompressionStylesTile } = await import(
|
||||
"../../../src/app/(dashboard)/dashboard/context/CompressionStylesTile"
|
||||
);
|
||||
let container!: HTMLElement;
|
||||
await act(async () => {
|
||||
container = mount(<CompressionStylesTile />);
|
||||
});
|
||||
await flush();
|
||||
expect(container.textContent).toContain("1234");
|
||||
expect(container.textContent).toContain("terse-prose");
|
||||
expect(container.textContent).toContain("less-code");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user