@@ -11,6 +11,9 @@ import { defaultToolCallRenderer } from './tool-renderers'
1111import type { ToolCallRenderer } from './tool-renderers'
1212import { MarkdownStreamRenderer } from '../display/markdown-renderer'
1313
14+ // Track active renderer instances with reference counting
15+ let activeRendererCount = 0
16+
1417/**
1518 * Creates a transform stream that processes XML tool calls
1619 * @param renderer Custom renderer for tool calls or a map of renderers per tool
@@ -24,12 +27,33 @@ export function createXMLStreamParser(
2427 // Create parser with tool schema validation
2528 const parser = new Saxy ( { [ toolXmlName ] : [ ] } )
2629
27- const md = new MarkdownStreamRenderer ( {
28- width : process . stdout . columns || 80 ,
29- isTTY : process . stdout . isTTY ,
30- syntaxHighlight : true ,
31- streamingMode : 'smart' , // Use smart content-aware streaming with loading indicators
32- } )
30+ let md : MarkdownStreamRenderer | null = null
31+
32+ function ensureRenderer ( ) {
33+ if ( ! md ) {
34+ md = new MarkdownStreamRenderer ( {
35+ width : process . stdout . columns || 80 ,
36+ isTTY : process . stdout . isTTY ,
37+ syntaxHighlight : true ,
38+ streamingMode : 'smart' , // Use smart content-aware streaming with loading indicators
39+ } )
40+ activeRendererCount ++
41+ }
42+ return md
43+ }
44+
45+ function safeCleanup ( ) {
46+ if ( md ) {
47+ try {
48+ md . cleanup ( )
49+ } catch ( e ) {
50+ // swallow errors to guarantee cleanup
51+ } finally {
52+ md = null
53+ activeRendererCount = Math . max ( 0 , activeRendererCount - 1 )
54+ }
55+ }
56+ }
3357
3458 // Current state
3559 let inToolCallTag = false
@@ -62,7 +86,7 @@ export function createXMLStreamParser(
6286
6387 parser . on ( 'text' , ( data ) => {
6488 if ( ! inToolCallTag ) {
65- const outs = md . write ( data . contents )
89+ const outs = ensureRenderer ( ) . write ( data . contents )
6690 for ( const out of outs ) {
6791 parser . push ( out )
6892 if ( callback ) callback ( out )
@@ -228,20 +252,27 @@ export function createXMLStreamParser(
228252 } )
229253
230254 parser . _flush = function ( done : ( error ?: Error | null ) => void ) {
231- const rem = md . end ( )
232- if ( rem ) {
233- this . push ( rem )
234- if ( callback ) callback ( rem )
255+ if ( md ) {
256+ const rem = md . end ( )
257+ if ( rem ) {
258+ this . push ( rem )
259+ if ( callback ) callback ( rem )
260+ }
235261 }
262+ safeCleanup ( )
236263 done ( )
237264 }
238265
239266 // Override destroy to ensure markdown renderer cleanup
240267 const originalDestroy = parser . destroy . bind ( parser )
241268 parser . destroy = function ( error ?: Error ) {
242- md . cleanup ( )
269+ safeCleanup ( )
243270 return originalDestroy ( error )
244271 }
245272
246273 return parser
247274}
275+
276+ export function getActiveRendererCount ( ) {
277+ return activeRendererCount
278+ }
0 commit comments