mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-24 13:25:01 +00:00
117 lines
3.2 KiB
TypeScript
117 lines
3.2 KiB
TypeScript
import { Binary } from "@opencode-ai/core/util/binary"
|
|
import { useGlobalSync } from "./global-sync"
|
|
import { useSDK } from "./sdk"
|
|
import type { Message, Part } from "@opencode-ai/sdk/v2/client"
|
|
|
|
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
|
|
|
|
function sortParts(parts: Part[]) {
|
|
return parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id))
|
|
}
|
|
|
|
const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
|
|
|
type OptimisticStore = {
|
|
message: Record<string, Message[] | undefined>
|
|
part: Record<string, Part[] | undefined>
|
|
}
|
|
|
|
type OptimisticAddInput = {
|
|
sessionID: string
|
|
message: Message
|
|
parts: Part[]
|
|
}
|
|
|
|
type OptimisticRemoveInput = {
|
|
sessionID: string
|
|
messageID: string
|
|
}
|
|
|
|
type OptimisticItem = {
|
|
message: Message
|
|
parts: Part[]
|
|
}
|
|
|
|
type MessagePage = {
|
|
session: Message[]
|
|
part: { id: string; part: Part[] }[]
|
|
cursor?: string
|
|
complete: boolean
|
|
}
|
|
|
|
const hasParts = (parts: Part[] | undefined, want: Part[]) => {
|
|
if (!parts) return want.length === 0
|
|
return want.every((part) => Binary.search(parts, part.id, (item) => item.id).found)
|
|
}
|
|
|
|
const mergeParts = (parts: Part[] | undefined, want: Part[]) => {
|
|
if (!parts) return sortParts(want)
|
|
const next = [...parts]
|
|
let changed = false
|
|
for (const part of want) {
|
|
const result = Binary.search(next, part.id, (item) => item.id)
|
|
if (result.found) continue
|
|
next.splice(result.index, 0, part)
|
|
changed = true
|
|
}
|
|
if (!changed) return parts
|
|
return next
|
|
}
|
|
|
|
export function mergeOptimisticPage(page: MessagePage, items: OptimisticItem[]) {
|
|
if (items.length === 0) return { ...page, confirmed: [] as string[] }
|
|
|
|
const session = [...page.session]
|
|
const part = new Map(page.part.map((item) => [item.id, sortParts(item.part)]))
|
|
const confirmed: string[] = []
|
|
|
|
for (const item of items) {
|
|
const result = Binary.search(session, item.message.id, (message) => message.id)
|
|
const found = result.found
|
|
if (!found) session.splice(result.index, 0, item.message)
|
|
|
|
const current = part.get(item.message.id)
|
|
if (found && hasParts(current, item.parts)) {
|
|
confirmed.push(item.message.id)
|
|
continue
|
|
}
|
|
|
|
part.set(item.message.id, mergeParts(current, item.parts))
|
|
}
|
|
|
|
return {
|
|
cursor: page.cursor,
|
|
complete: page.complete,
|
|
session,
|
|
part: [...part.entries()].sort((a, b) => cmp(a[0], b[0])).map(([id, part]) => ({ id, part })),
|
|
confirmed,
|
|
}
|
|
}
|
|
|
|
export function applyOptimisticAdd(draft: OptimisticStore, input: OptimisticAddInput) {
|
|
const messages = draft.message[input.sessionID]
|
|
if (messages) {
|
|
const result = Binary.search(messages, input.message.id, (m) => m.id)
|
|
messages.splice(result.index, 0, input.message)
|
|
} else {
|
|
draft.message[input.sessionID] = [input.message]
|
|
}
|
|
draft.part[input.message.id] = sortParts(input.parts)
|
|
}
|
|
|
|
export function applyOptimisticRemove(draft: OptimisticStore, input: OptimisticRemoveInput) {
|
|
const messages = draft.message[input.sessionID]
|
|
if (messages) {
|
|
const result = Binary.search(messages, input.messageID, (m) => m.id)
|
|
if (result.found) messages.splice(result.index, 1)
|
|
}
|
|
delete draft.part[input.messageID]
|
|
}
|
|
|
|
export const useSync = () => {
|
|
const globalSync = useGlobalSync()
|
|
const sdk = useSDK()
|
|
|
|
return globalSync.createDirSyncContext(sdk.directory)
|
|
}
|