From 7e5cf0f21a4ff04786051a39254ffe0cb6304708 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Mon, 2 Nov 2026 23:17:47 +0000 Subject: [PATCH] fix: accept ListTasks responses with fewer tasks than pageSize The A2A specification defines the response pageSize as the page size used for the response, and a server may return fewer tasks than that. ListTasksResult rejected any response where pageSize differed from the number of tasks, so clients failed with InvalidParamsError on every partial page. Fixes #1260 --- .../common/wrappers/ListTasksResult.java | 5 +-- .../sdk/grpc/utils/JSONRPCUtilsTest.java | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/jsonrpc-common/src/main/java/org/a2aproject/sdk/jsonrpc/common/wrappers/ListTasksResult.java b/jsonrpc-common/src/main/java/org/a2aproject/sdk/jsonrpc/common/wrappers/ListTasksResult.java index 7844547dd..4f4e1d8e5 100644 --- a/jsonrpc-common/src/main/java/org/a2aproject/sdk/jsonrpc/common/wrappers/ListTasksResult.java +++ b/jsonrpc-common/src/main/java/org/a2aproject/sdk/jsonrpc/common/wrappers/ListTasksResult.java @@ -12,7 +12,7 @@ * @param tasks Array of tasks matching the specified criteria * @param totalSize Total number of matching tasks across all pages, after any applicable authorization * filtering - * @param pageSize Number of tasks returned in this response + * @param pageSize The page size used for this response; may be greater than the number of tasks returned * @param nextPageToken Token for retrieving the next page of results (null if no more results) */ public record ListTasksResult( @@ -39,9 +39,6 @@ public record ListTasksResult( if (pageSize < 0) { throw new IllegalArgumentException("pageSize must be non-negative"); } - if (pageSize != tasks.size()) { - throw new IllegalArgumentException("pageSize must be equal to the number of tasks in the list. Got " + pageSize + ", but list has " + tasks.size() + " tasks."); - } // Make defensive copy tasks = List.copyOf(tasks); } diff --git a/spec-grpc/src/test/java/org/a2aproject/sdk/grpc/utils/JSONRPCUtilsTest.java b/spec-grpc/src/test/java/org/a2aproject/sdk/grpc/utils/JSONRPCUtilsTest.java index 300a2802a..fcead19af 100644 --- a/spec-grpc/src/test/java/org/a2aproject/sdk/grpc/utils/JSONRPCUtilsTest.java +++ b/spec-grpc/src/test/java/org/a2aproject/sdk/grpc/utils/JSONRPCUtilsTest.java @@ -39,6 +39,8 @@ import org.a2aproject.sdk.jsonrpc.common.wrappers.GetTaskPushNotificationConfigResponse; import org.a2aproject.sdk.jsonrpc.common.wrappers.GetTaskResponse; import org.a2aproject.sdk.jsonrpc.common.wrappers.ListTasksRequest; +import org.a2aproject.sdk.jsonrpc.common.wrappers.ListTasksResponse; +import org.a2aproject.sdk.jsonrpc.common.wrappers.ListTasksResult; import org.a2aproject.sdk.jsonrpc.common.wrappers.SendMessageRequest; import org.a2aproject.sdk.spec.DataPart; import org.a2aproject.sdk.spec.InvalidParamsError; @@ -142,6 +144,37 @@ public void testGetTask_WithNestedNulls_RoundTrips() throws JsonProcessingExcept assertEquals(task, roundTripped.getResult()); } + @Test + public void testListTasksResponse_PageSizeLargerThanReturnedTasks() throws JsonProcessingException { + String json = """ + { + "jsonrpc": "2.0", + "id": "1", + "result": { + "tasks": [{ + "id": "task-1", + "contextId": "context-1", + "status": { + "state": "TASK_STATE_COMPLETED" + } + }], + "nextPageToken": "", + "pageSize": 10, + "totalSize": 1 + } + } + """; + + ListTasksResponse response = assertInstanceOf(ListTasksResponse.class, + JSONRPCUtils.parseResponseBody(json, LIST_TASK_METHOD)); + ListTasksResult result = response.getResult(); + assertNotNull(result); + assertEquals(1, result.tasks().size()); + assertEquals(10, result.pageSize()); + assertEquals(1, result.totalSize()); + assertFalse(result.hasMoreResults()); + } + @Test public void testParseCreateTaskPushNotificationConfigRequest_ValidProtoFormat() throws JsonProcessingException { String validRequest = """