mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-31 18:22:22 +01:00
66 lines
2.7 KiB
JavaScript
66 lines
2.7 KiB
JavaScript
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();
|
|
}
|
|
}
|