sync.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { batch, createMemo } from "solid-js"
  2. import { produce, reconcile } from "solid-js/store"
  3. import { Binary } from "@opencode-ai/util/binary"
  4. import { retry } from "@opencode-ai/util/retry"
  5. import { createSimpleContext } from "@opencode-ai/ui/context"
  6. import { useGlobalSync } from "./global-sync"
  7. import { useSDK } from "./sdk"
  8. import type { Message, Part } from "@opencode-ai/sdk/v2/client"
  9. export const { use: useSync, provider: SyncProvider } = createSimpleContext({
  10. name: "Sync",
  11. init: () => {
  12. const globalSync = useGlobalSync()
  13. const sdk = useSDK()
  14. const [store, setStore] = globalSync.child(sdk.directory)
  15. const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
  16. return {
  17. data: store,
  18. set: setStore,
  19. get status() {
  20. return store.status
  21. },
  22. get ready() {
  23. return store.status !== "loading"
  24. },
  25. get project() {
  26. const match = Binary.search(globalSync.data.project, store.project, (p) => p.id)
  27. if (match.found) return globalSync.data.project[match.index]
  28. return undefined
  29. },
  30. session: {
  31. get(sessionID: string) {
  32. const match = Binary.search(store.session, sessionID, (s) => s.id)
  33. if (match.found) return store.session[match.index]
  34. return undefined
  35. },
  36. addOptimisticMessage(input: {
  37. sessionID: string
  38. messageID: string
  39. parts: Part[]
  40. agent: string
  41. model: { providerID: string; modelID: string }
  42. }) {
  43. const message: Message = {
  44. id: input.messageID,
  45. sessionID: input.sessionID,
  46. role: "user",
  47. time: { created: Date.now() },
  48. agent: input.agent,
  49. model: input.model,
  50. }
  51. setStore(
  52. produce((draft) => {
  53. const messages = draft.message[input.sessionID]
  54. if (!messages) {
  55. draft.message[input.sessionID] = [message]
  56. } else {
  57. const result = Binary.search(messages, input.messageID, (m) => m.id)
  58. messages.splice(result.index, 0, message)
  59. }
  60. draft.part[input.messageID] = input.parts
  61. .filter((p) => !!p?.id)
  62. .slice()
  63. .sort((a, b) => a.id.localeCompare(b.id))
  64. }),
  65. )
  66. },
  67. async sync(sessionID: string, _isRetry = false) {
  68. const [session, messages, todo, diff] = await Promise.all([
  69. retry(() => sdk.client.session.get({ sessionID })),
  70. retry(() => sdk.client.session.messages({ sessionID, limit: 1000 })),
  71. retry(() => sdk.client.session.todo({ sessionID })),
  72. retry(() => sdk.client.session.diff({ sessionID })),
  73. ])
  74. batch(() => {
  75. setStore(
  76. "session",
  77. produce((draft) => {
  78. const match = Binary.search(draft, sessionID, (s) => s.id)
  79. if (match.found) {
  80. draft[match.index] = session.data!
  81. return
  82. }
  83. draft.splice(match.index, 0, session.data!)
  84. }),
  85. )
  86. setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
  87. setStore(
  88. "message",
  89. sessionID,
  90. reconcile(
  91. (messages.data ?? [])
  92. .map((x) => x.info)
  93. .filter((m) => !!m?.id)
  94. .slice()
  95. .sort((a, b) => a.id.localeCompare(b.id)),
  96. { key: "id" },
  97. ),
  98. )
  99. for (const message of messages.data ?? []) {
  100. if (!message?.info?.id) continue
  101. setStore(
  102. "part",
  103. message.info.id,
  104. reconcile(
  105. message.parts
  106. .filter((p) => !!p?.id)
  107. .slice()
  108. .sort((a, b) => a.id.localeCompare(b.id)),
  109. { key: "id" },
  110. ),
  111. )
  112. }
  113. setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" }))
  114. })
  115. },
  116. fetch: async (count = 10) => {
  117. setStore("limit", (x) => x + count)
  118. await sdk.client.session.list().then((x) => {
  119. const sessions = (x.data ?? [])
  120. .filter((s) => !!s?.id)
  121. .slice()
  122. .sort((a, b) => a.id.localeCompare(b.id))
  123. .slice(0, store.limit)
  124. setStore("session", reconcile(sessions, { key: "id" }))
  125. })
  126. },
  127. more: createMemo(() => store.session.length >= store.limit),
  128. archive: async (sessionID: string) => {
  129. await sdk.client.session.update({ sessionID, time: { archived: Date.now() } })
  130. setStore(
  131. produce((draft) => {
  132. const match = Binary.search(draft.session, sessionID, (s) => s.id)
  133. if (match.found) draft.session.splice(match.index, 1)
  134. }),
  135. )
  136. },
  137. },
  138. absolute,
  139. get directory() {
  140. return store.path.directory
  141. },
  142. }
  143. },
  144. })