diff --git a/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/main/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutor.java b/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/main/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutor.java index 909a19f587..27aca98648 100644 --- a/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/main/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutor.java +++ b/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/main/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutor.java @@ -128,7 +128,7 @@ private AgentRequestOptions buildAgentRequestOptions(RequestContext context) { AgentRequestOptions requestOptions = new AgentRequestOptions(); requestOptions.setTaskId(context.getTaskId()); requestOptions.setUserId(getUserId(message)); - requestOptions.setSessionId(getSessionId(message)); + requestOptions.setSessionId(getSessionId(context, message)); return requestOptions; } @@ -139,7 +139,29 @@ private String getUserId(Message message) { return ""; } - private String getSessionId(Message message) { + /** + * Extract sessionId from the A2A request. + * + *
Resolution order: + *
This ensures any standard A2A client that sets {@code contextId} on the message
+ * can be correctly routed to per-session state without requiring a custom metadata key.
+ *
+ * @param context the request context containing the resolved contextId
+ * @param message the original A2A message
+ * @return the sessionId, or empty string if not found
+ */
+ private String getSessionId(RequestContext context, Message message) {
+ // Prefer the protocol-standard contextId field
+ String contextId = context.getContextId();
+ if (contextId != null && !contextId.isEmpty()) {
+ return contextId;
+ }
+ // Backward-compatible fallback: read from metadata
if (message.getMetadata() != null && message.getMetadata().containsKey("sessionId")) {
return String.valueOf(message.getMetadata().get("sessionId"));
}
diff --git a/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/test/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutorTest.java b/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/test/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutorTest.java
index 976d85a7a4..2f4f1fbcf7 100644
--- a/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/test/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutorTest.java
+++ b/agentscope-extensions/agentscope-extensions-protocol/agentscope-extensions-a2a/agentscope-extensions-a2a-server/src/test/java/io/agentscope/core/a2a/server/executor/AgentScopeAgentExecutorTest.java
@@ -551,6 +551,150 @@ void testHandleExceptionDuringTaskCancellation() throws JSONRPCError {
}
}
+ @Nested
+ @DisplayName("Session ID Resolution Tests")
+ class SessionIdResolutionTests {
+
+ @Test
+ @DisplayName("Should use contextId as sessionId when contextId is present")
+ void testSessionIdFromContextId() throws JSONRPCError {
+ // Given: contextId is set, no metadata sessionId
+ String taskId = UUID.randomUUID().toString();
+ String contextId = "context-session-123";
+
+ when(mockContext.getTaskId()).thenReturn(taskId);
+ when(mockContext.getContextId()).thenReturn(contextId);
+
+ Message mockMessage = mock(Message.class);
+ when(mockMessage.getTaskId()).thenReturn(taskId);
+ when(mockMessage.getContextId()).thenReturn(contextId);
+ when(mockMessage.getParts()).thenReturn(List.of(new TextPart("hello")));
+ when(mockMessage.getMetadata()).thenReturn(null); // No metadata at all
+ when(mockContext.getMessage()).thenReturn(mockMessage);
+
+ MessageSendParams mockParams = mock(MessageSendParams.class);
+ when(mockContext.getParams()).thenReturn(mockParams);
+ when(mockParams.message()).thenReturn(mockMessage);
+
+ when(mockContext.getCallContext()).thenReturn(serverCallContext);
+ when(serverCallContext.getState()).thenReturn(Map.of());
+
+ AtomicReference