// @ts-nocheck const DEFAULT_THRESHOLD = 24000; const DEFAULT_KEEP_RECENT = 6; const TOOL_CONTENT_SOFT = 1800; const TOOL_CONTENT_HARD = 800; export function estimateTokens(messages, toolsCount = 0) { let chars = 0; for (const m of messages) { if (typeof m.content === 'string') { chars += m.content.length; } if (m.toolCalls) { for (const tc of m.toolCalls) { chars += JSON.stringify(tc).length; } } chars += (m.role || '').length + 10; } chars += toolsCount * 2100; return Math.ceil(chars / 3.5); } export function compressIfNeeded(messages, opts = {}) { const threshold = opts.threshold || DEFAULT_THRESHOLD; const keepRecent = opts.keepRecent || DEFAULT_KEEP_RECENT; const toolsCount = opts.toolsCount || 0; const originalTokens = estimateTokens(messages, toolsCount); if (originalTokens <= threshold) { return { messages, compressed: false, originalTokens, finalTokens: originalTokens, hiddenCount: 0, }; } let working = slimToolMessages(messages, TOOL_CONTENT_SOFT); let tokens = estimateTokens(working, toolsCount); if (tokens <= threshold) { return { messages: working, compressed: true, originalTokens, finalTokens: tokens, hiddenCount: 0, }; } working = slimToolMessages(messages, TOOL_CONTENT_HARD); tokens = estimateTokens(working, toolsCount); if (tokens <= threshold) { return { messages: working, compressed: true, originalTokens, finalTokens: tokens, hiddenCount: 0, }; } const keepStart = findSafeKeepStart(working, keepRecent); const last = working.slice(keepStart); const lastUserIdx = (() => { for (let i = working.length - 1; i >= 0; i--) { if (working[i].role === 'user') return i; } return -1; })(); const compressed = []; if (lastUserIdx >= 0 && lastUserIdx < keepStart) { compressed.push(working[lastUserIdx]); } const middleCount = lastUserIdx >= 0 && lastUserIdx < keepStart ? Math.max(0, keepStart - lastUserIdx - 1) : Math.max(0, keepStart); if (middleCount > 0) { compressed.push({ role: 'system', content: `[上下文已压缩] 已隐藏中间 ${middleCount} 条历史消息,只保留完整的最近工具调用组。请根据仍保留的工具结果回答用户;若信息不足请如实说明。`, _compressed: true, }); } compressed.push(...last); const finalTokens = estimateTokens(compressed, toolsCount); return { messages: compressed, compressed: true, originalTokens, finalTokens, hiddenCount: middleCount, }; } function findSafeKeepStart(messages, minKeep) { if (!messages.length) return 0; let start = Math.max(0, messages.length - minKeep); while (start > 0 && messages[start].role === 'tool') { start--; } if (messages[start]?.role === 'tool') { while (start > 0 && messages[start].role === 'tool') start--; } return start; } function slimToolMessages(messages, maxLen) { return messages.map(m => { if (m.role !== 'tool') return m; const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? ''); if (content.length <= maxLen) return m; return { ...m, content: slimToolContent(content, maxLen), }; }); } function slimToolContent(content, maxLen) { try { const parsed = JSON.parse(content); const slim = slimToolObject(parsed); const out = JSON.stringify(slim); if (out.length <= maxLen) return out; return out.slice(0, maxLen) + '…[已截断]'; } catch (_) { return content.slice(0, maxLen) + '…[已截断]'; } } function slimToolObject(obj) { if (!obj || typeof obj !== 'object') return obj; if (Array.isArray(obj)) { return obj.length > 3 ? [...obj.slice(0, 3), `…另有 ${obj.length - 3} 条已省略`] : obj; } const out = {}; for (const [k, v] of Object.entries(obj)) { if (k === '_call' || k === 'guidance' || k === 'assistantGuidance' || k === 'ok' || k === 'error') { out[k] = v; continue; } if (k === 'result' && v && typeof v === 'object') { out[k] = slimToolObject(v); continue; } if (k === 'overview' || k === 'summary' || k === 'currentEntity' || k === 'lastQueryType') { out[k] = typeof v === 'object' ? slimToolObject(v) : v; continue; } if (Array.isArray(v)) { if (v.length === 0) { out[k] = v; } else if (typeof v[0] !== 'object') { out[k] = v.length > 8 ? [...v.slice(0, 8), `…+${v.length - 8}`] : v; } else { out[k] = { _omitted: true, count: v.length, sample: slimToolObject(v[0]), }; } continue; } if (v && typeof v === 'object') { out[k] = slimToolObject(v); continue; } out[k] = v; } return out; }