| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- import { Binary } from "@opencode-ai/core/util/binary"
- import type { AppMessage, AppPart, AppSession } from "./backend"
- import { createMemo } from "solid-js"
- import { createStore, produce, reconcile, type SetStoreFunction } from "solid-js/store"
- import type { createServerSdkContext } from "./server-sdk"
- import type { createServerSyncContextInner } from "./server-sync"
- import type { State } from "./global-sync/types"
- const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
- const sessionFields = new Set([
- "session_status",
- "session_working",
- "session_diff",
- "permission",
- "question",
- "message",
- "part",
- "part_text_accum_delta",
- ])
- export const createDirSyncContext = (
- directory: string,
- serverSync: ReturnType<typeof createServerSyncContextInner>,
- serverSDK: ReturnType<typeof createServerSdkContext>,
- ) => {
- const current = createMemo(() => serverSync.child(directory, { mcp: true }))
- const [sessionPage, setSessionPage] = createStore({ cursor: undefined as string | undefined, complete: false })
- const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
- const data = new Proxy({} as State, {
- get(_, property: keyof State) {
- if (property === "session_working") return serverSync.session.data.session_working.bind(serverSync.session.data)
- if (sessionFields.has(property)) return serverSync.session.data[property as keyof typeof serverSync.session.data]
- return current()[0][property]
- },
- })
- const set = ((...input: unknown[]) => {
- if (typeof input[0] === "string" && sessionFields.has(input[0])) {
- return (serverSync.session.set as (...args: unknown[]) => unknown)(...input)
- }
- const result = (current()[1] as (...args: unknown[]) => unknown)(...input)
- if (input[0] === "session") current()[0].session.forEach(serverSync.session.remember)
- return result
- }) as SetStoreFunction<State>
- const index = (sessionID: string) => {
- const session = serverSync.session.get(sessionID)
- if (!session || session.directory !== directory) return
- const [store, setStore] = current()
- const result = Binary.search(store.session, session.id, (item) => item.id)
- if (result.found) {
- setStore("session", result.index, reconcile(session))
- return
- }
- setStore(
- "session",
- produce((draft) => void draft.splice(result.index, 0, session)),
- )
- }
- return {
- data,
- set,
- get status() {
- return current()[0].status
- },
- get ready() {
- return current()[0].status !== "loading"
- },
- get project() {
- const store = current()[0]
- const match = Binary.search(serverSync.data.project, store.project, (project) => project.id)
- if (match.found) return serverSync.data.project[match.index]
- },
- session: {
- remember(session: AppSession) {
- serverSync.session.remember(session)
- index(session.id)
- },
- get(sessionID: string) {
- const session = serverSync.session.get(sessionID)
- if (session?.directory === directory) return session
- },
- optimistic: {
- add(input: { directory?: string; sessionID: string; message: AppMessage; parts: AppPart[] }) {
- serverSync.session.optimistic.add(input)
- },
- remove(input: { directory?: string; sessionID: string; messageID: string }) {
- serverSync.session.optimistic.remove(input)
- },
- },
- addOptimisticMessage(input: {
- sessionID: string
- messageID: string
- parts: AppPart[]
- agent: string
- model: { providerID: string; modelID: string }
- variant?: string
- }) {
- serverSync.session.optimistic.add({
- sessionID: input.sessionID,
- message: {
- id: input.messageID,
- sessionID: input.sessionID,
- role: "user",
- time: { created: Date.now() },
- agent: input.agent,
- model: { ...input.model, variant: input.variant },
- },
- parts: input.parts,
- })
- },
- async sync(sessionID: string, options?: { force?: boolean }) {
- await serverSync.session.sync(sessionID, { ...options, location: { directory } })
- index(sessionID)
- },
- diff: serverSync.session.diff,
- history: serverSync.session.history,
- evict(sessionID: string) {
- serverSync.session.evict(sessionID)
- },
- fetch: async (count = 10) => {
- const [store, setStore] = current()
- setStore("limit", (value) => value + count)
- const backend = await serverSDK.backend
- const response = await backend.common.sessions.list({
- location: { directory },
- roots: true,
- limit: count,
- cursor: sessionPage.cursor,
- })
- const sessions = [...new Map([...store.session, ...response.items].map((session) => [session.id, session])).values()]
- .sort((a, b) => cmp(a.id, b.id))
- sessions.forEach(serverSync.session.remember)
- setStore("session", reconcile(sessions, { key: "id" }))
- setSessionPage({ cursor: response.older, complete: !response.older })
- },
- more: createMemo(() => !sessionPage.complete),
- archive: async (sessionID: string) => {
- const backend = await serverSDK.backend
- const capability = backend.capabilities.sessionExtrasV1
- if (!capability) throw new Error("Server does not support session archiving")
- await capability.archive({ sessionID, archivedAt: Date.now(), location: { directory } })
- current()[1](
- "session",
- produce((draft) => {
- const match = Binary.search(draft, sessionID, (session) => session.id)
- if (match.found) draft.splice(match.index, 1)
- }),
- )
- },
- },
- mcp: {
- toggle: (name: string) => serverSync.mcp.toggle(directory, name),
- },
- absolute,
- get directory() {
- return current()[0].path.directory
- },
- }
- }
|