import type { ChatMessage } from '../types/workbench'; /** 收集消息的所有可对账身份键 */ export function getMessageKeys(m: ChatMessage): string[] { const keys: string[] = []; if (m.clientTurnId) { keys.push(`turn:${m.clientTurnId}:${m.role}`); } if (m.clientMessageId) { keys.push(`client:${m.clientMessageId}`); } keys.push(`id:${m.id}`); if (m.serverId) { keys.push(`server:${m.serverId}`); } return keys; } /** 判断两条消息是否代表同一条物理消息 */ export function isSameMessage(a: ChatMessage, b: ChatMessage): boolean { if (a.sessionId !== b.sessionId || a.role !== b.role) { return false; } if (a.id && b.id && a.id === b.id) { return true; } if (a.clientTurnId && b.clientTurnId && a.clientTurnId === b.clientTurnId) { return true; } if (a.serverId && b.serverId && a.serverId === b.serverId) { return true; } if (a.clientMessageId && b.clientMessageId && a.clientMessageId === b.clientMessageId) { return true; } if (a.content === b.content && a.timestamp === b.timestamp) { return true; } return false; } /** 构建历史 bucket 中已有消息的身份键集合 */ export function buildExistingMessageKeySet(messages: ChatMessage[]): Set { const keys = new Set(); for (const m of messages) { for (const k of getMessageKeys(m)) { keys.add(k); } } return keys; } /** live 消息是否已在历史中(按身份对账,而非仅 m.id) */ export function isLiveMessageInHistory( live: ChatMessage, historyMessages: ChatMessage[], ): boolean { return historyMessages.some((h) => isSameMessage(live, h)); } /** 过滤出尚未出现在历史中的 live 消息 */ export function filterPendingLiveMessages( liveMessages: ChatMessage[], historyMessages: ChatMessage[], ): ChatMessage[] { return liveMessages.filter((m) => !isLiveMessageInHistory(m, historyMessages)); } /** 用 live 流状态覆盖历史快照中的同一条消息 */ export function mergeLiveOverHistory( history: ChatMessage, live: ChatMessage, ): ChatMessage { if (!isSameMessage(history, live)) { return history; } return { ...history, ...live, id: history.id, serverId: live.serverId ?? history.serverId, clientTurnId: live.clientTurnId ?? history.clientTurnId, clientMessageId: live.clientMessageId ?? history.clientMessageId, phaseEvents: live.phaseEvents?.length ? live.phaseEvents : history.phaseEvents, livePhaseLabel: live.livePhaseLabel ?? history.livePhaseLabel, taskDurationMs: live.taskDurationMs ?? history.taskDurationMs, phaseSnapshot: live.phaseSnapshot ?? history.phaseSnapshot, }; }