Skip to content

Commit 83304bb

Browse files
a2a-botkthota-g
andauthored
feat: Type update to support fetching extended card (#361)
# Description This change adds required types for new JSON-RPC method `agent/getAuthenticatedExtendedCard` for fetching authenticated extended card. Release-As: 0.3.0 --------- Co-authored-by: Krishna Thota <[email protected]>
1 parent 2444034 commit 83304bb

File tree

3 files changed

+163
-5
lines changed

3 files changed

+163
-5
lines changed

src/a2a/types.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,26 @@ class AgentSkill(A2ABaseModel):
172172
"""
173173

174174

175+
class AuthenticatedExtendedCardNotConfiguredError(A2ABaseModel):
176+
"""
177+
An A2A-specific error indicating that the agent does not have an Authenticated Extended Card configured
178+
"""
179+
180+
code: Literal[-32007] = -32007
181+
"""
182+
The error code for when an authenticated extended card is not configured.
183+
"""
184+
data: Any | None = None
185+
"""
186+
A primitive or structured value containing additional information about the error.
187+
This may be omitted.
188+
"""
189+
message: str | None = 'Authenticated Extended Card is not configured'
190+
"""
191+
The error message.
192+
"""
193+
194+
175195
class AuthorizationCodeOAuthFlow(A2ABaseModel):
176196
"""
177197
Defines configuration details for the OAuth 2.0 Authorization Code flow.
@@ -375,6 +395,27 @@ class FileWithUri(A2ABaseModel):
375395
"""
376396

377397

398+
class GetAuthenticatedExtendedCardRequest(A2ABaseModel):
399+
"""
400+
Represents a JSON-RPC request for the `agent/getAuthenticatedExtendedCard` method.
401+
"""
402+
403+
id: str | int
404+
"""
405+
The identifier for this request.
406+
"""
407+
jsonrpc: Literal['2.0'] = '2.0'
408+
"""
409+
The version of the JSON-RPC protocol. MUST be exactly "2.0".
410+
"""
411+
method: Literal['agent/getAuthenticatedExtendedCard'] = (
412+
'agent/getAuthenticatedExtendedCard'
413+
)
414+
"""
415+
The method name. Must be 'agent/getAuthenticatedExtendedCard'.
416+
"""
417+
418+
378419
class GetTaskPushNotificationConfigParams(A2ABaseModel):
379420
"""
380421
Defines parameters for fetching a specific push notification configuration for a task.
@@ -999,6 +1040,7 @@ class A2AError(
9991040
| UnsupportedOperationError
10001041
| ContentTypeNotSupportedError
10011042
| InvalidAgentResponseError
1043+
| AuthenticatedExtendedCardNotConfiguredError
10021044
]
10031045
):
10041046
root: (
@@ -1013,6 +1055,7 @@ class A2AError(
10131055
| UnsupportedOperationError
10141056
| ContentTypeNotSupportedError
10151057
| InvalidAgentResponseError
1058+
| AuthenticatedExtendedCardNotConfiguredError
10161059
)
10171060
"""
10181061
A discriminated union of all standard JSON-RPC and A2A-specific error types.
@@ -1170,6 +1213,7 @@ class JSONRPCErrorResponse(A2ABaseModel):
11701213
| UnsupportedOperationError
11711214
| ContentTypeNotSupportedError
11721215
| InvalidAgentResponseError
1216+
| AuthenticatedExtendedCardNotConfiguredError
11731217
)
11741218
"""
11751219
An object describing the error that occurred.
@@ -1625,6 +1669,7 @@ class A2ARequest(
16251669
| TaskResubscriptionRequest
16261670
| ListTaskPushNotificationConfigRequest
16271671
| DeleteTaskPushNotificationConfigRequest
1672+
| GetAuthenticatedExtendedCardRequest
16281673
]
16291674
):
16301675
root: (
@@ -1637,6 +1682,7 @@ class A2ARequest(
16371682
| TaskResubscriptionRequest
16381683
| ListTaskPushNotificationConfigRequest
16391684
| DeleteTaskPushNotificationConfigRequest
1685+
| GetAuthenticatedExtendedCardRequest
16401686
)
16411687
"""
16421688
A discriminated union representing all possible JSON-RPC 2.0 requests supported by the A2A specification.
@@ -1750,6 +1796,25 @@ class AgentCard(A2ABaseModel):
17501796
"""
17511797

17521798

1799+
class GetAuthenticatedExtendedCardSuccessResponse(A2ABaseModel):
1800+
"""
1801+
Represents a successful JSON-RPC response for the `agent/getAuthenticatedExtendedCard` method.
1802+
"""
1803+
1804+
id: str | int | None = None
1805+
"""
1806+
The identifier established by the client.
1807+
"""
1808+
jsonrpc: Literal['2.0'] = '2.0'
1809+
"""
1810+
The version of the JSON-RPC protocol. MUST be exactly "2.0".
1811+
"""
1812+
result: AgentCard
1813+
"""
1814+
The result is an Agent Card object.
1815+
"""
1816+
1817+
17531818
class Task(A2ABaseModel):
17541819
"""
17551820
Represents a single, stateful operation or conversation between a client and an agent.
@@ -1769,7 +1834,7 @@ class Task(A2ABaseModel):
17691834
"""
17701835
id: str
17711836
"""
1772-
A unique identifier for the task, generated by the client for a new task or provided by the agent.
1837+
A unique identifier for the task, generated by the server for a new task.
17731838
"""
17741839
kind: Literal['task'] = 'task'
17751840
"""
@@ -1804,6 +1869,17 @@ class CancelTaskSuccessResponse(A2ABaseModel):
18041869
"""
18051870

18061871

1872+
class GetAuthenticatedExtendedCardResponse(
1873+
RootModel[
1874+
JSONRPCErrorResponse | GetAuthenticatedExtendedCardSuccessResponse
1875+
]
1876+
):
1877+
root: JSONRPCErrorResponse | GetAuthenticatedExtendedCardSuccessResponse
1878+
"""
1879+
Represents a JSON-RPC response for the `agent/getAuthenticatedExtendedCard` method.
1880+
"""
1881+
1882+
18071883
class GetTaskSuccessResponse(A2ABaseModel):
18081884
"""
18091885
Represents a successful JSON-RPC response for the `tasks/get` method.
@@ -1889,6 +1965,7 @@ class JSONRPCResponse(
18891965
| GetTaskPushNotificationConfigSuccessResponse
18901966
| ListTaskPushNotificationConfigSuccessResponse
18911967
| DeleteTaskPushNotificationConfigSuccessResponse
1968+
| GetAuthenticatedExtendedCardSuccessResponse
18921969
]
18931970
):
18941971
root: (
@@ -1901,6 +1978,7 @@ class JSONRPCResponse(
19011978
| GetTaskPushNotificationConfigSuccessResponse
19021979
| ListTaskPushNotificationConfigSuccessResponse
19031980
| DeleteTaskPushNotificationConfigSuccessResponse
1981+
| GetAuthenticatedExtendedCardSuccessResponse
19041982
)
19051983
"""
19061984
A discriminated union representing all possible JSON-RPC 2.0 responses

tests/server/test_integration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,8 @@ def test_invalid_request_structure(client: TestClient):
836836
'/',
837837
json={
838838
# Missing required fields
839-
'id': '123'
839+
'id': '123',
840+
'method': 'foo/bar',
840841
},
841842
)
842843
assert response.status_code == 200

