perf: optimize message loading based on streaming state

Decreased message limit to 90 for historical views when not streaming.
Add fetch buffer to preload additional messages for efficient syncing.
This commit is contained in:
Bohdan Triapitsyn
2026-01-17 02:32:22 +02:00
parent 195355ae8b
commit 8a3e432dd0
4 changed files with 31 additions and 21 deletions
+10 -6
View File
@@ -1,6 +1,7 @@
import React from 'react';
import type { AssistantMessage, Message, Part } from '@opencode-ai/sdk/v2';
import { useSessionStore, MEMORY_LIMITS } from '@/stores/useSessionStore';
import { useSessionStore } from '@/stores/useSessionStore';
import { MEMORY_LIMITS } from '@/stores/types/sessionTypes';
import { opencodeClient } from '@/lib/opencode/client';
import { readSessionCursor } from '@/lib/messageCursorPersistence';
import { extractTextFromPart } from '@/stores/utils/messageUtils';
@@ -124,7 +125,10 @@ export const useMessageSync = () => {
const currentMessages = (messages.get(currentSessionId) || []) as SessionMessageRecord[];
const latestMessages = (await opencodeClient.getSessionMessages(currentSessionId)) as SessionMessageRecord[];
const memoryState = useSessionStore.getState().sessionMemoryState.get(currentSessionId);
const targetLimit = memoryState?.isStreaming ? MEMORY_LIMITS.VIEWPORT_MESSAGES : MEMORY_LIMITS.HISTORICAL_MESSAGES;
const fetchLimit = targetLimit + MEMORY_LIMITS.FETCH_BUFFER;
const latestMessages = (await opencodeClient.getSessionMessages(currentSessionId, fetchLimit)) as SessionMessageRecord[];
const cursorRecord = await readSessionCursor(currentSessionId);
if (!latestMessages) return;
@@ -167,7 +171,7 @@ export const useMessageSync = () => {
} else {
if (isUserMessageInfo(lastLocalMessage.info)) {
const messagesToLoad = latestMessages.slice(-MEMORY_LIMITS.VIEWPORT_MESSAGES);
const messagesToLoad = latestMessages.slice(-targetLimit);
console.log('[SYNC] Local user message missing by ID; merging latest messages for deduplication');
const { syncMessages } = useSessionStore.getState();
syncMessages(currentSessionId, messagesToLoad);
@@ -181,7 +185,7 @@ export const useMessageSync = () => {
if (cursorIndex !== -1) {
if (cursorIndex < latestMessages.length - 1) {
const newMessages = latestMessages.slice(cursorIndex + 1);
const limited = newMessages.slice(-MEMORY_LIMITS.VIEWPORT_MESSAGES);
const limited = newMessages.slice(-targetLimit);
if (limited.length > 0) {
console.log(`[SYNC] Restoring ${limited.length} messages after cursor`);
const { syncMessages } = useSessionStore.getState();
@@ -190,13 +194,13 @@ export const useMessageSync = () => {
}
} else if (latestMessages.length > 0) {
console.log('[SYNC] Cursor not found on server response, loading recent messages');
const messagesToLoad = latestMessages.slice(-MEMORY_LIMITS.VIEWPORT_MESSAGES);
const messagesToLoad = latestMessages.slice(-targetLimit);
const { syncMessages } = useSessionStore.getState();
syncMessages(currentSessionId, messagesToLoad);
}
} else if (latestMessages.length > 0) {
const messagesToLoad = latestMessages.slice(-MEMORY_LIMITS.VIEWPORT_MESSAGES);
const messagesToLoad = latestMessages.slice(-targetLimit);
console.log(`[SYNC] Loading last ${messagesToLoad.length} messages`);
const { syncMessages } = useSessionStore.getState();
syncMessages(currentSessionId, messagesToLoad);
+3 -2
View File
@@ -354,10 +354,11 @@ class OpencodeService {
return response.data;
}
async getSessionMessages(id: string): Promise<{ info: Message; parts: Part[] }[]> {
async getSessionMessages(id: string, limit?: number): Promise<{ info: Message; parts: Part[] }[]> {
const response = await this.client.session.messages({
sessionID: id,
...(this.currentDirectory ? { directory: this.currentDirectory } : {})
...(this.currentDirectory ? { directory: this.currentDirectory } : {}),
...(typeof limit === 'number' ? { limit } : {}),
});
return response.data || [];
}
+16 -13
View File
@@ -385,8 +385,11 @@ export const useMessageStore = create<MessageStore>()(
pendingAssistantHeaderSessions: new Set(),
pendingUserMessageMetaBySession: new Map(),
loadMessages: async (sessionId: string, limit: number = MEMORY_LIMITS.VIEWPORT_MESSAGES) => {
const allMessages = await executeWithSessionDirectory(sessionId, () => opencodeClient.getSessionMessages(sessionId));
loadMessages: async (sessionId: string, limit: number = MEMORY_LIMITS.HISTORICAL_MESSAGES) => {
const isStreaming = get().sessionMemoryState.get(sessionId)?.isStreaming;
const targetLimit = isStreaming ? MEMORY_LIMITS.VIEWPORT_MESSAGES : limit;
const fetchLimit = isStreaming ? undefined : targetLimit + MEMORY_LIMITS.FETCH_BUFFER;
const allMessages = await executeWithSessionDirectory(sessionId, () => opencodeClient.getSessionMessages(sessionId, fetchLimit));
// Filter out reverted messages first
const revertMessageId = getSessionRevertMessageId(sessionId);
@@ -401,7 +404,7 @@ export const useMessageStore = create<MessageStore>()(
return isIdNewer(messageId, watermark);
})
: messagesWithoutReverted;
const messagesToKeep = afterWatermark.slice(-limit);
const messagesToKeep = afterWatermark.slice(-targetLimit);
set((state) => {
const newMessages = new Map(state.messages);
@@ -2418,28 +2421,28 @@ export const useMessageStore = create<MessageStore>()(
const allMessages = await executeWithSessionDirectory(sessionId, () => opencodeClient.getSessionMessages(sessionId));
if (direction === "up" && currentMessages.length > 0) {
const dedupedMessages = dedupeMessagesById(allMessages);
const firstCurrentMessage = currentMessages[0];
const indexInAll = allMessages.findIndex((m) => m.info.id === firstCurrentMessage.info.id);
const indexInAll = dedupedMessages.findIndex((message) => message.info.id === firstCurrentMessage.info.id);
if (indexInAll > 0) {
const loadCount = Math.min(MEMORY_LIMITS.VIEWPORT_MESSAGES, indexInAll);
const newMessages = allMessages.slice(indexInAll - loadCount, indexInAll);
const newMessages = dedupedMessages.slice(indexInAll - loadCount, indexInAll);
set((state) => {
const updatedMessages = [...newMessages, ...currentMessages];
const dedupedMessages = dedupeMessagesById(updatedMessages);
const newMessagesMap = new Map(state.messages);
newMessagesMap.set(sessionId, dedupedMessages);
const mergedMessages = dedupeMessagesById(updatedMessages);
const addedCount = Math.max(0, mergedMessages.length - currentMessages.length);
const newMessagesMap = new Map(state.messages);
newMessagesMap.set(sessionId, mergedMessages);
const addedCount = Math.max(0, dedupedMessages.length - currentMessages.length);
const newMemoryState = new Map(state.sessionMemoryState);
newMemoryState.set(sessionId, {
...memoryState,
viewportAnchor: memoryState.viewportAnchor + addedCount,
hasMoreAbove: indexInAll - loadCount > 0,
totalAvailableMessages: allMessages.length,
totalAvailableMessages: dedupedMessages.length,
});
return {
@@ -2448,12 +2451,12 @@ export const useMessageStore = create<MessageStore>()(
};
});
} else if (indexInAll === 0) {
set((state) => {
const newMemoryState = new Map(state.sessionMemoryState);
newMemoryState.set(sessionId, {
...memoryState,
hasMoreAbove: false,
totalAvailableMessages: dedupedMessages.length,
});
return { sessionMemoryState: newMemoryState };
});
@@ -54,6 +54,8 @@ export interface SessionContextUsage {
export const MEMORY_LIMITS = {
MAX_SESSIONS: 3,
VIEWPORT_MESSAGES: 120,
HISTORICAL_MESSAGES: 90,
FETCH_BUFFER: 20,
STREAMING_BUFFER: Infinity,
BACKGROUND_STREAMING_BUFFER: 120,
ZOMBIE_TIMEOUT: 10 * 60 * 1000,