@@ -422,6 +422,96 @@ suite('ByokLmProxyService', () => {
422422 } ) ;
423423 } ) ;
424424
425+ test ( 'keeps interleaved parent and subagent continuations in the same scope' , async ( ) => {
426+ const captured : IByokLmChatRequest [ ] = [ ] ;
427+ const parentInitial = [ { type : 'message' , role : 'user' , content : [ { type : 'input_text' , text : 'Parent' } ] } ] ;
428+ const subagentInitial = [ { type : 'message' , role : 'user' , content : [ { type : 'input_text' , text : 'Subagent' } ] } ] ;
429+ const replay = ( input : object [ ] , callId : string ) => [
430+ ...input ,
431+ { type : 'function_call' , call_id : callId , name : 'tool' , arguments : '{}' } ,
432+ { type : 'function_call_output' , call_id : callId , output : 'done' } ,
433+ ] ;
434+
435+ await withProxy (
436+ async request => {
437+ captured . push ( request ) ;
438+ if ( captured . length <= 2 ) {
439+ const trajectory = captured . length === 1 ? 'parent' : 'subagent' ;
440+ return {
441+ responseId : `resp_${ trajectory } ` ,
442+ output : [ { type : 'function_call' , callId : `call_${ trajectory } ` , name : 'tool' , argumentsJson : '{}' } ] ,
443+ } ;
444+ }
445+ return { output : [ { type : 'message' , content : [ { type : 'text' , text : 'done' } ] } ] } ;
446+ } ,
447+ async handle => {
448+ for ( const input of [
449+ parentInitial ,
450+ subagentInitial ,
451+ replay ( parentInitial , 'call_parent' ) ,
452+ replay ( subagentInitial , 'call_subagent' ) ,
453+ ] ) {
454+ const response = await fetch ( responsesUrl ( handle , 'acme' ) , {
455+ method : 'POST' ,
456+ headers : authHeaders ( handle ) ,
457+ body : JSON . stringify ( { model : 'm' , input } ) ,
458+ } ) ;
459+ assert . strictEqual ( response . status , 200 ) ;
460+ await response . text ( ) ;
461+ }
462+ } ,
463+ ) ;
464+
465+ assert . deepStrictEqual ( captured . map ( request => ( {
466+ previousResponseId : request . previousResponseId ,
467+ input : request . input ,
468+ } ) ) , [
469+ { previousResponseId : undefined , input : [ { type : 'message' , role : 'user' , content : [ { type : 'text' , text : 'Parent' } ] } ] } ,
470+ { previousResponseId : undefined , input : [ { type : 'message' , role : 'user' , content : [ { type : 'text' , text : 'Subagent' } ] } ] } ,
471+ { previousResponseId : 'resp_parent' , input : [ { type : 'function_call_output' , callId : 'call_parent' , output : 'done' } ] } ,
472+ { previousResponseId : 'resp_subagent' , input : [ { type : 'function_call_output' , callId : 'call_subagent' , output : 'done' } ] } ,
473+ ] ) ;
474+ } ) ;
475+
476+ test ( 'preserves full replay when multiple pending responses match' , async ( ) => {
477+ const captured : IByokLmChatRequest [ ] = [ ] ;
478+ const replayedInput = [
479+ { type : 'function_call' , call_id : 'call_shared' , name : 'tool' , arguments : '{}' } ,
480+ { type : 'function_call_output' , call_id : 'call_shared' , output : 'done' } ,
481+ ] ;
482+
483+ await withProxy (
484+ async request => {
485+ captured . push ( request ) ;
486+ return captured . length <= 2
487+ ? { responseId : `resp_${ captured . length } ` , output : [ { type : 'function_call' , callId : 'call_shared' , name : 'tool' , argumentsJson : '{}' } ] }
488+ : { output : [ { type : 'message' , content : [ { type : 'text' , text : 'done' } ] } ] } ;
489+ } ,
490+ async handle => {
491+ for ( const input of [ [ ] , [ ] , replayedInput ] ) {
492+ const response = await fetch ( responsesUrl ( handle , 'acme' ) , {
493+ method : 'POST' ,
494+ headers : authHeaders ( handle ) ,
495+ body : JSON . stringify ( { model : 'm' , input } ) ,
496+ } ) ;
497+ assert . strictEqual ( response . status , 200 ) ;
498+ await response . text ( ) ;
499+ }
500+ } ,
501+ ) ;
502+
503+ assert . deepStrictEqual ( {
504+ previousResponseId : captured [ 2 ] ?. previousResponseId ,
505+ input : captured [ 2 ] ?. input ,
506+ } , {
507+ previousResponseId : undefined ,
508+ input : [
509+ { type : 'function_call' , callId : 'call_shared' , name : 'tool' , argumentsJson : '{}' } ,
510+ { type : 'function_call_output' , callId : 'call_shared' , output : 'done' } ,
511+ ] ,
512+ } ) ;
513+ } ) ;
514+
425515 test ( 'bounds abandoned tool continuations while preserving recent state' , async ( ) => {
426516 const captured : IByokLmChatRequest [ ] = [ ] ;
427517 const maximumPendingContinuations = 256 ;
@@ -450,7 +540,7 @@ suite('ByokLmProxyService', () => {
450540 } ;
451541
452542 // Sessions can disappear after receiving a tool call. Fill the proxy,
453- // refresh its oldest entry , then overflow it with one abandoned session .
543+ // add another candidate in its oldest scope , then overflow it.
454544 for ( let index = 0 ; index < maximumPendingContinuations ; index ++ ) {
455545 await post ( index , [ ] ) ;
456546 }
0 commit comments