mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-01 10:42:23 +01:00
fix(compare): reject incomplete and errored SSE responses
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
function streamError(payload) {
|
||||
const detail = payload?.data ?? payload;
|
||||
const candidates = [detail?.message, detail?.text, detail?.error?.message,
|
||||
detail?.error, payload?.message, payload?.text, payload?.error?.message,
|
||||
payload?.error, typeof payload === 'string' ? payload : null];
|
||||
const message = candidates.find(value => typeof value === 'string' && value.trim());
|
||||
return new Error(message || 'Model request failed.');
|
||||
}
|
||||
|
||||
/** Decode complete SSE frames, requiring the backend's terminal DONE event. */
|
||||
export async function consumeCompareEvents(response, onEvent, onActivity) {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let line = '', event = 'message', data = [], skipLF = false;
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (!done) onActivity?.();
|
||||
let chunk = decoder.decode(value, { stream: !done });
|
||||
if (skipLF && chunk.length) {
|
||||
if (chunk.startsWith('\n')) chunk = chunk.slice(1);
|
||||
skipLF = false;
|
||||
}
|
||||
const separators = /\r\n|\r|\n/g;
|
||||
let start = 0, separator;
|
||||
while ((separator = separators.exec(chunk))) {
|
||||
line += chunk.slice(start, separator.index);
|
||||
start = separator.index + separator[0].length;
|
||||
skipLF = separator[0] === '\r' && start === chunk.length;
|
||||
if (!line) {
|
||||
if (data.length || event === 'error') {
|
||||
const raw = data.join('\n');
|
||||
if (event !== 'error' && raw === '[DONE]') return;
|
||||
let payload;
|
||||
try { payload = JSON.parse(raw); }
|
||||
catch (_) {
|
||||
if (event === 'error') throw streamError(raw);
|
||||
throw new Error('Invalid model stream data.');
|
||||
}
|
||||
if (event === 'error' || payload?.type === 'error') throw streamError(payload);
|
||||
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
||||
throw new Error('Invalid model stream data.');
|
||||
}
|
||||
await onEvent(payload);
|
||||
}
|
||||
event = 'message';
|
||||
data = [];
|
||||
} else if (!line.startsWith(':')) {
|
||||
const colon = line.indexOf(':');
|
||||
const field = colon < 0 ? line : line.slice(0, colon);
|
||||
let value = colon < 0 ? '' : line.slice(colon + 1);
|
||||
if (value.startsWith(' ')) value = value.slice(1);
|
||||
if (field === 'data') data.push(value);
|
||||
else if (field === 'event') event = value || 'message';
|
||||
}
|
||||
line = '';
|
||||
}
|
||||
line += chunk.slice(start);
|
||||
if (done) throw new Error('Stream ended before completion.');
|
||||
}
|
||||
} finally {
|
||||
try { await reader.cancel(); } catch (_) {}
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user