85 lines
2.7 KiB
JavaScript
85 lines
2.7 KiB
JavaScript
/**
|
|
* Normalize QVAC completion stream events for cap-chunk (keep under NMH ~1 MB).
|
|
*/
|
|
|
|
const MAX_DELTA = 64 * 1024;
|
|
|
|
function clip(s) {
|
|
const t = s == null ? '' : String(s);
|
|
if (t.length <= MAX_DELTA) return t;
|
|
return t.slice(0, MAX_DELTA);
|
|
}
|
|
|
|
function eventDelta(ev) {
|
|
if (ev == null) return '';
|
|
if (typeof ev === 'string' || typeof ev === 'number' || typeof ev === 'boolean') return clip(ev);
|
|
return clip(ev.text || ev.delta || ev.content || ev.chunk || '');
|
|
}
|
|
|
|
const THINK_OPEN = '<think>';
|
|
const THINK_CLOSE = '</think>';
|
|
|
|
function trailingPartial(buf, marker) {
|
|
const max = Math.min(buf.length, marker.length - 1);
|
|
for (let len = max; len > 0; len--) {
|
|
if (marker.startsWith(buf.slice(buf.length - len))) return len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function expandThinkEvents(n, state) {
|
|
const st = state && typeof state === 'object' ? state : { inThink: false, carry: '' };
|
|
if (!n) return { events: [], state: st };
|
|
if (n.type === 'thinkingDelta') {
|
|
const delta = n.delta || '';
|
|
return { events: delta ? [n] : [], state: st };
|
|
}
|
|
if (n.type !== 'contentDelta') return { events: [n], state: st };
|
|
let rest = String(st.carry || '') + String(n.delta || '');
|
|
const events = [];
|
|
let inThink = !!st.inThink;
|
|
while (rest) {
|
|
const marker = inThink ? THINK_CLOSE : THINK_OPEN;
|
|
const idx = rest.indexOf(marker);
|
|
if (idx < 0) {
|
|
const partial = trailingPartial(rest, marker);
|
|
const emit = partial ? rest.slice(0, rest.length - partial) : rest;
|
|
const carry = partial ? rest.slice(rest.length - partial) : '';
|
|
if (emit) events.push({ type: inThink ? 'thinkingDelta' : 'contentDelta', delta: emit });
|
|
return { events, state: { inThink, carry } };
|
|
}
|
|
const before = rest.slice(0, idx);
|
|
if (before) events.push({ type: inThink ? 'thinkingDelta' : 'contentDelta', delta: before });
|
|
rest = rest.slice(idx + marker.length);
|
|
inThink = !inThink;
|
|
}
|
|
return { events, state: { inThink, carry: '' } };
|
|
}
|
|
|
|
function normalizeCompletionEvent(ev) {
|
|
if (!ev || !ev.type) return null;
|
|
if (ev.type === 'contentDelta') {
|
|
return { type: 'contentDelta', delta: eventDelta(ev) };
|
|
}
|
|
if (ev.type === 'thinkingDelta') {
|
|
return { type: 'thinkingDelta', delta: eventDelta(ev) };
|
|
}
|
|
if (ev.type === 'toolCall') {
|
|
const call = ev.call || ev.toolCall || ev;
|
|
return {
|
|
type: 'toolCall',
|
|
call: {
|
|
id: call.id,
|
|
name: call.name,
|
|
arguments: call.arguments != null ? call.arguments : call.args,
|
|
},
|
|
};
|
|
}
|
|
if (ev.type === 'rawDelta') {
|
|
return { type: 'rawDelta', delta: clip(ev.delta) };
|
|
}
|
|
return { type: ev.type, delta: ev.delta != null ? clip(ev.delta) : undefined, call: ev.call };
|
|
}
|
|
|
|
module.exports = { normalizeCompletionEvent, expandThinkEvents, eventDelta, MAX_DELTA };
|