tests/test_types.py

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
FileWithUri,
2525
GetTaskPushNotificationConfigParams,
2626
GetTaskPushNotificationConfigRequest,
27+
GetAuthenticatedExtendedCardRequest,
28+
GetAuthenticatedExtendedCardResponse,
29+
GetAuthenticatedExtendedCardSuccessResponse,
2730
GetTaskPushNotificationConfigResponse,
2831
GetTaskPushNotificationConfigSuccessResponse,
2932
GetTaskRequest,
@@ -968,6 +971,21 @@ def test_a2a_request_root_model() -> None:
968971
assert isinstance(a2a_req_task_resubscribe_req.root.params, TaskIdParams)
969972
assert a2a_req_task_resubscribe_req.root.method == 'tasks/resubscribe'
970973

974+
# GetAuthenticatedExtendedCardRequest
975+
get_auth_card_req_data: dict[str, Any] = {
976+
'jsonrpc': '2.0',
977+
'method': 'agent/getAuthenticatedExtendedCard',
978+
'id': 2,
979+
}
980+
a2a_req_get_auth_card = A2ARequest.model_validate(get_auth_card_req_data)
981+
assert isinstance(
982+
a2a_req_get_auth_card.root, GetAuthenticatedExtendedCardRequest
983+
)
984+
assert (
985+
a2a_req_get_auth_card.root.method
986+
== 'agent/getAuthenticatedExtendedCard'
987+
)
988+
971989
# Invalid method case
972990
invalid_req_data: dict[str, Any] = {
973991
'jsonrpc': '2.0',
@@ -1055,6 +1073,14 @@ def test_a2a_request_root_model_id_validation() -> None:
10551073
with pytest.raises(ValidationError):
10561074
A2ARequest.model_validate(task_resubscribe_req_data)
10571075

1076+
# GetAuthenticatedExtendedCardRequest
1077+
get_auth_card_req_data: dict[str, Any] = {
1078+
'jsonrpc': '2.0',
1079+
'method': 'agent/getAuthenticatedExtendedCard',
1080+
}
1081+
with pytest.raises(ValidationError):
1082+
A2ARequest.model_validate(get_auth_card_req_data) # missing id
1083+
10581084

10591085
def test_content_type_not_supported_error():
10601086
# Test ContentTypeNotSupportedError
@@ -1544,11 +1570,11 @@ def test_camelCase() -> None:
15441570
description='Just a hello world agent',
15451571
url='http://localhost:9999/',
15461572
version='1.0.0',
1547-
defaultInputModes=['text'],
1548-
defaultOutputModes=['text'],
1573+
defaultInputModes=['text'], # type: ignore
1574+
defaultOutputModes=['text'], # type: ignore
15491575
capabilities=AgentCapabilities(streaming=True),
15501576
skills=[skill],
1551-
supportsAuthenticatedExtendedCard=True,
1577+
supportsAuthenticatedExtendedCard=True, # type: ignore
15521578
)
15531579

