33using DotCraft . Context ;
44using DotCraft . Context . Compaction ;
55using DotCraft . Hooks ;
6+ using System . Collections . Concurrent ;
67using System . ClientModel . Primitives ;
78using System . Text . Json ;
89using Microsoft . Extensions . AI ;
@@ -1030,6 +1031,11 @@ public async Task GetStreamingResponseAsync_ThrowsContextOverflow_WhenErrorConte
10301031 public async Task GetStreamingResponseAsync_EmitsToolExecutionCompletionAsEachParallelToolFinishes ( )
10311032 {
10321033 var inner = new ParallelToolsFakeChatClient ( ) ;
1034+ var slowStarted = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
1035+ var fastStarted = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
1036+ var releaseSlow = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
1037+ var releaseFast = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
1038+ var fastCompleted = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
10331039 var client = new StreamingFunctionInvokingChatClient ( inner )
10341040 {
10351041 AllowConcurrentInvocation = true ,
@@ -1042,16 +1048,18 @@ public async Task GetStreamingResponseAsync_EmitsToolExecutionCompletionAsEachPa
10421048 {
10431049 if ( context . CallContent . CallId == "call-slow" )
10441050 {
1045- await Task . Delay ( 100 ) ;
1051+ slowStarted . TrySetResult ( ) ;
1052+ await releaseSlow . Task ;
10461053 return "slow result" ;
10471054 }
10481055
1049- await Task . Delay ( 10 ) ;
1056+ fastStarted . TrySetResult ( ) ;
1057+ await releaseFast . Task ;
10501058 return "fast result" ;
10511059 }
10521060 } ;
10531061 var turn = new SessionTurn { Id = "turn_1" , ThreadId = "thread_1" , StartedAt = DateTimeOffset . UtcNow } ;
1054- var completed = new List < SessionItem > ( ) ;
1062+ var completed = new ConcurrentQueue < SessionItem > ( ) ;
10551063
10561064 using var scope = ToolExecutionRuntimeScope . Set ( new ToolExecutionRuntimeContext
10571065 {
@@ -1060,16 +1068,36 @@ public async Task GetStreamingResponseAsync_EmitsToolExecutionCompletionAsEachPa
10601068 Turn = turn ,
10611069 NextItemSequence = ( ) => turn . Items . Count + 1 ,
10621070 EmitItemStarted = _ => { } ,
1063- EmitItemCompleted = item => completed . Add ( item ) ,
1071+ EmitItemCompleted = item =>
1072+ {
1073+ completed . Enqueue ( item ) ;
1074+ if ( item . Payload is ToolExecutionPayload { CallId : "call-fast" } )
1075+ fastCompleted . TrySetResult ( ) ;
1076+ } ,
10641077 SupportsToolExecutionLifecycle = true
10651078 } ) ;
10661079 RegisterToolExecution ( turn , "item_slow" , "call-slow" , "Slow" ) ;
10671080 RegisterToolExecution ( turn , "item_fast" , "call-fast" , "Fast" ) ;
10681081
1069- await foreach ( var _ in client . GetStreamingResponseAsync ( [ new ChatMessage ( ChatRole . User , "start" ) ] ) )
1082+ var execution = CollectAsync ( client . GetStreamingResponseAsync ( [ new ChatMessage ( ChatRole . User , "start" ) ] ) ) ;
1083+ IReadOnlyList < string > completedBeforeSlowRelease ;
1084+ try
1085+ {
1086+ await Task . WhenAll ( slowStarted . Task , fastStarted . Task ) . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
1087+ releaseFast . TrySetResult ( ) ;
1088+ await fastCompleted . Task . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
1089+ completedBeforeSlowRelease = completed
1090+ . Select ( item => Assert . IsType < ToolExecutionPayload > ( item . Payload ) . CallId )
1091+ . ToList ( ) ;
1092+ }
1093+ finally
10701094 {
1095+ releaseFast . TrySetResult ( ) ;
1096+ releaseSlow . TrySetResult ( ) ;
10711097 }
10721098
1099+ await execution . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
1100+ Assert . Equal ( [ "call-fast" ] , completedBeforeSlowRelease ) ;
10731101 Assert . Equal ( [ "call-fast" , "call-slow" ] ,
10741102 completed . Select ( item => Assert . IsType < ToolExecutionPayload > ( item . Payload ) . CallId ) ) ;
10751103 Assert . Equal ( 2 , inner . Calls . Count ) ;
0 commit comments