-
Notifications
You must be signed in to change notification settings - Fork 614
fix(streaming): reduce stream events with batching scheduler #1271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add StreamUpdateScheduler to batch and debounce streaming updates, reducing event frequency by batching updates over 200ms intervals for rendering and 600ms for database writes. Introduces stream_kind to distinguish init/delta/final events and filters events by conversation ID for performance.
📝 WalkthroughWalkthroughThis pull request introduces a new Changes
Sequence Diagram(s)sequenceDiagram
participant Handler as ContentBuffer/<br>ToolCall/LLMHandler
participant Scheduler as StreamUpdateScheduler
participant EventBus as EventBus
participant Renderer as Renderer
participant DB as MessageManager/<br>Backend
Handler->>Scheduler: enqueueDelta(eventId, delta)
activate Scheduler
Scheduler->>Scheduler: batch delta into pendingDelta
alt shouldFlushBeforeEnqueue
Scheduler->>EventBus: emit delta event (seq++)
EventBus->>Renderer: render streamed content
Scheduler->>DB: flushDb (editMessageSilently)
DB-->>Scheduler: ✓ persisted
else schedule flush
Scheduler->>Scheduler: scheduleRenderFlush (timer)
end
deactivate Scheduler
Scheduler->>Scheduler: STREAM_RENDER_FLUSH_INTERVAL_MS expires
Scheduler->>EventBus: flushRender (send pendingDelta)
EventBus->>Renderer: update UI
Scheduler->>Scheduler: scheduleDbFlush (timer)
Scheduler->>Scheduler: STREAM_DB_FLUSH_INTERVAL_MS expires
Scheduler->>DB: flushDb (persist snapshot)
DB-->>Scheduler: ✓
sequenceDiagram
participant Renderer as Renderer
participant Store as Chat Store
participant Cache as Message Cache
Renderer->>Store: handleStreamResponse(msg: init)
activate Store
Store->>Store: findMainAssistantMessageByParentId
Store->>Cache: create skeleton / cache main message
deactivate Store
Renderer->>Store: handleStreamResponse(msg: delta, content)
activate Store
Store->>Cache: update main/variant message content
Store->>Renderer: refresh display
deactivate Store
Renderer->>Store: handleStreamResponse(msg: final)
activate Store
Store->>Store: attach variants to main message
Store->>Cache: update final message + variants
Store->>Renderer: final render
deactivate Store
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/main/presenter/sessionPresenter/managers/messageManager.ts (1)
382-385: Non-English log message.As per coding guidelines, all logs and comments must be in English.
Suggested fix
} catch (error) { - console.error('初始化未完成消息失败:', error) + console.error('Failed to initialize unfinished messages:', error) }src/renderer/src/components/message/MessageItemAssistant.vue (1)
281-284: Non-English log message.As per coding guidelines, all logs and comments must be in English.
Suggested fix
} catch (error) { - console.error('创建对话分支失败:', error) + console.error('Failed to create conversation fork:', error) }src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
42-50: Consider adding proper typing for messageManager.The
messageManagerfield is typed asany, which bypasses TypeScript's type checking. Consider importing and using theMessageManagertype for better type safety.♻️ Suggested typing improvement
+import type { MessageManager } from '../../sessionPresenter/managers/messageManager' + export class StreamUpdateScheduler { private readonly states: Map<string, SchedulerState> = new Map() - private readonly messageManager: any + private readonly messageManager: MessageManager private readonly onFlushRender?: () => void - constructor(options: { messageManager: any; onFlushRender?: () => void }) { + constructor(options: { messageManager: MessageManager; onFlushRender?: () => void }) { this.messageManager = options.messageManager this.onFlushRender = options.onFlushRender }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/agentPresenter/streaming/types.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/stores/chat.tssrc/shared/types/core/agent-events.ts
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
All logs and comments must be in English
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/renderer/src/components/message/MessageItemAssistant.vuesrc/shared/types/core/agent-events.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/renderer/src/stores/chat.ts
**/*.{js,ts,tsx,jsx,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use OxLint as the linter
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/shared/types/core/agent-events.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/renderer/src/stores/chat.ts
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}
📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)
Use Prettier as the code formatter
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/renderer/src/components/message/MessageItemAssistant.vuesrc/shared/types/core/agent-events.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/renderer/src/stores/chat.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Electron main process code should reside in
src/main/, with presenters organized inpresenter/subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed viaeventbus.ts
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Runpnpm run formatafter completing features
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/renderer/src/components/message/MessageItemAssistant.vuesrc/shared/types/core/agent-events.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/renderer/src/stores/chat.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits
Enable strict TypeScript type checking
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/shared/types/core/agent-events.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/renderer/src/stores/chat.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use English for all logs and comments
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/renderer/src/components/message/MessageItemAssistant.vuesrc/shared/types/core/agent-events.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/renderer/src/stores/chat.ts
src/main/presenter/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Implement all system capabilities as main-process presenters following the Presenter Pattern
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.ts
{src/main,src/renderer,test}/**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
Use IPC communication: Renderer calls main process via
usePresentercomposable, Main sends to Renderer via EventBus
Files:
src/main/presenter/agentPresenter/streaming/types.tssrc/main/presenter/agentPresenter/loop/toolCallHandler.tssrc/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.tssrc/renderer/src/stores/chat.ts
src/renderer/src/**/*.{vue,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
src/renderer/src/**/*.{vue,ts,tsx}: Use vue-i18n framework for internationalization located at src/renderer/src/i18n/
All user-facing strings must use i18n keys, not hardcoded text
src/renderer/src/**/*.{vue,ts,tsx}: Usereffor primitives and references,reactivefor objects in Vue 3 Composition API
Prefercomputedproperties over methods for derived state in Vue components
Import Shadcn Vue components from@/shadcn/components/ui/path alias
Use thecn()utility function combining clsx and tailwind-merge for dynamic Tailwind classes
UsedefineAsyncComponent()for lazy loading heavy Vue components
Use TypeScript for all Vue components and composables with explicit type annotations
Define TypeScript interfaces for Vue component props and data structures
UseusePresentercomposable for main process communication instead of direct IPC calls
Files:
src/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/stores/chat.ts
src/renderer/src/**/*.vue
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Import useI18n from vue-i18n in Vue components to access translation functions t and locale
src/renderer/src/**/*.vue: Use<script setup>syntax for concise Vue 3 component definitions with Composition API
Define props and emits explicitly in Vue components usingdefinePropsanddefineEmitswith TypeScript interfaces
Useprovide/injectfor dependency injection in Vue components instead of prop drilling
Use Tailwind CSS for all styling instead of writing scoped CSS files
Use mobile-first responsive design approach with Tailwind breakpoints
Use Iconify Vue with lucide icons as primary choice, following patternlucide:{icon-name}
Usev-memodirective for memoizing expensive computations in templates
Usev-oncedirective for rendering static content without reactivity updates
Use virtual scrolling withRecycleScrollercomponent for rendering long lists
Subscribe to events usingrendererEvents.on()and unsubscribe inonUnmountedlifecycle hook
Files:
src/renderer/src/components/message/MessageItemAssistant.vue
src/renderer/src/**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)
Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements
Use VueUse composables for common utilities like
useLocalStorage,useClipboard,useDebounceFnVue 3 renderer app code should be organized in
src/renderer/srcwith subdirectories forcomponents/,stores/,views/,i18n/, andlib/
Files:
src/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/stores/chat.ts
src/renderer/src/components/**/*.vue
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
Name Vue components using PascalCase (e.g.,
ChatInput.vue,MessageItemUser.vue)
Files:
src/renderer/src/components/message/MessageItemAssistant.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Vue components must be named in PascalCase (e.g.,
ChatInput.vue) and use Vue 3 Composition API with Pinia for state management and Tailwind for styling
**/*.vue: Use Vue 3 Composition API with<script setup>syntax
Use Pinia stores for state management
Style Vue components using Tailwind CSS (v4) with shadcn/ui (reka-ui)
Files:
src/renderer/src/components/message/MessageItemAssistant.vue
src/renderer/**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
All user-facing strings must use i18n keys (supports 12 languages)
Files:
src/renderer/src/components/message/MessageItemAssistant.vuesrc/renderer/src/stores/chat.ts
src/shared/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Shared TypeScript types and utilities should be placed in
src/shared/
Files:
src/shared/types/core/agent-events.ts
src/renderer/src/stores/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
src/renderer/src/stores/**/*.ts: Use Setup Store syntax withdefineStorefunction pattern in Pinia stores
Use getters (computed properties) for derived state in Pinia stores
Keep Pinia store actions focused on state mutations and async operations
Files:
src/renderer/src/stores/chat.ts
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
Use class-variance-authority (CVA) for defining component variants with Tailwind classes
Files:
src/renderer/src/stores/chat.ts
src/renderer/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)
src/renderer/src/**/*.{ts,tsx}: UseshallowRefandshallowReactivefor optimizing reactivity with large objects
Prefertypeoverinterfacein TypeScript unless using inheritance withextends
Files:
src/renderer/src/stores/chat.ts
🧠 Learnings (5)
📚 Learning: 2026-01-13T09:23:07.415Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T09:23:07.415Z
Learning: Applies to src/main/presenter/llmProviderPresenter/providers/*.ts : Implement `coreStream` method following standardized event interface for LLM providers
Applied to files:
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/main/presenter/agentPresenter/index.tssrc/main/presenter/agentPresenter/streaming/llmEventHandler.ts
📚 Learning: 2025-06-21T15:49:17.044Z
Learnt from: neoragex2002
Repo: ThinkInAIXYZ/deepchat PR: 550
File: src/renderer/src/stores/chat.ts:1011-1035
Timestamp: 2025-06-21T15:49:17.044Z
Learning: In src/renderer/src/stores/chat.ts, the user prefers to keep both `text` and `content` properties in the `handleMeetingInstruction` function's `sendMessage` call, even though they are redundant, rather than removing the `content` property.
Applied to files:
src/main/presenter/sessionPresenter/managers/messageManager.tssrc/main/presenter/agentPresenter/streaming/contentBufferHandler.tssrc/renderer/src/stores/chat.ts
📚 Learning: 2026-01-13T09:23:07.415Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T09:23:07.415Z
Learning: Applies to {src/main,src/renderer,test}/**/*.{ts,tsx,js} : Use IPC communication: Renderer calls main process via `usePresenter` composable, Main sends to Renderer via EventBus
Applied to files:
src/main/presenter/agentPresenter/index.ts
📚 Learning: 2026-01-13T09:23:07.415Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T09:23:07.415Z
Learning: Applies to src/main/presenter/**/*.ts : Implement all system capabilities as main-process presenters following the Presenter Pattern
Applied to files:
src/main/presenter/agentPresenter/index.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use `usePresenter` composable for main process communication instead of direct IPC calls
Applied to files:
src/main/presenter/agentPresenter/index.ts
🧬 Code graph analysis (7)
src/main/presenter/agentPresenter/loop/toolCallHandler.ts (1)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
StreamUpdateScheduler(42-408)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (3)
src/shared/types/core/agent-events.ts (1)
LLMAgentEventData(4-51)src/main/presenter/agentPresenter/streaming/types.ts (1)
StreamUpdateScheduler(39-39)src/main/eventbus.ts (1)
eventBus(151-151)
src/main/presenter/sessionPresenter/managers/messageManager.ts (1)
src/main/eventbus.ts (1)
eventBus(151-151)
src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts (2)
src/main/presenter/agentPresenter/streaming/types.ts (2)
GeneratingMessageState(4-37)StreamUpdateScheduler(39-39)src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
StreamUpdateScheduler(42-408)
src/main/presenter/agentPresenter/index.ts (2)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
StreamUpdateScheduler(42-408)src/main/presenter/agentPresenter/loop/toolCallHandler.ts (1)
ToolCallHandler(38-566)
src/main/presenter/agentPresenter/streaming/llmEventHandler.ts (3)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
StreamUpdateScheduler(42-408)src/main/presenter/agentPresenter/streaming/types.ts (1)
StreamUpdateScheduler(39-39)src/shared/types/core/agent-events.ts (1)
LLMAgentEventData(4-51)
src/renderer/src/stores/chat.ts (3)
src/renderer/src/lib/messageRuntimeCache.ts (2)
getCachedMessage(36-41)hasCachedMessage(43-45)src/shared/chat.d.ts (2)
AssistantMessage(39-42)Message(3-32)src/shared/chat/messageBlocks.ts (1)
finalizeAssistantMessageBlocks(3-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-check (x64)
🔇 Additional comments (30)
src/main/presenter/agentPresenter/streaming/types.ts (1)
36-39: LGTM!The
tabIdaddition toGeneratingMessageStatesupports the per-tab tracking feature, and the type re-export keeps the module's public API clean.src/main/presenter/sessionPresenter/managers/messageManager.ts (1)
122-151: LGTM! Clean API extension for silent updates.The
editMessagesignature extension with optionalemit/emitParentflags maintains backward compatibility (defaults to emitting), andeditMessageSilentlyprovides a clear API for the scheduler's database flush path.src/renderer/src/components/message/MessageItemAssistant.vue (1)
213-221: LGTM! Correct variant sibling matching.The updated condition properly identifies variants as siblings sharing the same
parentIdrather than treating the current message as the parent. This aligns with the variant model where multiple assistant responses to the same user message share a common parent.src/shared/types/core/agent-events.ts (1)
6-10: LGTM! Well-structured event metadata for streaming.The new fields properly support the scheduler's batching mechanism:
conversationIdandparentIdenable filtering events by contextstream_kinddistinguishes initialization, incremental updates, and finalizationseqprovides ordering guarantees for delta reconstructionsrc/main/presenter/agentPresenter/loop/toolCallHandler.ts (3)
48-59: LGTM! Clean dependency injection update.The migration from
MessageManagertoStreamUpdateSchedulercorrectly updates the constructor signature and member assignment.
291-299: LGTM! Correct scheduler integration for MCP UI resources.The
enqueueDeltacall with an empty delta andcontentSnapshotproperly schedules the database flush while allowing the render batching to proceed independently.
417-425: LGTM! Consistent scheduler integration for search results.Same pattern as the MCP UI resource handling—empty delta with content snapshot schedules persistence without redundant render emissions.
src/main/presenter/agentPresenter/streaming/llmEventHandler.ts (5)
12-41: LGTM! Clean integration of StreamUpdateScheduler.The constructor properly accepts and stores the
streamUpdateSchedulerdependency, following the existing pattern used for other handlers.
97-106: LGTM! Correct pattern for action blocks.The empty delta with full
contentSnapshotensures the maximum tool calls action block is properly synchronized to the renderer and database.
248-274: LGTM! Well-structured delta construction.The delta object is efficiently constructed, only including fields with values. The
contentSnapshotensures database persistence stays synchronized with the streaming state.
277-297: LGTM! Proper error handling sequence.The error handler correctly flushes pending updates via
flushAllbefore sending the error event, ensuring no streaming data is lost.
317-340: Scheduler state is properly cleaned up when the resumed permission flow completes.When
hasPendingPermissionsis true, the method returns early without callingflushAll. However, this does not cause a state leak. When the permission is handled and the stream resumes viarestartAgentLoopAfterPermission,continueAfterPermissionDenied, orresumeStreamCompletion, all three paths iterate through the resumed stream and callhandleLLMAgentEnd()on the final event. On this second call tohandleLLMAgentEnd(), the permission blocks will be 'granted' or 'denied' (not 'pending'), sohasPendingPermissionsevaluates to false, andflushAll()executes normally (line 347), which callsthis.states.delete(eventId)to clean up scheduler state. The early return when waiting for permissions is intentional design to preserve message content state during the permission flow.Likely an incorrect or invalid review comment.
src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts (3)
3-15: LGTM! Clean refactor to use StreamUpdateScheduler.The constructor properly accepts
streamUpdateSchedulerinstead ofmessageManager, aligning with the new streaming architecture.
107-115: LGTM! Proper batched content streaming.The large content handler correctly batches content updates and passes the full content snapshot for database persistence.
150-158: LGTM! Standard content streaming pattern.The normal content handler properly updates local state first, then enqueues the delta for batched delivery.
src/main/presenter/agentPresenter/index.ts (4)
74-76: LGTM! Centralized scheduler instantiation.The
StreamUpdateScheduleris constructed once and shared across all streaming handlers, ensuring consistent batching behavior.
86-106: LGTM! Consistent handler wiring.All streaming handlers receive the shared
streamUpdateSchedulerinstance, ensuring coordinated batching across different event types.
146-169: LGTM! Proper tabId propagation.The
tabIdparameter is now properly captured and passed totrackGeneratingMessage, enabling per-tab filtering in the renderer.
321-337: LGTM! GeneratingMessageState correctly includes tabId.The
tabIdis stored in the state object, making it available for all downstream streaming operations.src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (4)
78-113: LGTM! Smart handling of mixed content and reasoning.The recursive split for deltas containing both
contentandreasoning_contentensures these different content types are processed separately, preventing rendering confusion.
286-321: LGTM! Well-designed flush heuristics.The
shouldFlushBeforeEnqueuelogic correctly identifies when pending deltas must be flushed to preserve event ordering and prevent inappropriate batching of incompatible delta types.
323-340: LGTM! Resilient DB flush implementation.The
flushDbmethod correctly handles the case whencontentSnapshotis undefined, and error handling prevents DB write failures from disrupting the streaming flow.
342-391: LGTM! Complete finalization handling.The
flushAllmethod properly clears timers, sends the final delta, persists to database, and cleans up state in the correct order.src/renderer/src/stores/chat.ts (7)
191-206: LGTM! Efficient main message lookup.The
findMainAssistantMessageByParentIdhelper efficiently finds the main (non-variant) assistant message by iterating through cached messages.
718-734: LGTM! Proper variant attachment during retry.The retry logic correctly finds or creates the main message's variants array, attaches the new variant, and updates the cache and selection state.
918-958: LGTM! Proper skeleton message initialization.The skeleton message created on
initevents provides immediate visual feedback while streaming begins. Non-variant messages are correctly added to the view, while variants are only cached.
910-915: LGTM! Performance optimization via conversation filtering.Early filtering of events from non-active conversations prevents unnecessary processing and cache updates.
997-1097: LGTM! Comprehensive tool call lifecycle handling.The tool call handling covers all states (start, update, running, end, error) with proper block matching and status transitions.
1164-1186: LGTM! Proper variant synchronization during streaming.The variant handling correctly finds the main message, updates its variants array, and refreshes the cache to ensure UI consistency during streaming.
1279-1294: LGTM! Consistent error handling for variants.Error state is properly propagated to variant messages and their parent main messages using the same pattern as successful streaming.
| const shouldFlushImmediately = this.shouldFlushBeforeEnqueue(state, delta) | ||
| if (shouldFlushImmediately) { | ||
| this.flushRender(state) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double flush when shouldFlushImmediately is true.
When shouldFlushImmediately is true, flushRender is called twice:
- Line 133: Before accumulating the new delta (correct - flushes pending incompatible delta)
- Line 186: After accumulating the new delta (redundant - sends new delta immediately)
The second flush at line 186 defeats batching for these scenarios. While not functionally incorrect (it just sends more events), it undermines the batching optimization for events like tool_call transitions.
🔧 Suggested fix
const now = Date.now()
- if (shouldFlushImmediately) {
- this.flushRender(state)
- } else {
+ if (!shouldFlushImmediately) {
const renderDelay = Math.max(
0,
STREAM_RENDER_FLUSH_INTERVAL_MS - (now - state.lastRenderFlushAt)
)
this.scheduleRenderFlush(state, renderDelay)
}Also applies to: 185-193
Add StreamUpdateScheduler to batch and debounce streaming updates, reducing event frequency by batching updates over 200ms intervals for rendering and 600ms for database writes. Introduces stream_kind to distinguish init/delta/final events and filters events by conversation ID for performance.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Performance
✏️ Tip: You can customize this high-level summary in your review settings.