Skip to content

Commit 2334c36

Browse files
committed
feat: Add pagination support to ListTaskPushNotificationConfig with Result wrapper
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 Signed-off-by: Emmanuel Hugonnet <[email protected]>
1 parent bed152d commit 2334c36

File tree

25 files changed

+174
-86
lines changed

25 files changed

+174
-86
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

reference/jsonrpc/src/test/java/io/a2a/server/apps/quarkus/A2AServerRoutesTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static io.a2a.spec.AgentCard.CURRENT_PROTOCOL_VERSION;
44
import static io.a2a.transport.jsonrpc.context.JSONRPCContextKeys.METHOD_NAME_KEY;
5+
import static java.util.Collections.singletonList;
56
import static org.junit.jupiter.api.Assertions.assertEquals;
67
import static org.junit.jupiter.api.Assertions.assertNotNull;
78
import static org.mockito.ArgumentMatchers.any;
@@ -33,6 +34,7 @@
3334
import io.a2a.spec.GetTaskResponse;
3435
import io.a2a.spec.ListTaskPushNotificationConfigRequest;
3536
import io.a2a.spec.ListTaskPushNotificationConfigResponse;
37+
import io.a2a.spec.ListTaskPushNotificationConfigResult;
3638
import io.a2a.spec.PushNotificationConfig;
3739
import io.a2a.spec.SendMessageRequest;
3840
import io.a2a.spec.SendMessageResponse;
@@ -421,7 +423,7 @@ public void testListTaskPushNotificationConfig_MethodNameSetInContext() {
421423
.build(),
422424
null
423425
);
424-
ListTaskPushNotificationConfigResponse realResponse = new ListTaskPushNotificationConfigResponse("1", Collections.singletonList(config));
426+
ListTaskPushNotificationConfigResponse realResponse = new ListTaskPushNotificationConfigResponse("1", new ListTaskPushNotificationConfigResult(singletonList(config)));
425427
when(mockJsonRpcHandler.listPushNotificationConfig(any(ListTaskPushNotificationConfigRequest.class),
426428
any(ServerCallContext.class))).thenReturn(realResponse);
427429

reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@
5555
import io.a2a.spec.SendStreamingMessageRequest;
5656
import io.a2a.spec.SetTaskPushNotificationConfigRequest;
5757
import io.a2a.spec.SubscribeToTaskRequest;
58+
import io.a2a.util.Utils;
5859
import org.jspecify.annotations.Nullable;
5960

