import type { Message, Agent, Provider, Session, Part, Config, Path, File, FileNode } from "@opencode-ai/sdk" import { createStore, produce, reconcile } from "solid-js/store" import { createContext, createMemo, Show, useContext, type ParentProps } from "solid-js" import { useSDK, useEvent } from "@/context" import { Binary } from "@/utils/binary" function init() { const [store, setStore] = createStore<{ ready: boolean provider: Provider[] agent: Agent[] config: Config path: Path session: Session[] message: { [sessionID: string]: Message[] } part: { [messageID: string]: Part[] } node: FileNode[] changes: File[] }>({ config: {}, path: { state: "", config: "", worktree: "", directory: "" }, ready: false, agent: [], provider: [], session: [], message: {}, part: {}, node: [], changes: [], }) const bus = useEvent() bus.listen((event) => { switch (event.type) { case "session.updated": { const result = Binary.search(store.session, event.properties.info.id, (s) => s.id) if (result.found) { setStore("session", result.index, reconcile(event.properties.info)) break } setStore( "session", produce((draft) => { draft.splice(result.index, 0, event.properties.info) }), ) break } case "message.updated": { const messages = store.message[event.properties.info.sessionID] if (!messages) { setStore("message", event.properties.info.sessionID, [event.properties.info]) break } const result = Binary.search(messages, event.properties.info.id, (m) => m.id) if (result.found) { setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info)) break } setStore( "message", event.properties.info.sessionID, produce((draft) => { draft.splice(result.index, 0, event.properties.info) }), ) break } case "message.part.updated": { const parts = store.part[event.properties.part.messageID] if (!parts) { setStore("part", event.properties.part.messageID, [event.properties.part]) break } const result = Binary.search(parts, event.properties.part.id, (p) => p.id) if (result.found) { setStore("part", event.properties.part.messageID, result.index, reconcile(event.properties.part)) break } setStore( "part", event.properties.part.messageID, produce((draft) => { draft.splice(result.index, 0, event.properties.part) }), ) break } } }) const sdk = useSDK() const load = { provider: () => sdk.config.providers().then((x) => setStore("provider", x.data!.providers)), path: () => sdk.path.get().then((x) => setStore("path", x.data!)), agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])), session: () => sdk.session.list().then((x) => setStore( "session", (x.data ?? []).slice().sort((a, b) => a.id.localeCompare(b.id)), ), ), config: () => sdk.config.get().then((x) => setStore("config", x.data!)), changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)), node: () => sdk.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)), } Promise.all(Object.values(load).map((p) => p())).then(() => setStore("ready", true)) const sanitizer = createMemo(() => new RegExp(`${store.path.directory}/`, "g")) const sanitize = (text: string) => text.replace(sanitizer(), "") const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/") return { data: store, set: setStore, session: { get(sessionID: string) { const match = Binary.search(store.session, sessionID, (s) => s.id) if (match.found) return store.session[match.index] return undefined }, async sync(sessionID: string) { const [session, messages] = await Promise.all([ sdk.session.get({ path: { id: sessionID } }), sdk.session.messages({ path: { id: sessionID } }), ]) setStore( produce((draft) => { const match = Binary.search(draft.session, sessionID, (s) => s.id) draft.session[match.index] = session.data! draft.message[sessionID] = messages .data!.map((x) => x.info) .slice() .sort((a, b) => a.id.localeCompare(b.id)) for (const message of messages.data!) { draft.part[message.info.id] = message.parts.slice().sort((a, b) => a.id.localeCompare(b.id)) } }), ) }, }, load, absolute, sanitize, } } type SyncContext = ReturnType const ctx = createContext() export function SyncProvider(props: ParentProps) { const value = init() return ( {props.children} ) } export function useSync() { const value = useContext(ctx) if (!value) { throw new Error("useSync must be used within a SyncProvider") } return value }