mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-27 15:08:39 +02:00
The StreamingTextMerger (C#) and mergeStreamingText (TypeScript) contained custom word-boundary heuristics that inserted spaces between streaming deltas based on incorrect assumptions. Two bugs caused mid-word spaces: 1. Unconditional space insertion when incoming delta started with uppercase or digit characters, breaking acronyms (MDA -> M DA, UAG -> UA G) 2. LooksLikeWordBoundary using global token counts that fired on nearly every mid-word token split (writable -> wr itable, fragile -> frag ile) Both upstream projects (Copilot SDK and Agent Framework) use simple string concatenation for delta accumulation. LLM streaming tokens natively include whitespace when needed, so no boundary detection is required. Removed all boundary heuristic methods and replaced with plain concatenation. Kept overlap detection and snapshot handling for event redelivery scenarios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
68 lines
1.6 KiB
TypeScript
68 lines
1.6 KiB
TypeScript
function tokenize(value: string): string[] {
|
|
return value
|
|
.toLowerCase()
|
|
.split(/[^a-z0-9]+/i)
|
|
.filter((token) => token.length > 0);
|
|
}
|
|
|
|
function computeSuffixPrefixOverlap(current: string, incoming: string): number {
|
|
const maxOverlap = Math.min(current.length, incoming.length);
|
|
for (let length = maxOverlap; length > 0; length -= 1) {
|
|
if (current.slice(-length) === incoming.slice(0, length)) {
|
|
return length;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
function shouldReplaceWithSnapshot(current: string, incoming: string): boolean {
|
|
if (incoming.length < Math.floor(current.length * 0.6)) {
|
|
return false;
|
|
}
|
|
|
|
const currentTokens = new Set(tokenize(current));
|
|
const incomingTokens = new Set(tokenize(incoming));
|
|
if (currentTokens.size < 3 || incomingTokens.size < 3) {
|
|
return false;
|
|
}
|
|
|
|
let shared = 0;
|
|
for (const token of incomingTokens) {
|
|
if (currentTokens.has(token)) {
|
|
shared += 1;
|
|
}
|
|
}
|
|
|
|
return shared / Math.min(currentTokens.size, incomingTokens.size) >= 0.5;
|
|
}
|
|
|
|
export function mergeStreamingText(current: string, incoming: string): string {
|
|
if (!current) {
|
|
return incoming;
|
|
}
|
|
|
|
if (!incoming) {
|
|
return current;
|
|
}
|
|
|
|
if (incoming.startsWith(current) || incoming.includes(current)) {
|
|
return incoming;
|
|
}
|
|
|
|
if (current.includes(incoming)) {
|
|
return current;
|
|
}
|
|
|
|
const overlap = computeSuffixPrefixOverlap(current, incoming);
|
|
if (overlap > 0) {
|
|
return current + incoming.slice(overlap);
|
|
}
|
|
|
|
if (shouldReplaceWithSnapshot(current, incoming)) {
|
|
return incoming;
|
|
}
|
|
|
|
return current + incoming;
|
|
}
|