6061
@Singleton
6162
@Authenticated
6263
public class A2AServerRoutes {
6364

65+
private static final String HISTORY_LENGTH_PARAM = "historyLength";
66+
private static final String PAGE_SIZE_PARAM = "pageSize";
67+
private static final String PAGE_TOKEN_PARAM = "pageToken";
68+
6469
@Inject
6570
RestHandler jsonRestHandler;
6671

@@ -125,9 +130,9 @@ public void listTasks(RoutingContext rc) {
125130
if (statusStr != null && !statusStr.isEmpty()) {
126131
statusStr = statusStr.toUpperCase();
127132
}
128-
String pageSizeStr = rc.request().params().get("pageSize");
129-
String pageToken = rc.request().params().get("pageToken");
130-
String historyLengthStr = rc.request().params().get("historyLength");
133+
String pageSizeStr = rc.request().params().get(PAGE_SIZE_PARAM);
134+
String pageToken = rc.request().params().get(PAGE_TOKEN_PARAM);
135+
String historyLengthStr = rc.request().params().get(HISTORY_LENGTH_PARAM);
131136
String lastUpdatedAfter = rc.request().params().get("lastUpdatedAfter");
132137
String includeArtifactsStr = rc.request().params().get("includeArtifacts");
133138

@@ -170,13 +175,13 @@ public void getTask(RoutingContext rc) {
170175
response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad task id"));
171176
} else {
172177
Integer historyLength = null;
173-
if (rc.request().params().contains("history_length")) {
174-
historyLength = Integer.valueOf(rc.request().params().get("history_length"));
178+
if (rc.request().params().contains(HISTORY_LENGTH_PARAM)) {
179+
historyLength = Integer.valueOf(rc.request().params().get(HISTORY_LENGTH_PARAM));
175180
}
176181
response = jsonRestHandler.getTask(taskId, historyLength, extractTenant(rc), context);
177182
}
178183
} catch (NumberFormatException e) {
179-
response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad history_length"));
184+
response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad historyLength"));
180185
} catch (Throwable t) {
181186
response = jsonRestHandler.createErrorResponse(new InternalError(t.getMessage()));
182187
} finally {
@@ -312,8 +317,18 @@ public void listTaskPushNotificationConfigurations(RoutingContext rc) {
312317
if (taskId == null || taskId.isEmpty()) {
313318
response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad task id"));
314319
} else {
315-
response = jsonRestHandler.listTaskPushNotificationConfigurations(taskId, extractTenant(rc), context);
320+
int pageSize = 0;
321+
if (rc.request().params().contains(PAGE_SIZE_PARAM)) {
322+
pageSize = Integer.parseInt(rc.request().params().get(PAGE_SIZE_PARAM));
323+
}
324+
String pageToken = "";
325+
if (rc.request().params().contains(PAGE_TOKEN_PARAM)) {
326+
pageToken = Utils.defaultIfNull(rc.request().params().get(PAGE_TOKEN_PARAM), "");
327+
}
328+
response = jsonRestHandler.listTaskPushNotificationConfigurations(taskId, pageSize, pageToken, extractTenant(rc), context);
316329
}
330+
} catch (NumberFormatException e) {
331+
response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad " + PAGE_SIZE_PARAM));
317332
} catch (Throwable t) {
318333
response = jsonRestHandler.createErrorResponse(new InternalError(t.getMessage()));
319334
} finally {
@@ -355,6 +370,7 @@ private String extractTenant(RoutingContext rc) {
355370
}
356371
return tenantPath;
357372
}
373+
358374
/**
359375
* /**
360376
* Handles incoming GET requests to the agent card endpoint.

reference/rest/src/test/java/io/a2a/server/rest/quarkus/A2AServerRoutesTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.jupiter.api.Assertions.assertEquals;
55
import static org.junit.jupiter.api.Assertions.assertNotNull;
66
import static org.mockito.ArgumentMatchers.any;
7+
import static org.mockito.ArgumentMatchers.anyInt;
78
import static org.mockito.ArgumentMatchers.anyString;
89
import static org.mockito.ArgumentMatchers.eq;
910
import static org.mockito.Mockito.mock;
@@ -252,7 +253,7 @@ public void testListTaskPushNotificationConfigurations_MethodNameSetInContext()
252253
when(mockHttpResponse.getStatusCode()).thenReturn(200);
253254
when(mockHttpResponse.getContentType()).thenReturn("application/json");
254255
when(mockHttpResponse.getBody()).thenReturn("{}");
255-
when(mockRestHandler.listTaskPushNotificationConfigurations(anyString(), anyString(), any(ServerCallContext.class)))
256+
when(mockRestHandler.listTaskPushNotificationConfigurations(anyString(), anyInt(), anyString(), anyString(), any(ServerCallContext.class)))
256257
.thenReturn(mockHttpResponse);
257258

258259
ArgumentCaptor<ServerCallContext> contextCaptor = ArgumentCaptor.forClass(ServerCallContext.class);
@@ -261,7 +262,7 @@ public void testListTaskPushNotificationConfigurations_MethodNameSetInContext()
261262
routes.listTaskPushNotificationConfigurations(mockRoutingContext);
262263

263264
// Assert
264-
verify(mockRestHandler).listTaskPushNotificationConfigurations(eq("task123"), anyString(), contextCaptor.capture());
265+
verify(mockRestHandler).listTaskPushNotificationConfigurations(eq("task123"), anyInt(), anyString(), anyString(), contextCaptor.capture());
265266
ServerCallContext capturedContext = contextCaptor.getValue();
266267
assertNotNull(capturedContext);
267268
assertEquals(ListTaskPushNotificationConfigRequest.METHOD, capturedContext.getState().get(METHOD_NAME_KEY));

0 commit comments

Comments
 (0)