4545import com .google .adk .utils .CollectionUtils ;
4646import com .google .common .base .Preconditions ;
4747import com .google .common .collect .ImmutableList ;
48+ import com .google .common .collect .MapMaker ;
4849import com .google .errorprone .annotations .CanIgnoreReturnValue ;
4950import com .google .genai .types .AudioTranscriptionConfig ;
5051import com .google .genai .types .Content ;
5758import io .reactivex .rxjava3 .core .Flowable ;
5859import io .reactivex .rxjava3 .core .Maybe ;
5960import io .reactivex .rxjava3 .core .Single ;
61+ import io .reactivex .rxjava3 .subjects .CompletableSubject ;
62+ import java .time .Instant ;
6063import java .util .ArrayList ;
6164import java .util .Arrays ;
6265import java .util .Collections ;
6366import java .util .List ;
6467import java .util .Map ;
6568import java .util .Optional ;
6669import java .util .concurrent .ConcurrentHashMap ;
70+ import java .util .concurrent .ConcurrentMap ;
6771import org .jspecify .annotations .Nullable ;
6872
6973/** The main class for the GenAI Agents runner. */
@@ -76,6 +80,8 @@ public class Runner {
7680 private final PluginManager pluginManager ;
7781 @ Nullable private final EventsCompactionConfig eventsCompactionConfig ;
7882 @ Nullable private final ContextCacheConfig contextCacheConfig ;
83+ private final ConcurrentMap <String , Completable > activeSessionCompletables =
84+ new MapMaker ().weakValues ().makeMap ();
7985
8086 /** Builder for {@link Runner}. */
8187 public static class Builder {
@@ -380,25 +386,56 @@ public Flowable<Event> runAsync(
380386 Content newMessage ,
381387 RunConfig runConfig ,
382388 @ Nullable Map <String , Object > stateDelta ) {
389+ Flowable <Event > result =
390+ Flowable .defer (
391+ () ->
392+ this .sessionService
393+ .getSession (appName , userId , sessionId , Optional .empty ())
394+ .switchIfEmpty (
395+ Single .defer (
396+ () -> {
397+ if (runConfig .autoCreateSession ()) {
398+ return this .sessionService .createSession (
399+ appName , userId , (Map <String , Object >) null , sessionId );
400+ }
401+ return Single .error (
402+ new IllegalArgumentException (
403+ String .format (
404+ "Session not found: %s for user %s" ,
405+ sessionId , userId )));
406+ }))
407+ .flatMapPublisher (
408+ session ->
409+ this .runAsyncImpl (session , newMessage , runConfig , stateDelta )))
410+ .compose (Tracing .trace ("invocation" ));
411+
383412 return Flowable .defer (
384- () ->
385- this .sessionService
386- .getSession (appName , userId , sessionId , Optional .empty ())
387- .switchIfEmpty (
388- Single .defer (
389- () -> {
390- if (runConfig .autoCreateSession ()) {
391- return this .sessionService .createSession (
392- appName , userId , (Map <String , Object >) null , sessionId );
393- }
394- return Single .error (
395- new IllegalArgumentException (
396- String .format (
397- "Session not found: %s for user %s" , sessionId , userId )));
398- }))
399- .flatMapPublisher (
400- session -> this .runAsyncImpl (session , newMessage , runConfig , stateDelta )))
401- .compose (Tracing .trace ("invocation" ));
413+ () -> {
414+ CompletableSubject requestCompletion = CompletableSubject .create ();
415+
416+ Completable [] previousHolder = new Completable [1 ];
417+
418+ activeSessionCompletables .compute (
419+ sessionId ,
420+ (key , current ) -> {
421+ previousHolder [0 ] = current ;
422+ return requestCompletion ;
423+ });
424+
425+ Completable previous = previousHolder [0 ];
426+
427+ Flowable <Event > execution =
428+ result .doFinally (
429+ () -> {
430+ requestCompletion .onComplete ();
431+ activeSessionCompletables .remove (sessionId , requestCompletion );
432+ });
433+
434+ if (previous == null ) {
435+ return execution ;
436+ }
437+ return previous .onErrorComplete ().andThen (execution );
438+ });
402439 }
403440
404441 /** See {@link #runAsync(String, String, Content, RunConfig, Map)}. */
@@ -540,6 +577,8 @@ private Flowable<Event> runAgentWithUpdatedSession(
540577 registeredEvent -> {
541578 // TODO: remove this hack after deprecating runAsync with Session.
542579 copySessionStates (updatedSession , initialContext .session ());
580+ updatedSession .lastUpdateTime (
581+ Instant .ofEpochMilli (registeredEvent .timestamp ()));
543582 return contextWithUpdatedSession
544583 .pluginManager ()
545584 .onEventCallback (contextWithUpdatedSession , registeredEvent )
@@ -740,6 +779,9 @@ private BaseAgent findAgentToRun(Session session, BaseAgent rootAgent) {
740779
741780 for (Event event : events ) {
742781 String author = event .author ();
782+ if (author == null ) {
783+ continue ;
784+ }
743785 if (author .equals ("user" )) {
744786 continue ;
745787 }
0 commit comments