@@ -16,6 +16,7 @@ import {
1616 STREAM_EVENT_COMPACTION_THRESHOLD_BYTES ,
1717 STREAM_EVENT_MAX_PAYLOAD_BYTES ,
1818 STREAM_STRING_PREVIEW_UNITS ,
19+ serializedBytes ,
1920} from '@/lib/mothership/request/session/replay-compaction'
2021import type { StreamEvent } from '@/lib/mothership/request/session/types'
2122
@@ -343,4 +344,129 @@ describe('compactStreamEvent', () => {
343344
344345 expect ( compactStreamEvent ( event ) ) . toBe ( event )
345346 } )
347+
348+ it ( 'omits only the smallest sufficient bulk when no single child dominates, keeping its siblings' , ( ) => {
349+ const manyKeys = ( prefix : string ) =>
350+ Object . fromEntries (
351+ Array . from ( { length : 2_300 } , ( _ , index ) => [ `${ prefix } -${ index } ` , 'v' . repeat ( 300 ) ] )
352+ )
353+ const event : StreamEvent = {
354+ type : 'tool' ,
355+ payload : {
356+ toolCallId : 'c' ,
357+ toolName : 'cli_tables_rows_query' ,
358+ executor : 'sim' ,
359+ mode : 'async' ,
360+ phase : 'result' ,
361+ success : true ,
362+ output : { fileId : 'file-1' , rows : manyKeys ( 'r' ) , cols : manyKeys ( 'c' ) } ,
363+ } ,
364+ }
365+
366+ const compacted = payloadOf ( compactStreamEvent ( event ) )
367+ const output = toRecord ( compacted . output )
368+
369+ expect ( Buffer . byteLength ( JSON . stringify ( compacted ) ) ) . toBeLessThanOrEqual (
370+ STREAM_EVENT_MAX_PAYLOAD_BYTES
371+ )
372+ expect ( output . fileId ) . toBe ( 'file-1' )
373+ expect ( [ output . rows , output . cols ] . filter ( ( value ) => typeof value === 'string' ) ) . toHaveLength ( 1 )
374+ } )
375+
376+ it ( 'omits the smaller of two bulks when either alone would make the event fit' , ( ) => {
377+ const manyKeys = ( prefix : string , count : number ) =>
378+ Object . fromEntries (
379+ Array . from ( { length : count } , ( _ , index ) => [ `${ prefix } -${ index } ` , 'v' . repeat ( 300 ) ] )
380+ )
381+ const event : StreamEvent = {
382+ type : 'tool' ,
383+ payload : {
384+ toolCallId : 'c' ,
385+ toolName : 'cli_tables_rows_query' ,
386+ executor : 'sim' ,
387+ mode : 'async' ,
388+ phase : 'result' ,
389+ success : true ,
390+ output : { fileId : 'file-1' , rows : manyKeys ( 'r' , 3_000 ) , cols : manyKeys ( 'c' , 1_900 ) } ,
391+ } ,
392+ }
393+
394+ const output = toRecord ( payloadOf ( compactStreamEvent ( event ) ) . output )
395+
396+ expect ( output . fileId ) . toBe ( 'file-1' )
397+ expect ( Object . keys ( toRecord ( output . rows ) ) ) . toHaveLength ( 3_000 )
398+ expect ( output . cols ) . toMatch ( / ^ … \[ o m i t t e d , [ \d . ] + K B t o t a l \] $ / )
399+ } )
400+
401+ it ( 'omits a sufficient bulk whole when its own large parts cannot cover the overage' , ( ) => {
402+ const manyKeys = ( prefix : string , count : number ) =>
403+ Object . fromEntries (
404+ Array . from ( { length : count } , ( _ , index ) => [ `${ prefix } -${ index } ` , 'v' . repeat ( 3_000 ) ] )
405+ )
406+ const event : StreamEvent = {
407+ type : 'tool' ,
408+ payload : {
409+ toolCallId : 'c' ,
410+ toolName : 'cli_workflows_state_get' ,
411+ executor : 'sim' ,
412+ mode : 'async' ,
413+ phase : 'result' ,
414+ success : true ,
415+ output : { workflowId : 'wf-1' , state : { nested : manyKeys ( 'n' , 35 ) , ...manyKeys ( 'k' , 400 ) } } ,
416+ } ,
417+ }
418+
419+ const compacted = payloadOf ( compactStreamEvent ( event ) )
420+ const output = toRecord ( compacted . output )
421+
422+ expect ( Buffer . byteLength ( JSON . stringify ( compacted ) ) ) . toBeLessThanOrEqual (
423+ STREAM_EVENT_MAX_PAYLOAD_BYTES
424+ )
425+ expect ( output . workflowId ) . toBe ( 'wf-1' )
426+ expect ( output . state ) . toMatch ( / ^ … \[ o m i t t e d , [ \d . ] + M B t o t a l \] $ / )
427+ } )
428+
429+ it ( 'bounds a balanced 16 MB tree in one pass' , ( ) => {
430+ const tree = ( depth : number , leaf : number ) : unknown =>
431+ depth === 0
432+ ? 'x' . repeat ( leaf )
433+ : { l : tree ( depth - 1 , leaf + 100 ) , r : tree ( depth - 1 , Math . max ( leaf - 100 , 1 ) ) }
434+ const event : StreamEvent = {
435+ type : 'tool' ,
436+ payload : {
437+ toolCallId : 'c' ,
438+ toolName : 'cli_blocks_get' ,
439+ executor : 'sim' ,
440+ mode : 'async' ,
441+ phase : 'result' ,
442+ success : true ,
443+ output : tree ( 12 , 3_900 ) ,
444+ } ,
445+ }
446+
447+ const started = performance . now ( )
448+ const compacted = compactStreamEvent ( event )
449+ const elapsedMs = performance . now ( ) - started
450+
451+ expect ( Buffer . byteLength ( JSON . stringify ( compacted . payload ) ) ) . toBeLessThanOrEqual (
452+ STREAM_EVENT_MAX_PAYLOAD_BYTES
453+ )
454+ expect ( elapsedMs ) . toBeLessThan ( 2_000 )
455+ } )
456+
457+ it ( 'measures serialized size exactly without serializing' , ( ) => {
458+ const values : unknown [ ] = [
459+ { a : 'é😀"\\\n' , b : [ 1 , null , true , { c : 'd' } ] , e : undefined , f : - 1.5e-7 } ,
460+ [ undefined , 'x' , { 'kéy "q"' : 0 } ] ,
461+ 'plain' ,
462+ 42 ,
463+ null ,
464+ { } ,
465+ [ ] ,
466+ ]
467+
468+ for ( const value of values ) {
469+ expect ( serializedBytes ( value ) ) . toBe ( Buffer . byteLength ( JSON . stringify ( value ) ) )
470+ }
471+ } )
346472} )
0 commit comments