From 9554abb56ef2998d13a31adc6ec85cd92cc71bf6 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 7 Nov 2025 01:11:47 -0500 Subject: [PATCH] message storage performance improvements --- .../cli/cmd/tui/component/prompt/index.tsx | 1 - .../opencode/src/cli/cmd/tui/context/sync.tsx | 1 + packages/opencode/src/server/server.ts | 2 +- packages/opencode/src/session/compaction.ts | 4 +--- packages/opencode/src/session/index.ts | 24 ++++++++++++------- packages/opencode/src/session/message-v2.ts | 12 ++++++---- packages/opencode/src/session/prompt.ts | 4 +--- packages/opencode/src/session/summary.ts | 12 +--------- 8 files changed, 28 insertions(+), 32 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index c977f731..b9e40659 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -3,7 +3,6 @@ import { BoxRenderable, TextareaRenderable, MouseEvent, - KeyEvent, PasteEvent, t, dim, diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 3fe1a4f5..5c8d3146 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -270,6 +270,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }, async sync(sessionID: string) { const now = Date.now() + if (store.message[sessionID]) return console.log("syncing", sessionID) const [session, messages, todo, diff] = await Promise.all([ sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }), diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index c7265006..bb9f065e 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -756,7 +756,7 @@ export namespace Server { validator( "query", z.object({ - limit: z.coerce.number().optional(), + limit: z.coerce.number(), }), ), async (c) => { diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 115628cc..4924a35d 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -111,9 +111,7 @@ export namespace SessionCompaction { draft.time.compacting = undefined }) }) - const toSummarize = await Session.messages({ sessionID: input.sessionID }).then( - MessageV2.filterCompacted, - ) + const toSummarize = await MessageV2.filterCompacted(Session.messageStream(input.sessionID)) const model = await Provider.getModel(input.providerID, input.modelID) const system = [ ...SystemPrompt.summarize(model.providerID), diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index dc04f183..0971531f 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -273,6 +273,17 @@ export namespace Session { return diffs ?? [] }) + export const messageStream = fn(Identifier.schema("session"), async function* (sessionID) { + const list = await Array.fromAsync(await Storage.list(["message", sessionID])) + for (let i = list.length - 1; i >= 0; i--) { + const read = await Storage.read(list[i]) + yield { + info: read, + parts: await getParts(read.id), + } + } + }) + export const messages = fn( z.object({ sessionID: Identifier.schema("session"), @@ -280,16 +291,11 @@ export namespace Session { }), async (input) => { const result = [] as MessageV2.WithParts[] - const list = (await Array.fromAsync(await Storage.list(["message", input.sessionID]))) - .toSorted((a, b) => a.at(-1)!.localeCompare(b.at(-1)!)) - .slice(-1 * (input.limit ?? 1_000_000)) - for (const p of list) { - const read = await Storage.read(p) - result.push({ - info: read, - parts: await getParts(read.id), - }) + for await (const msg of messageStream(input.sessionID)) { + if (input.limit && result.length >= input.limit) break + result.push(msg) } + result.reverse() return result }, ) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index f35735b7..66d29319 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -655,10 +655,14 @@ export namespace MessageV2 { return convertToModelMessages(result) } - export function filterCompacted(msgs: { info: MessageV2.Info; parts: MessageV2.Part[] }[]) { - const i = msgs.findLastIndex((m) => m.info.role === "assistant" && !!m.info.summary) - if (i === -1) return msgs.slice() - return msgs.slice(i) + export async function filterCompacted(stream: AsyncIterable) { + const result = [] as MessageV2.WithParts[] + for await (const msg of stream) { + result.push(msg) + if (msg.info.role === "assistant" && msg.info.summary === true) break + } + result.reverse() + return result } export function fromError(e: unknown, ctx: { providerID: string }) { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 06b06513..cf11b129 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -434,9 +434,7 @@ export namespace SessionPrompt { providerID: string signal: AbortSignal }) { - let msgs = await Session.messages({ sessionID: input.sessionID }).then( - MessageV2.filterCompacted, - ) + let msgs = await MessageV2.filterCompacted(Session.messageStream(input.sessionID)) const lastAssistant = msgs.findLast((msg) => msg.info.role === "assistant") if ( lastAssistant?.info.role === "assistant" && diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 8783a36e..de3e22bd 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -151,17 +151,7 @@ export namespace SessionSummary { messageID: Identifier.schema("message").optional(), }), async (input) => { - let all = await Session.messages({ sessionID: input.sessionID }) - if (input.messageID) - all = all.filter( - (x) => - x.info.id === input.messageID || - (x.info.role === "assistant" && x.info.parentID === input.messageID), - ) - - return computeDiff({ - messages: all, - }) + return Storage.read(["session_diff", input.sessionID]) ?? [] }, )