| 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, |
| }; |
| } |
|
|