(prev: string, next: string)
| 83 | * earlier line does not). |
| 84 | */ |
| 85 | export function isRedundantAssistantText(prev: string, next: string): boolean { |
| 86 | const a = normCompare(prev ?? ''); |
| 87 | const b = normCompare(next ?? ''); |
| 88 | if (!a || !b) return false; |
| 89 | if (b.length < 40) return false; // don't suppress short acks |
| 90 | if (a === b) return true; |
| 91 | |
| 92 | const [shorter, longer] = a.length <= b.length ? [a, b] : [b, a]; |
| 93 | |
| 94 | // Fast path: exact containment (byte-identical re-emit, possibly with extra lines). |
| 95 | if (longer.includes(shorter) && shorter.length >= 100 && shorter.length >= longer.length * 0.6) { |
| 96 | return true; |
| 97 | } |
| 98 | |
| 99 | // Tolerant path: a re-emitted answer is rarely byte-identical — the model |
| 100 | // REGENERATES it with minor wording changes (or truncates it when it runs out |
| 101 | // of budget), so exact containment misses it and the user sees the whole answer |
| 102 | // twice. Mirror dedupeSelfRepeatedText: measure the shared LEADING word run. A |
| 103 | // genuine re-emit shares a long identical opening; two genuinely different |
| 104 | // assistant messages diverge almost immediately even when they reuse topic |
| 105 | // vocabulary, so this stays conservative. |
| 106 | const wa = a.split(' '); |
| 107 | const wb = b.split(' '); |
| 108 | const minLen = Math.min(wa.length, wb.length); |
| 109 | if (minLen < 30) return false; // too short to be a duplicated full answer |
| 110 | let common = 0; |
| 111 | while (common < minLen && wa[common] === wb[common]) common++; |
| 112 | const sharedRatio = common / minLen; |
| 113 | return common >= 30 && sharedRatio >= 0.6; |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Collapse a block of text that contains its own answer twice. Some local models |
no test coverage detected