Hide combo compression controls when disabled (#1840)

Integrated into release/v3.7.8
This commit is contained in:
Randi
2026-05-01 07:36:15 -04:00
committed by GitHub
parent 21f4f8ddce
commit 3d70ad414e
4 changed files with 199 additions and 18 deletions

View File

@@ -136,6 +136,7 @@ import {
isFallbackDecision,
EMERGENCY_FALLBACK_CONFIG,
} from "../services/emergencyFallback.ts";
import type { CompressionConfig } from "../services/compression/types.ts";
import { prepareWebSearchFallbackBody } from "../services/webSearchFallback.ts";
import {
resolveExplicitStreamAlias,
@@ -1561,15 +1562,37 @@ export async function handleChatCore({
body?.messages || body?.input || body?.contents || body?.request?.contents || [];
if (body && Array.isArray(allMessages) && allMessages.length > 0) {
let estimatedTokens = estimateTokens(JSON.stringify(allMessages));
let promptCompressionEnabled = false;
let compressionSettings: CompressionConfig | null = null;
try {
const { getCompressionSettings } = await import("../../src/lib/db/compression.ts");
compressionSettings = await getCompressionSettings();
promptCompressionEnabled = compressionSettings.enabled;
} catch (err) {
log?.warn?.(
"COMPRESSION",
"Compression settings lookup skipped: " + (err instanceof Error ? err.message : String(err))
);
}
// --- Modular Compression Pipeline (Phase 1 Lite + Phase 2 Standard/Caveman + Phase 3 Aggressive) ---
// Runs BEFORE the existing reactive compressContext() to proactively reduce tokens.
try {
const { getCompressionSettings } = await import("../../src/lib/db/compression.ts");
const { selectCompressionStrategy, applyCompression } =
await import("../services/compression/strategySelector.ts");
const { trackCompressionStats } = await import("../services/compression/stats.ts");
let config = await getCompressionSettings();
let config: CompressionConfig = compressionSettings ?? {
enabled: false,
defaultMode: "off",
autoTriggerTokens: 0,
cacheMinutes: 5,
preserveSystemPrompt: true,
comboOverrides: {},
};
if (!promptCompressionEnabled || !compressionSettings) {
log?.debug?.("COMPRESSION", "Prompt compression disabled or unavailable");
}
let compressionComboKey = comboName ?? null;
if (isCombo && comboName) {
try {
@@ -1704,6 +1727,12 @@ export async function handleChatCore({
}
// --- End Modular Compression Pipeline ---
if (!promptCompressionEnabled) {
log?.debug?.(
"CONTEXT",
"Skipping proactive context compression: Prompt Compression disabled"
);
}
let contextLimit = getTokenLimit(provider, effectiveModel);
if (isCombo && comboName) {
@@ -1748,7 +1777,7 @@ export async function handleChatCore({
`Checking compression: ${estimatedTokens} tokens vs ${threshold} threshold (${contextLimit} limit, ${reservedTokens} reserved)`
);
if (estimatedTokens > threshold) {
if (promptCompressionEnabled && estimatedTokens > threshold) {
log?.info?.(
"CONTEXT",
`Proactive compression triggered: ${estimatedTokens} tokens > ${threshold} threshold (${contextLimit} limit)`

View File

@@ -606,6 +606,7 @@ export default function CombosPage() {
const [comboDragOverIndex, setComboDragOverIndex] = useState(null);
const [savingComboOrder, setSavingComboOrder] = useState(false);
const [comboConfigMode, setComboConfigMode] = useState("guided");
const [promptCompressionEnabled, setPromptCompressionEnabled] = useState(false);
const [selectedIntelligentComboId, setSelectedIntelligentComboId] = useState<string | null>(null);
const comboDragIndexRef = useRef<number | null>(null);
const activeFilter = normalizeIntelligentRoutingFilter(searchParams.get("filter"));
@@ -650,6 +651,10 @@ export default function CombosPage() {
.then((r) => (r.ok ? r.json() : null))
.then((settings) => setComboConfigMode(normalizeComboConfigMode(settings?.comboConfigMode)))
.catch(() => setComboConfigMode("guided"));
fetch("/api/settings/compression")
.then((r) => (r.ok ? r.json() : null))
.then((settings) => setPromptCompressionEnabled(settings?.enabled === true))
.catch(() => setPromptCompressionEnabled(false));
fetch("/api/settings/proxy")
.then((r) => (r.ok ? r.json() : null))
.then((c) => setProxyConfig(c))
@@ -1128,6 +1133,7 @@ export default function CombosPage() {
<ComboCard
combo={combo}
metrics={metrics[combo.name]}
compressionEnabled={promptCompressionEnabled}
providerNodes={providerNodes}
copied={copied}
onCopy={copy}
@@ -1499,6 +1505,7 @@ function ComboReadinessPanel({ checks, blockers, showDescription = true }) {
function ComboCard({
combo,
metrics,
compressionEnabled,
copied,
onCopy,
onEdit,
@@ -1524,10 +1531,20 @@ function ComboCard({
const tc = useTranslations("common");
const emailsVisible = useEmailPrivacyStore((s) => s.emailsVisible);
const strategyDescription = getStrategyDescription(t, strategy);
const initialCompressionMode = combo?.config?.compressionMode || combo.compressionOverride || "";
const hasRuntimeConfig = combo?.config && typeof combo.config === "object";
const initialCompressionMode =
typeof combo?.config?.compressionMode === "string"
? combo.config.compressionMode
: hasRuntimeConfig
? ""
: combo.compressionOverride || "";
const [compressionOverride, setCompressionOverride] = useState(initialCompressionMode);
const [isSavingCompression, setIsSavingCompression] = useState(false);
useEffect(() => {
setCompressionOverride(initialCompressionMode);
}, [initialCompressionMode]);
const handleCompressionOverrideChange = async (value) => {
setCompressionOverride(value);
setIsSavingCompression(true);
@@ -1687,20 +1704,22 @@ function ComboCard({
</span>
</div>
<div className="flex items-center gap-1.5 transition-opacity">
<select
value={compressionOverride}
onChange={(e) => handleCompressionOverrideChange(e.target.value)}
disabled={isSavingCompression}
className="text-xs py-1 px-2 rounded border border-black/10 dark:border-white/10 bg-white dark:bg-bg-main text-text-main focus:border-primary focus:outline-none transition-colors disabled:opacity-50 max-w-[130px] md:max-w-none"
title="Compression Override"
>
<option value="">Default</option>
<option value="off">Off</option>
<option value="lite">Lite</option>
<option value="standard">Standard</option>
<option value="aggressive">Aggressive</option>
<option value="ultra">Ultra</option>
</select>
{compressionEnabled && (
<select
value={compressionOverride}
onChange={(e) => handleCompressionOverrideChange(e.target.value)}
disabled={isSavingCompression}
className="text-xs py-1 px-2 rounded border border-black/10 dark:border-white/10 bg-white dark:bg-bg-main text-text-main focus:border-primary focus:outline-none transition-colors disabled:opacity-50 max-w-[130px] md:max-w-none"
title="Compression Override"
>
<option value="">Default</option>
<option value="off">Off</option>
<option value="lite">Lite</option>
<option value="standard">Standard</option>
<option value="aggressive">Aggressive</option>
<option value="ultra">Ultra</option>
</select>
)}
<button
onClick={onTest}
disabled={testing}

View File

@@ -47,6 +47,12 @@ test("chatCore integration: compressContext called proactively when context exce
const provider = "openai";
const model = "gpt-4";
await compressionDb.updateCompressionSettings({
enabled: true,
defaultMode: "off",
autoTriggerTokens: 0,
});
// Create multiple messages with history that can be compressed
// Use the same pattern as test 3 which successfully tests compression
const body = {
@@ -120,9 +126,113 @@ test("chatCore integration: compressContext called proactively when context exce
}
});
test("chatCore integration: disabled prompt compression leaves combo override requests unchanged", async () => {
const provider = "openai";
const model = "gpt-4";
const originalContextLength = process.env.CONTEXT_LENGTH_OPENAI;
process.env.CONTEXT_LENGTH_OPENAI = "8192";
await compressionDb.updateCompressionSettings({
enabled: false,
defaultMode: "off",
autoTriggerTokens: 1,
comboOverrides: { "disabled-compression-combo": "lite" },
});
const connection = await providersDb.createProviderConnection({
provider,
apiKey: "test-key",
isActive: true,
});
await combosDb.createCombo({
name: "disabled-compression-combo",
strategy: "priority",
models: [
{
kind: "model",
model: `${provider}/${model}`,
connectionId: connection.id,
},
],
config: {
compressionMode: "lite",
},
});
const body = {
model: "combo/disabled-compression-combo",
stream: false,
messages: [
{ role: "system", content: "You are helpful." },
{ role: "user", content: `${"Keep spacing.\n\n\n".repeat(2000)}First long turn.` },
{ role: "assistant", content: "Response 1" },
{ role: "user", content: `${"Keep spacing.\n\n\n".repeat(2000)}Second long turn.` },
{ role: "assistant", content: "Response 2" },
{ role: "user", content: `${"Keep spacing.\n\n\n".repeat(2000)}Final question.` },
],
};
const contextLimit = getTokenLimit(provider, model);
const proactiveThreshold = Math.floor(contextLimit * 0.7);
assert.ok(
estimateTokens(JSON.stringify(body.messages)) > proactiveThreshold,
"Test body should exceed proactive compression threshold"
);
let capturedBody: any = null;
globalThis.fetch = async (_url: string | URL | Request, init?: RequestInit) => {
if (init?.body) {
capturedBody = JSON.parse(init.body as string);
}
return new Response(
JSON.stringify({
choices: [{ message: { role: "assistant", content: "test" } }],
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
}),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
};
try {
const result = await handleChatCore({
body,
modelInfo: { provider, model },
credentials: { apiKey: "test-key" },
log: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },
clientRawRequest: { endpoint: "/v1/chat/completions", headers: new Map() },
connectionId: connection.id,
isCombo: true,
comboName: "disabled-compression-combo",
});
assert.ok(result.success, "Request should succeed");
assert.ok(capturedBody, "Fetch should have been called");
assert.deepEqual(capturedBody.messages, body.messages);
const summary = compressionAnalyticsDb.getCompressionAnalyticsSummary();
assert.equal(summary.totalRequests, 0, "Disabled compression should not record analytics");
} finally {
globalThis.fetch = originalFetch;
if (originalContextLength === undefined) {
delete process.env.CONTEXT_LENGTH_OPENAI;
} else {
process.env.CONTEXT_LENGTH_OPENAI = originalContextLength;
}
}
});
test("chatCore integration: compressContext NOT called when context is below 85% threshold", async () => {
const provider = "openai";
const model = "gpt-4";
await compressionDb.updateCompressionSettings({
enabled: true,
defaultMode: "off",
autoTriggerTokens: 0,
});
const contextLimit = getTokenLimit(provider, model);
const threshold = Math.floor(contextLimit * 0.85);
@@ -199,6 +309,12 @@ test("chatCore integration: compression preserves message structure", async () =
const provider = "openai";
const model = "gpt-4";
await compressionDb.updateCompressionSettings({
enabled: true,
defaultMode: "off",
autoTriggerTokens: 0,
});
const body = {
model,
stream: false,
@@ -343,6 +459,12 @@ test("chatCore integration: combo requests run proactive compression before Kiro
const provider = "kiro";
const model = "claude-sonnet-4.5";
await compressionDb.updateCompressionSettings({
enabled: true,
defaultMode: "off",
autoTriggerTokens: 0,
});
const connection = await providersDb.createProviderConnection({
provider,
apiKey: "test-key",

View File

@@ -65,6 +65,17 @@ describe("getEffectiveMode", () => {
assert.equal(getEffectiveMode(config, null, 100), "off");
});
it("keeps disabled config off despite combo override and auto-trigger", () => {
const config = {
...baseConfig,
enabled: false,
autoTriggerTokens: 100,
comboOverrides: { "my-combo": "lite" as const },
};
assert.equal(getEffectiveMode(config, "my-combo", 500), "off");
});
it("returns default mode when no overrides", () => {
assert.equal(getEffectiveMode(baseConfig, null, 100), "lite");
});