@@ -65,29 +65,68 @@ const jsonToMarkdown = (value: any, depth = 0): string => {
6565/**
6666 * Detect raw JSON blocks in the streaming buffer and replace them with a
6767 * readable Markdown rendering so the Details section doesn't expose raw JSON.
68- * Lines that already sit inside an existing fenced code block are left alone.
68+ * Handles both bare JSON blocks and fenced code blocks containing JSON
69+ * (e.g. ```json ... ``` or ``` { ... } ```).
6970 */
7071const formatBufferContent = ( content : string ) : string => {
7172 if ( ! content ) return content ;
7273
7374 const lines = content . split ( '\n' ) ;
7475 const out : string [ ] = [ ] ;
75- let insideExistingFence = false ;
7676 let i = 0 ;
7777
78+ const tryRenderJsonBlock = ( block : string ) : string | null => {
79+ const trimmed = block . trim ( ) ;
80+ if ( ! trimmed ) return null ;
81+ if ( ! ( trimmed . startsWith ( '{' ) || trimmed . startsWith ( '[' ) ) ) return null ;
82+ try {
83+ const parsed = JSON . parse ( trimmed ) ;
84+ if ( parsed === null || typeof parsed !== 'object' ) return null ;
85+ return jsonToMarkdown ( parsed ) ;
86+ } catch {
87+ return null ;
88+ }
89+ } ;
90+
7891 while ( i < lines . length ) {
7992 const line = lines [ i ] ;
8093 const trimmed = line . trim ( ) ;
8194
95+ // Fenced code block — try to render as readable JSON if applicable
8296 if ( trimmed . startsWith ( '```' ) ) {
83- insideExistingFence = ! insideExistingFence ;
84- out . push ( line ) ;
85- i ++ ;
97+ const fenceLang = trimmed . replace ( / ^ ` ` ` / , '' ) . trim ( ) . toLowerCase ( ) ;
98+ // Find closing fence
99+ let endIdx = - 1 ;
100+ for ( let j = i + 1 ; j < lines . length ; j ++ ) {
101+ if ( lines [ j ] . trim ( ) . startsWith ( '```' ) ) {
102+ endIdx = j ;
103+ break ;
104+ }
105+ }
106+ if ( endIdx === - 1 ) {
107+ // Unterminated fence — pass remaining lines through unchanged
108+ out . push ( line ) ;
109+ i ++ ;
110+ continue ;
111+ }
112+
113+ const inner = lines . slice ( i + 1 , endIdx ) . join ( '\n' ) ;
114+ const isJsonLang = fenceLang === 'json' || fenceLang === '' ;
115+ const rendered = isJsonLang ? tryRenderJsonBlock ( inner ) : null ;
116+ if ( rendered !== null ) {
117+ out . push ( rendered ) ;
118+ out . push ( '' ) ;
119+ } else {
120+ // Keep original fenced block as-is
121+ out . push ( line ) ;
122+ for ( let j = i + 1 ; j <= endIdx ; j ++ ) out . push ( lines [ j ] ) ;
123+ }
124+ i = endIdx + 1 ;
86125 continue ;
87126 }
88127
89- if ( ! insideExistingFence && ( trimmed . startsWith ( '{' ) || trimmed . startsWith ( '[' ) ) ) {
90- // Try to capture a balanced JSON block starting at this line
128+ // Bare JSON block (not inside a fence)
129+ if ( trimmed . startsWith ( '{' ) || trimmed . startsWith ( '[' ) ) {
91130 let depth = 0 ;
92131 let endIdx = - 1 ;
93132 for ( let j = i ; j < lines . length ; j ++ ) {
@@ -103,14 +142,12 @@ const formatBufferContent = (content: string): string => {
103142 }
104143 if ( endIdx !== - 1 ) {
105144 const block = lines . slice ( i , endIdx + 1 ) . join ( '\n' ) ;
106- try {
107- const parsed = JSON . parse ( block ) ;
108- out . push ( jsonToMarkdown ( parsed ) ) ;
145+ const rendered = tryRenderJsonBlock ( block ) ;
146+ if ( rendered !== null ) {
147+ out . push ( rendered ) ;
109148 out . push ( '' ) ;
110149 i = endIdx + 1 ;
111150 continue ;
112- } catch {
113- // Not valid JSON — fall through and keep the original line
114151 }
115152 }
116153 }
0 commit comments