88a1dab810
---ci--- phase: 7 milestone: v0.2 status: complete requirements: covered: [REQ-2-001, REQ-2-002, REQ-2-003, REQ-2-004, REQ-2-005, REQ-2-006, REQ-2-007, REQ-2-008, REQ-2-009, REQ-2-010, REQ-2-011, REQ-2-012] partial: [] ---/ci--- Milestone v0.2 (ai-tutor-architecture) merged to main. Escalation record (audit remediation, durable): P1 executor delegation failed twice (empty subagent results, zero files created); auto-resolved at full autonomy to inline execution with identical plan fidelity (commit 3271373, reflog-only after phase branch squash-delete).
28 lines
1.0 KiB
TypeScript
28 lines
1.0 KiB
TypeScript
/**
|
|
* Shared SSE parsing for the AI service streams.
|
|
*
|
|
* Normalizes CRLF (sse-starlette's wire terminator is \r\n) to LF, then
|
|
* splits frames on blank lines. Multiple `data:` lines within one frame
|
|
* are joined with \n per the SSE spec. Frames with no data lines
|
|
* (keep-alive `: ping` comments) are ignored (G-1).
|
|
*/
|
|
export function parseSseEvents(buffer: string): { events: string[]; rest: string } {
|
|
const normalized = buffer.replace(/\r\n/g, '\n');
|
|
const events: string[] = [];
|
|
|
|
const separatorIndex = normalized.lastIndexOf('\n\n');
|
|
if (separatorIndex === -1) return { events, rest: normalized };
|
|
|
|
const complete = normalized.slice(0, separatorIndex);
|
|
const rest = normalized.slice(separatorIndex + 2);
|
|
|
|
for (const frame of complete.split('\n\n')) {
|
|
const dataLines = frame
|
|
.split('\n')
|
|
.filter((line) => line.startsWith('data:'))
|
|
.map((line) => line.slice(5).trimStart());
|
|
if (dataLines.length === 0) continue; // ping/comment frame — ignore (G-1)
|
|
events.push(dataLines.join('\n'));
|
|
}
|
|
return { events, rest };
|
|
} |