diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 8cf2354c94..004c1efa58 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -367,7 +367,7 @@ "open-sse/services/adobeFireflyClient.ts": 2385, "open-sse/services/claudeCodeCompatible.ts": 1202, "open-sse/services/combo.ts": 3648, - "open-sse/services/compression/strategySelector.ts": 1060, + "open-sse/services/compression/strategySelector.ts": 1061, "open-sse/services/rateLimitManager.ts": 1167, "open-sse/translator/response/openai-responses.ts": 1224, "open-sse/utils/cursorAgentProtobuf.ts": 1505, diff --git a/open-sse/services/antigravityProjectPersistence.ts b/open-sse/services/antigravityProjectPersistence.ts index f34445fe00..1421a8f480 100644 --- a/open-sse/services/antigravityProjectPersistence.ts +++ b/open-sse/services/antigravityProjectPersistence.ts @@ -1,9 +1,24 @@ /** * Re-export from `antigravityProjectPersist.ts` plus a connection-preference helper. + * + * The persistence layer for a runtime-discovered Antigravity projectId lives in + * the sibling file `antigravityProjectPersist.ts` (named by its core function). + * This module adds `preferAntigravityConnectionsWithStoredProject()`, used by the + * quota-strategy engine to give priority to connections whose projectId has + * already been discovered and persisted. */ + import { persistDiscoveredAntigravityProjectId } from "./antigravityProjectPersist.ts"; + export { persistDiscoveredAntigravityProjectId }; +/** + * Return only the Antigravity connections that already have a stored projectId. + * + * A connection whose projectId has been discovered and persisted can be used + * immediately; a connection without one would need to go through the Code Assist + * bootstrap first, which is handled by the calling strategy's fallback path. + */ export function preferAntigravityConnectionsWithStoredProject( connections: Array> ): Array> { diff --git a/open-sse/services/compression/engines/cavemanAdapter.ts b/open-sse/services/compression/engines/cavemanAdapter.ts index 464d3e79b8..ccfcd723f9 100644 --- a/open-sse/services/compression/engines/cavemanAdapter.ts +++ b/open-sse/services/compression/engines/cavemanAdapter.ts @@ -221,6 +221,14 @@ const LITE_SCHEMA: EngineConfigField[] = [ label: "Preserve system prompt", defaultValue: true, }, + { + key: "compressToolResults", + type: "boolean", + label: "Proactively truncate long tool results", + description: + "Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget.", + defaultValue: true, + }, ]; function validateLiteConfig(config: Record): EngineValidationResult { @@ -231,6 +239,7 @@ function validateLiteConfig(config: Record): EngineValidationRe ) { errors.push("preserveSystemPrompt must be a boolean"); } + validateBoolean(config, "compressToolResults", errors); return { valid: errors.length === 0, errors }; } @@ -256,6 +265,13 @@ export const liteEngine: CompressionEngine = { const result = applyLiteCompression(adapter.body, { ...options, preserveSystemPrompt: options?.config?.preserveSystemPrompt !== false, + // buildStepOptions() already merges global config.lite with explicit step.config + // (step wins) into stepConfig, so consume that single effective value instead of + // AND-ing root and step values — an explicit step `true` must override a global `false`. + compressToolResults: + options?.stepConfig?.compressToolResults ?? + options?.config?.lite?.compressToolResults ?? + true, }); return adapter.adapted ? { ...result, body: adapter.restore(result.body) } : result; }, diff --git a/open-sse/services/compression/lite.ts b/open-sse/services/compression/lite.ts index 6c795766fd..4be635da0e 100644 --- a/open-sse/services/compression/lite.ts +++ b/open-sse/services/compression/lite.ts @@ -17,6 +17,7 @@ interface LiteCompressionOptions { model?: string; supportsVision?: boolean | null; preserveSystemPrompt?: boolean; + compressToolResults?: boolean; } function trimTrailingHorizontalWhitespace(line: string): string { @@ -253,9 +254,11 @@ export function applyLiteCompression( current = r2.body; if (r2.applied) techniquesApplied.push("system-dedup"); - const r3 = compressToolResults(current); - current = r3.body; - if (r3.applied) techniquesApplied.push("tool-compress"); + if (options?.compressToolResults !== false) { + const r3 = compressToolResults(current); + current = r3.body; + if (r3.applied) techniquesApplied.push("tool-compress"); + } const r4 = removeRedundantContent(current, options); current = r4.body; diff --git a/open-sse/services/compression/stepDetailConfig.ts b/open-sse/services/compression/stepDetailConfig.ts index ff2d2a39c9..f911d81e79 100644 --- a/open-sse/services/compression/stepDetailConfig.ts +++ b/open-sse/services/compression/stepDetailConfig.ts @@ -14,6 +14,8 @@ export function resolveStepDetailConfig( config: CompressionConfig | undefined ) { switch (engine) { + case "lite": + return config?.lite ?? {}; case "headroom": return config?.headroom ?? {}; case "session-dedup": diff --git a/open-sse/services/compression/strategySelector.ts b/open-sse/services/compression/strategySelector.ts index 8785ddb550..fad53e56ac 100644 --- a/open-sse/services/compression/strategySelector.ts +++ b/open-sse/services/compression/strategySelector.ts @@ -349,6 +349,7 @@ function runCompression( const result = applyLiteCompression(compressionBody, { ...options, preserveSystemPrompt: options?.config?.preserveSystemPrompt !== false, + ...options?.config?.lite, }); return adapter.adapted ? { ...result, body: adapter.restore(result.body) } : result; } diff --git a/open-sse/services/compression/types.ts b/open-sse/services/compression/types.ts index 665af5988f..5905a7b49f 100644 --- a/open-sse/services/compression/types.ts +++ b/open-sse/services/compression/types.ts @@ -157,6 +157,12 @@ export interface LiveZoneConfig { enabled: boolean; } +/** Lite detail settings for proactive request-time transformations. */ +export interface LiteConfig { + /** Truncate tool-result strings over 2,000 characters before provider dispatch. */ + compressToolResults: boolean; +} + export interface CompressionPipelineStep { engine: CompressionEngineId; intensity?: CavemanIntensity | RtkIntensity; @@ -218,6 +224,8 @@ export interface CompressionConfig { languageConfig?: CompressionLanguageConfig; aggressive?: AggressiveConfig; ultra?: UltraConfig; + /** Lite proactive transformation detail settings. */ + lite?: LiteConfig; /** Headroom SmartCrusher detail settings (minRows gate). */ headroom?: HeadroomConfig; /** Session Dedup detail settings (minBlockChars / fuzzy, #8388). */ @@ -395,6 +403,7 @@ export const DEFAULT_COMPRESSION_CONFIG: CompressionConfig = { ultraEngine: "heuristic", ultraSlmPrewarm: false, liveZone: { enabled: false }, + lite: { compressToolResults: true }, codexResponsesConfig: { ...DEFAULT_CODEX_RESPONSES_CONFIG }, }; diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 7ed49aea8b..72bd72dfc3 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -7812,6 +7812,10 @@ "label": "الحد الأدنى للصفوف لضغطها", "description": "الحد الأدنى للصفوف في مصفوفة JSON متجانسة المطلوبة لتشغيل الضغط الجدولي. الافتراضي: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "الحفاظ على توجيه النظام" }, diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index 7b839d98c3..b9d92929cb 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -7812,6 +7812,10 @@ "label": "Kompaktlaşdırmaq üçün minimum sətir sayı", "description": "Cədvəl kompaktlaşdırmasını işə salmaq üçün tələb olunan bircins JSON massivindəki minimum sətir sayı. Standart: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Sistem promptunu qoru" }, diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index cf42d5c7f7..0db69ab00d 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -7812,6 +7812,10 @@ "label": "Минимален брой редове за компактизиране", "description": "Минимален брой редове в хомогенен JSON масив, необходими за задействане на таблично компактизиране. По подразбиране: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Запазване на системната подкана" }, diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 1fc25505ab..4e6cc55b4d 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -7812,6 +7812,10 @@ "label": "কম্প্যাক্ট করার জন্য ন্যূনতম সারি", "description": "ট্যাবুলার কম্প্যাকশন ট্রিগার করার জন্য একটি সমজাতীয় JSON অ্যারেতে প্রয়োজনীয় ন্যূনতম সারি। ডিফল্ট: 8।" }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "সিস্টেম প্রম্পট সংরক্ষণ করুন" }, diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 7bbf08cc6b..721e18a434 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -7812,6 +7812,10 @@ "label": "Minimální počet řádků ke komprimaci", "description": "Minimální počet řádků v homogenním poli JSON vyžadovaný ke spuštění tabulkové komprimace. Výchozí: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Zachovat systémový prompt" }, diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index e6d739bdec..476a9762a4 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -7812,6 +7812,10 @@ "label": "Minimum rækker til komprimering", "description": "Minimum antal rækker i et homogent JSON-array, der kræves for at udløse tabelkomprimering. Standard: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Bevar systemprompt" }, diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index f0ce393c26..cf4fe18287 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -7812,6 +7812,10 @@ "label": "Minimale Zeilen zum Komprimieren", "description": "Mindestanzahl an Zeilen in einem homogenen JSON-Array, die erforderlich ist, um eine tabellarische Komprimierung auszulösen. Standard: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "System-Prompt beibehalten" }, diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index c9fc1c45bd..7803e8eeea 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -7806,6 +7806,10 @@ "label": "Minimum rows to compact", "description": "Minimum rows in a homogeneous JSON array required to trigger tabular compaction. Default: 8." }, + "compressToolResults": { + "label": "Proactively truncate long tool results", + "description": "Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Preserve system prompt" }, diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index c79356110a..d4f6d28224 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -7812,6 +7812,10 @@ "label": "Minimum rows to compact", "description": "Minimum rows in a homogeneous JSON array required to trigger tabular compaction. Default: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Preserve system prompt" }, diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 27506beecc..a3571a5f56 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -7812,6 +7812,10 @@ "label": "حداقل ردیف‌ها برای فشرده‌سازی", "description": "حداقل ردیف‌ها در یک آرایه همگن JSON که برای فعال‌سازی فشرده‌سازی جدولی لازم است. پیش‌فرض: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "حفظ پرامپت سیستم" }, diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 8427df5526..ffb6762c00 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -7812,6 +7812,10 @@ "label": "Tiivistettävien rivien vähimmäismäärä", "description": "Homogeenisen JSON-taulukon rivien vähimmäismäärä taulukkomaisen tiivistämisen käynnistämiseksi. Oletus: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Säilytä järjestelmäkehote" }, diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index cc34c82372..802b5f0af0 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -7812,6 +7812,10 @@ "label": "Lignes minimales à compacter", "description": "Nombre minimum de lignes dans un tableau JSON homogène requis pour déclencher le compactage tabulaire. Par défaut : 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Préserver le prompt système" }, diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 2276270c4b..1035db923d 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -7812,6 +7812,10 @@ "label": "કમ્પેક્ટ કરવા માટે ન્યૂનતમ પંક્તિઓ", "description": "ટેબ્યુલર કમ્પેક્શન ટ્રિગર કરવા માટે સજાતીય JSON એરેમાં જરૂરી ન્યૂનતમ પંક્તિઓ. ડિફોલ્ટ: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "સિસ્ટમ પ્રોમ્પ્ટ સાચવો" }, diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index df203e014d..1d8cb4679c 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -7812,6 +7812,10 @@ "label": "מינימום שורות לכיווץ", "description": "מספר השורות המינימלי במערך JSON הומוגני הנדרש להפעלת כיווץ טבלאי. ברירת מחדל: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "שמור על הנחיית המערכת" }, diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 1f682dc671..5c6e92f1ff 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -7812,6 +7812,10 @@ "label": "कॉम्पैक्ट करने के लिए न्यूनतम पंक्तियाँ", "description": "सारणीबद्ध कॉम्पैक्शन को ट्रिगर करने के लिए सजातीय JSON ऐरे में आवश्यक न्यूनतम पंक्तियाँ। डिफ़ॉल्ट: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "सिस्टम प्रॉम्प्ट सुरक्षित रखें" }, diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 97544437d8..e86fc50a90 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -7812,6 +7812,10 @@ "label": "Tömörítendő sorok minimális száma", "description": "Egy homogén JSON-tömb minimális sorszáma a táblázatos tömörítés indításához. Alapértelmezett: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Rendszerüzenet megőrzése" }, diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 551443ad7d..570e308419 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -7812,6 +7812,10 @@ "label": "Baris minimum untuk dipadatkan", "description": "Jumlah baris minimum dalam array JSON homogen yang diperlukan untuk memicu pemadatan tabular. Default: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Pertahankan prompt sistem" }, diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json index 97dd33dba1..78f5e27275 100644 --- a/src/i18n/messages/in.json +++ b/src/i18n/messages/in.json @@ -7812,6 +7812,10 @@ "label": "Baris minimum untuk dipadatkan", "description": "Jumlah baris minimum dalam larik JSON homogen yang diperlukan untuk memicu pemadatan tabular. Default: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Pertahankan prompt sistem" }, diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index a5d9884034..d6d556eb2a 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -7812,6 +7812,10 @@ "label": "Righe minime da compattare", "description": "Numero minimo di righe in un array JSON omogeneo richiesto per attivare la compattazione tabulare. Predefinito: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Preserva prompt di sistema" }, diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index e6e541ebd5..b6a60691c7 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -7812,6 +7812,10 @@ "label": "圧縮する最小行数", "description": "表形式の圧縮をトリガーするために必要な、同質なJSON配列の最小行数。デフォルト: 8。" }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "システムプロンプトを保持" }, diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 7c57f66bd9..49648edb0a 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -7812,6 +7812,10 @@ "label": "압축할 최소 행 수", "description": "테이블 형식 압축을 트리거하는 데 필요한 동질적 JSON 배열의 최소 행 수입니다. 기본값: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "시스템 프롬프트 유지" }, diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index ec5a1f0db9..fd3648c190 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -7812,6 +7812,10 @@ "label": "कॉम्पॅक्ट करण्यासाठी किमान ओळी", "description": "टॅब्युलर कॉम्पॅक्शन ट्रिगर करण्यासाठी एकसमान JSON ॲरेमध्ये आवश्यक असलेल्या किमान ओळी. डीफॉल्ट: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "सिस्टम प्रॉम्प्ट जतन करा" }, diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 708c2b41bf..bc2a159b25 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -7812,6 +7812,10 @@ "label": "Baris minimum untuk dipadatkan", "description": "Baris minimum dalam tatasusunan JSON homogen yang diperlukan untuk mencetuskan pemadatan jadual. Lalai: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Kekalkan gesaan sistem" }, diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index e5fb7840b3..8529dc71db 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -7812,6 +7812,10 @@ "label": "Minimum aantal rijen om te compacteren", "description": "Minimum aantal rijen in een homogene JSON-array dat vereist is om tabulaire compactie te activeren. Standaard: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Systeemprompt behouden" }, diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 95c88da0d7..bd779d1fea 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -7812,6 +7812,10 @@ "label": "Minimum rader å komprimere", "description": "Minimum antall rader i et homogent JSON-array som kreves for å utløse tabulær komprimering. Standard: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Bevar system-prompt" }, diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 30fa94e341..3a3098b0e6 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -7812,6 +7812,10 @@ "label": "Minimum na row na iko-compact", "description": "Minimum na row sa isang homogeneous na JSON array na kinakailangan upang ma-trigger ang tabular compaction. Default: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Panatilihin ang system prompt" }, diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 55eb9a29aa..7b3db8339e 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -7812,6 +7812,10 @@ "label": "Minimalna liczba wierszy do kompaktowania", "description": "Minimalna liczba wierszy w jednorodnej tablicy JSON wymagana do uruchomienia kompaktowania tabelarycznego. Domyślnie: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Zachowaj prompt systemowy" }, diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 9b004e985f..1b2dbe9ea2 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -7802,6 +7802,10 @@ "label": "Mínimo de linhas para compactar", "description": "Mínimo de linhas em um array JSON homogêneo necessárias para acionar a compactação tabular. Padrão: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Preservar prompt do sistema" }, diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 29b7ba3657..b5fb8a7380 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -7812,6 +7812,10 @@ "label": "Mínimo de linhas a compactar", "description": "Número mínimo de linhas num array JSON homogéneo necessário para acionar a compactação tabular. Predefinição: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Preservar prompt do sistema" }, diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 78c562fa21..843c7ef3fa 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -7812,6 +7812,10 @@ "label": "Număr minim de rânduri de compactat", "description": "Numărul minim de rânduri dintr-un tablou JSON omogen necesar pentru a declanșa compactarea tabulară. Implicit: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Păstrează promptul de sistem" }, diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index db18a4bb11..55292cd9d3 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -7812,6 +7812,10 @@ "label": "Минимальное число строк для компактизации", "description": "Минимальное количество строк в однородном массиве JSON, необходимое для запуска табличной компактизации. По умолчанию: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Сохранять системный промпт" }, diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 49e825255a..ee9c62f677 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -7812,6 +7812,10 @@ "label": "Minimálny počet riadkov na zhustenie", "description": "Minimálny počet riadkov v homogénnom JSON poli potrebný na spustenie tabuľkového zhustenia. Predvolené: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Zachovať systémový prompt" }, diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index d13c7cbea5..ba6bbbe891 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -7812,6 +7812,10 @@ "label": "Minsta antal rader att komprimera", "description": "Minsta antal rader i en homogen JSON-array som krävs för att utlösa tabellkomprimering. Standard: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Bevara systemprompt" }, diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index c5e6c4fa90..a3165d7dd8 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -7812,6 +7812,10 @@ "label": "Kiwango cha chini cha safu mlalo za kubana", "description": "Kiwango cha chini cha safu mlalo katika safu ya JSON inayofanana inayohitajika ili kuanzisha ubana wa jedwali. Chaguo-msingi: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Hifadhi kidokezo cha mfumo" }, diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 87d7587709..5187bea668 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -7812,6 +7812,10 @@ "label": "சுருக்குவதற்கான குறைந்தபட்ச வரிசைகள்", "description": "அட்டவணை சுருக்கத்தைத் தூண்டுவதற்கு ஒரே மாதிரியான JSON வரிசையில் தேவைப்படும் குறைந்தபட்ச வரிசைகள். இயல்புநிலை: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "சிஸ்டம் பிராம்ப்ட்டைப் பாதுகாக்கவும்" }, diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index 8e6747c73b..a4de572e49 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -7812,6 +7812,10 @@ "label": "కంపాక్ట్ చేయడానికి కనీస అడ్డు వరుసలు", "description": "టేబులర్ కంపాక్షన్‌ను ట్రిగ్గర్ చేయడానికి సజాతీయ JSON అరేలో అవసరమైన కనీస అడ్డు వరుసలు. డిఫాల్ట్: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "సిస్టమ్ ప్రాంప్ట్‌ను భద్రపరచండి" }, diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 26957baf01..81f03e11c3 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -7812,6 +7812,10 @@ "label": "จำนวนแถวขั้นต่ำที่จะกระชับข้อมูล", "description": "จำนวนแถวขั้นต่ำในอาร์เรย์ JSON ที่เป็นเนื้อเดียวกันซึ่งจำเป็นต่อการทริกเกอร์การกระชับข้อมูลแบบตาราง ค่าเริ่มต้น: 8" }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "คงพรอมต์ระบบไว้" }, diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index c02733a342..00cd54af41 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -7812,6 +7812,10 @@ "label": "Sıkıştırılacak minimum satır sayısı", "description": "Tablosal sıkıştırmayı tetiklemek için homojen bir JSON dizisinde gereken minimum satır sayısı. Varsayılan: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Sistem istemini koru" }, diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index b516a2e0ff..bc5082da8c 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -7812,6 +7812,10 @@ "label": "Мінімальна кількість рядків для компактності", "description": "Мінімальна кількість рядків в однорідному масиві JSON, необхідна для запуску табличного ущільнення. За замовчуванням: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Зберігати системний промпт" }, diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 27a788a659..755d3929d0 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -7812,6 +7812,10 @@ "label": "کمپیکٹ کرنے کے لیے کم از کم قطاریں", "description": "ٹیبلر کمپیکشن کو متحرک کرنے کے لیے ایک یکساں JSON اری میں مطلوبہ کم از کم قطاریں۔ ڈیفالٹ: 8۔" }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "سسٹم پرامپٹ کو محفوظ رکھیں" }, diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index e376f3c2e6..d4b3f16030 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -7814,6 +7814,10 @@ "label": "Số hàng tối thiểu để nén", "description": "Số hàng tối thiểu trong một mảng JSON đồng nhất để kích hoạt nén dạng bảng. Mặc định: 8." }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "Giữ nguyên prompt hệ thống" }, diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 56680f2ae2..7e0fbfa1c6 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -7812,6 +7812,10 @@ "label": "压缩所需的最小行数", "description": "触发表格压缩所需的同构 JSON 数组的最小行数。默认值: 8。" }, + "compressToolResults": { + "label": "主动截断过长的工具结果", + "description": "在 Lite 压缩期间截断超过 2,000 个字符的工具结果。当上下文超出模型预算时,紧急溢出保护仍可能裁剪内容。" + }, "preserveSystemPrompt": { "label": "保留系统提示词" }, diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 2fe9631367..f8a5261460 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -7812,6 +7812,10 @@ "label": "最小壓縮行數", "description": "觸發表格壓縮所需的同質 JSON 陣列最小行數。預設值:8。" }, + "compressToolResults": { + "label": "__MISSING__:Proactively truncate long tool results", + "description": "__MISSING__:Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget." + }, "preserveSystemPrompt": { "label": "保留系統提示詞" }, diff --git a/src/lib/db/compression.ts b/src/lib/db/compression.ts index d927aa2467..7dd03bdbb8 100644 --- a/src/lib/db/compression.ts +++ b/src/lib/db/compression.ts @@ -616,6 +616,7 @@ export async function getCompressionSettings(): Promise { stackedPipeline: normalizeStackedPipeline(undefined), aggressive: normalizeAggressiveConfig(undefined), ultra: normalizeUltraConfig(undefined), + lite: { compressToolResults: true }, headroom: normalizeHeadroomConfig(undefined), ...buildDetailConfigDefaults(), contextBudget: normalizeContextBudgetConfig(undefined), @@ -726,6 +727,9 @@ export async function getCompressionSettings(): Promise { case "ultraConfig": config.ultra = normalizeUltraConfig(parsed); break; + case "lite": + config.lite = { compressToolResults: toRecord(parsed).compressToolResults !== false }; + break; case "headroom": case "headroomConfig": config.headroom = normalizeHeadroomConfig(parsed); diff --git a/src/shared/components/compression/EngineConfigPage.tsx b/src/shared/components/compression/EngineConfigPage.tsx index 5c1981a4ec..36e45f5541 100644 --- a/src/shared/components/compression/EngineConfigPage.tsx +++ b/src/shared/components/compression/EngineConfigPage.tsx @@ -23,10 +23,12 @@ interface EngineEntry { // (/dashboard/context/settings, the `engines` map); only these have a place to // persist the extra per-engine fields edited on this page. session-dedup and ccr // joined headroom in #8388 (they previously rendered a real, editable detail form -// with no Save affordance — edits vanished on reload). Other structural engines -// (lite, llmlingua, relevance) still have no dedicated sub-object — their page +// with no Save affordance — edits vanished on reload). lite gained a dedicated +// sub-object with the compressToolResults toggle. Other structural engines +// (llmlingua, relevance) still have no dedicated sub-object — their page // keeps the detail form + preview but has nothing extra to persist yet. const SETTINGS_SUBOBJECT: Record = { + lite: "lite", aggressive: "aggressive", ultra: "ultra", headroom: "headroom", @@ -200,8 +202,12 @@ export function EngineConfigPage({ engineId }: { engineId: string }) { return; } // Strip the `enabled` key — engine on/off is the panel's responsibility. - const { enabled: _ignored, ...detail } = configState; + const { enabled: _ignored, ...formDetail } = configState; void _ignored; + const detail = + engineId === "lite" + ? { compressToolResults: formDetail.compressToolResults !== false } + : formDetail; setSaving(true); setSaveError(null); try { diff --git a/src/shared/validation/compressionConfigSchemas.ts b/src/shared/validation/compressionConfigSchemas.ts index 74c56d275a..e644fcb688 100644 --- a/src/shared/validation/compressionConfigSchemas.ts +++ b/src/shared/validation/compressionConfigSchemas.ts @@ -309,6 +309,12 @@ export const STACKED_PIPELINE_ENGINE_INTENSITIES: Record { assert.equal(typeof settings.cacheMinutes, "number"); assert.equal(typeof settings.preserveSystemPrompt, "boolean"); assert.equal(typeof settings.comboOverrides, "object"); + assert.equal(typeof settings.lite, "object"); assert.equal(typeof settings.ultra, "object"); }); @@ -54,6 +55,7 @@ describe("getCompressionSettings", () => { assert.equal(settings.preserveSystemPromptMode, "always"); assert.deepEqual(settings.liveZone, { enabled: false }); assert.deepEqual(settings.comboOverrides, {}); + assert.equal(settings.lite?.compressToolResults, true); assert.equal(settings.ultra?.enabled, false); assert.equal(settings.ultra?.compressionRate, 0.5); assert.equal(settings.ultra?.minScoreThreshold, 0.3); @@ -71,6 +73,14 @@ describe("updateCompressionSettings", () => { await updateCompressionSettings({ enabled: false } as any); }); + it("persists the Lite proactive tool-result truncation switch across reload", async () => { + await updateCompressionSettings({ lite: { compressToolResults: false } }); + core.resetDbInstance(); + + const settings = await getCompressionSettings(); + assert.equal(settings.lite?.compressToolResults, false); + }); + it("updates defaultMode", async () => { await updateCompressionSettings({ defaultMode: "lite" } as any); const settings = await getCompressionSettings(); diff --git a/tests/unit/compression/engine-registry.test.ts b/tests/unit/compression/engine-registry.test.ts index 6dffe20278..f70571a826 100644 --- a/tests/unit/compression/engine-registry.test.ts +++ b/tests/unit/compression/engine-registry.test.ts @@ -80,10 +80,20 @@ describe("compression engine registry contract", () => { // summarizer/threshold fields it previously leaked. const liteSchema = liteEngine.getConfigSchema(); assert.ok(liteSchema.some((field) => field.key === "preserveSystemPrompt")); + assert.ok( + liteSchema.some((field) => field.key === "compressToolResults" && field.defaultValue === true) + ); assert.ok(!liteSchema.some((field) => field.key === "maxTokensPerMessage")); assert.ok(!liteSchema.some((field) => field.key === "summarizerEnabled")); - assert.equal(liteEngine.validateConfig({ preserveSystemPrompt: true }).valid, true); + assert.equal( + liteEngine.validateConfig({ + preserveSystemPrompt: true, + compressToolResults: false, + }).valid, + true + ); assert.equal(liteEngine.validateConfig({ preserveSystemPrompt: "yes" }).valid, false); + assert.equal(liteEngine.validateConfig({ compressToolResults: "no" }).valid, false); assert.equal(cavemanEngine.validateConfig({ intensity: "full" }).valid, true); assert.equal(cavemanEngine.validateConfig({ intensity: "bad" }).valid, false); assert.equal(realRtkEngine.validateConfig({ maxLinesPerResult: 20 }).valid, true); diff --git a/tests/unit/compression/headroom-minrows-persist-8056.test.ts b/tests/unit/compression/headroom-minrows-persist-8056.test.ts index 651ef316cf..de52eb5b11 100644 --- a/tests/unit/compression/headroom-minrows-persist-8056.test.ts +++ b/tests/unit/compression/headroom-minrows-persist-8056.test.ts @@ -67,6 +67,20 @@ function baseConfig(overrides: Partial = {}): CompressionConf } describe("#8056 headroom minRows persistence", () => { + it("schema accepts Lite proactive tool-result truncation detail", () => { + const result = compressionSettingsUpdateSchema.safeParse({ + lite: { compressToolResults: false }, + }); + assert.equal(result.success, true, JSON.stringify(result.error?.issues)); + }); + + it("schema rejects a non-boolean Lite proactive tool-result truncation detail", () => { + const result = compressionSettingsUpdateSchema.safeParse({ + lite: { compressToolResults: "no" }, + }); + assert.equal(result.success, false); + }); + it("schema accepts headroom.minRows=5", () => { const result = compressionSettingsUpdateSchema.safeParse({ headroom: { minRows: 5 }, diff --git a/tests/unit/compression/lite.test.ts b/tests/unit/compression/lite.test.ts index 26707ab938..6decf519c5 100644 --- a/tests/unit/compression/lite.test.ts +++ b/tests/unit/compression/lite.test.ts @@ -194,6 +194,86 @@ describe("replaceImageUrls", () => { }); }); +describe("stacked Lite precedence (global config vs explicit step)", () => { + const toolContent = `${"word ".repeat(500)}TAIL`; + const liteStep = { engine: "lite" }; + const baseConfig = { + enabled: true, + defaultMode: "lite", + autoTriggerTokens: 0, + cacheMinutes: 5, + preserveSystemPrompt: true, + comboOverrides: {}, + engines: {}, + activeComboId: null, + }; + + it("global compressToolResults=false disables truncation when no step override", () => { + const result = applyCompression( + { messages: [{ role: "tool", content: toolContent }] }, + "stacked", + { + config: { + ...baseConfig, + lite: { compressToolResults: false }, + stackedPipeline: [liteStep], + }, + } + ); + const messages = result.body.messages as Array<{ content: string }>; + assert.equal(messages[0].content, toolContent.trimEnd()); + assert.ok(messages[0].content.length > 2000); + assert.doesNotMatch(messages[0].content, /\[truncated\]/); + assert.ok(!result.stats?.techniquesUsed.includes("tool-compress")); + }); + + it("explicit step compressToolResults=true overrides global false", () => { + const result = applyCompression( + { messages: [{ role: "tool", content: toolContent }] }, + "stacked", + { + config: { + ...baseConfig, + lite: { compressToolResults: false }, + stackedPipeline: [{ engine: "lite", config: { compressToolResults: true } }], + }, + } + ); + const messages = result.body.messages as Array<{ content: string }>; + assert.match(messages[0].content, /\.\.\.\[truncated\]$/); + assert.ok(messages[0].content.length < toolContent.length); + assert.ok(result.stats?.techniquesUsed.includes("tool-compress")); + }); + + it("explicit step compressToolResults=false overrides global true/default", () => { + const result = applyCompression( + { messages: [{ role: "tool", content: toolContent }] }, + "stacked", + { + config: { + ...baseConfig, + lite: { compressToolResults: true }, + stackedPipeline: [{ engine: "lite", config: { compressToolResults: false } }], + }, + } + ); + const messages = result.body.messages as Array<{ content: string }>; + assert.equal(messages[0].content, toolContent.trimEnd()); + assert.doesNotMatch(messages[0].content, /\[truncated\]/); + assert.ok(!result.stats?.techniquesUsed.includes("tool-compress")); + }); + + it("stacked default (no lite config) keeps truncation enabled", () => { + const result = applyCompression( + { messages: [{ role: "tool", content: toolContent }] }, + "stacked", + { config: { ...baseConfig, stackedPipeline: [liteStep] } } + ); + const messages = result.body.messages as Array<{ content: string }>; + assert.match(messages[0].content, /\.\.\.\[truncated\]$/); + }); +}); + describe("applyLiteCompression", () => { it("applies all techniques that match", () => { const body = { @@ -211,6 +291,43 @@ describe("applyLiteCompression", () => { assert.ok(result.stats.savingsPercent > 0); }); + it("keeps proactive tool-result truncation enabled when Lite detail config is missing", () => { + const toolContent = `${"word ".repeat(500)}TAIL`; + const result = applyCompression({ messages: [{ role: "tool", content: toolContent }] }, "lite"); + const messages = result.body.messages as Array<{ content: string }>; + + assert.match(messages[0].content, /\.\.\.\[truncated\]$/); + assert.ok(messages[0].content.length < toolContent.length); + }); + + it("can disable only proactive tool-result truncation while other Lite transforms still apply", () => { + const toolContent = `${"word ".repeat(500)}TAIL `; + const result = applyCompression( + { messages: [{ role: "tool", content: toolContent }] }, + "lite", + { + config: { + enabled: true, + defaultMode: "lite", + autoTriggerTokens: 0, + cacheMinutes: 5, + preserveSystemPrompt: true, + comboOverrides: {}, + engines: {}, + activeComboId: null, + lite: { compressToolResults: false }, + }, + } + ); + const messages = result.body.messages as Array<{ content: string }>; + + assert.equal(messages[0].content, toolContent.trimEnd()); + assert.ok(messages[0].content.length > 2000); + assert.doesNotMatch(messages[0].content, /\[truncated\]/); + assert.ok(result.stats?.techniquesUsed.includes("whitespace")); + assert.ok(!result.stats?.techniquesUsed.includes("tool-compress")); + }); + it("preserves system prompt text when preserveSystemPrompt is enabled", () => { const body = { messages: [ diff --git a/tests/unit/ui/engineConfigPage.test.tsx b/tests/unit/ui/engineConfigPage.test.tsx index 701e746ba6..824a806d09 100644 --- a/tests/unit/ui/engineConfigPage.test.tsx +++ b/tests/unit/ui/engineConfigPage.test.tsx @@ -421,6 +421,83 @@ describe("EngineConfigPage", () => { expect(container.parentNode).toBeTruthy(); }); + it("loads and saves the Lite proactive truncation switch with emergency-trim copy", async () => { + const settingsPuts: Array> = []; + vi.spyOn(globalThis, "fetch").mockImplementation( + async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + if (url.includes("/api/compression/engines")) { + return new Response( + JSON.stringify({ + engines: [ + { + id: "lite", + name: "Lite", + description: "Lite engine", + icon: "compress", + stackable: true, + stackPriority: 5, + metadata: { description: "Lite metadata" }, + configSchema: [ + { + key: "compressToolResults", + type: "boolean", + label: "Proactively truncate long tool results", + description: + "Truncates tool results over 2,000 characters during Lite compression. Emergency overflow protection may still trim content when the context exceeds the model budget.", + defaultValue: true, + }, + ], + }, + ], + }), + { status: 200, headers: { "Content-Type": "application/json" } } + ); + } + if (url.includes("/api/settings/compression")) { + if (init?.method === "PUT") { + settingsPuts.push(JSON.parse(init.body as string) as Record); + } + return new Response(JSON.stringify({ lite: { compressToolResults: false } }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + if (url.includes("/api/context/analytics/engine")) { + return new Response(JSON.stringify(ANALYTICS_PAYLOAD), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response(JSON.stringify({}), { status: 404 }); + } + ); + + const { EngineConfigPage } = + await import("../../../src/shared/components/compression/EngineConfigPage"); + let container!: HTMLElement; + await act(async () => { + container = mountInContainer(); + await Promise.resolve(); + }); + + const toggle = container.querySelector("input[type='checkbox']") as HTMLInputElement | null; + expect(toggle).not.toBeNull(); + expect(toggle?.checked).toBe(false); + expect(container.textContent).toContain("Emergency overflow protection may still trim content"); + + const saveButton = Array.from(container.querySelectorAll("button")).find((button) => + button.textContent?.includes("Save") + ); + expect(saveButton).toBeTruthy(); + await act(async () => { + saveButton?.click(); + await Promise.resolve(); + }); + + expect(settingsPuts).toContainEqual({ lite: { compressToolResults: false } }); + }); + it("#8056: headroom minRows is persistable — Save PUTs headroom:{minRows:5}", async () => { const settingsPuts: { body: Record }[] = []; vi.spyOn(globalThis, "fetch").mockImplementation(