87 lines
2.8 KiB
JavaScript
87 lines
2.8 KiB
JavaScript
|
|
export const SSE_EVENT_TAXONOMY = {
|
||
|
|
DELTA: 'delta',
|
||
|
|
CONTROL: 'control',
|
||
|
|
TERMINAL: 'terminal',
|
||
|
|
};
|
||
|
|
|
||
|
|
const TERMINAL_RUN_STATUSES = new Set(['succeeded', 'failed']);
|
||
|
|
|
||
|
|
function envFlag(value, fallback = false) {
|
||
|
|
const raw = String(value ?? '').trim().toLowerCase();
|
||
|
|
if (!raw) return fallback;
|
||
|
|
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function isSseEventTaxonomyEnabled(env = process.env) {
|
||
|
|
return envFlag(env.MEMIND_SSE_EVENT_TAXONOMY, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function classifySessionEventTaxonomy(event) {
|
||
|
|
const type = String(event?.type ?? '').trim();
|
||
|
|
if (!type) return null;
|
||
|
|
switch (type) {
|
||
|
|
case 'Message':
|
||
|
|
return SSE_EVENT_TAXONOMY.DELTA;
|
||
|
|
case 'Finish':
|
||
|
|
case 'Error':
|
||
|
|
return SSE_EVENT_TAXONOMY.TERMINAL;
|
||
|
|
case 'UpdateConversation':
|
||
|
|
case 'Ping':
|
||
|
|
case 'ActiveRequests':
|
||
|
|
return SSE_EVENT_TAXONOMY.CONTROL;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
const normalized = type.toLowerCase();
|
||
|
|
if (normalized === 'message') return SSE_EVENT_TAXONOMY.DELTA;
|
||
|
|
if (normalized === 'finish' || normalized === 'error') return SSE_EVENT_TAXONOMY.TERMINAL;
|
||
|
|
if (
|
||
|
|
normalized === 'updateconversation' ||
|
||
|
|
normalized === 'ping' ||
|
||
|
|
normalized === 'activerequests'
|
||
|
|
) {
|
||
|
|
return SSE_EVENT_TAXONOMY.CONTROL;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function classifyRunStreamTaxonomy(eventName, payload = {}) {
|
||
|
|
const name = String(eventName ?? '').trim().toLowerCase();
|
||
|
|
if (name === 'error') return SSE_EVENT_TAXONOMY.TERMINAL;
|
||
|
|
if (name !== 'run') return null;
|
||
|
|
const status = String(payload?.run?.status ?? payload?.status ?? '').trim().toLowerCase();
|
||
|
|
if (TERMINAL_RUN_STATUSES.has(status)) return SSE_EVENT_TAXONOMY.TERMINAL;
|
||
|
|
if (status) return SSE_EVENT_TAXONOMY.CONTROL;
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function finalizeSessionStreamEvent(event, env = process.env) {
|
||
|
|
if (!event || typeof event !== 'object' || Array.isArray(event)) return event;
|
||
|
|
if (!isSseEventTaxonomyEnabled(env)) return event;
|
||
|
|
const taxonomy = classifySessionEventTaxonomy(event);
|
||
|
|
if (!taxonomy || event.taxonomy === taxonomy) return event;
|
||
|
|
return { ...event, taxonomy };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function wrapRunStreamPayload(eventName, data, env = process.env) {
|
||
|
|
if (!data || typeof data !== 'object' || Array.isArray(data)) return data;
|
||
|
|
if (!isSseEventTaxonomyEnabled(env)) return data;
|
||
|
|
const taxonomy = classifyRunStreamTaxonomy(eventName, data);
|
||
|
|
if (!taxonomy || data.taxonomy === taxonomy) return data;
|
||
|
|
return { ...data, taxonomy };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function writeSseErrorAndEnd(res, message, { status = 502 } = {}) {
|
||
|
|
if (res.writableEnded) return;
|
||
|
|
if (res.headersSent) {
|
||
|
|
try {
|
||
|
|
res.write(`event: error\ndata: ${JSON.stringify({ message })}\n\n`);
|
||
|
|
} catch {
|
||
|
|
// Client may already be gone.
|
||
|
|
}
|
||
|
|
res.end();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
res.status(status).json({ message });
|
||
|
|
}
|