@@ -40,28 +40,183 @@ export interface ApprovalPayload {
4040 resolution ?: "approved" | "rejected" ;
4141}
4242
43+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
44+ return typeof value === "object" && value !== null && ! Array . isArray ( value ) ;
45+ }
46+
47+ function parseToolArguments ( value : unknown ) : Record < string , unknown > {
48+ if ( isRecord ( value ) ) return value ;
49+ if ( typeof value !== "string" ) return { } ;
50+
51+ try {
52+ const parsed = JSON . parse ( value ) ;
53+ return isRecord ( parsed ) ? parsed : { raw : parsed } ;
54+ } catch {
55+ return { raw : value } ;
56+ }
57+ }
58+
59+ function parseDelegationStatus ( value : unknown ) : DelegationPayload [ "status" ] {
60+ const status = typeof value === "string" ? value . toLowerCase ( ) : "" ;
61+ if ( status . includes ( "complete" ) || status . includes ( "done" ) || status . includes ( "finish" ) ) {
62+ return "completed" ;
63+ }
64+ if ( status . includes ( "spin" ) || status . includes ( "pending" ) || status . includes ( "start" ) ) {
65+ return "spinning_up" ;
66+ }
67+ return "active" ;
68+ }
69+
70+ function parseApprovalResolution ( value : unknown ) : ApprovalPayload [ "resolution" ] {
71+ const normalized = typeof value === "string" ? value . toLowerCase ( ) : "" ;
72+ if ( [ "approved" , "approve" , "accepted" , "allow" , "allowed" , "ok" ] . includes ( normalized ) ) {
73+ return "approved" ;
74+ }
75+ if ( [ "rejected" , "reject" , "denied" , "deny" , "blocked" ] . includes ( normalized ) ) {
76+ return "rejected" ;
77+ }
78+ return undefined ;
79+ }
80+
4381/**
4482 * Parses raw message content into an array of structured ParsedEvents.
4583 */
4684export function parseEvents ( content : unknown , fallbackId : string = Math . random ( ) . toString ( 36 ) . slice ( 2 ) ) : ParsedEvent [ ] {
4785 const events : ParsedEvent [ ] = [ ] ;
86+ let textEventCounter = 0 ;
87+
88+ const parseObjectEvent = ( obj : Record < string , unknown > , idx : number ) => {
89+ if ( obj . type === "tool_use" ) {
90+ events . push ( {
91+ id : ( obj . id as string ) || `${ fallbackId } -tool-${ idx } ` ,
92+ type : "tool_call" ,
93+ payload : {
94+ toolName : obj . name as string ,
95+ arguments : ( obj . input as Record < string , unknown > ) || { } ,
96+ } as ToolCallPayload
97+ } ) ;
98+ return ;
99+ }
100+
101+ if ( obj . type === "tool_result" ) {
102+ events . push ( {
103+ id : ( obj . tool_use_id as string ) || `${ fallbackId } -result-${ idx } ` ,
104+ type : "tool_result" ,
105+ payload : {
106+ result : obj . content || obj ,
107+ } as ToolCallPayload
108+ } ) ;
109+ return ;
110+ }
111+
112+ if ( "text" in obj ) {
113+ parseTextWithThoughts ( String ( obj . text ) ) ;
114+ return ;
115+ }
116+
117+ if ( "tool_calls" in obj && Array . isArray ( obj . tool_calls ) ) {
118+ ( obj . tool_calls as Array < Record < string , unknown > > ) . forEach ( ( tc : Record < string , unknown > , tcIdx : number ) => {
119+ const fn = tc . function as Record < string , unknown > | undefined ;
120+ events . push ( {
121+ id : ( tc . id as string ) || `${ fallbackId } -tool-${ idx } -${ tcIdx } ` ,
122+ type : "tool_call" ,
123+ payload : {
124+ toolName : ( fn ?. name as string ) || "unknown" ,
125+ arguments : parseToolArguments ( fn ?. arguments )
126+ } as ToolCallPayload
127+ } ) ;
128+ } ) ;
129+ return ;
130+ }
131+
132+ const maybeDelegationType = typeof obj . type === "string"
133+ && [ "delegation" , "delegate" , "sub_agent_delegation" , "agent_delegation" ] . includes ( obj . type . toLowerCase ( ) ) ;
134+ const hasDelegationFields = "agentId" in obj || "agent_id" in obj || "roleDescription" in obj || "role" in obj ;
135+
136+ if ( maybeDelegationType || hasDelegationFields ) {
137+ const nestedSource = Array . isArray ( obj . nestedEvents )
138+ ? obj . nestedEvents
139+ : Array . isArray ( obj . events )
140+ ? obj . events
141+ : undefined ;
142+
143+ const nestedEvents = nestedSource ? parseEvents ( nestedSource , `${ fallbackId } -delegation-${ idx } ` ) : undefined ;
144+ const delegationPayload : DelegationPayload = {
145+ agentId : String ( obj . agentId ?? obj . agent_id ?? obj . agent ?? "unknown-agent" ) ,
146+ roleDescription : String ( obj . roleDescription ?? obj . role_description ?? obj . role ?? "" ) ,
147+ status : parseDelegationStatus ( obj . status ) ,
148+ handoffSummary : obj . handoffSummary != null
149+ ? String ( obj . handoffSummary )
150+ : obj . handoff_summary != null
151+ ? String ( obj . handoff_summary )
152+ : obj . summary != null
153+ ? String ( obj . summary )
154+ : undefined ,
155+ nestedEvents,
156+ } ;
157+
158+ events . push ( {
159+ id : ( obj . id as string ) || `${ fallbackId } -delegation-${ idx } ` ,
160+ type : "delegation" ,
161+ payload : delegationPayload
162+ } ) ;
163+ return ;
164+ }
165+
166+ const maybeApprovalType = typeof obj . type === "string"
167+ && [
168+ "approval" ,
169+ "approval_req" ,
170+ "approval_request" ,
171+ "exec.approval.requested" ,
172+ "exec.approval.resolved"
173+ ] . includes ( obj . type . toLowerCase ( ) ) ;
174+ const hasApprovalFields = "actionDescription" in obj || "action" in obj || "resolution" in obj || "approval" in obj ;
175+
176+ if ( maybeApprovalType || hasApprovalFields ) {
177+ const resolution = parseApprovalResolution ( obj . resolution ?? obj . status ?? obj . decision ?? obj . result ) ;
178+ const approvalPayload : ApprovalPayload = {
179+ actionDescription : String (
180+ obj . actionDescription
181+ ?? obj . action_description
182+ ?? obj . action
183+ ?? obj . description
184+ ?? "Approval required"
185+ ) ,
186+ resolution,
187+ } ;
188+
189+ events . push ( {
190+ id : ( obj . id as string ) || `${ fallbackId } -approval-${ idx } ` ,
191+ type : "approval" ,
192+ payload : approvalPayload
193+ } ) ;
194+ return ;
195+ }
196+
197+ events . push ( {
198+ id : `${ fallbackId } -unknown-${ idx } ` ,
199+ type : "unknown" ,
200+ payload : obj
201+ } ) ;
202+ } ;
48203
49204 const parseTextWithThoughts = ( text : string ) => {
50205 const parts = text . split ( / ( < t h i n k > [ \s \S ] * ?< \/ t h i n k > ) / g) ;
51- for ( let i = 0 ; i < parts . length ; i ++ ) {
52- const part = parts [ i ] ;
206+ for ( const part of parts ) {
53207 if ( ! part ) continue ;
208+ const currentIndex = textEventCounter ++ ;
54209 if ( part . startsWith ( "<think>" ) && part . endsWith ( "</think>" ) ) {
55210 // Push the thought
56211 const thoughtText = part . slice ( 7 , - 8 ) . trim ( ) ;
57212 events . push ( {
58- id : `${ fallbackId } -thought-${ i } ` ,
213+ id : `${ fallbackId } -thought-${ currentIndex } ` ,
59214 type : "thought" ,
60215 payload : { rawText : thoughtText } as ThoughtPayload
61216 } ) ;
62217 } else if ( part . trim ( ) ) {
63218 events . push ( {
64- id : `${ fallbackId } -text-${ i } ` ,
219+ id : `${ fallbackId } -text-${ currentIndex } ` ,
65220 type : "text" ,
66221 payload : part
67222 } ) ;
@@ -80,64 +235,15 @@ export function parseEvents(content: unknown, fallbackId: string = Math.random()
80235 content . forEach ( ( block , idx ) => {
81236 if ( typeof block === "string" ) {
82237 parseTextWithThoughts ( block ) ;
83- } else if ( block && typeof block === "object" ) {
84- const obj = block as Record < string , unknown > ;
85- if ( obj . type === "tool_use" ) {
86- events . push ( {
87- id : ( obj . id as string ) || `${ fallbackId } -tool-${ idx } ` ,
88- type : "tool_call" ,
89- payload : {
90- toolName : obj . name as string ,
91- arguments : ( obj . input as Record < string , unknown > ) || { } ,
92- } as ToolCallPayload
93- } ) ;
94- } else if ( obj . type === "tool_result" ) {
95- events . push ( {
96- id : ( obj . tool_use_id as string ) || `${ fallbackId } -result-${ idx } ` ,
97- type : "tool_result" ,
98- payload : {
99- result : obj . content || obj ,
100- } as ToolCallPayload
101- } ) ;
102- } else if ( "text" in obj ) {
103- parseTextWithThoughts ( String ( obj . text ) ) ;
104- } else if ( "tool_calls" in obj && Array . isArray ( obj . tool_calls ) ) {
105- ( obj . tool_calls as Array < Record < string , unknown > > ) . forEach ( ( tc : Record < string , unknown > , tcIdx : number ) => {
106- const fn = tc . function as Record < string , unknown > | undefined ;
107- events . push ( {
108- id : ( tc . id as string ) || `${ fallbackId } -tool-${ idx } -${ tcIdx } ` ,
109- type : "tool_call" ,
110- payload : {
111- toolName : ( fn ?. name as string ) || "unknown" ,
112- arguments : typeof fn ?. arguments === 'string'
113- ? JSON . parse ( fn . arguments )
114- : ( fn ?. arguments as Record < string , unknown > ) || { }
115- } as ToolCallPayload
116- } ) ;
117- } ) ;
118- } else {
119- events . push ( {
120- id : `${ fallbackId } -unknown-${ idx } ` ,
121- type : "unknown" ,
122- payload : block
123- } ) ;
124- }
238+ } else if ( isRecord ( block ) ) {
239+ parseObjectEvent ( block , idx ) ;
125240 }
126241 } ) ;
127242 return events ;
128243 }
129244
130- if ( typeof content === "object" ) {
131- const obj = content as Record < string , unknown > ;
132- if ( "text" in obj ) {
133- parseTextWithThoughts ( String ( obj . text ) ) ;
134- return events ;
135- }
136- events . push ( {
137- id : `${ fallbackId } -unknown` ,
138- type : "unknown" ,
139- payload : content
140- } ) ;
245+ if ( isRecord ( content ) ) {
246+ parseObjectEvent ( content , 0 ) ;
141247 return events ;
142248 }
143249
0 commit comments