-
Notifications
You must be signed in to change notification settings - Fork 17.8k
Expand file tree
/
Copy pathrevert-page.ts
More file actions
77 lines (67 loc) · 3.08 KB
/
revert-page.ts
File metadata and controls
77 lines (67 loc) · 3.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
type MessagePage = {
session: Message[]
part: { id: string; part: Part[] }[]
cursor?: string
complete: boolean
clearedRevert?: boolean
}
type MessageWithParts = {
info: Message
parts: Part[]
cursor?: string
}
const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
export const compareMessages = (a: Message, b: Message) => a.time.created - b.time.created || cmp(a.id, b.id)
export const messageBefore = (message: Message, boundary: Message) => compareMessages(message, boundary) < 0
const sortParts = (parts: Part[]) => parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id))
export function hasVisibleUserBeforeRevert(messages: Message[], revertMessageID?: string, boundary?: Message) {
if (!revertMessageID) return true
const revert = boundary ?? messages.find((message) => message.id === revertMessageID)
if (!revert) return messages.some((message) => message.role === "user" && message.id < revertMessageID)
return messages.some((message) => message.role === "user" && messageBefore(message, revert))
}
function mergeMessages(current: Message[], older: Message[], boundary: Message) {
const merged = new Map(current.filter((message) => !!message?.id).map((message) => [message.id, message] as const))
for (const message of older) {
if (!message?.id) continue
merged.set(message.id, message)
}
merged.set(boundary.id, boundary)
return [...merged.values()].sort(compareMessages)
}
function mergeParts(current: MessagePage["part"], older: MessagePage["part"], boundary: MessageWithParts) {
const merged = new Map(current.filter((item) => !!item?.id).map((item) => [item.id, sortParts(item.part)] as const))
for (const item of older) {
if (!item?.id) continue
merged.set(item.id, sortParts(item.part))
}
merged.set(boundary.info.id, sortParts(boundary.parts))
return [...merged.entries()].sort((a, b) => cmp(a[0], b[0])).map(([id, part]) => ({ id, part }))
}
export async function loadRevertAwareLatestPage(input: {
current: MessagePage
revertMessageID?: string
fetchMessage: (messageID: string) => Promise<MessageWithParts | undefined>
fetchPage: (before: string) => Promise<MessagePage>
}) {
if (!input.revertMessageID) return input.current
const boundary = await input.fetchMessage(input.revertMessageID)
if (!boundary) return { ...input.current, clearedRevert: true }
if (hasVisibleUserBeforeRevert(input.current.session, input.revertMessageID, boundary.info)) return input.current
if (!boundary.cursor) return input.current
let older = await input.fetchPage(boundary.cursor)
let session = mergeMessages(input.current.session, older.session, boundary.info)
let part = mergeParts(input.current.part, older.part, boundary)
while (!hasVisibleUserBeforeRevert(session, input.revertMessageID) && older.cursor) {
older = await input.fetchPage(older.cursor)
session = mergeMessages(session, older.session, boundary.info)
part = mergeParts(part, older.part, boundary)
}
return {
session,
part,
cursor: older.cursor,
complete: older.complete,
}
}