Skip to content

Commit 6b17fb3

Browse files
committed
feat: add list tasks functionality
1 parent 204ffad commit 6b17fb3

File tree

28 files changed

+1576
-25
lines changed

28 files changed

+1576
-25
lines changed

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import io.a2a.spec.DeleteTaskPushNotificationConfigParams;
1414
import io.a2a.spec.GetTaskPushNotificationConfigParams;
1515
import io.a2a.spec.ListTaskPushNotificationConfigParams;
16+
import io.a2a.spec.ListTasksParams;
17+
import io.a2a.spec.ListTasksResult;
1618
import io.a2a.spec.Message;
1719
import io.a2a.spec.PushNotificationConfig;
1820
import io.a2a.spec.Task;
@@ -172,6 +174,27 @@ public Task getTask(TaskQueryParams request) throws A2AClientException {
172174
*/
173175
public abstract Task getTask(TaskQueryParams request, @Nullable ClientCallContext context) throws A2AClientException;
174176

177+
/**
178+
* List tasks with optional filtering and pagination.
179+
*
180+
* @param request the list tasks parameters including filters and pagination
181+
* @return the list tasks result containing tasks and pagination information
182+
* @throws A2AClientException if listing tasks fails for any reason
183+
*/
184+
public ListTasksResult listTasks(ListTasksParams request) throws A2AClientException {
185+
return listTasks(request, null);
186+
}
187+
188+
/**
189+
* List tasks with optional filtering and pagination.
190+
*
191+
* @param request the list tasks parameters including filters and pagination
192+
* @param context optional client call context for the request (may be {@code null})
193+
* @return the list tasks result containing tasks and pagination information
194+
* @throws A2AClientException if listing tasks fails for any reason
195+
*/
196+
public abstract ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallContext context) throws A2AClientException;
197+
175198
/**
176199
* Request the agent to cancel a specific task.
177200
*

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import io.a2a.spec.EventKind;
1717
import io.a2a.spec.GetTaskPushNotificationConfigParams;
1818
import io.a2a.spec.ListTaskPushNotificationConfigParams;
19+
import io.a2a.spec.ListTasksParams;
20+
import io.a2a.spec.ListTasksResult;
1921
import io.a2a.spec.Message;
2022
import io.a2a.spec.MessageSendConfiguration;
2123
import io.a2a.spec.MessageSendParams;
@@ -85,6 +87,11 @@ public Task getTask(TaskQueryParams request, @Nullable ClientCallContext context
8587
return clientTransport.getTask(request, context);
8688
}
8789

90+
@Override
91+
public ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallContext context) throws A2AClientException {
92+
return clientTransport.listTasks(request, context);
93+
}
94+
8895
@Override
8996
public Task cancelTask(TaskIdParams request, @Nullable ClientCallContext context) throws A2AClientException {
9097
return clientTransport.cancelTask(request, context);

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.a2a.grpc.GetTaskPushNotificationConfigRequest;
2323
import io.a2a.grpc.GetTaskRequest;
2424
import io.a2a.grpc.ListTaskPushNotificationConfigRequest;
25+
import io.a2a.grpc.ListTasksRequest;
2526
import io.a2a.grpc.SendMessageRequest;
2627
import io.a2a.grpc.SendMessageResponse;
2728
import io.a2a.grpc.StreamResponse;
@@ -34,6 +35,8 @@
3435
import io.a2a.spec.EventKind;
3536
import io.a2a.spec.GetTaskPushNotificationConfigParams;
3637
import io.a2a.spec.ListTaskPushNotificationConfigParams;
38+
import io.a2a.spec.ListTasksParams;
39+
import io.a2a.spec.ListTasksResult;
3740
import io.a2a.spec.MessageSendParams;
3841
import io.a2a.spec.SendStreamingMessageRequest;
3942
import io.a2a.spec.SetTaskPushNotificationConfigRequest;
@@ -154,6 +157,51 @@ public Task cancelTask(TaskIdParams request, @Nullable ClientCallContext context
154157
}
155158
}
156159

160+
@Override
161+
public ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallContext context) throws A2AClientException {
162+
checkNotNullParam("request", request);
163+
164+
ListTasksRequest.Builder requestBuilder = ListTasksRequest.newBuilder();
165+
if (request.contextId() != null) {
166+
requestBuilder.setContextId(request.contextId());
167+
}
168+
if (request.status() != null) {
169+
requestBuilder.setStatus(ToProto.taskState(request.status()));
170+
}
171+
if (request.pageSize() != null) {
172+
requestBuilder.setPageSize(request.pageSize());
173+
}
174+
if (request.pageToken() != null) {
175+
requestBuilder.setPageToken(request.pageToken());
176+
}
177+
if (request.historyLength() != null) {
178+
requestBuilder.setHistoryLength(request.historyLength());
179+
}
180+
if (request.includeArtifacts() != null && request.includeArtifacts()) {
181+
requestBuilder.setIncludeArtifacts(true);
182+
}
183+
184+
ListTasksRequest listTasksRequest = requestBuilder.build();
185+
PayloadAndHeaders payloadAndHeaders = applyInterceptors(io.a2a.spec.ListTasksRequest.METHOD, listTasksRequest,
186+
agentCard, context);
187+
188+
try {
189+
A2AServiceBlockingV2Stub stubWithMetadata = createBlockingStubWithMetadata(context, payloadAndHeaders);
190+
io.a2a.grpc.ListTasksResponse grpcResponse = stubWithMetadata.listTasks(listTasksRequest);
191+
192+
return new io.a2a.spec.ListTasksResult(
193+
grpcResponse.getTasksList().stream()
194+
.map(FromProto::task)
195+
.collect(Collectors.toList()),
196+
grpcResponse.getTotalSize(),
197+
grpcResponse.getTasksCount(),
198+
grpcResponse.getNextPageToken().isEmpty() ? null : grpcResponse.getNextPageToken()
199+
);
200+
} catch (StatusRuntimeException e) {
201+
throw GrpcErrorMapper.mapGrpcError(e, "Failed to list tasks: ");
202+
}
203+
}
204+
157205
@Override
158206
public TaskPushNotificationConfig setTaskPushNotificationConfiguration(TaskPushNotificationConfig request,
159207
@Nullable ClientCallContext context) throws A2AClientException {

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
import io.a2a.spec.ListTaskPushNotificationConfigParams;
4141
import io.a2a.spec.ListTaskPushNotificationConfigRequest;
4242
import io.a2a.spec.ListTaskPushNotificationConfigResponse;
43+
import io.a2a.spec.ListTasksParams;
44+
import io.a2a.spec.ListTasksRequest;
45+
import io.a2a.spec.ListTasksResponse;
46+
import io.a2a.spec.ListTasksResult;
4347
import io.a2a.spec.DeleteTaskPushNotificationConfigRequest;
4448
import io.a2a.spec.DeleteTaskPushNotificationConfigResponse;
4549
import io.a2a.spec.MessageSendParams;
@@ -66,6 +70,7 @@ public class JSONRPCTransport implements ClientTransport {
6670
private static final TypeReference<SendMessageResponse> SEND_MESSAGE_RESPONSE_REFERENCE = new TypeReference<>() {};
6771
private static final TypeReference<GetTaskResponse> GET_TASK_RESPONSE_REFERENCE = new TypeReference<>() {};
6872
private static final TypeReference<CancelTaskResponse> CANCEL_TASK_RESPONSE_REFERENCE = new TypeReference<>() {};
73+
private static final TypeReference<ListTasksResponse> LIST_TASKS_RESPONSE_REFERENCE = new TypeReference<>() {};
6974
private static final TypeReference<GetTaskPushNotificationConfigResponse> GET_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
7075
private static final TypeReference<SetTaskPushNotificationConfigResponse> SET_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
7176
private static final TypeReference<ListTaskPushNotificationConfigResponse> LIST_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
@@ -197,6 +202,29 @@ public Task cancelTask(TaskIdParams request, @Nullable ClientCallContext context
197202
}
198203
}
199204

205+
@Override
206+
public ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallContext context) throws A2AClientException {
207+
checkNotNullParam("request", request);
208+
ListTasksRequest listTasksRequest = new ListTasksRequest.Builder()
209+
.jsonrpc(JSONRPCMessage.JSONRPC_VERSION)
210+
.method(ListTasksRequest.METHOD)
211+
.params(request)
212+
.build(); // id will be randomly generated
213+
214+
PayloadAndHeaders payloadAndHeaders = applyInterceptors(ListTasksRequest.METHOD, listTasksRequest,
215+
agentCard, context);
216+
217+
try {
218+
String httpResponseBody = sendPostRequest(payloadAndHeaders);
219+
ListTasksResponse response = unmarshalResponse(httpResponseBody, LIST_TASKS_RESPONSE_REFERENCE);
220+
return response.getResult();
221+
} catch (A2AClientException e) {
222+
throw e;
223+
} catch (IOException | InterruptedException e) {
224+
throw new A2AClientException("Failed to list tasks: " + e, e);
225+
}
226+
}
227+
200228
@Override
201229
public TaskPushNotificationConfig setTaskPushNotificationConfiguration(TaskPushNotificationConfig request,
202230
@Nullable ClientCallContext context) throws A2AClientException {

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

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@
2020
import io.a2a.grpc.GetTaskPushNotificationConfigRequest;
2121
import io.a2a.grpc.GetTaskRequest;
2222
import io.a2a.grpc.ListTaskPushNotificationConfigRequest;
23+
import io.a2a.grpc.ListTasksRequest;
2324
import io.a2a.spec.TaskPushNotificationConfig;
2425
import io.a2a.spec.A2AClientException;
2526
import io.a2a.spec.AgentCard;
2627
import io.a2a.spec.DeleteTaskPushNotificationConfigParams;
2728
import io.a2a.spec.EventKind;
2829
import io.a2a.spec.GetTaskPushNotificationConfigParams;
2930
import io.a2a.spec.ListTaskPushNotificationConfigParams;
31+
import io.a2a.spec.ListTasksParams;
32+
import io.a2a.spec.ListTasksResult;
3033
import io.a2a.spec.MessageSendParams;
3134
import io.a2a.spec.StreamingEventKind;
3235
import io.a2a.spec.Task;
@@ -38,6 +41,8 @@
3841
import io.a2a.spec.SetTaskPushNotificationConfigRequest;
3942
import io.a2a.util.Utils;
4043
import java.io.IOException;
44+
import java.net.URLEncoder;
45+
import java.nio.charset.StandardCharsets;
4146
import java.util.Collections;
4247
import java.util.List;
4348
import java.util.logging.Logger;
@@ -170,6 +175,97 @@ public Task cancelTask(TaskIdParams taskIdParams, @Nullable ClientCallContext co
170175
}
171176
}
172177

178+
@Override
179+
public ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallContext context) throws A2AClientException {
180+
checkNotNullParam("request", request);
181+
ListTasksRequest.Builder builder = ListTasksRequest.newBuilder();
182+
if (request.contextId() != null) {
183+
builder.setContextId(request.contextId());
184+
}
185+
if (request.status() != null) {
186+
builder.setStatus(ProtoUtils.ToProto.taskState(request.status()));
187+
}
188+
if (request.pageSize() != null) {
189+
builder.setPageSize(request.pageSize());
190+
}
191+
if (request.pageToken() != null) {
192+
builder.setPageToken(request.pageToken());
193+
}
194+
if (request.historyLength() != null) {
195+
builder.setHistoryLength(request.historyLength());
196+
}
197+
if (request.includeArtifacts() != null && request.includeArtifacts()) {
198+
builder.setIncludeArtifacts(true);
199+
}
200+
201+
PayloadAndHeaders payloadAndHeaders = applyInterceptors(io.a2a.spec.ListTasksRequest.METHOD, builder,
202+
agentCard, context);
203+
204+
try {
205+
// Build query string
206+
StringBuilder urlBuilder = new StringBuilder(agentUrl).append("/v1/tasks");
207+
String queryParams = buildListTasksQueryString(request);
208+
if (!queryParams.isEmpty()) {
209+
urlBuilder.append("?").append(queryParams);
210+
}
211+
212+
A2AHttpClient.GetBuilder getBuilder = httpClient.createGet().url(urlBuilder.toString());
213+
if (payloadAndHeaders.getHeaders() != null) {
214+
for (Map.Entry<String, String> entry : payloadAndHeaders.getHeaders().entrySet()) {
215+
getBuilder.addHeader(entry.getKey(), entry.getValue());
216+
}
217+
}
218+
A2AHttpResponse response = getBuilder.get();
219+
if (!response.success()) {
220+
throw RestErrorMapper.mapRestError(response);
221+
}
222+
String httpResponseBody = response.body();
223+
io.a2a.grpc.ListTasksResponse.Builder responseBuilder = io.a2a.grpc.ListTasksResponse.newBuilder();
224+
JsonFormat.parser().merge(httpResponseBody, responseBuilder);
225+
226+
return new ListTasksResult(
227+
responseBuilder.getTasksList().stream()
228+
.map(ProtoUtils.FromProto::task)
229+
.toList(),
230+
responseBuilder.getTotalSize(),
231+
responseBuilder.getTasksCount(),
232+
responseBuilder.getNextPageToken().isEmpty() ? null : responseBuilder.getNextPageToken()
233+
);
234+
} catch (A2AClientException e) {
235+
throw e;
236+
} catch (IOException | InterruptedException e) {
237+
throw new A2AClientException("Failed to list tasks: " + e, e);
238+
}
239+
}
240+
241+
private String buildListTasksQueryString(ListTasksParams request) {
242+
StringBuilder sb = new StringBuilder();
243+
if (request.contextId() != null) {
244+
sb.append("contextId=").append(URLEncoder.encode(request.contextId(), StandardCharsets.UTF_8));
245+
}
246+
if (request.status() != null) {
247+
if (sb.length() > 0) sb.append("&");
248+
sb.append("status=").append(request.status().asString());
249+
}
250+
if (request.pageSize() != null) {
251+
if (sb.length() > 0) sb.append("&");
252+
sb.append("pageSize=").append(request.pageSize());
253+
}
254+
if (request.pageToken() != null) {
255+
if (sb.length() > 0) sb.append("&");
256+
sb.append("pageToken=").append(URLEncoder.encode(request.pageToken(), StandardCharsets.UTF_8));
257+
}
258+
if (request.historyLength() != null) {
259+
if (sb.length() > 0) sb.append("&");
260+
sb.append("historyLength=").append(request.historyLength());
261+
}
262+
if (request.includeArtifacts() != null && request.includeArtifacts()) {
263+
if (sb.length() > 0) sb.append("&");
264+
sb.append("includeArtifacts=true");
265+
}
266+
return sb.toString();
267+
}
268+
173269
@Override
174270
public TaskPushNotificationConfig setTaskPushNotificationConfiguration(TaskPushNotificationConfig request, @Nullable ClientCallContext context) throws A2AClientException {
175271
checkNotNullParam("request", request);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import io.a2a.spec.EventKind;
1111
import io.a2a.spec.GetTaskPushNotificationConfigParams;
1212
import io.a2a.spec.ListTaskPushNotificationConfigParams;
13+
import io.a2a.spec.ListTasksParams;
14+
import io.a2a.spec.ListTasksResult;
1315
import io.a2a.spec.MessageSendParams;
1416
import io.a2a.spec.StreamingEventKind;
1517
import io.a2a.spec.Task;
@@ -66,6 +68,16 @@ void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEventKind
6668
*/
6769
Task cancelTask(TaskIdParams request, @Nullable ClientCallContext context) throws A2AClientException;
6870

71+
/**
72+
* List tasks with optional filtering and pagination.
73+
*
74+
* @param request the list tasks parameters including filters and pagination
75+
* @param context optional client call context for the request (may be {@code null})
76+
* @return the list tasks result containing tasks and pagination information
77+
* @throws A2AClientException if listing tasks fails for any reason
78+
*/
79+
ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallContext context) throws A2AClientException;
80+
6981
/**
7082
* Set or update the push notification configuration for a specific task.
7183
*

0 commit comments

Comments
 (0)