Spaces:
Sleeping
Sleeping
| /** | |
| * contextSizeGuard.ts — S496: extracted from agentLoop.ts | |
| * | |
| * Guard per la dimensione del contesto API: se il contesto supera 60KB | |
| * (≈15k token), prima taglia le sezioni opzionali del system prompt | |
| * (project_map, skill_hint), poi scarda i messaggi meno preziosi. | |
| * | |
| * Priorità messaggi: REAL_DATA/OBSERVATION/EPISTEMIC > recenti > vecchi. | |
| * | |
| * GAP-4: soglia abbassata da 90KB a 60KB — margine proattivo più sicuro | |
| * per evitare timeout e OOM su modelli con finestra effettiva < 128k token. | |
| * | |
| * @module contextSizeGuard | |
| */ | |
| import type { ApiMsg } from "./networkTools"; | |
| const CTX_MAX_BYTES = 60_000; // GAP-4: era 90_000 — abbassato per budget context proattivo | |
| /** | |
| * Riduce il contesto se supera la soglia, preservando i messaggi più preziosi. | |
| * Restituisce il nuovo array di messaggi (potenzialmente ridotto). | |
| */ | |
| export function applyContextSizeGuard( | |
| loopMessages: ApiMsg[], | |
| onStatus?: (msg: string) => void, | |
| ): ApiMsg[] { | |
| const _ctxSize = loopMessages.reduce((acc, m) => acc + String(m.content ?? "").length, 0); | |
| if (_ctxSize <= CTX_MAX_BYTES) return loopMessages; | |
| const _origCount = loopMessages.filter(m => m.role !== "system").length; | |
| onStatus?.(`🗜️ Contesto troppo lungo (${Math.round(_ctxSize / 1024)}KB) — comprimo…`); | |
| // Step 1 (RT-11): taglia sezioni opzionali del system prompt — preserva history | |
| let msgs = loopMessages; | |
| const _sysMsgObj = msgs.find(m => m.role === "system"); | |
| if (_sysMsgObj) { | |
| const _sysRaw = String(_sysMsgObj.content ?? ""); | |
| const _sysTrimmed = _sysRaw | |
| .replace(/\n?## project_map\b[\s\S]*?(?=\n## |\n---|\s*$)/, "") | |
| .replace(/\n?## skill_hint\b[\s\S]*?(?=\n## |\n---|\s*$)/, ""); | |
| if (_sysTrimmed.length < _sysRaw.length) { | |
| msgs = msgs.map(m => m.role === "system" ? { ...m, content: _sysTrimmed } : m); | |
| } | |
| } | |
| // Step 1.5: trim optional bracket sections (ordered by expendability) before message-level trim | |
| // U5: DOCS/PAGINE prime (rigenerabili), SAGGEZZA ACQUISITA per ultima (lezioni uniche) | |
| // Ordine: DOCS INDICIZZATI → PAGINE GIÀ LETTE → MEMORIA RILEVANTE → SAGGEZZA ACQUISITA | |
| const _OPTIONAL_CTX_SECTIONS: RegExp[] = [ | |
| /\n?\[DOCS INDICIZZATI\][\s\S]*?(?=\n\[|$)/, | |
| /\n?\[PAGINE G\u00c0 LETTE\][\s\S]*?(?=\n\[|$)/, | |
| /\n?\[MEMORIA RILEVANTE\][\s\S]*?(?=\n\[|$)/, | |
| /\n?\[SAGGEZZA ACQUISITA[^\]]*\][\s\S]*?(?=\n\[|$)/, | |
| ]; | |
| for (const _re of _OPTIONAL_CTX_SECTIONS) { | |
| const _curSize = msgs.reduce((acc, m) => acc + String(m.content ?? "").length, 0); | |
| if (_curSize <= CTX_MAX_BYTES) break; // già sotto soglia | |
| msgs = msgs.map(m => | |
| m.role === "system" ? { ...m, content: String(m.content ?? "").replace(_re, "") } : m, | |
| ); | |
| } | |
| // Step 2: se ancora sopra soglia, scarta i messaggi più vecchi (non-system) | |
| const _ctxAfterTrim = msgs.reduce((acc, m) => acc + String(m.content ?? "").length, 0); | |
| if (_ctxAfterTrim > CTX_MAX_BYTES) { | |
| const _sysMsg = msgs.filter(m => m.role === "system"); | |
| let _rest = msgs.filter(m => m.role !== "system"); | |
| // Fix D (S380): taglia prima i messaggi non-preziosi — preserva REAL_DATA/OBSERVATION/EPISTEMIC | |
| // N4: più segnali di contenuto "prezioso" da preservare nel trim | |
| const _isValuable = (m: { content?: unknown }) => | |
| /REAL_DATA|OBSERVATION|RISULTATO|\[EPISTEMIC:|PROOF|CONFIRMED|SCRAPED|\[TOOL_RESULT|\[SEARCH_RESULT|SUCCESSO_TOOL/.test(String(m.content ?? "")); | |
| const _keepCount = Math.ceil(_rest.length * 0.65); | |
| const _valuable = _rest.filter(_isValuable); | |
| const _nonVal = _rest.filter(m => !_isValuable(m)); | |
| const _nonValKeep = Math.max(0, _keepCount - _valuable.length); | |
| const _keptSet = new Set<(typeof _rest)[number]>([..._nonVal.slice(_nonVal.length - _nonValKeep), ..._valuable]); | |
| _rest = _rest.filter(m => _keptSet.has(m)); | |
| msgs = [..._sysMsg, ..._rest]; | |
| } | |
| // GAP-R1: narrazione post-trim — informa su quanti messaggi sono stati eliminati | |
| const _keptCount = msgs.filter(m => m.role !== "system").length; | |
| if (_keptCount < _origCount) { | |
| onStatus?.(`✓ Contesto compresso: ${_keptCount}/${_origCount} messaggi mantenuti (priorità: osservazioni + recenti)`); | |
| } | |
| return msgs; | |
| } | |