@@ -38,71 +38,6 @@ type ClaudeJsonRecord = {
3838 session_id ?: string ;
3939} ;
4040
41- type SessionLikeEvent = {
42- type : string ;
43- properties : Record < string , unknown > ;
44- } ;
45-
46- type ClaudeToolState = {
47- id : string ;
48- name : string ;
49- inputBuffer ?: string ;
50- input ?: Record < string , unknown > ;
51- } ;
52-
53- function tryParseObject ( input : string ) : Record < string , unknown > | null {
54- const trimmed = input . trim ( ) ;
55- if ( ! trimmed ) return null ;
56- try {
57- const parsed = JSON . parse ( trimmed ) as unknown ;
58- return parsed && typeof parsed === "object" && ! Array . isArray ( parsed )
59- ? ( parsed as Record < string , unknown > )
60- : null ;
61- } catch {
62- return null ;
63- }
64- }
65-
66- function extractSessionTitle ( value : unknown ) : string | undefined {
67- if ( ! value || typeof value !== "object" ) return undefined ;
68- const queue : unknown [ ] = [ value ] ;
69- while ( queue . length > 0 ) {
70- const current = queue . shift ( ) ;
71- if ( ! current || typeof current !== "object" ) continue ;
72-
73- if ( Array . isArray ( current ) ) {
74- for ( const item of current ) queue . push ( item ) ;
75- continue ;
76- }
77-
78- const record = current as Record < string , unknown > ;
79- const directTitle = record . title ;
80- if ( typeof directTitle === "string" ) {
81- const trimmed = directTitle . trim ( ) ;
82- if ( trimmed && ! trimmed . startsWith ( "New session" ) ) {
83- return trimmed ;
84- }
85- }
86-
87- const info = record . info ;
88- if ( info && typeof info === "object" && ! Array . isArray ( info ) ) {
89- const infoTitle = ( info as Record < string , unknown > ) . title ;
90- if ( typeof infoTitle === "string" ) {
91- const trimmed = infoTitle . trim ( ) ;
92- if ( trimmed && ! trimmed . startsWith ( "New session" ) ) {
93- return trimmed ;
94- }
95- }
96- }
97-
98- for ( const nested of Object . values ( record ) ) {
99- if ( nested && typeof nested === "object" ) queue . push ( nested ) ;
100- }
101- }
102-
103- return undefined ;
104- }
105-
10641function deriveSessionTitleFromPrompt ( message : string ) : string | undefined {
10742 const normalized = message . replace ( / \s + / g, " " ) . trim ( ) ;
10843 if ( ! normalized ) return undefined ;
@@ -289,273 +224,22 @@ function publishSessionEvent(sessionId: string, event: unknown): void {
289224 }
290225}
291226
292- function statusFromClaudeRecord (
293- record : ClaudeJsonRecord ,
294- toolByIndex : Map < number , ClaudeToolState >
295- ) : string | null {
296- if ( record . type === "assistant" ) {
297- return "Drafting response" ;
298- }
299- if ( record . type === "result" ) {
300- return record . is_error ? "Claude reported an error" : "Finalizing response" ;
301- }
302- if ( record . type !== "stream_event" || ! record . event ?. type ) {
303- return null ;
304- }
305-
306- switch ( record . event . type ) {
307- case "message_start" :
308- return "Thinking" ;
309- case "content_block_start" : {
310- const block = record . event . content_block ;
311- if ( block ?. type === "tool_use" ) {
312- const toolName = typeof block . name === "string" ? block . name : "tool" ;
313- return `Running tool: ${ toolName } ` ;
314- }
315- if ( block ?. type === "thinking" ) {
316- return "Thinking" ;
317- }
318- return "Drafting response" ;
319- }
320- case "content_block_delta" : {
321- const delta = record . event . delta ;
322- if ( delta ?. type === "text_delta" ) {
323- return "Drafting response" ;
324- }
325- if ( delta ?. type === "input_json_delta" ) {
326- const index = typeof record . event ?. index === "number" ? record . event . index : - 1 ;
327- const tool = toolByIndex . get ( index ) ;
328- return tool ? `Running tool: ${ tool . name } ` : "Running tool" ;
329- }
330- if ( delta ?. type === "thinking_delta" ) {
331- return "Thinking" ;
332- }
333- return null ;
334- }
335- case "content_block_stop" : {
336- const index = typeof record . event . index === "number" ? record . event . index : - 1 ;
337- const tool = toolByIndex . get ( index ) ;
338- return tool ? `Finished tool: ${ tool . name } ` : "Finished step" ;
339- }
340- case "message_stop" :
341- return "Finalizing response" ;
342- default :
343- return null ;
344- }
345- }
346-
347- export function mapClaudeRecordToSessionEvents (
348- record : unknown ,
349- fallbackSessionId : string ,
350- textByIndex : Map < number , string > ,
351- toolByIndex : Map < number , ClaudeToolState > ,
352- thinkingByIndex : Map < number , string >
353- ) : SessionLikeEvent [ ] {
354- const parsedRecord = record as ClaudeJsonRecord ;
355- const events : SessionLikeEvent [ ] = [ ] ;
356- const sessionId = getRecordSessionId ( parsedRecord , fallbackSessionId ) ;
357- const sessionTitle = extractSessionTitle ( parsedRecord ) ;
358- if ( sessionTitle ) {
359- events . push ( {
360- type : "session.updated" ,
361- properties : {
362- sessionID : sessionId ,
363- info : {
364- title : sessionTitle ,
365- } ,
366- } ,
367- } ) ;
368- }
369- const status = statusFromClaudeRecord ( parsedRecord , toolByIndex ) ;
370- if ( status ) {
371- events . push ( {
372- type : "session.status" ,
373- properties : {
374- sessionID : sessionId ,
375- status,
376- } ,
377- } ) ;
378- }
379-
380- if ( parsedRecord . type === "assistant" ) {
381- const text = parsedRecord . message ?. content
382- ?. filter ( ( block ) => block ?. type === "text" )
383- . map ( ( block ) => block . text ?? "" )
384- . join ( "" )
385- . trim ( ) ;
386- if ( text ) {
387- events . push ( {
388- type : "message.part.updated" ,
389- properties : {
390- part : {
391- type : "text" ,
392- text,
393- sessionID : sessionId ,
394- } ,
395- } ,
396- } ) ;
397- }
398- return events ;
399- }
400-
401- if ( parsedRecord . type !== "stream_event" || ! parsedRecord . event ?. type ) {
402- return events ;
403- }
404-
405- const eventType = parsedRecord . event . type ;
406- const index = typeof parsedRecord . event . index === "number" ? parsedRecord . event . index : - 1 ;
407-
408- if ( eventType === "content_block_start" ) {
409- const contentBlock = parsedRecord . event . content_block ;
410- if ( contentBlock ?. type === "tool_use" ) {
411- const id = typeof contentBlock . id === "string" ? contentBlock . id : `tool-${ Date . now ( ) } -${ index } ` ;
412- const name = typeof contentBlock . name === "string" ? contentBlock . name : "tool" ;
413- const input =
414- contentBlock && typeof contentBlock . input === "object"
415- ? ( contentBlock . input as Record < string , unknown > )
416- : { } ;
417- toolByIndex . set ( index , { id, name, input } ) ;
418- events . push ( {
419- type : "message.part.updated" ,
420- properties : {
421- part : {
422- type : "tool" ,
423- id,
424- tool : name ,
425- sessionID : sessionId ,
426- state : {
427- status : "running" ,
428- input,
429- } ,
430- } ,
431- } ,
432- } ) ;
433- }
434- if ( contentBlock ?. type === "thinking" ) {
435- const thinking = typeof contentBlock . thinking === "string" ? contentBlock . thinking : "" ;
436- if ( thinking ) {
437- thinkingByIndex . set ( index , thinking ) ;
438- events . push ( {
439- type : "message.part.updated" ,
440- properties : {
441- part : {
442- type : "thinking" ,
443- text : thinking ,
444- sessionID : sessionId ,
445- } ,
446- } ,
447- } ) ;
448- }
449- }
450- return events ;
451- }
452-
453- if ( eventType === "content_block_delta" ) {
454- const delta = parsedRecord . event . delta ;
455- if ( delta ?. type === "text_delta" ) {
456- const chunk = typeof delta . text === "string" ? delta . text : "" ;
457- if ( ! chunk ) return events ;
458- const next = `${ textByIndex . get ( index ) ?? "" } ${ chunk } ` ;
459- textByIndex . set ( index , next ) ;
460- events . push ( {
461- type : "message.part.updated" ,
462- properties : {
463- part : {
464- type : "text" ,
465- text : next ,
466- sessionID : sessionId ,
467- } ,
468- } ,
469- } ) ;
470- return events ;
471- }
472-
473- if ( delta ?. type === "input_json_delta" ) {
474- const tool = toolByIndex . get ( index ) ;
475- if ( ! tool ) return events ;
476- const chunk = typeof delta . partial_json === "string" ? delta . partial_json : "" ;
477- if ( chunk ) {
478- tool . inputBuffer = `${ tool . inputBuffer ?? "" } ${ chunk } ` ;
479- const parsedInput = tryParseObject ( tool . inputBuffer ) ;
480- if ( parsedInput ) {
481- tool . input = parsedInput ;
482- }
483- }
484- events . push ( {
485- type : "message.part.updated" ,
486- properties : {
487- part : {
488- type : "tool" ,
489- id : tool . id ,
490- tool : tool . name ,
491- sessionID : sessionId ,
492- state : {
493- status : "running" ,
494- input : tool . input ,
495- } ,
496- } ,
497- } ,
498- } ) ;
499- }
500-
501- if ( delta ?. type === "thinking_delta" ) {
502- const chunk = typeof delta . thinking === "string" ? delta . thinking : "" ;
503- if ( ! chunk ) return events ;
504- const next = `${ thinkingByIndex . get ( index ) ?? "" } ${ chunk } ` ;
505- thinkingByIndex . set ( index , next ) ;
506- events . push ( {
507- type : "message.part.updated" ,
508- properties : {
509- part : {
510- type : "thinking" ,
511- text : next ,
512- sessionID : sessionId ,
513- } ,
514- } ,
515- } ) ;
516- }
517- return events ;
518- }
519-
520- if ( eventType === "content_block_stop" ) {
521- const tool = toolByIndex . get ( index ) ;
522- if ( ! tool ) return events ;
523- events . push ( {
524- type : "message.part.updated" ,
525- properties : {
526- part : {
527- type : "tool" ,
528- id : tool . id ,
529- tool : tool . name ,
530- sessionID : sessionId ,
531- state : {
532- status : "completed" ,
533- input : tool . input ,
534- } ,
535- } ,
536- } ,
537- } ) ;
538- }
539-
540- return events ;
541- }
542-
543227function publishClaudeRecordAsSessionEvents (
544228 record : ClaudeJsonRecord ,
545- fallbackSessionId : string ,
546- textByIndex : Map < number , string > ,
547- toolByIndex : Map < number , ClaudeToolState > ,
548- thinkingByIndex : Map < number , string >
229+ fallbackSessionId : string
549230) : void {
550- for ( const event of mapClaudeRecordToSessionEvents (
551- record ,
552- fallbackSessionId ,
553- textByIndex ,
554- toolByIndex ,
555- thinkingByIndex
556- ) ) {
557- publishSessionEvent ( getRecordSessionId ( record , fallbackSessionId ) , event ) ;
558- }
231+ const sessionId = getRecordSessionId ( record , fallbackSessionId ) ;
232+ const rawType = typeof record . type === "string" && record . type . trim ( )
233+ ? record . type . trim ( )
234+ : "unknown" ;
235+ publishSessionEvent ( sessionId , {
236+ type : `claude.raw.${ rawType } ` ,
237+ properties : {
238+ record,
239+ recordType : rawType ,
240+ streamEventType : typeof record . event ?. type === "string" ? record . event . type : undefined ,
241+ } ,
242+ } ) ;
559243}
560244
561245function parseClaudeResult ( output : string ) : {
@@ -795,22 +479,13 @@ export async function sendMessage(
795479 } ) ;
796480
797481 const envOverrides = sessionEnvironments . get ( sessionId ) ?? { } ;
798- const textByIndex = new Map < number , string > ( ) ;
799- const toolByIndex = new Map < number , ClaudeToolState > ( ) ;
800- const thinkingByIndex = new Map < number , string > ( ) ;
801482 const { output, permissionMode, command } = await runClaudeWithFallback (
802483 args ,
803484 workingPath ,
804485 envOverrides ,
805486 entry ,
806487 ( record ) => {
807- publishClaudeRecordAsSessionEvents (
808- record ,
809- sessionId ,
810- textByIndex ,
811- toolByIndex ,
812- thinkingByIndex
813- ) ;
488+ publishClaudeRecordAsSessionEvents ( record , sessionId ) ;
814489 }
815490 ) ;
816491
0 commit comments