@@ -34,14 +34,16 @@ function cleanHeadlessOutput(output: string, command: string) {
3434}
3535
3636type PiJsonDetection = 'unknown' | 'plain' | 'pi-json' ;
37+ type PiJsonStreamingProgressKind = 'thinking' | 'toolcall' | 'tool-output' ;
3738
3839interface PiJsonRunState {
3940 pending : string ;
4041 detection : PiJsonDetection ;
4142 progress : string ;
4243 assistantText : string ;
4344 preJsonText : string ;
44- streamingProgressKind : 'thinking' | 'toolcall' | null ;
45+ streamingProgressKind : PiJsonStreamingProgressKind | null ;
46+ toolOutputTextByCallId : Record < string , string > ;
4547}
4648
4749const piJsonStates = new Map < string , PiJsonRunState > ( ) ;
@@ -63,7 +65,7 @@ function appendProgressLine(state: PiJsonRunState, line: string) {
6365 state . streamingProgressKind = null ;
6466}
6567
66- function appendProgressDelta ( state : PiJsonRunState , kind : 'thinking' | 'toolcall' , value : string ) {
68+ function appendProgressDelta ( state : PiJsonRunState , kind : PiJsonStreamingProgressKind , value : string ) {
6769 const clean = stripAnsi ( value ) ;
6870 if ( ! clean ) return ;
6971 if ( state . streamingProgressKind !== kind && state . progress && ! state . progress . endsWith ( '\n' ) ) {
@@ -92,10 +94,53 @@ function getDeltaText(value: unknown) {
9294 }
9395}
9496
97+ function extractTextContent ( value : unknown ) : string {
98+ if ( ! value ) return '' ;
99+ if ( typeof value === 'string' ) return value ;
100+ if ( Array . isArray ( value ) ) return value . map ( extractTextContent ) . filter ( Boolean ) . join ( '' ) ;
101+ if ( ! isRecord ( value ) ) return '' ;
102+ if ( typeof value . text === 'string' ) return value . text ;
103+ if ( typeof value . content === 'string' ) return value . content ;
104+ if ( Array . isArray ( value . content ) ) return value . content . map ( extractTextContent ) . filter ( Boolean ) . join ( '' ) ;
105+ return '' ;
106+ }
107+
108+ function formatToolName ( event : Record < string , unknown > ) {
109+ return getString ( event . toolName ) || getString ( event . name ) || 'Tool' ;
110+ }
111+
112+ function formatToolInvocation ( event : Record < string , unknown > ) {
113+ const toolName = formatToolName ( event ) ;
114+ const args = isRecord ( event . args ) ? event . args : isRecord ( event . input ) ? event . input : null ;
115+ const command = args ? getString ( args . command ) : '' ;
116+ const path = args ? getString ( args . path ) : '' ;
117+ if ( command ) return `${ toolName } : ${ command } ` ;
118+ if ( path ) return `${ toolName } : ${ path } ` ;
119+ return toolName ;
120+ }
121+
122+ function appendToolExecutionUpdate ( state : PiJsonRunState , event : Record < string , unknown > ) {
123+ const text = extractTextContent ( event . partialResult ?? event . result ?? event . output ) ;
124+ if ( ! text . trim ( ) ) return ;
125+ const key = getString ( event . toolCallId ) || getString ( event . id ) || formatToolName ( event ) ;
126+ const previous = state . toolOutputTextByCallId [ key ] ?? '' ;
127+ let delta = '' ;
128+ if ( text . startsWith ( previous ) ) {
129+ delta = text . slice ( previous . length ) ;
130+ state . toolOutputTextByCallId [ key ] = text ;
131+ } else if ( previous && previous . endsWith ( text ) ) {
132+ return ;
133+ } else {
134+ delta = text ;
135+ state . toolOutputTextByCallId [ key ] = appendBounded ( previous , text , PI_JSON_PROGRESS_MAX_CHARS ) ;
136+ }
137+ appendProgressDelta ( state , 'tool-output' , delta . replace ( / ^ \n + / , '' ) ) ;
138+ }
139+
95140function isPiJsonEvent ( value : unknown ) : boolean {
96141 if ( ! isRecord ( value ) ) return false ;
97142 const type = getString ( value . type ) ;
98- if ( [ 'agent_start' , 'turn_start' , 'message_update' , 'tool_execution_start' , 'tool_execution_end' , 'message_end' , 'turn_end' , 'agent_end' , 'auto_retry_start' , 'compaction_start' , 'compaction_end' ] . includes ( type ) ) return true ;
143+ if ( [ 'agent_start' , 'turn_start' , 'message_update' , 'tool_execution_start' , 'tool_execution_update' , ' tool_execution_end', 'message_end' , 'turn_end' , 'agent_end' , 'auto_retry_start' , 'compaction_start' , 'compaction_end' ] . includes ( type ) ) return true ;
99144 if ( isRecord ( value . assistantMessageEvent ) ) return true ;
100145 if ( isRecord ( value . message ) || Array . isArray ( value . messages ) ) return true ;
101146 return false ;
@@ -136,8 +181,12 @@ function formatPiJsonEvent(event: unknown, state: PiJsonRunState): Partial<Headl
136181 const type = getString ( event . type ) ;
137182 if ( type === 'agent_start' ) appendProgressLine ( state , 'Agent started' ) ;
138183 else if ( type === 'turn_start' ) appendProgressLine ( state , 'Thinking…' ) ;
139- else if ( type === 'tool_execution_start' ) appendProgressLine ( state , `Running ${ getString ( event . toolName ) || getString ( event . name ) || 'tool' } …` ) ;
140- else if ( type === 'tool_execution_end' ) appendProgressLine ( state , `${ getString ( event . toolName ) || getString ( event . name ) || 'Tool' } ${ event . isError ? 'failed' : 'completed' } ` ) ;
184+ else if ( type === 'tool_execution_start' ) appendProgressLine ( state , `Running ${ formatToolInvocation ( event ) } …` ) ;
185+ else if ( type === 'tool_execution_update' ) appendToolExecutionUpdate ( state , event ) ;
186+ else if ( type === 'tool_execution_end' ) {
187+ appendToolExecutionUpdate ( state , event ) ;
188+ appendProgressLine ( state , `${ formatToolName ( event ) } ${ event . isError ? 'failed' : 'completed' } ` ) ;
189+ }
141190 else if ( type === 'auto_retry_start' ) appendProgressLine ( state , 'Retrying…' ) ;
142191 else if ( type === 'compaction_start' ) appendProgressLine ( state , 'Compacting context…' ) ;
143192 else if ( type === 'compaction_end' ) appendProgressLine ( state , 'Context compacted' ) ;
@@ -174,7 +223,7 @@ function commandLooksLikePiJson(command: string) {
174223function getPiJsonState ( id : string , command : string ) {
175224 let state = piJsonStates . get ( id ) ;
176225 if ( ! state ) {
177- state = { pending : '' , detection : commandLooksLikePiJson ( command ) ? 'pi-json' : 'unknown' , progress : '' , assistantText : '' , preJsonText : '' , streamingProgressKind : null } ;
226+ state = { pending : '' , detection : commandLooksLikePiJson ( command ) ? 'pi-json' : 'unknown' , progress : '' , assistantText : '' , preJsonText : '' , streamingProgressKind : null , toolOutputTextByCallId : { } } ;
178227 piJsonStates . set ( id , state ) ;
179228 }
180229 return state ;
0 commit comments