Files
ds2api/internal/js/chat-stream/stream_emitter.js

99 lines
2.0 KiB
JavaScript

'use strict';
const MIN_DELTA_FLUSH_CHARS = 160;
const MAX_DELTA_FLUSH_WAIT_MS = 80;
function createChatCompletionEmitter({ res, sessionID, created, model, isClosed }) {
let firstChunkSent = false;
const sendFrame = (obj) => {
if (isClosed() || res.writableEnded || res.destroyed) {
return;
}
res.write(`data: ${JSON.stringify(obj)}\n\n`);
if (typeof res.flush === 'function') {
res.flush();
}
};
const sendDeltaFrame = (delta) => {
const payloadDelta = { ...delta };
if (!firstChunkSent) {
payloadDelta.role = 'assistant';
firstChunkSent = true;
}
sendFrame({
id: sessionID,
object: 'chat.completion.chunk',
created,
model,
choices: [{ delta: payloadDelta, index: 0 }],
});
};
return {
sendFrame,
sendDeltaFrame,
};
}
function createDeltaCoalescer({ sendDeltaFrame, minFlushChars = MIN_DELTA_FLUSH_CHARS, maxFlushWaitMS = MAX_DELTA_FLUSH_WAIT_MS }) {
let pendingField = '';
let pendingText = '';
let flushTimer = null;
const clearFlushTimer = () => {
if (flushTimer) {
clearTimeout(flushTimer);
flushTimer = null;
}
};
const flush = () => {
clearFlushTimer();
if (!pendingField || !pendingText) {
return;
}
const delta = { [pendingField]: pendingText };
pendingField = '';
pendingText = '';
sendDeltaFrame(delta);
};
const scheduleFlush = () => {
if (flushTimer || maxFlushWaitMS <= 0) {
return;
}
flushTimer = setTimeout(flush, maxFlushWaitMS);
if (typeof flushTimer.unref === 'function') {
flushTimer.unref();
}
};
const append = (field, text) => {
if (!field || !text) {
return;
}
if (pendingField && pendingField !== field) {
flush();
}
pendingField = field;
pendingText += text;
if ([...pendingText].length >= minFlushChars) {
flush();
return;
}
scheduleFlush();
};
return {
append,
flush,
};
}
module.exports = {
createChatCompletionEmitter,
createDeltaCoalescer,
};