import type { ChatTimeAssistantItem, ChatTimeHistoryRecord, ChatTimeRoundMeta, ChatTimeThinkingStatus, ChatTimeTimelineItem, ChatTimeTimelineItemBase, ChatTimeToolItem, ChatTimeToolMutationPayload, ChatTimeToolStatus, } from '../../../types/src/chat-time'; import {uuid} from './uuid'; type ChatTimeToolMeta = { arguments?: string; name?: string; }; /** * 聊天时间线实时构建器。 */ class ChatTimeTimelineBuilder { /** * 追加用户消息。 */ static appendUserMessage( items: ChatTimeTimelineItem[], payload: { content?: string; created?: number | string; id?: string; messageKind?: string; roundId?: number | string; roundNo?: number; senderName?: string; }, ) { const item: ChatTimeTimelineItem = { content: normalizePlainText(payload.content), created: normalizeTimestamp(payload.created), id: payload.id || uuid(), placement: 'end', role: 'user', senderName: payload.senderName, }; applyRoundMeta(item, payload); items.push(item); } /** * 将最新一条待绑定的用户消息补齐到当前轮次。 */ static bindLatestPendingUserMessage( items: ChatTimeTimelineItem[], meta?: ChatTimeRoundMeta, ) { const roundId = normalizeRoundId(meta?.roundId); if (!roundId) { return; } for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (!item) { continue; } if (item.role !== 'user') { continue; } if (item.roundId) { return; } applyRoundMeta(item, { roundId, roundNo: meta?.roundNo, }); return; } } /** * 更新指定轮次的可切换状态。 */ static setRoundSwitchable( items: ChatTimeTimelineItem[], roundId: number | string | undefined, switchable: boolean, ) { const normalizedRoundId = normalizeRoundId(roundId); if (!normalizedRoundId) { return; } for (const item of items) { if (item.roundId === normalizedRoundId && item.role !== 'user') { item.switchable = switchable; } } } /** * 追加思考增量。 */ static appendThinkingDelta( items: ChatTimeTimelineItem[], delta?: string, created?: number | string, meta?: ChatTimeRoundMeta, ) { const normalizedDelta = normalizePlainText(delta); if (!normalizedDelta) { return; } prepareRoundVariant(items, meta); const assistant = ensureAssistantTail(items, created, meta); const tail = assistant.segments[assistant.segments.length - 1]; if (tail?.type === 'thinking' && tail.status === 'thinking') { tail.content += normalizedDelta; } else { assistant.segments.push({ content: normalizedDelta, expanded: false, id: uuid(), status: 'thinking', type: 'thinking', }); } assistant.loading = true; assistant.typing = true; } /** * 追加回答增量。 */ static appendMessageDelta( items: ChatTimeTimelineItem[], delta?: string, created?: number | string, meta?: ChatTimeRoundMeta, ) { const normalizedDelta = normalizeAssistantText(delta); if (!normalizedDelta) { return; } prepareRoundVariant(items, meta); const assistant = ensureAssistantTail(items, created, meta); stopThinkingForAssistant(assistant); const tail = assistant.segments[assistant.segments.length - 1]; if (tail?.type === 'text') { tail.content += normalizedDelta; } else { assistant.segments.push({ content: normalizedDelta, id: uuid(), type: 'text', }); } assistant.content += normalizedDelta; assistant.loading = false; assistant.typing = true; } /** * 用最终完整回答替换当前 assistant 文本。 */ static replaceMessageContent( items: ChatTimeTimelineItem[], content?: string, created?: number | string, meta?: ChatTimeRoundMeta, ) { const normalizedContent = normalizeAssistantText(content); if (!normalizedContent) { return; } prepareRoundVariant(items, meta); const assistant = ensureAssistantTail(items, created, meta); stopThinkingForAssistant(assistant); assistant.content = normalizedContent; assistant.segments = [ ...assistant.segments.filter((segment) => segment.type !== 'text'), { content: normalizedContent, id: uuid(), type: 'text' as const, }, ]; assistant.loading = false; assistant.typing = false; } /** * 停止当前 assistant 的思考态。 */ static stopThinking(items: ChatTimeTimelineItem[]) { const last = items[items.length - 1]; if (!isAssistantItem(last)) { return; } stopThinkingForAssistant(last); } /** * 更新工具调用状态。 */ static upsertToolCall( items: ChatTimeTimelineItem[], payload: ChatTimeToolMutationPayload, ) { prepareRoundVariant(items, payload); this.stopThinking(items); const toolItem = ensureToolItem( items, payload.toolCallId, payload.created, payload.name, payload, ); toolItem.arguments = normalizePayloadValue(payload.value); toolItem.content = ''; toolItem.status = 'TOOL_CALL'; } /** * 更新工具结果状态。 */ static upsertToolResult( items: ChatTimeTimelineItem[], payload: ChatTimeToolMutationPayload, ) { prepareRoundVariant(items, payload); const toolItem = ensureToolItem( items, payload.toolCallId, payload.created, payload.name, payload, ); toolItem.result = normalizePayloadValue(payload.result); toolItem.content = toolItem.result; toolItem.status = 'TOOL_RESULT'; } /** * 追加系统错误。 */ static applySystemError( items: ChatTimeTimelineItem[], message?: string, created?: number | string, ) { const errorMessage = normalizePlainText(message); if (!errorMessage) { return; } const last = items[items.length - 1]; if (isAssistantItem(last)) { stopThinkingForAssistant(last); appendAssistantText(last, errorMessage); last.loading = false; last.typing = false; return; } const assistant = createAssistantItem(created); appendAssistantText(assistant, errorMessage); assistant.loading = false; assistant.typing = false; items.push(assistant); } /** * 结束当前轮的 assistant 状态。 */ static finalize(items: ChatTimeTimelineItem[]) { const last = findLastAssistant(items); if (!isAssistantItem(last)) { return; } stopThinkingForAssistant(last); last.loading = false; last.typing = false; } /** * 按轮次替换当前主线可见的 assistant/tool 片段。 */ static replaceRoundMessages( items: ChatTimeTimelineItem[], roundId: number | string | undefined, nextMessages: ChatTimeTimelineItem[], ) { const normalizedRoundId = normalizeRoundId(roundId); if (!normalizedRoundId) { return; } const range = resolveRoundReplaceRange(items, normalizedRoundId); if (range) { items.splice(range.start, range.deleteCount, ...nextMessages); return; } items.splice(resolveRoundInsertIndex(items, normalizedRoundId), 0, ...nextMessages); } } /** * 聊天时间线历史映射器。 */ class ChatTimeHistoryMapper { /** * 从聊天历史记录恢复时间线。 */ static fromHistoryRecords(records: ChatTimeHistoryRecord[]) { return normalizeVisibleHistoryRecords(records).flatMap((record) => this.fromHistoryRecord(record), ); } /** * 从旧版消息列表恢复时间线。 */ static fromLegacyMessages(records: ChatTimeHistoryRecord[]) { return records.flatMap((record) => this.fromLegacyRecord(record)); } private static fromHistoryRecord(record: ChatTimeHistoryRecord) { const role = normalizeRole(record.senderRole || record.role); if (role === 'user') { return [createUserItem(record)]; } if (role === 'tool') { return [createToolItemFromTopLevelRecord(record)]; } if (role !== 'assistant') { return []; } const payload = toObjectRecord(record.contentPayload); const messageChain = toObjectArray(payload.messageChain); if (messageChain.length > 0) { const structuredItems = this.fromStructuredAssistantRecord( record, messageChain, ); if (structuredItems.length > 0) { return structuredItems; } } return this.fromLegacyRecord(record); } private static fromLegacyRecord(record: ChatTimeHistoryRecord) { const role = normalizeRole(record.senderRole || record.role); if (role === 'user') { return [createUserItem(record)]; } if (role === 'tool') { return [createToolItemFromTopLevelRecord(record)]; } if (role !== 'assistant') { return []; } const content = normalizeAssistantText(record.contentText || record.content); const chains = extractDisplayChains(record); const assistant = createAssistantItem(record.created, { id: record.id == null ? undefined : String(record.id), loading: record.loading, messageKind: record.messageKind, roundId: normalizeRoundId(record.roundId), roundNo: record.roundNo, selectedVariantIndex: record.selectedVariantIndex, senderName: record.senderName, switchable: record.switchable, typing: record.typing, variantCount: record.variantCount, variantIndex: record.variantIndex, }); const tools: ChatTimeTimelineItem[] = []; for (const rawChain of chains) { const reasoning = normalizePlainText(rawChain.reasoning_content); if (reasoning) { assistant.segments.push({ content: reasoning, expanded: Boolean(rawChain.thinkingExpanded), id: uuid(), status: normalizeThinkingStatus(rawChain.thinkingStatus), type: 'thinking', }); continue; } const toolItem = createToolItemFromChain(rawChain, record.created, record); if (toolItem) { tools.push(toolItem); } } if (content) { appendAssistantText(assistant, content); } const results: ChatTimeTimelineItem[] = []; if (assistant.segments.length > 0) { assistant.loading = false; assistant.typing = false; results.push(assistant); } return [...results, ...tools]; } private static fromStructuredAssistantRecord( record: ChatTimeHistoryRecord, messageChain: Array>, ) { const toolMetaMap = new Map(); const items: ChatTimeTimelineItem[] = []; let assistantIndex = 0; for (const rawMessage of messageChain) { const role = normalizeRole(rawMessage.role); if (role === 'assistant') { collectToolMeta(rawMessage, toolMetaMap); const assistant = createAssistantItemFromStructuredMessage( rawMessage, record, assistantIndex, ); if (assistant) { items.push(assistant); assistantIndex += 1; } continue; } if (role === 'tool') { items.push( createToolItemFromStructuredMessage( rawMessage, toolMetaMap, record.created, record, ), ); } } return items; } } function normalizeVisibleHistoryRecords(records: ChatTimeHistoryRecord[]) { const dedupedRecords = dedupeHistoryRecords(records); const userSelectedVariantByRound = new Map(); const assistantSelectedVariantByRound = new Map(); const fallbackVariantByRound = new Map(); for (const record of dedupedRecords) { const roundId = normalizeRoundId(record.roundId); if (!roundId) { continue; } const selectedVariantIndex = normalizePositiveInteger( record.selectedVariantIndex, ); if (selectedVariantIndex) { if (isUserHistoryRecord(record)) { userSelectedVariantByRound.set(roundId, selectedVariantIndex); } else { assistantSelectedVariantByRound.set(roundId, selectedVariantIndex); } } const variantIndex = normalizePositiveInteger(record.variantIndex); if (!isUserHistoryRecord(record) && variantIndex) { fallbackVariantByRound.set(roundId, variantIndex); } } return dedupedRecords.filter((record) => { const roundId = normalizeRoundId(record.roundId); if (!roundId || isUserHistoryRecord(record)) { return true; } const variantIndex = normalizePositiveInteger(record.variantIndex); if (!variantIndex) { return true; } const selectedVariantIndex = userSelectedVariantByRound.get(roundId) || assistantSelectedVariantByRound.get(roundId) || fallbackVariantByRound.get(roundId); return !selectedVariantIndex || variantIndex === selectedVariantIndex; }); } function dedupeHistoryRecords(records: ChatTimeHistoryRecord[]) { const seen = new Set(); const result: ChatTimeHistoryRecord[] = []; for (const record of records) { const key = resolveHistoryRecordKey(record); if (seen.has(key)) { continue; } seen.add(key); result.push(record); } return result; } function resolveHistoryRecordKey(record: ChatTimeHistoryRecord) { if (record.id != null) { return `id:${String(record.id)}`; } return [ 'fallback', normalizeRoundId(record.roundId) || '', normalizeRole(record.senderRole || record.role), normalizePositiveInteger(record.variantIndex) || '', normalizePlainText(record.contentText || record.content), ].join(':'); } function isUserHistoryRecord(record: ChatTimeHistoryRecord) { return normalizeRole(record.senderRole || record.role) === 'user'; } function createAssistantItem( created?: number | string, patch?: Omit, 'roundId'> & ChatTimeRoundMeta, ): ChatTimeAssistantItem { const item: ChatTimeAssistantItem = { content: patch?.content || '', created: normalizeTimestamp(created), id: patch?.id || uuid(), loading: patch?.loading, placement: 'start', role: 'assistant', segments: patch?.segments ? [...patch.segments] : [], senderName: patch?.senderName, typing: patch?.typing, }; applyRoundMeta(item, patch); return item; } function createAssistantItemFromStructuredMessage( rawMessage: Record, record: ChatTimeHistoryRecord, assistantIndex: number, ) { const content = normalizeAssistantText(rawMessage.content); const reasoning = normalizePlainText( rawMessage.reasoningContent ?? rawMessage.reasoning_content, ); if (!content && !reasoning) { return null; } const assistant = createAssistantItem(record.created, { id: record.id == null ? undefined : `${String(record.id)}-assistant-${assistantIndex}`, loading: false, messageKind: record.messageKind, roundId: normalizeRoundId(record.roundId), roundNo: record.roundNo, selectedVariantIndex: record.selectedVariantIndex, senderName: record.senderName, switchable: record.switchable, typing: false, variantCount: record.variantCount, variantIndex: record.variantIndex, }); if (reasoning) { assistant.segments.push({ content: reasoning, expanded: false, id: uuid(), status: 'end', type: 'thinking', }); } if (content) { appendAssistantText(assistant, content); } return assistant; } function createToolItemFromChain( rawChain: Record, created?: number | string, record?: ChatTimeHistoryRecord, ) { const toolCallId = normalizePlainText(rawChain.id); const name = normalizePlainText(rawChain.name); const argumentsValue = normalizePayloadValue(rawChain.arguments ?? rawChain.result); const status = normalizeToolStatus(rawChain.status); if (!toolCallId && !name && !argumentsValue) { return null; } return createToolItem({ arguments: status === 'TOOL_CALL' ? argumentsValue : undefined, created, id: toolCallId || uuid(), messageKind: record?.messageKind, name, roundId: record?.roundId, roundNo: record?.roundNo, result: status === 'TOOL_RESULT' ? argumentsValue : undefined, selectedVariantIndex: record?.selectedVariantIndex, status, switchable: record?.switchable, toolCallId, variantCount: record?.variantCount, variantIndex: record?.variantIndex, }); } function createToolItemFromStructuredMessage( rawMessage: Record, toolMetaMap: Map, created?: number | string, record?: ChatTimeHistoryRecord, ) { const toolCallId = normalizePlainText( rawMessage.toolCallId ?? rawMessage.tool_call_id, ); const toolMeta = toolMetaMap.get(toolCallId); const result = normalizePayloadValue(rawMessage.content); return createToolItem({ arguments: toolMeta?.arguments, created, id: toolCallId || uuid(), messageKind: record?.messageKind, name: toolMeta?.name, roundId: record?.roundId, roundNo: record?.roundNo, result, selectedVariantIndex: record?.selectedVariantIndex, status: 'TOOL_RESULT', switchable: record?.switchable, toolCallId, variantCount: record?.variantCount, variantIndex: record?.variantIndex, }); } function createToolItemFromTopLevelRecord(record: ChatTimeHistoryRecord) { const payload = toObjectRecord(record.contentPayload); const toolCallId = normalizePlainText( payload.toolCallId ?? payload.tool_call_id ?? record.id, ); return createToolItem({ created: record.created, id: record.id == null ? toolCallId || uuid() : String(record.id), messageKind: record.messageKind, name: normalizePlainText(payload.name), roundId: record.roundId, roundNo: record.roundNo, result: normalizePayloadValue( payload.content ?? payload.result ?? record.contentText ?? record.content, ), selectedVariantIndex: record.selectedVariantIndex, status: 'TOOL_RESULT', switchable: record.switchable, toolCallId, variantCount: record.variantCount, variantIndex: record.variantIndex, }); } function createToolItem(payload: { arguments?: string; created?: number | string; id?: string; messageKind?: string; name?: string; roundId?: number | string; roundNo?: number; result?: string; selectedVariantIndex?: number; status: ChatTimeToolStatus; switchable?: boolean; toolCallId?: string; variantCount?: number; variantIndex?: number; }): ChatTimeToolItem { const item: ChatTimeToolItem = { arguments: payload.arguments, content: payload.result || '', created: normalizeTimestamp(payload.created), id: payload.id || payload.toolCallId || uuid(), name: payload.name || '', placement: 'start', result: payload.result, role: 'tool', status: payload.status, toolCallId: payload.toolCallId || payload.id || uuid(), }; applyRoundMeta(item, payload); return item; } function createUserItem(record: ChatTimeHistoryRecord): ChatTimeTimelineItem { const item: ChatTimeTimelineItem = { content: normalizePlainText(record.contentText || record.content), created: normalizeTimestamp(record.created), id: record.id == null ? uuid() : String(record.id), loading: record.loading, placement: record.placement || 'end', role: 'user', senderName: record.senderName, typing: record.typing, }; applyRoundMeta(item, record); return item; } function appendAssistantText(item: ChatTimeAssistantItem, content: string) { const normalized = normalizeAssistantText(content); if (!normalized) { return; } item.content += normalized; item.segments.push({ content: normalized, id: uuid(), type: 'text', }); } function collectToolMeta( rawMessage: Record, toolMetaMap: Map, ) { const toolCalls = toObjectArray(rawMessage.toolCalls ?? rawMessage.tool_calls); for (const toolCall of toolCalls) { const toolCallId = normalizePlainText(toolCall.id); if (!toolCallId) { continue; } toolMetaMap.set(toolCallId, { arguments: normalizePayloadValue(toolCall.arguments), name: normalizePlainText(toolCall.name), }); } } function ensureAssistantTail( items: ChatTimeTimelineItem[], created?: number | string, meta?: ChatTimeRoundMeta, ) { const last = items[items.length - 1]; if (isAssistantItem(last) && isSameRoundVariant(last, meta)) { applyRoundMeta(last, meta); return last; } const assistant = createAssistantItem(created, { loading: true, typing: true, ...normalizeRoundMeta(meta), }); items.push(assistant); return assistant; } function ensureToolItem( items: ChatTimeTimelineItem[], toolCallId?: string, created?: number | string, name?: string, meta?: ChatTimeRoundMeta, ) { const normalizedToolCallId = normalizePlainText(toolCallId); const found = findToolItem(items, normalizedToolCallId, meta); if (found) { if (name) { found.name = name; } applyRoundMeta(found, meta); return found; } const toolItem = createToolItem({ created, id: normalizedToolCallId || uuid(), messageKind: meta?.messageKind, name, roundId: meta?.roundId, roundNo: meta?.roundNo, selectedVariantIndex: meta?.selectedVariantIndex, status: 'TOOL_CALL', switchable: meta?.switchable, toolCallId: normalizedToolCallId, variantCount: meta?.variantCount, variantIndex: meta?.variantIndex, }); items.push(toolItem); return toolItem; } function findToolItem( items: ChatTimeTimelineItem[], toolCallId?: string, meta?: ChatTimeRoundMeta, ) { const normalizedRoundId = normalizeRoundId(meta?.roundId); const normalizedVariantIndex = normalizePositiveInteger(meta?.variantIndex); if (toolCallId) { for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (!item) { continue; } if ( isToolItem(item) && item.toolCallId === toolCallId && matchesRoundVariant(item, normalizedRoundId, normalizedVariantIndex) ) { return item; } } } for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (!item) { continue; } if ( isToolItem(item) && item.status === 'TOOL_CALL' && matchesRoundVariant(item, normalizedRoundId, normalizedVariantIndex) ) { return item; } } return null; } function extractDisplayChains(record: ChatTimeHistoryRecord) { if (Array.isArray(record.chains)) { return record.chains.map((item) => toObjectRecord(item)); } const payload = toObjectRecord(record.contentPayload); if (Array.isArray(payload.chains)) { return payload.chains.map((item: unknown) => toObjectRecord(item)); } return []; } function isAssistantItem( item?: ChatTimeTimelineItem, ): item is ChatTimeAssistantItem { return item?.role === 'assistant'; } function isToolItem(item?: ChatTimeTimelineItem): item is ChatTimeToolItem { return item?.role === 'tool'; } function findLastAssistant(items: ChatTimeTimelineItem[]) { for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (isAssistantItem(item)) { return item; } } return undefined; } function prepareRoundVariant( items: ChatTimeTimelineItem[], meta?: ChatTimeRoundMeta, ) { const normalizedRoundId = normalizeRoundId(meta?.roundId); const normalizedVariantIndex = normalizePositiveInteger(meta?.variantIndex); if (!normalizedRoundId || !normalizedVariantIndex) { return; } const assistant = items.find( (item) => item.role === 'assistant' && item.roundId === normalizedRoundId, ); if (assistant?.variantIndex === normalizedVariantIndex) { return; } for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (!item) { continue; } if (item.roundId === normalizedRoundId && item.role !== 'user') { items.splice(index, 1); } } } function resolveRoundInsertIndex( items: ChatTimeTimelineItem[], roundId: string, ) { const firstRoundItemIndex = items.findIndex( (item) => item.roundId === roundId && item.role !== 'user', ); if (firstRoundItemIndex >= 0) { return firstRoundItemIndex; } for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (!item) { continue; } if (item.roundId === roundId && item.role === 'user') { return index + 1; } } return items.length; } function resolveRoundReplaceRange( items: ChatTimeTimelineItem[], roundId: string, ) { let start = -1; let end = -1; for (let index = 0; index < items.length; index += 1) { const item = items[index]; if (item?.roundId === roundId && item.role !== 'user') { if (start < 0) { start = index; } end = index; } else if (start >= 0) { break; } } if (start < 0) { return null; } return { deleteCount: end - start + 1, start, }; } function matchesRoundVariant( item: ChatTimeTimelineItem, roundId?: string, variantIndex?: number, ) { if (roundId && item.roundId !== roundId) { return false; } if (variantIndex && item.variantIndex && item.variantIndex !== variantIndex) { return false; } return true; } function isSameRoundVariant( item: ChatTimeTimelineItem, meta?: ChatTimeRoundMeta, ) { const normalizedRoundId = normalizeRoundId(meta?.roundId); const normalizedVariantIndex = normalizePositiveInteger(meta?.variantIndex); if (!normalizedRoundId || !normalizedVariantIndex) { return true; } return ( item.roundId === normalizedRoundId && normalizePositiveInteger(item.variantIndex) === normalizedVariantIndex ); } function applyRoundMeta( target: Partial, source?: ChatTimeRoundMeta | null, ) { if (!source) { return; } const roundId = normalizeRoundId(source.roundId); if (roundId) { target.roundId = roundId; } const roundNo = normalizePositiveInteger(source.roundNo); if (roundNo) { target.roundNo = roundNo; } const variantIndex = normalizePositiveInteger(source.variantIndex); if (variantIndex) { target.variantIndex = variantIndex; } const variantCount = normalizePositiveInteger(source.variantCount); if (variantCount) { target.variantCount = variantCount; } const selectedVariantIndex = normalizePositiveInteger( source.selectedVariantIndex, ); if (selectedVariantIndex) { target.selectedVariantIndex = selectedVariantIndex; } if (typeof source.switchable === 'boolean') { target.switchable = source.switchable; } const messageKind = normalizePlainText(source.messageKind).trim(); if (messageKind) { target.messageKind = messageKind; } } function normalizeRoundMeta(meta?: ChatTimeRoundMeta): ChatTimeRoundMeta { return { messageKind: meta?.messageKind, roundId: normalizeRoundId(meta?.roundId), roundNo: meta?.roundNo, selectedVariantIndex: meta?.selectedVariantIndex, switchable: meta?.switchable, variantCount: meta?.variantCount, variantIndex: meta?.variantIndex, }; } function normalizeRoundId(value: any) { const normalized = normalizePlainText(value).trim(); return normalized || undefined; } function normalizePositiveInteger(value: any) { if (value == null || value === '') { return undefined; } const parsed = Number.parseInt(String(value), 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; } function normalizeAssistantText(value: any) { return normalizePlainText(value); } function normalizePayloadValue(value: any) { if (value == null) { return ''; } if (typeof value === 'string') { return value; } try { return JSON.stringify(value, null, 2); } catch { return String(value); } } function normalizePlainText(value: any) { return typeof value === 'string' ? value : value == null ? '' : String(value); } function normalizeRole(value: any) { return normalizePlainText(value).trim().toLowerCase(); } function normalizeThinkingStatus(value: any): ChatTimeThinkingStatus { return normalizeRole(value) === 'thinking' ? 'thinking' : 'end'; } function normalizeTimestamp(value?: number | string) { if (typeof value === 'number' && Number.isFinite(value)) { return value; } if (typeof value === 'string' && value.trim()) { const parsed = Date.parse(value); return Number.isNaN(parsed) ? value : parsed; } return Date.now(); } function normalizeToolStatus(value: any): ChatTimeToolStatus { return normalizePlainText(value).toUpperCase() === 'TOOL_CALL' ? 'TOOL_CALL' : 'TOOL_RESULT'; } function stopThinkingForAssistant(item: ChatTimeAssistantItem) { item.segments = item.segments.map( (segment: ChatTimeAssistantItem['segments'][number]) => { if (segment.type !== 'thinking' || segment.status !== 'thinking') { return segment; } return { ...segment, status: 'end' as ChatTimeThinkingStatus, }; }, ); } function toObjectArray(value: any): Array> { if (!Array.isArray(value)) { return []; } return value .map((item: unknown) => toObjectRecord(item)) .filter((item) => Object.keys(item).length > 0); } function toObjectRecord(value: any): Record { if (!value || typeof value !== 'object' || Array.isArray(value)) { return {}; } return value as Record; } export { ChatTimeHistoryMapper, ChatTimeTimelineBuilder };