/** * Detect and collapse degenerate generation loops. No Bare imports. * * Small Qwen runs with no predict cap will repeat a clause until the idle * timeout. Catch the repeating tail and stop the stream. */ function repeatingUnit(text, minLen, copies) { const s = String(text || ''); const min = minLen > 0 ? minLen : 24; const need = copies > 0 ? copies : 3; if (s.length < min * need) return ''; const window = s.length > 2400 ? s.slice(-2400) : s; const maxLen = Math.min(180, Math.floor(window.length / need)); for (let len = min; len <= maxLen; len++) { const unit = window.slice(-len); if (unit.replace(/\s+/g, '').length < 12) continue; let matched = true; for (let i = 2; i <= need; i++) { if (window.slice(-len * i, -len * (i - 1)) !== unit) { matched = false; break; } } if (matched) return unit; } return ''; } function isRepeating(text) { return !!repeatingUnit(text); } function escapeRe(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function collapseRepeats(text) { const s = String(text || ''); const unit = repeatingUnit(s); if (!unit) return s.trim(); return s.replace(new RegExp('(?:' + escapeRe(unit) + '){3,}$'), unit).trim(); } module.exports = { repeatingUnit, isRepeating, collapseRepeats };