import { Binary } from "@opencode-ai/util/binary" import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import type { FileDiff, Message, Part, PermissionRequest, Project, QuestionRequest, Session, SessionStatus, Todo, } from "@opencode-ai/sdk/v2/client" import type { State, VcsCache } from "./types" import { trimSessions } from "./session-trim" export function applyGlobalEvent(input: { event: { type: string; properties?: unknown } project: Project[] setGlobalProject: (next: Project[] | ((draft: Project[]) => void)) => void refresh: () => void }) { if (input.event.type === "global.disposed") { input.refresh() return } if (input.event.type !== "project.updated") return const properties = input.event.properties as Project const result = Binary.search(input.project, properties.id, (s) => s.id) if (result.found) { input.setGlobalProject((draft) => { draft[result.index] = { ...draft[result.index], ...properties } }) return } input.setGlobalProject((draft) => { draft.splice(result.index, 0, properties) }) } function cleanupSessionCaches(store: Store, setStore: SetStoreFunction, sessionID: string) { if (!sessionID) return const hasAny = store.message[sessionID] !== undefined || store.session_diff[sessionID] !== undefined || store.todo[sessionID] !== undefined || store.permission[sessionID] !== undefined || store.question[sessionID] !== undefined || store.session_status[sessionID] !== undefined if (!hasAny) return setStore( produce((draft) => { const messages = draft.message[sessionID] if (messages) { for (const message of messages) { const id = message?.id if (!id) continue delete draft.part[id] } } delete draft.message[sessionID] delete draft.session_diff[sessionID] delete draft.todo[sessionID] delete draft.permission[sessionID] delete draft.question[sessionID] delete draft.session_status[sessionID] }), ) } export function applyDirectoryEvent(input: { event: { type: string; properties?: unknown } store: Store setStore: SetStoreFunction push: (directory: string) => void directory: string loadLsp: () => void vcsCache?: VcsCache }) { const event = input.event switch (event.type) { case "server.instance.disposed": { input.push(input.directory) return } case "session.created": { const info = (event.properties as { info: Session }).info const result = Binary.search(input.store.session, info.id, (s) => s.id) if (result.found) { input.setStore("session", result.index, reconcile(info)) break } const next = input.store.session.slice() next.splice(result.index, 0, info) const trimmed = trimSessions(next, { limit: input.store.limit, permission: input.store.permission }) input.setStore("session", reconcile(trimmed, { key: "id" })) if (!info.parentID) input.setStore("sessionTotal", (value) => value + 1) break } case "session.updated": { const info = (event.properties as { info: Session }).info const result = Binary.search(input.store.session, info.id, (s) => s.id) if (info.time.archived) { if (result.found) { input.setStore( "session", produce((draft) => { draft.splice(result.index, 1) }), ) } cleanupSessionCaches(input.store, input.setStore, info.id) if (info.parentID) break input.setStore("sessionTotal", (value) => Math.max(0, value - 1)) break } if (result.found) { input.setStore("session", result.index, reconcile(info)) break } const next = input.store.session.slice() next.splice(result.index, 0, info) const trimmed = trimSessions(next, { limit: input.store.limit, permission: input.store.permission }) input.setStore("session", reconcile(trimmed, { key: "id" })) break } case "session.deleted": { const info = (event.properties as { info: Session }).info const result = Binary.search(input.store.session, info.id, (s) => s.id) if (result.found) { input.setStore( "session", produce((draft) => { draft.splice(result.index, 1) }), ) } cleanupSessionCaches(input.store, input.setStore, info.id) if (info.parentID) break input.setStore("sessionTotal", (value) => Math.max(0, value - 1)) break } case "session.diff": { const props = event.properties as { sessionID: string; diff: FileDiff[] } input.setStore("session_diff", props.sessionID, reconcile(props.diff, { key: "file" })) break } case "todo.updated": { const props = event.properties as { sessionID: string; todos: Todo[] } input.setStore("todo", props.sessionID, reconcile(props.todos, { key: "id" })) break } case "session.status": { const props = event.properties as { sessionID: string; status: SessionStatus } input.setStore("session_status", props.sessionID, reconcile(props.status)) break } case "message.updated": { const info = (event.properties as { info: Message }).info const messages = input.store.message[info.sessionID] if (!messages) { input.setStore("message", info.sessionID, [info]) break } const result = Binary.search(messages, info.id, (m) => m.id) if (result.found) { input.setStore("message", info.sessionID, result.index, reconcile(info)) break } input.setStore( "message", info.sessionID, produce((draft) => { draft.splice(result.index, 0, info) }), ) break } case "message.removed": { const props = event.properties as { sessionID: string; messageID: string } input.setStore( produce((draft) => { const messages = draft.message[props.sessionID] if (messages) { const result = Binary.search(messages, props.messageID, (m) => m.id) if (result.found) messages.splice(result.index, 1) } delete draft.part[props.messageID] }), ) break } case "message.part.updated": { const part = (event.properties as { part: Part }).part const parts = input.store.part[part.messageID] if (!parts) { input.setStore("part", part.messageID, [part]) break } const result = Binary.search(parts, part.id, (p) => p.id) if (result.found) { input.setStore("part", part.messageID, result.index, reconcile(part)) break } input.setStore( "part", part.messageID, produce((draft) => { draft.splice(result.index, 0, part) }), ) break } case "message.part.removed": { const props = event.properties as { messageID: string; partID: string } const parts = input.store.part[props.messageID] if (!parts) break const result = Binary.search(parts, props.partID, (p) => p.id) if (result.found) { input.setStore( produce((draft) => { const list = draft.part[props.messageID] if (!list) return const next = Binary.search(list, props.partID, (p) => p.id) if (!next.found) return list.splice(next.index, 1) if (list.length === 0) delete draft.part[props.messageID] }), ) } break } case "vcs.branch.updated": { const props = event.properties as { branch: string } const next = { branch: props.branch } input.setStore("vcs", next) if (input.vcsCache) input.vcsCache.setStore("value", next) break } case "permission.asked": { const permission = event.properties as PermissionRequest const permissions = input.store.permission[permission.sessionID] if (!permissions) { input.setStore("permission", permission.sessionID, [permission]) break } const result = Binary.search(permissions, permission.id, (p) => p.id) if (result.found) { input.setStore("permission", permission.sessionID, result.index, reconcile(permission)) break } input.setStore( "permission", permission.sessionID, produce((draft) => { draft.splice(result.index, 0, permission) }), ) break } case "permission.replied": { const props = event.properties as { sessionID: string; requestID: string } const permissions = input.store.permission[props.sessionID] if (!permissions) break const result = Binary.search(permissions, props.requestID, (p) => p.id) if (!result.found) break input.setStore( "permission", props.sessionID, produce((draft) => { draft.splice(result.index, 1) }), ) break } case "question.asked": { const question = event.properties as QuestionRequest const questions = input.store.question[question.sessionID] if (!questions) { input.setStore("question", question.sessionID, [question]) break } const result = Binary.search(questions, question.id, (q) => q.id) if (result.found) { input.setStore("question", question.sessionID, result.index, reconcile(question)) break } input.setStore( "question", question.sessionID, produce((draft) => { draft.splice(result.index, 0, question) }), ) break } case "question.replied": case "question.rejected": { const props = event.properties as { sessionID: string; requestID: string } const questions = input.store.question[props.sessionID] if (!questions) break const result = Binary.search(questions, props.requestID, (q) => q.id) if (!result.found) break input.setStore( "question", props.sessionID, produce((draft) => { draft.splice(result.index, 1) }), ) break } case "lsp.updated": { input.loadLsp() break } } }