chat-dev / server /chatSessionSerializer.js
sharktide's picture
Create chatSessionSerializer.js
b682f80 verified
const ROOT_JSON_KEY = '__historyRootJson';
const VERSION_META_FIELDS = ['toolCalls', 'responseEdits', 'responseSegments', 'error'];
function validateAndRepairTree(rootMessage) {
const repair = (msg) => {
if (!msg) return;
if (msg.content === undefined || msg.content === null) {
msg.content = '';
}
if (msg.versions && Array.isArray(msg.versions)) {
for (const version of msg.versions) {
if (version.content === undefined || version.content === null) {
version.content = '';
}
if (version.tail && Array.isArray(version.tail)) {
for (const tailMsg of version.tail) {
repair(tailMsg);
}
}
}
}
};
repair(rootMessage);
return rootMessage;
}
function cloneAndRepairTree(rootMessage) {
return validateAndRepairTree(JSON.parse(JSON.stringify(rootMessage)));
}
function getActiveVersion(message) {
if (!message) return null;
const versions = Array.isArray(message.versions) ? message.versions : [];
if (!versions.length) {
message.versions = [{ content: message.content ?? '', tail: [], timestamp: Date.now() }];
message.currentVersionIdx = 0;
return message.versions[0];
}
const currentVersionIdx = Number.isInteger(message.currentVersionIdx)
? Math.max(0, Math.min(message.currentVersionIdx, versions.length - 1))
: 0;
message.currentVersionIdx = currentVersionIdx;
if (!Array.isArray(message.versions[currentVersionIdx].tail)) {
message.versions[currentVersionIdx].tail = [];
}
if (message.versions[currentVersionIdx].content === undefined || message.versions[currentVersionIdx].content === null) {
message.versions[currentVersionIdx].content = message.content ?? '';
}
return message.versions[currentVersionIdx];
}
function cloneVersionMetaValue(value) {
if (value === undefined) return undefined;
return JSON.parse(JSON.stringify(value));
}
function syncMessageFromActiveVersion(message) {
if (!message) return message;
const currentVersion = getActiveVersion(message);
if (!currentVersion) return message;
message.content = currentVersion.content ?? message.content ?? '';
VERSION_META_FIELDS.forEach((key) => {
if (key in currentVersion) {
message[key] = cloneVersionMetaValue(currentVersion[key]);
} else {
delete message[key];
}
});
return message;
}
function cloneJson(value) {
return JSON.parse(JSON.stringify(value));
}
export function extractFlatHistory(rootMessage) {
if (!rootMessage) return [];
const toFlatEntry = (message) => {
const cloned = cloneJson(message);
if (cloned.content === undefined || cloned.content === null) {
cloned.content = '';
}
syncMessageFromActiveVersion(cloned);
if (Array.isArray(cloned.versions)) {
cloned.versions = cloned.versions.map((version) => ({
...version,
tail: [],
}));
}
return cloned;
};
const history = [toFlatEntry(rootMessage)];
const currentVerIdx = rootMessage.currentVersionIdx ?? 0;
if (!Array.isArray(rootMessage.versions) || currentVerIdx >= rootMessage.versions.length) {
return history;
}
const currentTail = rootMessage.versions[currentVerIdx]?.tail;
if (currentTail && Array.isArray(currentTail)) {
const walkTail = (tail) => {
for (let i = 0; i < tail.length; i++) {
const msg = tail[i];
if (msg?.content === undefined || msg?.content === null) {
msg.content = '';
}
syncMessageFromActiveVersion(msg);
history.push(toFlatEntry(msg));
const ver = msg.versions?.[msg.currentVersionIdx ?? 0];
if (ver?.tail && Array.isArray(ver.tail)) {
walkTail(ver.tail);
}
if (msg.role === 'user' && Array.isArray(msg.versions) && msg.versions.length > 1) {
break;
}
}
};
walkTail(currentTail);
}
return history;
}
export function serializeSessionForClient(session) {
const rootMessage = Array.isArray(session?.history) && session.history[0]
? cloneAndRepairTree(session.history[0])
: null;
return {
id: session?.id,
name: session?.name,
created: session?.created,
history: rootMessage ? extractFlatHistory(rootMessage) : [],
model: session?.model || null,
updatedAt: session?.updatedAt || null,
[ROOT_JSON_KEY]: rootMessage ? JSON.stringify(rootMessage) : null,
};
}