Skip to content

Commit 9434949

Browse files
google-genai-botcopybara-github
authored andcommitted
fix: Align InMemorySessionService listSessions with Python implementation
The Java implementation of listSessions now mirrors the Python implementation: it returns session copies that include merged app-level and user-level state, and it clears the events list before returning the sessions. This ensures behavioral consistency between the Java and Python in-memory session services. Tests have been updated to reflect this change. PiperOrigin-RevId: 848235874
1 parent 9611f89 commit 9434949

File tree

2 files changed

+42
-25
lines changed

2 files changed

+42
-25
lines changed

core/src/main/java/com/google/adk/sessions/InMemorySessionService.java

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.reactivex.rxjava3.core.Single;
2828
import java.time.Instant;
2929
import java.util.ArrayList;
30+
import java.util.Collection;
3031
import java.util.List;
3132
import java.util.Map;
3233
import java.util.Objects;
@@ -35,8 +36,6 @@
3536
import java.util.concurrent.ConcurrentHashMap;
3637
import java.util.concurrent.ConcurrentMap;
3738
import org.jspecify.annotations.Nullable;
38-
import org.slf4j.Logger;
39-
import org.slf4j.LoggerFactory;
4039

4140
/**
4241
* An in-memory implementation of {@link BaseSessionService} assuming {@link Session} objects are
@@ -50,9 +49,6 @@
5049
* during retrieval operations ({@code getSession}, {@code createSession}).
5150
*/
5251
public final class InMemorySessionService implements BaseSessionService {
53-
54-
private static final Logger logger = LoggerFactory.getLogger(InMemorySessionService.class);
55-
5652
// Structure: appName -> userId -> sessionId -> Session
5753
private final ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<String, Session>>>
5854
sessions;
@@ -178,9 +174,7 @@ public Single<ListSessionsResponse> listSessions(String appName, String userId)
178174

179175
// Create copies with empty events and state for the response
180176
List<Session> sessionCopies =
181-
userSessionsMap.values().stream()
182-
.map(this::copySessionMetadata)
183-
.collect(toCollection(ArrayList::new));
177+
prepareSessionsForListResponse(appName, userId, userSessionsMap.values());
184178

185179
return Single.just(ListSessionsResponse.builder().sessions(sessionCopies).build());
186180
}
@@ -298,21 +292,6 @@ private Session copySession(Session original) {
298292
.build();
299293
}
300294

301-
/**
302-
* Creates a copy of the session containing only metadata fields (ID, appName, userId, timestamp).
303-
* State and Events are explicitly *not* copied.
304-
*
305-
* @param original The session whose metadata to copy.
306-
* @return A new Session instance with only metadata fields populated.
307-
*/
308-
private Session copySessionMetadata(Session original) {
309-
return Session.builder(original.id())
310-
.appName(original.appName())
311-
.userId(original.userId())
312-
.lastUpdateTime(original.lastUpdateTime())
313-
.build();
314-
}
315-
316295
/**
317296
* Merges the app-specific and user-specific state into the provided *mutable* session's state
318297
* map.
@@ -338,4 +317,24 @@ private Session mergeWithGlobalState(String appName, String userId, Session sess
338317

339318
return session;
340319
}
320+
321+
/**
322+
* Prepares copies of sessions for use in a {@code listSessions} response.
323+
*
324+
* <p>For each session provided, this method creates a deep copy, clears the copy's event list,
325+
* and merges app-level and user-level state into the copy's state map.
326+
*
327+
* @param appName The application name.
328+
* @param userId The user ID.
329+
* @param sessions The collection of sessions to process.
330+
* @return A list of processed {@link Session} copies.
331+
*/
332+
private List<Session> prepareSessionsForListResponse(
333+
String appName, String userId, Collection<Session> sessions) {
334+
return sessions.stream()
335+
.map(this::copySession)
336+
.peek(s -> s.events().clear())
337+
.map(s -> mergeWithGlobalState(appName, userId, s))
338+
.collect(toCollection(ArrayList::new));
339+
}
341340
}

core/src/test/java/com/google/adk/sessions/InMemorySessionServiceTest.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,31 @@ public void lifecycle_getSession() {
8282
public void lifecycle_listSessions() {
8383
InMemorySessionService sessionService = new InMemorySessionService();
8484

85-
Session session = sessionService.createSession("app-name", "user-id").blockingGet();
85+
Session session =
86+
sessionService
87+
.createSession("app-name", "user-id", new ConcurrentHashMap<>(), "session-1")
88+
.blockingGet();
89+
90+
ConcurrentMap<String, Object> stateDelta = new ConcurrentHashMap<>();
91+
stateDelta.put("sessionKey", "sessionValue");
92+
stateDelta.put("_app_appKey", "appValue");
93+
stateDelta.put("_user_userKey", "userValue");
94+
95+
Event event =
96+
Event.builder().actions(EventActions.builder().stateDelta(stateDelta).build()).build();
97+
98+
var unused = sessionService.appendEvent(session, event).blockingGet();
8699

87100
ListSessionsResponse response =
88101
sessionService.listSessions(session.appName(), session.userId()).blockingGet();
102+
Session listedSession = response.sessions().get(0);
89103

90104
assertThat(response.sessions()).hasSize(1);
91-
assertThat(response.sessions().get(0).id()).isEqualTo(session.id());
105+
assertThat(listedSession.id()).isEqualTo(session.id());
106+
assertThat(listedSession.events()).isEmpty();
107+
assertThat(listedSession.state()).containsEntry("sessionKey", "sessionValue");
108+
assertThat(listedSession.state()).containsEntry("_app_appKey", "appValue");
109+
assertThat(listedSession.state()).containsEntry("_user_userKey", "userValue");
92110
}
93111

94112
@Test

0 commit comments

Comments
 (0)