11import { TextAttributes } from '@opentui/core'
2- import React , { memo , useCallback , useMemo , type ReactNode } from 'react'
2+ import React , { memo , useCallback , useMemo , useRef , type ReactNode } from 'react'
33
44import { AgentBlockGrid } from './agent-block-grid'
55import { AgentBranchItem } from './agent-branch-item'
@@ -14,6 +14,7 @@ import { shouldRenderAsSimpleText } from '../../utils/constants'
1414import { isImplementorAgent , getImplementorIndex } from '../../utils/implementor-helpers'
1515import { processBlocks , type BlockProcessorHandlers } from '../../utils/block-processor'
1616import { getAgentStatusInfo } from '../../utils/agent-helpers'
17+ import { extractHtmlBlockMargins } from '../../utils/block-margins'
1718import { isTextBlock } from '../../types/chat'
1819import type {
1920 AgentContentBlock ,
@@ -36,6 +37,22 @@ interface AgentBodyProps {
3637 isLastMessage ?: boolean
3738}
3839
40+ /** Props stored in ref for stable handler access in AgentBody */
41+ interface AgentBodyPropsRef {
42+ keyPrefix : string
43+ nestedBlocks : ContentBlock [ ]
44+ parentIsStreaming : boolean
45+ availableWidth : number
46+ markdownPalette : MarkdownPalette
47+ streamingAgents : Set < string >
48+ onToggleCollapsed : ( id : string ) => void
49+ onBuildFast : ( ) => void
50+ onBuildMax : ( ) => void
51+ isLastMessage ?: boolean
52+ theme : ReturnType < typeof useTheme >
53+ getAgentMarkdownOptions : ( indent : number ) => { codeBlockWidth : number ; palette : MarkdownPalette }
54+ }
55+
3956const AgentBody = memo (
4057 ( {
4158 agentBlock,
@@ -69,83 +86,114 @@ const AgentBody = memo(
6986 [ availableWidth , markdownPalette , theme . foreground ] ,
7087 )
7188
89+ // Store props in ref for stable handler access (avoids 12+ useMemo dependencies)
90+ const propsRef = useRef < AgentBodyPropsRef > ( null ! )
91+ propsRef . current = {
92+ keyPrefix,
93+ nestedBlocks,
94+ parentIsStreaming,
95+ availableWidth,
96+ markdownPalette,
97+ streamingAgents,
98+ onToggleCollapsed,
99+ onBuildFast,
100+ onBuildMax,
101+ isLastMessage,
102+ theme,
103+ getAgentMarkdownOptions,
104+ }
105+
106+ // Handlers are stable (empty deps) and read latest props from ref
72107 const handlers : BlockProcessorHandlers = useMemo (
73108 ( ) => ( {
74- onReasoningGroup : ( reasoningBlocks , startIndex ) => (
75- < ThinkingBlock
76- key = { reasoningBlocks [ 0 ] ?. thinkingId ?? `${ keyPrefix } -thinking-${ startIndex } ` }
77- blocks = { reasoningBlocks }
78- onToggleCollapsed = { onToggleCollapsed }
79- availableWidth = { availableWidth }
80- isNested = { true }
81- />
82- ) ,
109+ onReasoningGroup : ( reasoningBlocks , startIndex ) => {
110+ const p = propsRef . current
111+ return (
112+ < ThinkingBlock
113+ key = { reasoningBlocks [ 0 ] ?. thinkingId ?? `${ p . keyPrefix } -thinking-${ startIndex } ` }
114+ blocks = { reasoningBlocks }
115+ onToggleCollapsed = { p . onToggleCollapsed }
116+ availableWidth = { p . availableWidth }
117+ isNested = { true }
118+ />
119+ )
120+ } ,
83121
84- onToolGroup : ( toolBlocks , startIndex , nextIndex ) => (
85- < ToolBlockGroup
86- key = { `${ keyPrefix } -tool-group-${ startIndex } ` }
87- toolBlocks = { toolBlocks }
88- keyPrefix = { keyPrefix }
89- startIndex = { startIndex }
90- nextIndex = { nextIndex }
91- siblingBlocks = { nestedBlocks }
92- availableWidth = { availableWidth }
93- streamingAgents = { streamingAgents }
94- onToggleCollapsed = { onToggleCollapsed }
95- markdownPalette = { markdownPalette }
96- />
97- ) ,
122+ onToolGroup : ( toolBlocks , startIndex , nextIndex ) => {
123+ const p = propsRef . current
124+ return (
125+ < ToolBlockGroup
126+ key = { `${ p . keyPrefix } -tool-group-${ startIndex } ` }
127+ toolBlocks = { toolBlocks }
128+ keyPrefix = { p . keyPrefix }
129+ startIndex = { startIndex }
130+ nextIndex = { nextIndex }
131+ siblingBlocks = { p . nestedBlocks }
132+ availableWidth = { p . availableWidth }
133+ streamingAgents = { p . streamingAgents }
134+ onToggleCollapsed = { p . onToggleCollapsed }
135+ markdownPalette = { p . markdownPalette }
136+ />
137+ )
138+ } ,
98139
99- onImplementorGroup : ( implementors , startIndex ) => (
100- < ImplementorGroup
101- key = { `${ keyPrefix } -implementor-group-${ startIndex } ` }
102- implementors = { implementors }
103- siblingBlocks = { nestedBlocks }
104- availableWidth = { availableWidth }
105- />
106- ) ,
140+ onImplementorGroup : ( implementors , startIndex ) => {
141+ const p = propsRef . current
142+ return (
143+ < ImplementorGroup
144+ key = { `${ p . keyPrefix } -implementor-group-${ startIndex } ` }
145+ implementors = { implementors }
146+ siblingBlocks = { p . nestedBlocks }
147+ availableWidth = { p . availableWidth }
148+ />
149+ )
150+ } ,
107151
108- onAgentGroup : ( agentBlocks , startIndex ) => (
109- < AgentBlockGrid
110- key = { `${ keyPrefix } -agent-grid-${ startIndex } ` }
111- agentBlocks = { agentBlocks }
112- keyPrefix = { `${ keyPrefix } -agent-grid-${ startIndex } ` }
113- availableWidth = { availableWidth }
114- streamingAgents = { streamingAgents }
115- renderAgentBranch = { ( innerAgentBlock , prefix , width ) => (
116- < AgentBranchWrapper
117- agentBlock = { innerAgentBlock }
118- keyPrefix = { prefix }
119- availableWidth = { width }
120- markdownPalette = { markdownPalette }
121- streamingAgents = { streamingAgents }
122- onToggleCollapsed = { onToggleCollapsed }
123- onBuildFast = { onBuildFast }
124- onBuildMax = { onBuildMax }
125- siblingBlocks = { nestedBlocks }
126- isLastMessage = { isLastMessage }
127- />
128- ) }
129- />
130- ) ,
152+ onAgentGroup : ( agentBlocks , startIndex ) => {
153+ const p = propsRef . current
154+ return (
155+ < AgentBlockGrid
156+ key = { `${ p . keyPrefix } -agent-grid-${ startIndex } ` }
157+ agentBlocks = { agentBlocks }
158+ keyPrefix = { `${ p . keyPrefix } -agent-grid-${ startIndex } ` }
159+ availableWidth = { p . availableWidth }
160+ streamingAgents = { p . streamingAgents }
161+ renderAgentBranch = { ( innerAgentBlock , prefix , width ) => (
162+ < AgentBranchWrapper
163+ agentBlock = { innerAgentBlock }
164+ keyPrefix = { prefix }
165+ availableWidth = { width }
166+ markdownPalette = { p . markdownPalette }
167+ streamingAgents = { p . streamingAgents }
168+ onToggleCollapsed = { p . onToggleCollapsed }
169+ onBuildFast = { p . onBuildFast }
170+ onBuildMax = { p . onBuildMax }
171+ siblingBlocks = { p . nestedBlocks }
172+ isLastMessage = { p . isLastMessage }
173+ />
174+ ) }
175+ />
176+ )
177+ } ,
131178
132179 onSingleBlock : ( block , index ) => {
180+ const p = propsRef . current
133181 if ( block . type === 'text' ) {
134182 const textBlock = block as TextContentBlock
135183 const nestedStatus = textBlock . status
136- const isNestedStreamingText = parentIsStreaming || nestedStatus === 'running'
184+ const isNestedStreamingText = p . parentIsStreaming || nestedStatus === 'running'
137185 const filteredNestedContent = isNestedStreamingText
138186 ? trimTrailingNewlines ( textBlock . content )
139187 : textBlock . content . trim ( )
140- const markdownOptionsForLevel = getAgentMarkdownOptions ( 0 )
188+ const markdownOptionsForLevel = p . getAgentMarkdownOptions ( 0 )
141189 const marginTop = textBlock . marginTop ?? 0
142190 const marginBottom = textBlock . marginBottom ?? 0
143191 const explicitColor = textBlock . color
144- const nestedTextColor = explicitColor ?? theme . foreground
192+ const nestedTextColor = explicitColor ?? p . theme . foreground
145193
146194 return (
147195 < text
148- key = { `${ keyPrefix } -text-${ index } ` }
196+ key = { `${ p . keyPrefix } -text-${ index } ` }
149197 style = { {
150198 wrapMode : 'word' ,
151199 fg : nestedTextColor ,
@@ -165,12 +213,11 @@ const AgentBody = memo(
165213
166214 if ( block . type === 'html' ) {
167215 const htmlBlock = block as HtmlContentBlock
168- const marginTop = htmlBlock . marginTop ?? 0
169- const marginBottom = htmlBlock . marginBottom ?? 0
216+ const { marginTop, marginBottom } = extractHtmlBlockMargins ( htmlBlock )
170217
171218 return (
172219 < box
173- key = { `${ keyPrefix } -html-${ index } ` }
220+ key = { `${ p . keyPrefix } -html-${ index } ` }
174221 style = { {
175222 flexDirection : 'column' ,
176223 gap : 0 ,
@@ -179,8 +226,8 @@ const AgentBody = memo(
179226 } }
180227 >
181228 { htmlBlock . render ( {
182- textColor : theme . foreground ,
183- theme,
229+ textColor : p . theme . foreground ,
230+ theme : p . theme ,
184231 } ) }
185232 </ box >
186233 )
@@ -190,20 +237,7 @@ const AgentBody = memo(
190237 return null
191238 } ,
192239 } ) ,
193- [
194- keyPrefix ,
195- nestedBlocks ,
196- parentIsStreaming ,
197- availableWidth ,
198- markdownPalette ,
199- streamingAgents ,
200- onToggleCollapsed ,
201- onBuildFast ,
202- onBuildMax ,
203- isLastMessage ,
204- theme ,
205- getAgentMarkdownOptions ,
206- ] ,
240+ [ ] , // Empty deps - handlers read from propsRef.current
207241 )
208242
209243 return processBlocks ( nestedBlocks , handlers ) as ReactNode [ ]
0 commit comments