15541580
# Test setting an attribute via camelCase alias
@@ -1568,3 +1594,56 @@ def test_camelCase() -> None:
15681594
assert agent_card.supports_authenticated_extended_card is False
15691595
assert default_input_modes == ['text']
15701596
assert agent_card.default_input_modes == ['text']
1597+
1598+
1599+
def test_get_authenticated_extended_card_request() -> None:
1600+
req_data: dict[str, Any] = {
1601+
'jsonrpc': '2.0',
1602+
'method': 'agent/getAuthenticatedExtendedCard',
1603+
'id': 5,
1604+
}
1605+
req = GetAuthenticatedExtendedCardRequest.model_validate(req_data)
1606+
assert req.method == 'agent/getAuthenticatedExtendedCard'
1607+
assert req.id == 5
1608+
# This request has no params, so we don't check for that.
1609+
1610+
with pytest.raises(ValidationError): # Wrong method literal
1611+
GetAuthenticatedExtendedCardRequest.model_validate(
1612+
{**req_data, 'method': 'wrong/method'}
1613+
)
1614+
1615+
with pytest.raises(ValidationError): # Missing id
1616+
GetAuthenticatedExtendedCardRequest.model_validate(
1617+
{'jsonrpc': '2.0', 'method': 'agent/getAuthenticatedExtendedCard'}
1618+
)
1619+
1620+
1621+
def test_get_authenticated_extended_card_response() -> None:
1622+
resp_data: dict[str, Any] = {
1623+
'jsonrpc': '2.0',
1624+
'result': MINIMAL_AGENT_CARD,
1625+
'id': 'resp-1',
1626+
}
1627+
resp = GetAuthenticatedExtendedCardResponse.model_validate(resp_data)
1628+
assert resp.root.id == 'resp-1'
1629+
assert isinstance(resp.root, GetAuthenticatedExtendedCardSuccessResponse)
1630+
assert isinstance(resp.root.result, AgentCard)
1631+
assert resp.root.result.name == 'TestAgent'
1632+
1633+
with pytest.raises(ValidationError): # Result is not an AgentCard
1634+
GetAuthenticatedExtendedCardResponse.model_validate(
1635+
{'jsonrpc': '2.0', 'result': {'wrong': 'data'}, 'id': 1}
1636+
)
1637+
1638+
resp_data_err: dict[str, Any] = {
1639+
'jsonrpc': '2.0',
1640+
'error': JSONRPCError(**TaskNotFoundError().model_dump()),
1641+
'id': 'resp-1',
1642+
}
1643+
resp_err = GetAuthenticatedExtendedCardResponse.model_validate(
1644+
resp_data_err
1645+
)
1646+
assert resp_err.root.id == 'resp-1'
1647+
assert isinstance(resp_err.root, JSONRPCErrorResponse)
1648+
assert resp_err.root.error is not None
1649+
assert isinstance(resp_err.root.error, JSONRPCError)

0 commit comments

Comments
 (0)