summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src/context/sync.tsx
blob: 38058c370816b4d2d33a985ac8494946c215e400 (plain)
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import type { Part } from "@opencode-ai/sdk"
import { produce } from "solid-js/store"
import { createMemo } from "solid-js"
import { Binary } from "@opencode-ai/util/binary"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSync } from "./global-sync"
import { useSDK } from "./sdk"

export const { use: useSync, provider: SyncProvider } = createSimpleContext({
  name: "Sync",
  init: () => {
    const globalSync = useGlobalSync()
    const sdk = useSDK()
    const [store, setStore] = globalSync.child(sdk.directory)

    const load = {
      project: () => sdk.client.project.current().then((x) => setStore("project", x.data!)),
      provider: () => sdk.client.config.providers().then((x) => setStore("provider", x.data!.providers)),
      path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)),
      agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
      session: () =>
        sdk.client.session.list().then((x) => {
          const sessions = (x.data ?? [])
            .slice()
            .sort((a, b) => a.id.localeCompare(b.id))
            .slice(0, store.limit)
          setStore("session", sessions)
        }),
      status: () => sdk.client.session.status().then((x) => setStore("session_status", x.data!)),
      config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)),
      changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)),
      node: () => sdk.client.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("//", "/")
    const sanitizePart = (part: Part) => {
      if (part.type === "tool") {
        if (part.state.status === "completed" || part.state.status === "error") {
          for (const key in part.state.metadata) {
            if (typeof part.state.metadata[key] === "string") {
              part.state.metadata[key] = sanitize(part.state.metadata[key] as string)
            }
          }
          for (const key in part.state.input) {
            if (typeof part.state.input[key] === "string") {
              part.state.input[key] = sanitize(part.state.input[key] as string)
            }
          }
          if ("error" in part.state) {
            part.state.error = sanitize(part.state.error as string)
          }
        }
      }
      return part
    }

    return {
      data: store,
      set: setStore,
      get ready() {
        return store.ready
      },
      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, _isRetry = false) {
          const [session, messages, todo, diff] = await Promise.all([
            sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }),
            sdk.client.session.messages({ path: { id: sessionID }, query: { limit: 100 } }),
            sdk.client.session.todo({ path: { id: sessionID } }),
            sdk.client.session.diff({ path: { id: sessionID } }),
          ])
          setStore(
            produce((draft) => {
              const match = Binary.search(draft.session, sessionID, (s) => s.id)
              if (match.found) draft.session[match.index] = session.data!
              if (!match.found) draft.session.splice(match.index, 0, session.data!)
              draft.todo[sessionID] = todo.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()
                  .map(sanitizePart)
                  .sort((a, b) => a.id.localeCompare(b.id))
              }
              draft.session_diff[sessionID] = diff.data ?? []
            }),
          )
        },
        fetch: async (count = 10) => {
          setStore("limit", (x) => x + count)
          await load.session()
        },
        more: createMemo(() => store.session.length >= store.limit),
      },
      load,
      absolute,
      sanitize,
    }
  },
})