Skip to content

Commit 6038ee2

Browse files
ehsavoieclaudekabirgemini-code-assist[bot]
authored
feat!: Add pagination support to ListTaskPushNotificationConfig with Result wrapper (#552)
# Description Implements pagination support for push notification config listing by introducing ListTaskPushNotificationConfigResult record, following the same pattern as ListTasksResult. This allows the API to return both the list of configs and a nextPageToken for pagination. Changes: - Created ListTaskPushNotificationConfigResult record with configs list and nextPageToken - Updated ListTaskPushNotificationConfigResponse to use Result type - Updated all server handlers to return Result wrapper - Updated ProtoUtils mappers for Result conversion - Updated all transport implementations (REST, gRPC, JSONRPC) on both client and server - Updated client interface and implementations - Fixed test to use Result type Updated all PushNotificationConfigStore implementations and tests to use the new ListTaskPushNotificationConfigParams/ListTaskPushNotificationConfigResult API instead of the old String-based getInfo method. Changes to implementations: - InMemoryPushNotificationConfigStore: Fixed bug where pageSize - 1 was used instead of pageSize, causing one less item to be returned than requested - JpaDatabasePushNotificationConfigStore: Implemented pagination logic matching the in-memory implementation - Added helper methods for pagination: findFirstIndex, convertPushNotificationConfig - Updated all test files with getInfoAsList helper method to maintain compatibility Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [X] Follow the [`CONTRIBUTING` Guide](../CONTRIBUTING.md). - [X] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [X] Ensure the tests pass - [X] Appropriate READMEs were updated (if necessary) Fixes #535 🦕 --------- Signed-off-by: Emmanuel Hugonnet <[email protected]> Co-authored-by: Claude Sonnet 4.5 <[email protected]> Co-authored-by: Kabir Khan <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent bed152d commit 6038ee2

File tree

41 files changed

+1142
-218
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1142
-218
lines changed

client/base/src/main/java/io/a2a/client/AbstractClient.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.a2a.spec.DeleteTaskPushNotificationConfigParams;
1414
import io.a2a.spec.GetTaskPushNotificationConfigParams;
1515
import io.a2a.spec.ListTaskPushNotificationConfigParams;
16+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
1617
import io.a2a.spec.ListTasksParams;
1718
import io.a2a.spec.ListTasksResult;
1819
import io.a2a.spec.Message;
@@ -271,26 +272,26 @@ public abstract TaskPushNotificationConfig getTaskPushNotificationConfiguration(
271272
@Nullable ClientCallContext context) throws A2AClientException;
272273

273274
/**
274-
* Retrieve the list of push notification configurations for a specific task.
275+
* Retrieve the list of push notification configurations for a specific task with pagination support.
275276
*
276277
* @param request the parameters specifying which task's notification configs to retrieve
277-
* @return the list of task push notification configs
278+
* @return the result containing the list of task push notification configs and pagination information
278279
* @throws A2AClientException if getting the task push notification configs fails for any reason
279280
*/
280-
public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(
281+
public ListTaskPushNotificationConfigResult listTaskPushNotificationConfigurations(
281282
ListTaskPushNotificationConfigParams request) throws A2AClientException {
282283
return listTaskPushNotificationConfigurations(request, null);
283284
}
284285

285286
/**
286-
* Retrieve the list of push notification configurations for a specific task.
287+
* Retrieve the list of push notification configurations for a specific task with pagination support.
287288
*
288289
* @param request the parameters specifying which task's notification configs to retrieve
289290
* @param context optional client call context for the request (may be {@code null})
290-
* @return the list of task push notification configs
291+
* @return the result containing the list of task push notification configs and pagination information
291292
* @throws A2AClientException if getting the task push notification configs fails for any reason
292293
*/
293-
public abstract List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(
294+
public abstract ListTaskPushNotificationConfigResult listTaskPushNotificationConfigurations(
294295
ListTaskPushNotificationConfigParams request,
295296
@Nullable ClientCallContext context) throws A2AClientException;
296297

client/base/src/main/java/io/a2a/client/Client.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.a2a.spec.EventKind;
1717
import io.a2a.spec.GetTaskPushNotificationConfigParams;
1818
import io.a2a.spec.ListTaskPushNotificationConfigParams;
19+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
1920
import io.a2a.spec.ListTasksParams;
2021
import io.a2a.spec.ListTasksResult;
2122
import io.a2a.spec.Message;
@@ -108,7 +109,7 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(
108109
}
109110

110111
@Override
111-
public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(
112+
public ListTaskPushNotificationConfigResult listTaskPushNotificationConfigurations(
112113
ListTaskPushNotificationConfigParams request, @Nullable ClientCallContext context) throws A2AClientException {
113114
return clientTransport.listTaskPushNotificationConfigurations(request, context);
114115
}

client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import io.a2a.spec.GetTaskRequest;
3030
import io.a2a.spec.ListTaskPushNotificationConfigParams;
3131
import io.a2a.spec.ListTaskPushNotificationConfigRequest;
32+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
3233
import io.a2a.spec.ListTasksParams;
3334
import io.a2a.spec.ListTasksRequest;
3435
import io.a2a.spec.ListTasksResult;
@@ -265,23 +266,24 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(
265266
}
266267

267268
@Override
268-
public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(
269+
public ListTaskPushNotificationConfigResult listTaskPushNotificationConfigurations(
269270
ListTaskPushNotificationConfigParams request,
270271
@Nullable ClientCallContext context) throws A2AClientException {
271272
checkNotNullParam("request", request);
272273

273274
io.a2a.grpc.ListTaskPushNotificationConfigRequest grpcRequest = io.a2a.grpc.ListTaskPushNotificationConfigRequest.newBuilder()
274275
.setParent("tasks/" + request.id())
275276
.setTenant(resolveTenant(request.tenant()))
277+
.setPageSize(request.pageSize())
278+
.setPageToken(request.pageToken())
276279
.build();
277280
PayloadAndHeaders payloadAndHeaders = applyInterceptors(ListTaskPushNotificationConfigRequest.METHOD,
278281
grpcRequest, agentCard, context);
279282

280283
try {
281284
A2AServiceBlockingV2Stub stubWithMetadata = createBlockingStubWithMetadata(context, payloadAndHeaders);
282-
return stubWithMetadata.listTaskPushNotificationConfig(grpcRequest).getConfigsList().stream()
283-
.map(FromProto::taskPushNotificationConfig)
284-
.collect(Collectors.toList());
285+
io.a2a.grpc.ListTaskPushNotificationConfigResponse grpcResponse = stubWithMetadata.listTaskPushNotificationConfig(grpcRequest);
286+
return FromProto.listTaskPushNotificationConfigResult(grpcResponse);
285287
} catch (StatusRuntimeException | StatusException e) {
286288
throw GrpcErrorMapper.mapGrpcError(e, "Failed to list task push notification config: ");
287289
}

client/transport/jsonrpc/src/main/java/io/a2a/client/transport/jsonrpc/JSONRPCTransport.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import io.a2a.spec.ListTaskPushNotificationConfigParams;
4646
import io.a2a.spec.ListTaskPushNotificationConfigRequest;
4747
import io.a2a.spec.ListTaskPushNotificationConfigResponse;
48+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
4849
import io.a2a.spec.ListTasksParams;
4950
import io.a2a.spec.ListTasksRequest;
5051
import io.a2a.spec.ListTasksResponse;
@@ -222,7 +223,7 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(GetTaskPu
222223
}
223224

224225
@Override
225-
public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(
226+
public ListTaskPushNotificationConfigResult listTaskPushNotificationConfigurations(
226227
ListTaskPushNotificationConfigParams request,
227228
@Nullable ClientCallContext context) throws A2AClientException {
228229
checkNotNullParam("request", request);

client/transport/rest/src/main/java/io/a2a/client/transport/rest/RestTransport.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import io.a2a.spec.GetTaskRequest;
4242
import io.a2a.spec.ListTaskPushNotificationConfigParams;
4343
import io.a2a.spec.ListTaskPushNotificationConfigRequest;
44+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
4445
import io.a2a.spec.ListTasksParams;
4546
import io.a2a.spec.ListTasksRequest;
4647
import io.a2a.spec.ListTasksResult;
@@ -334,7 +335,7 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(GetTaskPu
334335
}
335336

336337
@Override
337-
public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(ListTaskPushNotificationConfigParams request, @Nullable ClientCallContext context) throws A2AClientException {
338+
public ListTaskPushNotificationConfigResult listTaskPushNotificationConfigurations(ListTaskPushNotificationConfigParams request, @Nullable ClientCallContext context) throws A2AClientException {
338339
checkNotNullParam("request", request);
339340
io.a2a.grpc.ListTaskPushNotificationConfigRequest.Builder builder
340341
= io.a2a.grpc.ListTaskPushNotificationConfigRequest.newBuilder();
@@ -356,7 +357,7 @@ public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(L
356357
String httpResponseBody = response.body();
357358
io.a2a.grpc.ListTaskPushNotificationConfigResponse.Builder responseBuilder = io.a2a.grpc.ListTaskPushNotificationConfigResponse.newBuilder();
358359
JsonFormat.parser().merge(httpResponseBody, responseBuilder);
359-
return ProtoUtils.FromProto.listTaskPushNotificationConfigParams(responseBuilder);
360+
return ProtoUtils.FromProto.listTaskPushNotificationConfigResult(responseBuilder);
360361
} catch (A2AClientException e) {
361362
throw e;
362363
} catch (IOException | InterruptedException e) {

client/transport/rest/src/test/java/io/a2a/client/transport/rest/RestTransportTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import io.a2a.spec.FileWithUri;
4545
import io.a2a.spec.GetTaskPushNotificationConfigParams;
4646
import io.a2a.spec.ListTaskPushNotificationConfigParams;
47+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
4748
import io.a2a.spec.Message;
4849
import io.a2a.spec.MessageSendConfiguration;
4950
import io.a2a.spec.MessageSendParams;
@@ -361,18 +362,18 @@ public void testListTaskPushNotificationConfigurations() throws Exception {
361362
);
362363

363364
RestTransport client = new RestTransport(CARD);
364-
List<TaskPushNotificationConfig> taskPushNotificationConfigs = client.listTaskPushNotificationConfigurations(
365+
ListTaskPushNotificationConfigResult result = client.listTaskPushNotificationConfigurations(
365366
new ListTaskPushNotificationConfigParams("de38c76d-d54c-436c-8b9f-4c2703648d64"), null);
366-
assertEquals(2, taskPushNotificationConfigs.size());
367-
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfigs.get(0).pushNotificationConfig();
367+
assertEquals(2, result.configs().size());
368+
PushNotificationConfig pushNotificationConfig = result.configs().get(0).pushNotificationConfig();
368369
assertNotNull(pushNotificationConfig);
369370
assertEquals("https://example.com/callback", pushNotificationConfig.url());
370371
assertEquals("10", pushNotificationConfig.id());
371372
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
372373
assertTrue(authenticationInfo.schemes().size() == 1);
373374
assertEquals("jwt", authenticationInfo.schemes().get(0));
374375
assertEquals("", authenticationInfo.credentials());
375-
pushNotificationConfig = taskPushNotificationConfigs.get(1).pushNotificationConfig();
376+
pushNotificationConfig = result.configs().get(1).pushNotificationConfig();
376377
assertNotNull(pushNotificationConfig);
377378
assertEquals("https://test.com/callback", pushNotificationConfig.url());
378379
assertEquals("5", pushNotificationConfig.id());

client/transport/spi/src/main/java/io/a2a/client/transport/spi/ClientTransport.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.a2a.spec.EventKind;
1111
import io.a2a.spec.GetTaskPushNotificationConfigParams;
1212
import io.a2a.spec.ListTaskPushNotificationConfigParams;
13+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
1314
import io.a2a.spec.ListTasksParams;
1415
import io.a2a.spec.ListTasksResult;
1516
import io.a2a.spec.MessageSendParams;
@@ -102,14 +103,14 @@ TaskPushNotificationConfig getTaskPushNotificationConfiguration(
102103
@Nullable ClientCallContext context) throws A2AClientException;
103104

104105
/**
105-
* Retrieve the list of push notification configurations for a specific task.
106+
* Retrieve the list of push notification configurations for a specific task with pagination support.
106107
*
107108
* @param request the parameters specifying which task's notification configs to retrieve
108109
* @param context optional client call context for the request (may be {@code null})
109-
* @return the list of task push notification configs
110+
* @return the result containing the list of task push notification configs and pagination information
110111
* @throws A2AClientException if getting the task push notification configs fails for any reason
111112
*/
112-
List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(
113+
ListTaskPushNotificationConfigResult listTaskPushNotificationConfigurations(
113114
ListTaskPushNotificationConfigParams request,
114115
@Nullable ClientCallContext context) throws A2AClientException;
115116

extras/push-notification-config-store-database-jpa/src/main/java/io/a2a/extras/pushnotificationconfigstore/database/jpa/JpaDatabasePushNotificationConfigStore.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.a2a.extras.pushnotificationconfigstore.database.jpa;
22

3+
import java.util.ArrayList;
4+
import java.util.Collections;
35
import java.util.List;
46

57
import jakarta.annotation.Priority;
@@ -11,7 +13,10 @@
1113

1214
import io.a2a.json.JsonProcessingException;
1315
import io.a2a.server.tasks.PushNotificationConfigStore;
16+
import io.a2a.spec.ListTaskPushNotificationConfigParams;
17+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
1418
import io.a2a.spec.PushNotificationConfig;
19+
import io.a2a.spec.TaskPushNotificationConfig;
1520
import org.slf4j.Logger;
1621
import org.slf4j.LoggerFactory;
1722

@@ -65,7 +70,8 @@ public PushNotificationConfig setInfo(String taskId, PushNotificationConfig noti
6570

6671
@Transactional
6772
@Override
68-
public List<PushNotificationConfig> getInfo(String taskId) {
73+
public ListTaskPushNotificationConfigResult getInfo(ListTaskPushNotificationConfigParams params) {
74+
String taskId = params.id();
6975
LOGGER.debug("Retrieving PushNotificationConfigs for Task '{}'", taskId);
7076
try {
7177
List<JpaPushNotificationConfig> jpaConfigs = em.createQuery(
@@ -88,13 +94,58 @@ public List<PushNotificationConfig> getInfo(String taskId) {
8894
.toList();
8995

9096
LOGGER.debug("Successfully retrieved {} PushNotificationConfigs for Task '{}'", configs.size(), taskId);
91-
return configs;
97+
98+
// Handle pagination
99+
if (configs.isEmpty()) {
100+
return new ListTaskPushNotificationConfigResult(Collections.emptyList());
101+
}
102+
103+
if (params.pageSize() <= 0) {
104+
return new ListTaskPushNotificationConfigResult(convertPushNotificationConfig(configs, params), null);
105+
}
106+
107+
// Apply pageToken filtering if provided
108+
List<PushNotificationConfig> paginatedConfigs = configs;
109+
if (params.pageToken() != null && !params.pageToken().isBlank()) {
110+
int index = findFirstIndex(configs, params.pageToken());
111+
if (index < configs.size()) {
112+
paginatedConfigs = configs.subList(index, configs.size());
113+
}
114+
}
115+
116+
// Apply page size limit
117+
if (paginatedConfigs.size() <= params.pageSize()) {
118+
return new ListTaskPushNotificationConfigResult(convertPushNotificationConfig(paginatedConfigs, params), null);
119+
}
120+
121+
String nextToken = paginatedConfigs.get(params.pageSize()).token();
122+
return new ListTaskPushNotificationConfigResult(
123+
convertPushNotificationConfig(paginatedConfigs.subList(0, params.pageSize()), params),
124+
nextToken);
92125
} catch (Exception e) {
93126
LOGGER.error("Failed to retrieve PushNotificationConfigs for Task '{}'", taskId, e);
94127
throw e;
95128
}
96129
}
97130

131+
private int findFirstIndex(List<PushNotificationConfig> configs, String token) {
132+
for (int i = 0; i < configs.size(); i++) {
133+
if (token.equals(configs.get(i).token())) {
134+
return i;
135+
}
136+
}
137+
return configs.size();
138+
}
139+
140+
private List<TaskPushNotificationConfig> convertPushNotificationConfig(List<PushNotificationConfig> pushNotificationConfigList, ListTaskPushNotificationConfigParams params) {
141+
List<TaskPushNotificationConfig> taskPushNotificationConfigList = new ArrayList<>(pushNotificationConfigList.size());
142+
for (PushNotificationConfig pushNotificationConfig : pushNotificationConfigList) {
143+
TaskPushNotificationConfig taskPushNotificationConfig = new TaskPushNotificationConfig(params.id(), pushNotificationConfig, params.tenant());
144+
taskPushNotificationConfigList.add(taskPushNotificationConfig);
145+
}
146+
return taskPushNotificationConfigList;
147+
}
148+
98149
@Transactional
99150
@Override
100151
public void deleteInfo(String taskId, String configId) {

0 commit comments

Comments
 (0)