Progress lived only in the open SSE stream, and interrupted history was shown as completed so the card could not be continued. Co-authored-by: Cursor <cursoragent@cursor.com>
88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
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<string> {
|
|
const keys = new Set<string>();
|
|
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,
|
|
};
|
|
}
|