feat(compression): T05/C5 — preserveSystemPrompt mode enum (always|whenNoCache|never)

Replace the implicit 3-state behaviour of the boolean preserveSystemPrompt with an
explicit authoritative enum, keeping the boolean as the engine-facing effective value:

- always      -> never compress the system prompt (legacy true).
- whenNoCache -> compress it only when there is no cache to protect (legacy false; the
                 #3890/#3955 cache guard already did exactly this).
- never       -> always compress the system prompt, even when it breaks a prompt cache (new).

The mode is resolved to the effective boolean at resolveCacheAwareConfig (already upstream
in chatCore with the caching context), generalizing the previous hard-coded force-true.
Back-compat shim derives the mode from the legacy boolean, so existing configs and the
cache-guard tests are behaviour-identical. Persisted in compression settings, validated by
Zod, exposed as a 3-way select in both compression panels.

New helper open-sse/services/compression/preserveSystemPromptMode.ts is the single source
of the enum<->boolean mapping. TDD: helper, cache-aware resolution incl. never, DB
round-trip, UI select render + save.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-30 11:00:55 -03:00
parent 6a4d0d2fae
commit 72e223b9a9
12 changed files with 378 additions and 31 deletions

View File

@@ -1,23 +1,36 @@
import type { CompressionConfig } from "./types.ts";
import type { CachingDetectionContext } from "./cachingAware.ts";
import { detectCachingContext, getCacheAwareStrategy } from "./cachingAware.ts";
import {
normalizePreserveSystemPromptMode,
resolvePreserveSystemPrompt,
} from "./preserveSystemPromptMode.ts";
/**
* #3890: honor the cache-aware `skipSystemPrompt` decision that
* `getCacheAwareStrategy` already computes but `selectCompressionStrategy`
* cannot return. In a caching context the system prompt is part of the
* cacheable prefix, so compressing it breaks the upstream prompt cache.
* #3890/#3955 + T05/C5: materialize the engine-facing `preserveSystemPrompt`
* boolean from the authoritative `preserveSystemPromptMode` intent, using the
* cache-aware `skipSystemPrompt` signal that `getCacheAwareStrategy` already
* computes (a caching provider — or `cache_control` — means the system prompt is
* part of the cacheable prefix, so compressing it breaks the upstream cache).
*
* This generalizes the previous hard-coded "force `true` when a cache is present
* and the operator disabled preservation" into the three modes:
* - `always` → always `true`.
* - `whenNoCache` → `true` only when a cache is present (the legacy `false`
* behaviour — preserved exactly for back-compat).
* - `never` → always `false`, even when it breaks a prompt cache.
*/
export function resolveCacheAwareConfig(
config: CompressionConfig,
body?: Record<string, unknown>,
context?: CachingDetectionContext
): CompressionConfig {
if (!body) return config;
const ctx = detectCachingContext(body, context);
const cacheAware = getCacheAwareStrategy(config.defaultMode, ctx);
if (cacheAware.skipSystemPrompt && config.preserveSystemPrompt === false) {
return { ...config, preserveSystemPrompt: true };
}
return config;
const mode = normalizePreserveSystemPromptMode(config);
// No request body → no cacheable prefix to detect; honor the mode at its no-cache baseline.
const hasCache = body
? getCacheAwareStrategy(config.defaultMode, detectCachingContext(body, context)).skipSystemPrompt
: false;
const effective = resolvePreserveSystemPrompt(mode, { hasCache });
if (effective === config.preserveSystemPrompt) return config;
return { ...config, preserveSystemPrompt: effective };
}

View File

@@ -0,0 +1,72 @@
import type { CompressionConfig, PreserveSystemPromptMode } from "./types.ts";
/**
* T05/C5 — system-prompt preservation mode.
*
* The engine-facing field is the boolean `CompressionConfig.preserveSystemPrompt`
* (truthy = skip/preserve the system prompt). Its authoritative *intent* is the
* `preserveSystemPromptMode` enum, resolved to that boolean at the cache-aware layer
* (`resolveCacheAwareConfig`), which already runs upstream in `chatCore` with the
* caching context. This module is the single source of the enum<->boolean mapping so
* the legacy boolean and the new enum can never drift.
*
* Mode semantics:
* - `always` → preserve the system prompt unconditionally.
* - `whenNoCache` → preserve it only when there is a cache to protect (provider caches
* the prefix or `cache_control` is present); compress it otherwise.
* This is exactly what the legacy `preserveSystemPrompt: false` already
* did via the #3890/#3955 cache guard.
* - `never` → compress the system prompt even when it breaks a prompt cache.
*/
export const PRESERVE_SYSTEM_PROMPT_MODES: readonly PreserveSystemPromptMode[] = [
"always",
"whenNoCache",
"never",
];
export function isPreserveSystemPromptMode(value: unknown): value is PreserveSystemPromptMode {
return (
typeof value === "string" &&
(PRESERVE_SYSTEM_PROMPT_MODES as readonly string[]).includes(value)
);
}
/**
* Back-compat shim: derive the authoritative mode from a config. An explicit
* `preserveSystemPromptMode` always wins; otherwise the legacy boolean is mapped
* 1:1 to the behaviour it already had (`false → whenNoCache`, anything else → `always`).
*/
export function normalizePreserveSystemPromptMode(
config: Pick<CompressionConfig, "preserveSystemPrompt" | "preserveSystemPromptMode">
): PreserveSystemPromptMode {
if (isPreserveSystemPromptMode(config.preserveSystemPromptMode)) {
return config.preserveSystemPromptMode;
}
return config.preserveSystemPrompt === false ? "whenNoCache" : "always";
}
/**
* Resolve a mode to the effective engine-facing boolean given whether a cacheable
* prefix is present. `true` = preserve (skip), `false` = compress the system prompt.
*/
export function resolvePreserveSystemPrompt(
mode: PreserveSystemPromptMode,
{ hasCache }: { hasCache: boolean }
): boolean {
switch (mode) {
case "always":
return true;
case "never":
return false;
case "whenNoCache":
return hasCache;
}
}
/**
* The no-cache projection of a mode — the stored/effective boolean used as a sane
* default for any reader that runs before the cache-aware layer materializes it.
*/
export function modeToBaselineBoolean(mode: PreserveSystemPromptMode): boolean {
return resolvePreserveSystemPrompt(mode, { hasCache: false });
}

View File

@@ -145,13 +145,34 @@ export interface EngineToggle {
level?: string;
}
/** T05/C5 — system-prompt preservation intent (see `CompressionConfig.preserveSystemPromptMode`). */
export type PreserveSystemPromptMode = "always" | "whenNoCache" | "never";
export interface CompressionConfig {
enabled: boolean;
defaultMode: CompressionMode;
autoTriggerMode?: CompressionMode;
autoTriggerTokens: number;
cacheMinutes: number;
/**
* Effective, engine-facing boolean: when truthy the system prompt is skipped
* (preserved, not compressed). Kept as the materialized value all engines read.
* Its authoritative *intent* is `preserveSystemPromptMode` (T05/C5); this boolean
* is the no-cache projection of that mode, refined up to `true` by
* `resolveCacheAwareConfig` when a cacheable prefix is detected.
*/
preserveSystemPrompt: boolean;
/**
* T05/C5 — authoritative system-prompt preservation intent:
* - `always`: never compress the system prompt.
* - `whenNoCache`: compress it only when there is no cache to protect
* (preserve when the provider caches or `cache_control` is present). This is the
* behaviour the legacy `preserveSystemPrompt: false` already had via the cache guard.
* - `never`: always compress the system prompt, even when it breaks a prompt cache.
* Optional/back-compat: absent → derived from the legacy boolean
* (`false → whenNoCache`, otherwise `always`).
*/
preserveSystemPromptMode?: PreserveSystemPromptMode;
mcpDescriptionCompressionEnabled?: boolean;
comboOverrides: Record<string, CompressionMode>;
compressionComboId?: string | null;
@@ -289,6 +310,7 @@ export const DEFAULT_COMPRESSION_CONFIG: CompressionConfig = {
autoTriggerTokens: 0,
cacheMinutes: 5,
preserveSystemPrompt: true,
preserveSystemPromptMode: "always",
mcpDescriptionCompressionEnabled: true,
comboOverrides: {},
compressionComboId: null,

View File

@@ -51,6 +51,7 @@ interface CompressionConfig {
enabled: boolean;
autoTriggerTokens: number;
preserveSystemPrompt: boolean;
preserveSystemPromptMode?: "always" | "whenNoCache" | "never";
engines: Record<string, EngineToggle>;
activeComboId: string | null;
cavemanOutputMode?: CavemanOutputModeConfig;
@@ -458,15 +459,25 @@ export default function CompressionPanel() {
</label>
<label className="flex items-center justify-between">
<span className="text-sm text-text-muted">{t("compressionPreserveSystem")}</span>
<span data-testid="preserve-system-toggle">
<Toggle
size="sm"
checked={config.preserveSystemPrompt}
onChange={(preserveSystemPrompt) => save({ preserveSystemPrompt })}
disabled={saving}
ariaLabel={t("compressionPreserveSystem")}
/>
</span>
<select
value={
config.preserveSystemPromptMode ??
(config.preserveSystemPrompt === false ? "whenNoCache" : "always")
}
onChange={(e) =>
save({
preserveSystemPromptMode: e.target.value as "always" | "whenNoCache" | "never",
})
}
disabled={saving}
aria-label={t("compressionPreserveSystem")}
data-testid="preserve-system-mode-select"
className="w-36 rounded border border-border bg-surface px-2 py-1 text-sm text-text-main"
>
<option value="always">{t("compressionPreserveSystemAlways")}</option>
<option value="whenNoCache">{t("compressionPreserveSystemWhenNoCache")}</option>
<option value="never">{t("compressionPreserveSystemNever")}</option>
</select>
</label>
</div>
</Card>

View File

@@ -65,6 +65,7 @@ interface CompressionConfig extends CompressionTokenSaverConfig {
autoTriggerTokens: number;
cacheMinutes: number;
preserveSystemPrompt: boolean;
preserveSystemPromptMode?: "always" | "whenNoCache" | "never";
mcpDescriptionCompressionEnabled?: boolean;
comboOverrides: Record<string, CompressionMode>;
cavemanConfig?: CavemanConfig;
@@ -372,18 +373,26 @@ export default function CompressionSettingsTab() {
<label className="flex items-center justify-between">
<span className="text-sm text-text-muted">{t("compressionPreserveSystem")}</span>
<button
onClick={() => save({ preserveSystemPrompt: !config.preserveSystemPrompt })}
className={`relative w-10 h-5 rounded-full transition-colors ${
config.preserveSystemPrompt ? "bg-green-500" : "bg-border"
}`}
<select
value={
config.preserveSystemPromptMode ??
(config.preserveSystemPrompt === false ? "whenNoCache" : "always")
}
onChange={(e) =>
save({
preserveSystemPromptMode: e.target.value as
| "always"
| "whenNoCache"
| "never",
})
}
className="w-36 px-2 py-1 text-sm rounded border border-border bg-surface text-text-main"
data-testid="preserve-system-mode-select"
>
<span
className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-transform ${
config.preserveSystemPrompt ? "left-5" : "left-0.5"
}`}
/>
</button>
<option value="always">{t("compressionPreserveSystemAlways")}</option>
<option value="whenNoCache">{t("compressionPreserveSystemWhenNoCache")}</option>
<option value="never">{t("compressionPreserveSystemNever")}</option>
</select>
</label>
<label className="flex items-center justify-between">

View File

@@ -5635,6 +5635,9 @@
"compressionAutoTrigger": "Auto-Trigger Threshold",
"compressionCacheTTL": "Cache TTL",
"compressionPreserveSystem": "Preserve System Prompt",
"compressionPreserveSystemAlways": "Always",
"compressionPreserveSystemWhenNoCache": "When no cache",
"compressionPreserveSystemNever": "Never",
"compressionCavemanConfig": "Caveman Engine Configuration",
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
"compressionCavemanPanelHint": "Its on/off and level are set in the panel:",

View File

@@ -28,6 +28,7 @@ import {
type RtkConfig,
type UltraConfig,
} from "@omniroute/open-sse/services/compression/types.ts";
import { isPreserveSystemPromptMode } from "@omniroute/open-sse/services/compression/preserveSystemPromptMode.ts";
import { maybePrewarmUltraSlmOnConfig } from "@omniroute/open-sse/services/compression/ultra.ts";
const NAMESPACE = "compression";
@@ -587,6 +588,12 @@ export async function getCompressionSettings(): Promise<CompressionConfig> {
case "preserveSystemPrompt":
config.preserveSystemPrompt = parsed !== false;
break;
case "preserveSystemPromptMode":
// T05/C5 — authoritative intent; ignore unknown tokens (keep the default mode).
if (isPreserveSystemPromptMode(parsed)) {
config.preserveSystemPromptMode = parsed;
}
break;
case "mcpDescriptionCompressionEnabled":
config.mcpDescriptionCompressionEnabled = parsed !== false;
break;

View File

@@ -210,6 +210,7 @@ export const compressionSettingsUpdateSchema = z
autoTriggerTokens: z.number().int().min(0).optional(),
cacheMinutes: z.number().int().min(1).max(60).optional(),
preserveSystemPrompt: z.boolean().optional(),
preserveSystemPromptMode: z.enum(["always", "whenNoCache", "never"]).optional(),
mcpDescriptionCompressionEnabled: z.boolean().optional(),
comboOverrides: z.record(z.string(), compressionModeSchema).optional(),
compressionComboId: z.string().trim().min(1).nullable().optional(),

View File

@@ -0,0 +1,81 @@
/**
* T05/C5 — resolveCacheAwareConfig materializes the `preserveSystemPromptMode` enum
* into the engine-facing `preserveSystemPrompt` boolean using the cache signal.
* The legacy boolean behaviour is covered by strategySelector-cache-aware.test.ts /
* compression-cache-guard-3955.test.ts (unchanged); this file covers the new enum.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { resolveCacheAwareConfig } from "../../../open-sse/services/compression/cacheAwareConfig.ts";
import type {
CompressionConfig,
PreserveSystemPromptMode,
} from "../../../open-sse/services/compression/types.ts";
function cfg(overrides: Partial<CompressionConfig> = {}): CompressionConfig {
return {
enabled: true,
defaultMode: "standard",
autoTriggerTokens: 0,
cacheMinutes: 5,
preserveSystemPrompt: true,
comboOverrides: {},
...overrides,
} as CompressionConfig;
}
// A request whose prefix the upstream caches (anthropic + explicit cache_control).
const CACHING_BODY = {
messages: [{ role: "system", content: "x", cache_control: { type: "ephemeral" } }],
};
const CACHING_CTX = { provider: "anthropic", targetFormat: "claude" } as const;
// google has no prompt caching → no cacheable prefix to protect.
const NON_CACHING_BODY = { messages: [{ role: "system", content: "x" }] };
const NON_CACHING_CTX = { provider: "google" } as const;
function resolved(mode: PreserveSystemPromptMode, caching: boolean): boolean {
const out = resolveCacheAwareConfig(
cfg({ preserveSystemPromptMode: mode, preserveSystemPrompt: true }),
caching ? CACHING_BODY : NON_CACHING_BODY,
caching ? CACHING_CTX : NON_CACHING_CTX
);
return out.preserveSystemPrompt;
}
describe("T05/C5 preserveSystemPromptMode -> effective boolean", () => {
it("always: preserves regardless of cache", () => {
assert.equal(resolved("always", true), true);
assert.equal(resolved("always", false), true);
});
it("whenNoCache: preserves only when a cache is present", () => {
assert.equal(resolved("whenNoCache", true), true);
assert.equal(resolved("whenNoCache", false), false);
});
it("never: compresses the system prompt even when it would break the prompt cache", () => {
assert.equal(resolved("never", true), false);
assert.equal(resolved("never", false), false);
});
it("the explicit mode overrides a contradicting legacy boolean", () => {
// mode=never wins even though the legacy boolean asks to preserve.
const out = resolveCacheAwareConfig(
cfg({ preserveSystemPromptMode: "never", preserveSystemPrompt: true }),
CACHING_BODY,
CACHING_CTX
);
assert.equal(out.preserveSystemPrompt, false);
});
it("no body: honors the mode at its no-cache baseline", () => {
assert.equal(
resolveCacheAwareConfig(cfg({ preserveSystemPromptMode: "whenNoCache" })).preserveSystemPrompt,
false
);
assert.equal(
resolveCacheAwareConfig(cfg({ preserveSystemPromptMode: "always" })).preserveSystemPrompt,
true
);
});
});

View File

@@ -51,6 +51,7 @@ describe("getCompressionSettings", () => {
assert.equal(settings.autoTriggerTokens, 0);
assert.equal(settings.cacheMinutes, 5);
assert.equal(settings.preserveSystemPrompt, true);
assert.equal(settings.preserveSystemPromptMode, "always");
assert.deepEqual(settings.comboOverrides, {});
assert.equal(settings.ultra?.enabled, false);
assert.equal(settings.ultra?.compressionRate, 0.5);
@@ -77,6 +78,25 @@ describe("updateCompressionSettings", () => {
await updateCompressionSettings({ defaultMode: "off" } as any);
});
it("round-trips preserveSystemPromptMode and ignores unknown tokens (T05/C5)", async () => {
await updateCompressionSettings({ preserveSystemPromptMode: "never" } as any);
let settings = await getCompressionSettings();
assert.equal(settings.preserveSystemPromptMode, "never");
await updateCompressionSettings({ preserveSystemPromptMode: "whenNoCache" } as any);
settings = await getCompressionSettings();
assert.equal(settings.preserveSystemPromptMode, "whenNoCache");
// An unknown persisted token is rejected on read and falls back to the safe
// default mode ("always"), never crashing the settings load.
await updateCompressionSettings({ preserveSystemPromptMode: "garbage" } as any);
settings = await getCompressionSettings();
assert.equal(settings.preserveSystemPromptMode, "always");
// Reset
await updateCompressionSettings({ preserveSystemPromptMode: "always" } as any);
});
it("updates autoTriggerTokens", async () => {
await updateCompressionSettings({ autoTriggerTokens: 5000 } as any);
const settings = await getCompressionSettings();

View File

@@ -0,0 +1,78 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
PRESERVE_SYSTEM_PROMPT_MODES,
isPreserveSystemPromptMode,
normalizePreserveSystemPromptMode,
resolvePreserveSystemPrompt,
modeToBaselineBoolean,
} from "../../../open-sse/services/compression/preserveSystemPromptMode.ts";
// T05/C5 — preserveSystemPrompt boolean -> enum (always | whenNoCache | never) + back-compat shim.
test("isPreserveSystemPromptMode only accepts the three known tokens", () => {
for (const m of PRESERVE_SYSTEM_PROMPT_MODES) assert.equal(isPreserveSystemPromptMode(m), true);
for (const bad of ["", "Always", "no-cache", true, false, 1, null, undefined, {}]) {
assert.equal(isPreserveSystemPromptMode(bad), false, `${String(bad)} must be rejected`);
}
});
test("normalize: an explicit mode wins over the legacy boolean", () => {
assert.equal(
normalizePreserveSystemPromptMode({ preserveSystemPrompt: true, preserveSystemPromptMode: "never" }),
"never"
);
assert.equal(
normalizePreserveSystemPromptMode({ preserveSystemPrompt: false, preserveSystemPromptMode: "always" }),
"always"
);
});
test("normalize: the shim maps the legacy boolean 1:1 (true->always, false->whenNoCache)", () => {
// The shim is a behaviour identity: legacy `false` already meant "compress unless
// there is a cache" via the #3890/#3955 guard, i.e. exactly `whenNoCache`.
assert.equal(normalizePreserveSystemPromptMode({ preserveSystemPrompt: true }), "always");
assert.equal(normalizePreserveSystemPromptMode({ preserveSystemPrompt: false }), "whenNoCache");
});
test("normalize: an invalid mode string falls back to the legacy boolean", () => {
assert.equal(
normalizePreserveSystemPromptMode({
preserveSystemPrompt: false,
preserveSystemPromptMode: "garbage" as unknown as "always",
}),
"whenNoCache"
);
});
test("resolve: always preserves and never compresses regardless of cache", () => {
assert.equal(resolvePreserveSystemPrompt("always", { hasCache: true }), true);
assert.equal(resolvePreserveSystemPrompt("always", { hasCache: false }), true);
assert.equal(resolvePreserveSystemPrompt("never", { hasCache: true }), false);
assert.equal(resolvePreserveSystemPrompt("never", { hasCache: false }), false);
});
test("resolve: whenNoCache preserves only when a cache is present", () => {
assert.equal(resolvePreserveSystemPrompt("whenNoCache", { hasCache: true }), true);
assert.equal(resolvePreserveSystemPrompt("whenNoCache", { hasCache: false }), false);
});
test("modeToBaselineBoolean is the no-cache projection", () => {
assert.equal(modeToBaselineBoolean("always"), true);
assert.equal(modeToBaselineBoolean("whenNoCache"), false);
assert.equal(modeToBaselineBoolean("never"), false);
});
test("back-compat identity: legacy config resolves to the old cache-guard outcome", () => {
// Old behaviour: preserveSystemPrompt=false stays false without cache, forced true with cache.
const legacyFalse = { preserveSystemPrompt: false };
const mode = normalizePreserveSystemPromptMode(legacyFalse);
assert.equal(resolvePreserveSystemPrompt(mode, { hasCache: false }), false);
assert.equal(resolvePreserveSystemPrompt(mode, { hasCache: true }), true);
// Old behaviour: preserveSystemPrompt=true is always preserved.
const legacyTrue = { preserveSystemPrompt: true };
const modeT = normalizePreserveSystemPromptMode(legacyTrue);
assert.equal(resolvePreserveSystemPrompt(modeT, { hasCache: false }), true);
assert.equal(resolvePreserveSystemPrompt(modeT, { hasCache: true }), true);
});

View File

@@ -111,4 +111,34 @@ describe("CompressionSettingsTab — compression controls consolidation (T11)",
expect(container.textContent).toContain("compressionSkipRules");
expect(container.textContent).toContain("compressionPreservePatterns");
});
it("renders the preserveSystemPrompt 3-way mode select reflecting the shim (T05/C5)", async () => {
await renderTab();
const select = container.querySelector<HTMLSelectElement>(
'[data-testid="preserve-system-mode-select"]'
);
expect(select).not.toBeNull();
const values = Array.from(select!.querySelectorAll("option")).map((o) => o.value);
expect(values).toEqual(["always", "whenNoCache", "never"]);
// CONFIG has preserveSystemPrompt: true and no explicit mode → shim renders "always".
expect(select!.value).toBe("always");
});
it("saves the chosen mode via PUT (T05/C5)", async () => {
await renderTab();
const select = container.querySelector<HTMLSelectElement>(
'[data-testid="preserve-system-mode-select"]'
);
await act(async () => {
select!.value = "never";
select!.dispatchEvent(new Event("change", { bubbles: true }));
await new Promise((resolve) => setTimeout(resolve, 0));
});
const putCall = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls.find(
([, init]) => (init as RequestInit | undefined)?.method === "PUT"
);
expect(putCall).toBeTruthy();
const body = JSON.parse(String((putCall![1] as RequestInit).body));
expect(body.preserveSystemPromptMode).toBe("never");
});
});