Skip to content

Commit 5682458

Browse files
a2a-botlkawkalkawkaholtskinner
authored andcommitted
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 <lkawka@google.com> Co-authored-by: lkawka <luk.kawka@gmail.com> Co-authored-by: Holt Skinner <13262395+holtskinner@users.noreply.github.com>
1 parent 2acd838 commit 5682458

35 files changed

+1630
-48
lines changed

src/a2a/client/base_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from a2a.types import (
1919
AgentCard,
2020
GetTaskPushNotificationConfigParams,
21+
ListTasksParams,
22+
ListTasksResult,
2123
Message,
2224
MessageSendConfiguration,
2325
MessageSendParams,
@@ -173,6 +175,15 @@ async def get_task(
173175
request, context=context, extensions=extensions
174176
)
175177

178+
async def list_tasks(
179+
self,
180+
request: ListTasksParams,
181+
*,
182+
context: ClientCallContext | None = None,
183+
) -> ListTasksResult:
184+
"""Retrieves tasks for an agent."""
185+
return await self._transport.list_tasks(request, context=context)
186+
176187
async def cancel_task(
177188
self,
178189
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
@@ -8,6 +8,8 @@
88
from a2a.types import (
99
AgentCard,
1010
GetTaskPushNotificationConfigParams,
11+
ListTasksParams,
12+
ListTasksResult,
1113
Message,
1214
MessageSendParams,
1315
Task,
@@ -69,6 +71,15 @@ async def get_task(
6971
) -> Task:
7072
"""Retrieves the current state and history of a specific task."""
7173

74+
@abstractmethod
75+
async def list_tasks(
76+
self,
77+
request: ListTasksParams,
78+
*,
79+
context: ClientCallContext | None = None,
80+
) -> ListTasksResult:
81+
"""Retrieves tasks for an agent."""
82+
7283
@abstractmethod
7384
async def cancel_task(
7485
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, Callable
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,
@@ -169,6 +173,19 @@ async def get_task(
169173
)
170174
return proto_utils.FromProto.task(task)
171175

176+
async def list_tasks(
177+
self,
178+
request: ListTasksParams,
179+
*,
180+
context: ClientCallContext | None = None,
181+
) -> ListTasksResult:
182+
"""Retrieves tasks for an agent."""
183+
response = await self.stub.ListTasks(
184+
proto_utils.ToProto.list_tasks_request(request)
185+
)
186+
page_size = request.page_size or DEFAULT_LIST_TASKS_PAGE_SIZE
187+
return proto_utils.FromProto.list_tasks_result(response, page_size)
188+
172189
async def cancel_task(
173190
self,
174191
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,
@@ -246,6 +250,26 @@ async def get_task(
246250
raise A2AClientJSONRPCError(response.root)
247251
return response.root.result
248252

253+
async def list_tasks(
254+
self,
255+
request: ListTasksParams,
256+
*,
257+
context: ClientCallContext | None = None,
258+
) -> ListTasksResult:
259+
"""Retrieves tasks for an agent."""
260+
rpc_request = ListTasksRequest(params=request, id=str(uuid4()))
261+
payload, modified_kwargs = await self._apply_interceptors(
262+
'tasks/list',
263+
rpc_request.model_dump(mode='json', exclude_none=True),
264+
self._get_http_args(context),
265+
context,
266+
)
267+
response_data = await self._send_request(payload, modified_kwargs)
268+
response = ListTasksResponse.model_validate(response_data)
269+
if isinstance(response.root, JSONRPCErrorResponse):
270+
raise A2AClientJSONRPCError(response.root)
271+
return response.root.result
272+
249273
async def cancel_task(
250274
self,
251275
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 (
@@ -22,6 +23,8 @@
2223
from a2a.types import (
2324
AgentCard,
2425
GetTaskPushNotificationConfigParams,
26+
ListTasksParams,
27+
ListTasksResult,
2528
Message,
2629
MessageSendParams,
2730
Task,
@@ -32,6 +35,7 @@
3235
TaskStatusUpdateEvent,
3336
)
3437
from a2a.utils import proto_utils
38+
from a2a.utils.constants import DEFAULT_LIST_TASKS_PAGE_SIZE
3539
from a2a.utils.telemetry import SpanKind, trace_class
3640

3741

@@ -252,6 +256,28 @@ async def get_task(
252256
ParseDict(response_data, task)
253257
return proto_utils.FromProto.task(task)
254258

259+
async def list_tasks(
260+
self,
261+
request: ListTasksParams,
262+
*,
263+
context: ClientCallContext | None = None,
264+
) -> ListTasksResult:
265+
"""Retrieves tasks for an agent."""
266+
_, modified_kwargs = await self._apply_interceptors(
267+
request.model_dump(mode='json', exclude_none=True),
268+
self._get_http_args(context),
269+
context,
270+
)
271+
response_data = await self._send_get_request(
272+
'/v1/tasks',
273+
_model_to_query_params(request),
274+
modified_kwargs,
275+
)
276+
response = a2a_pb2.ListTasksResponse()
277+
ParseDict(response_data, response)
278+
page_size = request.page_size or DEFAULT_LIST_TASKS_PAGE_SIZE
279+
return proto_utils.FromProto.list_tasks_result(response, page_size)
280+
255281
async def cancel_task(
256282
self,
257283
request: TaskIdParams,
@@ -425,3 +451,21 @@ async def get_card(
425451
async def close(self) -> None:
426452
"""Closes the httpx client."""
427453
await self.httpx_client.aclose()
454+
455+
456+
def _model_to_query_params(instance: BaseModel) -> dict[str, str]:
457+
data = instance.model_dump(mode='json', exclude_none=True)
458+
return _json_to_query_params(data)
459+
460+
461+
def _json_to_query_params(data: dict[str, Any]) -> dict[str, str]:
462+
query_dict = {}
463+
for key, value in data.items():
464+
if isinstance(value, list):
465+
query_dict[key] = ','.join(map(str, value))
466+
elif isinstance(value, bool):
467+
query_dict[key] = str(value).lower()
468+
else:
469+
query_dict[key] = str(value)
470+
471+
return query_dict

0 commit comments

Comments
 (0)