From 4d19f7d92becff955de12e2a58bc6bb23f14492d Mon Sep 17 00:00:00 2001 From: Kamil Tomaszek Date: Thu, 23 Jul 2026 06:32:16 -0700 Subject: [PATCH] fix(sessions): apply numRecentEvents and afterTimestamp together in InMemorySessionService `getSession` applied `afterTimestamp` only when `numRecentEvents` was unset, so the two `GetSessionConfig` filters could not be combined. Apply `numRecentEvents` first, then `afterTimestamp`, so both compose. Mirrors Python and Go, which already apply both. PiperOrigin-RevId: 952725154 --- .../adk/sessions/InMemorySessionService.java | 12 +++++----- .../sessions/InMemorySessionServiceTest.java | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java index 72a14cc4d..e54289b76 100644 --- a/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java +++ b/core/src/main/java/com/google/adk/sessions/InMemorySessionService.java @@ -150,12 +150,12 @@ public Maybe getSession( } }); - // Only apply timestamp filter if numRecentEvents was not applied - if (config.numRecentEvents().isEmpty() && config.afterTimestamp().isPresent()) { - Instant threshold = config.afterTimestamp().get(); - - eventsInCopy.removeIf(event -> getInstantFromEvent(event).isBefore(threshold)); - } + // Then drop events before afterTimestamp, so both filters compose. + config + .afterTimestamp() + .ifPresent( + threshold -> + eventsInCopy.removeIf(event -> getInstantFromEvent(event).isBefore(threshold))); // Merge state into the potentially filtered copy and return return Maybe.just(mergeWithGlobalState(appName, userId, sessionCopy)); diff --git a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java index 6a271efac..58c445641 100644 --- a/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java +++ b/core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java @@ -317,4 +317,27 @@ public void deleteSession_doesNotRemoveUserMapWhenOtherSessionsExist() throws Ex assertThat(sessions.get("app-name").get("user-id")).isNotNull(); assertThat(sessions.get("app-name").get("user-id")).hasSize(1); } + + @Test + public void getSession_numRecentEventsAndAfterTimestamp_appliesBothFilters() { + InMemorySessionService sessionService = new InMemorySessionService(); + Session session = sessionService.createSession("app", "user").blockingGet(); + for (long ts : new long[] {100, 200, 300, 400, 500}) { + var unused = + sessionService.appendEvent(session, Event.builder().timestamp(ts).build()).blockingGet(); + } + GetSessionConfig config = + GetSessionConfig.builder() + .numRecentEvents(4) + .afterTimestamp(Instant.ofEpochMilli(300)) + .build(); + + Session retrieved = + sessionService.getSession("app", "user", session.id(), Optional.of(config)).blockingGet(); + + // numRecentEvents keeps 200..500, then afterTimestamp drops 200; the pre-fix code kept 200. + assertThat(retrieved.events().stream().map(Event::timestamp)) + .containsExactly(300L, 400L, 500L) + .inOrder(); + } }