Skip to content

Commit d5818e5

Browse files
a2a-botlkawkalkawkaholtskinner
authored
feat(spec): Add tasks/list method with filtering and pagination to the specification (#511)
Commit: a2aproject/A2A@0a9f629 This PR introduces support for the new `tasks/list` method, including: - Automatically generated type definitions from the specification. - Complete client-side and server-side implementations. Fixes #515 🦕 --------- Co-authored-by: lkawka <[email protected]> Co-authored-by: lkawka <[email protected]> Co-authored-by: Holt Skinner <[email protected]>
1 parent 0111633 commit d5818e5

36 files changed

+1631
-49
lines changed

src/a2a/client/base_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from a2a.types import (
1616
AgentCard,
1717
GetTaskPushNotificationConfigParams,
18+
ListTasksParams,
19+
ListTasksResult,
1820
Message,
1921
MessageSendConfiguration,
2022
MessageSendParams,
@@ -146,6 +148,15 @@ async def get_task(
146148
request, context=context, extensions=extensions
147149
)
148150

151+
async def list_tasks(
152+
self,
153+
request: ListTasksParams,
154+
*,
155+
context: ClientCallContext | None = None,
156+
) -> ListTasksResult:
157+
"""Retrieves tasks for an agent."""
158+
return await self._transport.list_tasks(request, context=context)
159+
149160
async def cancel_task(
150161
self,
151162
request: TaskIdParams,

src/a2a/client/client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from a2a.types import (
1313
AgentCard,
1414
GetTaskPushNotificationConfigParams,
15+
ListTasksParams,
16+
ListTasksResult,
1517
Message,
1618
PushNotificationConfig,
1719
Task,
@@ -137,6 +139,15 @@ async def get_task(
137139
) -> Task:
138140
"""Retrieves the current state and history of a specific task."""
139141

142+
@abstractmethod
143+
async def list_tasks(
144+
self,
145+
request: ListTasksParams,
146+
*,
147+
context: ClientCallContext | None = None,
148+
) -> ListTasksResult:
149+
"""Retrieves tasks for an agent."""
150+
140151
@abstractmethod
141152
async def cancel_task(
142153
self,

src/a2a/client/transports/base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from a2a.types import (
66
AgentCard,
77
GetTaskPushNotificationConfigParams,
8+
ListTasksParams,
9+
ListTasksResult,
810
Message,
911
MessageSendParams,
1012
Task,
@@ -53,6 +55,15 @@ async def get_task(
5355
) -> Task:
5456
"""Retrieves the current state and history of a specific task."""
5557

58+
@abstractmethod
59+
async def list_tasks(
60+
self,
61+
request: ListTasksParams,
62+
*,
63+
context: ClientCallContext | None = None,
64+
) -> ListTasksResult:
65+
"""Retrieves tasks for an agent."""
66+
5667
@abstractmethod
5768
async def cancel_task(
5869
self,

src/a2a/client/transports/grpc.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from collections.abc import AsyncGenerator
44

5+
from a2a.utils.constants import DEFAULT_LIST_TASKS_PAGE_SIZE
6+
57

68
try:
79
import grpc
@@ -22,6 +24,8 @@
2224
from a2a.types import (
2325
AgentCard,
2426
GetTaskPushNotificationConfigParams,
27+
ListTasksParams,
28+
ListTasksResult,
2529
Message,
2630
MessageSendParams,
2731
Task,
@@ -168,6 +172,19 @@ async def get_task(
168172
)
169173
return proto_utils.FromProto.task(task)
170174

175+
async def list_tasks(
176+
self,
177+
request: ListTasksParams,
178+
*,
179+
context: ClientCallContext | None = None,
180+
) -> ListTasksResult:
181+
"""Retrieves tasks for an agent."""
182+
response = await self.stub.ListTasks(
183+
proto_utils.ToProto.list_tasks_request(request)
184+
)
185+
page_size = request.page_size or DEFAULT_LIST_TASKS_PAGE_SIZE
186+
return proto_utils.FromProto.list_tasks_result(response, page_size)
187+
171188
async def cancel_task(
172189
self,
173190
request: TaskIdParams,

src/a2a/client/transports/jsonrpc.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
GetTaskRequest,
3232
GetTaskResponse,
3333
JSONRPCErrorResponse,
34+
ListTasksParams,
35+
ListTasksRequest,
36+
ListTasksResponse,
37+
ListTasksResult,
3438
Message,
3539
MessageSendParams,
3640
SendMessageRequest,
@@ -239,6 +243,26 @@ async def get_task(
239243
raise A2AClientJSONRPCError(response.root)
240244
return response.root.result
241245

246+
async def list_tasks(
247+
self,
248+
request: ListTasksParams,
249+
*,
250+
context: ClientCallContext | None = None,
251+
) -> ListTasksResult:
252+
"""Retrieves tasks for an agent."""
253+
rpc_request = ListTasksRequest(params=request, id=str(uuid4()))
254+
payload, modified_kwargs = await self._apply_interceptors(
255+
'tasks/list',
256+
rpc_request.model_dump(mode='json', exclude_none=True),
257+
self._get_http_args(context),
258+
context,
259+
)
260+
response_data = await self._send_request(payload, modified_kwargs)
261+
response = ListTasksResponse.model_validate(response_data)
262+
if isinstance(response.root, JSONRPCErrorResponse):
263+
raise A2AClientJSONRPCError(response.root)
264+
return response.root.result
265+
242266
async def cancel_task(
243267
self,
244268
request: TaskIdParams,

src/a2a/client/transports/rest.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from google.protobuf.json_format import MessageToDict, Parse, ParseDict
1010
from httpx_sse import SSEError, aconnect_sse
11+
from pydantic import BaseModel
1112

1213
from a2a.client.card_resolver import A2ACardResolver
1314
from a2a.client.errors import A2AClientHTTPError, A2AClientJSONError
@@ -18,6 +19,8 @@
1819
from a2a.types import (
1920
AgentCard,
2021
GetTaskPushNotificationConfigParams,
22+
ListTasksParams,
23+
ListTasksResult,
2124
Message,
2225
MessageSendParams,
2326
Task,
@@ -28,6 +31,7 @@
2831
TaskStatusUpdateEvent,
2932
)
3033
from a2a.utils import proto_utils
34+
from a2a.utils.constants import DEFAULT_LIST_TASKS_PAGE_SIZE
3135
from a2a.utils.telemetry import SpanKind, trace_class
3236

3337

@@ -239,6 +243,28 @@ async def get_task(
239243
ParseDict(response_data, task)
240244
return proto_utils.FromProto.task(task)
241245

246+
async def list_tasks(
247+
self,
248+
request: ListTasksParams,
249+
*,
250+
context: ClientCallContext | None = None,
251+
) -> ListTasksResult:
252+
"""Retrieves tasks for an agent."""
253+
_, modified_kwargs = await self._apply_interceptors(
254+
request.model_dump(mode='json', exclude_none=True),
255+
self._get_http_args(context),
256+
context,
257+
)
258+
response_data = await self._send_get_request(
259+
'/v1/tasks',
260+
_model_to_query_params(request),
261+
modified_kwargs,
262+
)
263+
response = a2a_pb2.ListTasksResponse()
264+
ParseDict(response_data, response)
265+
page_size = request.page_size or DEFAULT_LIST_TASKS_PAGE_SIZE
266+
return proto_utils.FromProto.list_tasks_result(response, page_size)
267+
242268
async def cancel_task(
243269
self,
244270
request: TaskIdParams,
@@ -404,3 +430,21 @@ async def get_card(
404430
async def close(self) -> None:
405431
"""Closes the httpx client."""
406432
await self.httpx_client.aclose()
433+
434+
435+
def _model_to_query_params(instance: BaseModel) -> dict[str, str]:
436+
data = instance.model_dump(mode='json', exclude_none=True)
437+
return _json_to_query_params(data)
438+
439+
440+
def _json_to_query_params(data: dict[str, Any]) -> dict[str, str]:
441+
query_dict = {}
442+
for key, value in data.items():
443+
if isinstance(value, list):
444+
query_dict[key] = ','.join(map(str, value))
445+
elif isinstance(value, bool):
446+
query_dict[key] = str(value).lower()
447+
else:
448+
query_dict[key] = str(value)
449+
450+
return query_dict

0 commit comments

Comments
 (0)