Skip to content
Open
42 changes: 21 additions & 21 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
AAgent
ACMRTUXB
ACard
AClient
ACMRTUXB
aconnect
adk
AError
AFast
agentic
AGrpc
aio
aiomysql
amannn
aproject
ARequest
ARun
AServer
AServers
AService
AStarlette
AUser
DSNs
EUR
GBP
GVsb
INR
JPY
JSONRPCt
JWS
Llm
POSTGRES
RUF
SLF
Tful
aconnect
adk
agentic
aio
aiomysql
amannn
aproject
autouse
backticks
cla
Expand All @@ -29,32 +42,23 @@ coro
datamodel
deepwiki
drivername
DSNs
dunders
euo
EUR
excinfo
fernet
fetchrow
fetchval
GBP
genai
getkwargs
gle
GVsb
ietf
initdb
inmemory
INR
isready
JPY
JSONRPCt
JWS
kwarg
langgraph
lifecycles
linting
Llm
lstrips
mikeas
mockurl
Expand All @@ -64,7 +68,6 @@ oidc
opensource
otherurl
postgres
POSTGRES
postgresql
protoc
pyi
Expand All @@ -74,14 +77,11 @@ pyversions
redef
respx
resub
RUF
SLF
socio
sse
tagwords
taskupdate
testuuid
Tful
tiangolo
typeerror
vulnz
14 changes: 12 additions & 2 deletions src/a2a/client/helpers.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
"""Helper functions for the A2A client."""

from typing import Any
from uuid import uuid4

from a2a.types import Message, Part, Role, TextPart


def create_text_message_object(
role: Role = Role.user, content: str = ''
role: Role = Role.user,
content: str = '',
extensions: list[str] | None = None,
metadata: dict[str, Any] | None = None,
) -> Message:
"""Create a Message object containing a single TextPart.

Args:
role: The role of the message sender (user or agent). Defaults to Role.user.
content: The text content of the message. Defaults to an empty string.
extensions: The extensions of the message. Defaults to an empty list.
metadata: The metadata of the message. Defaults to an empty dictionary.

Returns:
A `Message` object with a new UUID message_id.
"""
return Message(
role=role, parts=[Part(TextPart(text=content))], message_id=str(uuid4())
role=role,
parts=[Part(TextPart(text=content))],
message_id=str(uuid4()),
extensions=extensions or [],
metadata=metadata or {},
)
3 changes: 2 additions & 1 deletion src/a2a/utils/proto_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def make_dict_serializable(value: Any) -> Any:
Returns:
A serializable value.
"""
if isinstance(value, (str, int, float, bool)) or value is None:
if isinstance(value, str | int | float | bool) or value is None:
return value
if isinstance(value, dict):
return {k: make_dict_serializable(v) for k, v in value.items()}
Expand Down Expand Up @@ -140,6 +140,7 @@ def message(cls, message: types.Message | None) -> a2a_pb2.Message | None:
task_id=message.task_id or '',
role=cls.role(message.role),
metadata=cls.metadata(message.metadata),
extensions=message.extensions or [],
)

@classmethod
Expand Down
58 changes: 58 additions & 0 deletions tests/client/test_jsonrpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,64 @@ async def test_send_message_success(
assert isinstance(response, Message)
assert response.model_dump() == success_response.model_dump()

@pytest.mark.asyncio
async def test_send_message_success_with_extensions(
self, mock_httpx_client: AsyncMock, mock_agent_card: MagicMock
):
client = JsonRpcTransport(
httpx_client=mock_httpx_client, agent_card=mock_agent_card
)
params = MessageSendParams(
message=create_text_message_object(
content='Hello', extensions=['test']
)
)
success_response = create_text_message_object(
role=Role.agent, content='Hi there!', extensions=['test']
)
rpc_response = SendMessageSuccessResponse(
id='123', jsonrpc='2.0', result=success_response
)
response = httpx.Response(
200, json=rpc_response.model_dump(mode='json')
)
response.request = httpx.Request('POST', 'http://agent.example.com/api')
mock_httpx_client.post.return_value = response

response = await client.send_message(request=params)

assert isinstance(response, Message)
assert response.model_dump() == success_response.model_dump()

@pytest.mark.asyncio
async def test_send_message_success_with_metadata(
self, mock_httpx_client: AsyncMock, mock_agent_card: MagicMock
):
client = JsonRpcTransport(
httpx_client=mock_httpx_client, agent_card=mock_agent_card
)
params = MessageSendParams(
message=create_text_message_object(
content='Hello', metadata={'test': 'test'}
)
)
success_response = create_text_message_object(
role=Role.agent, content='Hi there!', metadata={'test': 'test'}
)
rpc_response = SendMessageSuccessResponse(
id='123', jsonrpc='2.0', result=success_response
)
response = httpx.Response(
200, json=rpc_response.model_dump(mode='json')
)
response.request = httpx.Request('POST', 'http://agent.example.com/api')
mock_httpx_client.post.return_value = response

response = await client.send_message(request=params)

assert isinstance(response, Message)
assert response.model_dump() == success_response.model_dump()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is much value in adding additional tests for the send_message method here, but it introduces unnecessary complexity. Could you directly test the create_text_message_object function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi will add it over weekend, thanks


@pytest.mark.asyncio
async def test_send_message_error_response(
self, mock_httpx_client: AsyncMock, mock_agent_card: MagicMock
Expand Down
Loading