summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-07 08:41:16 -0600
committerAdam <[email protected]>2026-01-08 17:48:15 -0600
commit374275eeb691006fa8f422d6267aa694ab38a992 (patch)
tree9159db2dabb0ba2e10e82b7efb3cd4fbabf024d0 /packages/app/src/context
parentfaa848cfb13df4e435dc236fe895032ea6f26dde (diff)
downloadopencode-374275eeb691006fa8f422d6267aa694ab38a992.tar.gz
opencode-374275eeb691006fa8f422d6267aa694ab38a992.zip
feat(app): chunk message loading, lazy load diffs
Diffstat (limited to 'packages/app/src/context')
-rw-r--r--packages/app/src/context/sync.tsx200
1 files changed, 144 insertions, 56 deletions
diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx
index a237871f9..7138c4ab4 100644
--- a/packages/app/src/context/sync.tsx
+++ b/packages/app/src/context/sync.tsx
@@ -1,5 +1,5 @@
import { batch, createMemo } from "solid-js"
-import { produce, reconcile } from "solid-js/store"
+import { createStore, produce, reconcile } from "solid-js/store"
import { Binary } from "@opencode-ai/util/binary"
import { retry } from "@opencode-ai/util/retry"
import { createSimpleContext } from "@opencode-ai/ui/context"
@@ -14,6 +14,60 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const sdk = useSDK()
const [store, setStore] = globalSync.child(sdk.directory)
const absolute = (path: string) => (store.path.directory + "/" + path).replace("//", "/")
+ const chunk = 200
+ const inflight = new Map<string, Promise<void>>()
+ const inflightDiff = new Map<string, Promise<void>>()
+ const inflightTodo = new Map<string, Promise<void>>()
+ const [meta, setMeta] = createStore({
+ limit: {} as Record<string, number>,
+ complete: {} as Record<string, boolean>,
+ loading: {} as Record<string, boolean>,
+ })
+
+ const getSession = (sessionID: string) => {
+ const match = Binary.search(store.session, sessionID, (s) => s.id)
+ if (match.found) return store.session[match.index]
+ return undefined
+ }
+
+ const loadMessages = async (sessionID: string, limit: number) => {
+ if (meta.loading[sessionID]) return
+
+ setMeta("loading", sessionID, true)
+ await retry(() => sdk.client.session.messages({ sessionID, limit }))
+ .then((messages) => {
+ const items = (messages.data ?? []).filter((x) => !!x?.info?.id)
+ const next = items
+ .map((x) => x.info)
+ .filter((m) => !!m?.id)
+ .slice()
+ .sort((a, b) => a.id.localeCompare(b.id))
+
+ batch(() => {
+ setStore("message", sessionID, reconcile(next, { key: "id" }))
+
+ for (const message of items) {
+ setStore(
+ "part",
+ message.info.id,
+ reconcile(
+ message.parts
+ .filter((p) => !!p?.id)
+ .slice()
+ .sort((a, b) => a.id.localeCompare(b.id)),
+ { key: "id" },
+ ),
+ )
+ }
+
+ setMeta("limit", sessionID, limit)
+ setMeta("complete", sessionID, next.length < limit)
+ })
+ })
+ .finally(() => {
+ setMeta("loading", sessionID, false)
+ })
+ }
return {
data: store,
@@ -30,11 +84,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
return undefined
},
session: {
- get(sessionID: string) {
- const match = Binary.search(store.session, sessionID, (s) => s.id)
- if (match.found) return store.session[match.index]
- return undefined
- },
+ get: getSession,
addOptimisticMessage(input: {
sessionID: string
messageID: string
@@ -66,58 +116,96 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}),
)
},
- async sync(sessionID: string, _isRetry = false) {
- const [session, messages, todo, diff] = await Promise.all([
- retry(() => sdk.client.session.get({ sessionID })),
- retry(() => sdk.client.session.messages({ sessionID, limit: 1000 })),
- retry(() => sdk.client.session.todo({ sessionID })),
- retry(() => sdk.client.session.diff({ sessionID })),
- ])
+ async sync(sessionID: string) {
+ const hasSession = getSession(sessionID) !== undefined
+ const hasMessages = store.message[sessionID] !== undefined && meta.limit[sessionID] !== undefined
+ if (hasSession && hasMessages) return
- batch(() => {
- setStore(
- "session",
- produce((draft) => {
- const match = Binary.search(draft, sessionID, (s) => s.id)
- if (match.found) {
- draft[match.index] = session.data!
- return
- }
- draft.splice(match.index, 0, session.data!)
- }),
- )
-
- setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
- setStore(
- "message",
- sessionID,
- reconcile(
- (messages.data ?? [])
- .map((x) => x.info)
- .filter((m) => !!m?.id)
- .slice()
- .sort((a, b) => a.id.localeCompare(b.id)),
- { key: "id" },
- ),
- )
-
- for (const message of messages.data ?? []) {
- if (!message?.info?.id) continue
- setStore(
- "part",
- message.info.id,
- reconcile(
- message.parts
- .filter((p) => !!p?.id)
- .slice()
- .sort((a, b) => a.id.localeCompare(b.id)),
- { key: "id" },
- ),
- )
- }
+ const pending = inflight.get(sessionID)
+ if (pending) return pending
- setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" }))
- })
+ const limit = meta.limit[sessionID] ?? chunk
+
+ const sessionReq = hasSession
+ ? Promise.resolve()
+ : retry(() => sdk.client.session.get({ sessionID })).then((session) => {
+ const data = session.data
+ if (!data) return
+ setStore(
+ "session",
+ produce((draft) => {
+ const match = Binary.search(draft, sessionID, (s) => s.id)
+ if (match.found) {
+ draft[match.index] = data
+ return
+ }
+ draft.splice(match.index, 0, data)
+ }),
+ )
+ })
+
+ const messagesReq = hasMessages ? Promise.resolve() : loadMessages(sessionID, limit)
+
+ const promise = Promise.all([sessionReq, messagesReq])
+ .then(() => {})
+ .finally(() => {
+ inflight.delete(sessionID)
+ })
+
+ inflight.set(sessionID, promise)
+ return promise
+ },
+ async diff(sessionID: string) {
+ if (store.session_diff[sessionID] !== undefined) return
+
+ const pending = inflightDiff.get(sessionID)
+ if (pending) return pending
+
+ const promise = retry(() => sdk.client.session.diff({ sessionID }))
+ .then((diff) => {
+ setStore("session_diff", sessionID, reconcile(diff.data ?? [], { key: "file" }))
+ })
+ .finally(() => {
+ inflightDiff.delete(sessionID)
+ })
+
+ inflightDiff.set(sessionID, promise)
+ return promise
+ },
+ async todo(sessionID: string) {
+ if (store.todo[sessionID] !== undefined) return
+
+ const pending = inflightTodo.get(sessionID)
+ if (pending) return pending
+
+ const promise = retry(() => sdk.client.session.todo({ sessionID }))
+ .then((todo) => {
+ setStore("todo", sessionID, reconcile(todo.data ?? [], { key: "id" }))
+ })
+ .finally(() => {
+ inflightTodo.delete(sessionID)
+ })
+
+ inflightTodo.set(sessionID, promise)
+ return promise
+ },
+ history: {
+ more(sessionID: string) {
+ if (store.message[sessionID] === undefined) return false
+ if (meta.limit[sessionID] === undefined) return false
+ if (meta.complete[sessionID]) return false
+ return true
+ },
+ loading(sessionID: string) {
+ return meta.loading[sessionID] ?? false
+ },
+ async loadMore(sessionID: string, count = chunk) {
+ if (meta.loading[sessionID]) return
+ if (meta.complete[sessionID]) return
+
+ const current = meta.limit[sessionID] ?? chunk
+ await loadMessages(sessionID, current + count)
+ },
},
fetch: async (count = 10) => {
setStore("limit", (x) => x + count)