From 0621aea6a4fa4cd9a4e742aa5744e2ef71a5acbf Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Thu, 15 May 2025 17:18:09 -0700 Subject: [PATCH 1/7] Handle reusing queue on task events --- src/a2a/server/request_handlers/default_request_handler.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/a2a/server/request_handlers/default_request_handler.py b/src/a2a/server/request_handlers/default_request_handler.py index bf616c0c..6113740d 100644 --- a/src/a2a/server/request_handlers/default_request_handler.py +++ b/src/a2a/server/request_handlers/default_request_handler.py @@ -195,8 +195,10 @@ async def on_message_send_stream( consumer = EventConsumer(queue) producer_task.add_done_callback(consumer.agent_task_callback) async for event in result_aggregator.consume_and_emit(consumer): - # Now we know we have a Task, register the queue - if isinstance(event, Task): + if isinstance(event, Task) and task_id != event.id: + logger.warning( + f'Agent generated task_id={event.id} does not match the RequestContext task_id={task_id}.' + ) try: await self._queue_manager.add(event.id, queue) task_id = event.id From 4affcb1b2ffdc6f10f48e6a32be7d917e2b8f67e Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Thu, 15 May 2025 18:50:09 -0700 Subject: [PATCH 2/7] Rename type to itemType --- .gitignore | 1 + src/a2a/types.py | 76 +++++++++++++++++-------------------- tests/client/test_client.py | 4 +- tests/test_types.py | 30 +++++++-------- 4 files changed, 52 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index 4da52568..6252577e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ __pycache__ .venv coverage.xml .nox +spec.json \ No newline at end of file diff --git a/src/a2a/types.py b/src/a2a/types.py index e7dc4c91..05d6887e 100644 --- a/src/a2a/types.py +++ b/src/a2a/types.py @@ -133,13 +133,13 @@ class DataPart(BaseModel): """ Structured data content """ - metadata: dict[str, Any] | None = None + itemType: Literal['data'] = 'data' """ - Optional metadata associated with the part. + Part type - data for DataParts """ - type: Literal['data'] = 'data' + metadata: dict[str, Any] | None = None """ - Part type - data for DataParts + Optional metadata associated with the part. """ @@ -577,11 +577,11 @@ class TaskState(Enum): submitted = 'submitted' working = 'working' input_required = 'input-required' - auth_required = 'auth-required' completed = 'completed' canceled = 'canceled' failed = 'failed' rejected = 'rejected' + auth_required = 'auth-required' unknown = 'unknown' @@ -590,6 +590,10 @@ class TextPart(BaseModel): Represents a text segment within parts. """ + itemType: Literal['text'] = 'text' + """ + Part type - text for TextParts + """ metadata: dict[str, Any] | None = None """ Optional metadata associated with the part. @@ -598,10 +602,6 @@ class TextPart(BaseModel): """ Text content """ - type: Literal['text'] = 'text' - """ - Part type - text for TextParts - """ class UnsupportedOperationError(BaseModel): @@ -745,13 +745,13 @@ class FilePart(BaseModel): """ File content either as url or bytes """ - metadata: dict[str, Any] | None = None + itemType: Literal['file'] = 'file' """ - Optional metadata associated with the part. + Part type - file for FileParts """ - type: Literal['file'] = 'file' + metadata: dict[str, Any] | None = None """ - Part type - file for FileParts + Optional metadata associated with the part. """ @@ -933,7 +933,7 @@ class SetTaskPushNotificationConfigSuccessResponse(BaseModel): class Artifact(BaseModel): """ - Represents an artifact generated for a task. + Represents an artifact generated for a task task. """ artifactId: str @@ -959,9 +959,7 @@ class Artifact(BaseModel): class GetTaskPushNotificationConfigResponse( - RootModel[ - JSONRPCErrorResponse | GetTaskPushNotificationConfigSuccessResponse - ] + RootModel[JSONRPCErrorResponse | GetTaskPushNotificationConfigSuccessResponse] ): root: JSONRPCErrorResponse | GetTaskPushNotificationConfigSuccessResponse """ @@ -982,6 +980,10 @@ class Message(BaseModel): """ indicates the end of the event stream """ + itemType: Literal['message'] = 'message' + """ + event type + """ messageId: str """ identifier created by the message creator @@ -1002,10 +1004,6 @@ class Message(BaseModel): """ identifier of task the message is related to """ - type: Literal['message'] = 'message' - """ - event type - """ class MessageSendParams(BaseModel): @@ -1076,9 +1074,7 @@ class SendStreamingMessageRequest(BaseModel): class SetTaskPushNotificationConfigResponse( - RootModel[ - JSONRPCErrorResponse | SetTaskPushNotificationConfigSuccessResponse - ] + RootModel[JSONRPCErrorResponse | SetTaskPushNotificationConfigSuccessResponse] ): root: JSONRPCErrorResponse | SetTaskPushNotificationConfigSuccessResponse """ @@ -1103,6 +1099,10 @@ class TaskArtifactUpdateEvent(BaseModel): """ the context the task is associated with """ + itemType: Literal['artifact-update'] = 'artifact-update' + """ + event type + """ lastChunk: bool | None = None """ Indicates if this is the last chunk of the artifact @@ -1115,10 +1115,6 @@ class TaskArtifactUpdateEvent(BaseModel): """ Task id """ - type: Literal['artifact-update'] = 'artifact-update' - """ - event type - """ class TaskStatus(BaseModel): @@ -1150,6 +1146,10 @@ class TaskStatusUpdateEvent(BaseModel): """ indicates the end of the event stream """ + itemType: Literal['status-update'] = 'status-update' + """ + event type + """ metadata: dict[str, Any] | None = None """ extension metadata @@ -1162,10 +1162,6 @@ class TaskStatusUpdateEvent(BaseModel): """ Task id """ - type: Literal['status-update'] = 'status-update' - """ - event type - """ class A2ARequest( @@ -1207,6 +1203,10 @@ class Task(BaseModel): """ unique identifier for the task """ + itemType: Literal['task'] = 'task' + """ + event type + """ metadata: dict[str, Any] | None = None """ extension metadata @@ -1215,10 +1215,6 @@ class Task(BaseModel): """ current status of the task """ - type: Literal['task'] = 'task' - """ - event type - """ class CancelTaskSuccessResponse(BaseModel): @@ -1301,9 +1297,7 @@ class SendStreamingMessageSuccessResponse(BaseModel): """ -class CancelTaskResponse( - RootModel[JSONRPCErrorResponse | CancelTaskSuccessResponse] -): +class CancelTaskResponse(RootModel[JSONRPCErrorResponse | CancelTaskSuccessResponse]): root: JSONRPCErrorResponse | CancelTaskSuccessResponse """ JSON-RPC response for the 'tasks/cancel' method. @@ -1342,9 +1336,7 @@ class JSONRPCResponse( """ -class SendMessageResponse( - RootModel[JSONRPCErrorResponse | SendMessageSuccessResponse] -): +class SendMessageResponse(RootModel[JSONRPCErrorResponse | SendMessageSuccessResponse]): root: JSONRPCErrorResponse | SendMessageSuccessResponse """ JSON-RPC response model for the 'message/send' method. diff --git a/tests/client/test_client.py b/tests/client/test_client.py index aedc8f10..8c1e5a52 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -63,14 +63,14 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'working'}, - 'type': 'task', + 'itemType': 'task', } MINIMAL_CANCELLED_TASK: dict[str, Any] = { 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'canceled'}, - 'type': 'task', + 'itemType': 'task', } diff --git a/tests/test_types.py b/tests/test_types.py index 5a01eea0..bd757a61 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -106,22 +106,22 @@ 'version': '1.0', } -TEXT_PART_DATA: dict[str, Any] = {'type': 'text', 'text': 'Hello'} +TEXT_PART_DATA: dict[str, Any] = {'itemType': 'text', 'text': 'Hello'} FILE_URI_PART_DATA: dict[str, Any] = { - 'type': 'file', + 'itemType': 'file', 'file': {'uri': 'file:///path/to/file.txt', 'mimeType': 'text/plain'}, } FILE_BYTES_PART_DATA: dict[str, Any] = { - 'type': 'file', + 'itemType': 'file', 'file': {'bytes': 'aGVsbG8=', 'name': 'hello.txt'}, # base64 for "hello" } -DATA_PART_DATA: dict[str, Any] = {'type': 'data', 'data': {'key': 'value'}} +DATA_PART_DATA: dict[str, Any] = {'itemType': 'data', 'data': {'key': 'value'}} MINIMAL_MESSAGE_USER: dict[str, Any] = { 'role': 'user', 'parts': [TEXT_PART_DATA], 'messageId': 'msg-123', - 'type': 'message', + 'itemType': 'message', } AGENT_MESSAGE_WITH_FILE: dict[str, Any] = { @@ -142,7 +142,7 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': MINIMAL_TASK_STATUS, - 'type': 'task', + 'itemType': 'task', } FULL_TASK: dict[str, Any] = { 'id': 'task-abc', @@ -157,7 +157,7 @@ } ], 'metadata': {'priority': 'high'}, - 'type': 'task', + 'itemType': 'task', } MINIMAL_TASK_ID_PARAMS: dict[str, Any] = {'id': 'task-123'} @@ -269,7 +269,7 @@ def test_agent_card_invalid(): def test_text_part(): part = TextPart(**TEXT_PART_DATA) - assert part.type == 'text' + assert part.itemType == 'text' assert part.text == 'Hello' assert part.metadata is None @@ -277,7 +277,7 @@ def test_text_part(): TextPart(type='text') # Missing text # type: ignore with pytest.raises(ValidationError): TextPart( - type='file', # type: ignore + itemType='file', # type: ignore text='hello', ) # Wrong type literal @@ -287,7 +287,7 @@ def test_file_part_variants(): file_uri = FileWithUri( uri='file:///path/to/file.txt', mimeType='text/plain' ) - part_uri = FilePart(type='file', file=file_uri) + part_uri = FilePart(itemType='file', file=file_uri) assert isinstance(part_uri.file, FileWithUri) assert part_uri.file.uri == 'file:///path/to/file.txt' assert part_uri.file.mimeType == 'text/plain' @@ -295,7 +295,7 @@ def test_file_part_variants(): # Bytes variant file_bytes = FileWithBytes(bytes='aGVsbG8=', name='hello.txt') - part_bytes = FilePart(type='file', file=file_bytes) + part_bytes = FilePart(itemType='file', file=file_bytes) assert isinstance(part_bytes.file, FileWithBytes) assert part_bytes.file.bytes == 'aGVsbG8=' assert part_bytes.file.name == 'hello.txt' @@ -312,14 +312,14 @@ def test_file_part_variants(): # Invalid - wrong type literal with pytest.raises(ValidationError): - FilePart(type='text', file=file_uri) # type: ignore + FilePart(itemType='text', file=file_uri) # type: ignore FilePart(**FILE_URI_PART_DATA, extra='extra') # type: ignore def test_data_part(): part = DataPart(**DATA_PART_DATA) - assert part.type == 'data' + assert part.itemType == 'data' assert part.data == {'key': 'value'} with pytest.raises(ValidationError): @@ -656,7 +656,7 @@ def test_send_message_streaming_status_update_response() -> None: 'taskId': '1', 'contextId': '2', 'final': False, - 'type': 'status-update', + 'itemType': 'status-update', } event_data: dict[str, Any] = { @@ -716,7 +716,7 @@ def test_send_message_streaming_artifact_update_response() -> None: 'contextId': '2', 'append': False, 'lastChunk': True, - 'type': 'artifact-update', + 'itemType': 'artifact-update', } event_data: dict[str, Any] = { 'jsonrpc': '2.0', From cd7cafaf316869f31aa8ce6e556f1b67f3425374 Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Thu, 15 May 2025 22:14:10 -0700 Subject: [PATCH 3/7] Replace usages of type --- examples/helloworld/test_client.py | 2 +- examples/langgraph/test_client.py | 2 +- tests/server/events/test_event_consumer.py | 2 +- tests/server/events/test_event_queue.py | 2 +- tests/server/request_handlers/test_jsonrpc_handler.py | 2 +- tests/server/tasks/test_inmemory_task_store.py | 2 +- tests/server/tasks/test_task_manager.py | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/helloworld/test_client.py b/examples/helloworld/test_client.py index 0ade8d2a..71d18721 100644 --- a/examples/helloworld/test_client.py +++ b/examples/helloworld/test_client.py @@ -18,7 +18,7 @@ async def main() -> None: 'message': { 'role': 'user', 'parts': [ - {'type': 'text', 'text': 'how much is 10 USD in INR?'} + {'itemType': 'text', 'text': 'how much is 10 USD in INR?'} ], 'messageId': uuid4().hex, }, diff --git a/examples/langgraph/test_client.py b/examples/langgraph/test_client.py index 01d925ae..535e8053 100644 --- a/examples/langgraph/test_client.py +++ b/examples/langgraph/test_client.py @@ -26,7 +26,7 @@ def create_send_message_payload( payload: dict[str, Any] = { 'message': { 'role': 'user', - 'parts': [{'type': 'text', 'text': text}], + 'parts': [{'itemType': 'text', 'text': text}], 'messageId': uuid4().hex, }, } diff --git a/tests/server/events/test_event_consumer.py b/tests/server/events/test_event_consumer.py index 56db30a1..c8906f28 100644 --- a/tests/server/events/test_event_consumer.py +++ b/tests/server/events/test_event_consumer.py @@ -28,7 +28,7 @@ 'id': '123', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'type': 'task', + 'itemType': 'task', } MESSAGE_PAYLOAD: dict[str, Any] = { diff --git a/tests/server/events/test_event_queue.py b/tests/server/events/test_event_queue.py index 5b440fa1..0c500a8b 100644 --- a/tests/server/events/test_event_queue.py +++ b/tests/server/events/test_event_queue.py @@ -21,7 +21,7 @@ 'id': '123', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'type': 'task', + 'itemType': 'task', } MESSAGE_PAYLOAD: dict[str, Any] = { 'role': 'agent', diff --git a/tests/server/request_handlers/test_jsonrpc_handler.py b/tests/server/request_handlers/test_jsonrpc_handler.py index 8adb41db..4b78c9e2 100644 --- a/tests/server/request_handlers/test_jsonrpc_handler.py +++ b/tests/server/request_handlers/test_jsonrpc_handler.py @@ -53,7 +53,7 @@ 'id': 'task_123', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'type': 'task', + 'itemType': 'task', } MESSAGE_PAYLOAD: dict[str, Any] = { 'role': 'agent', diff --git a/tests/server/tasks/test_inmemory_task_store.py b/tests/server/tasks/test_inmemory_task_store.py index 9eb8eae9..0e1b3c91 100644 --- a/tests/server/tasks/test_inmemory_task_store.py +++ b/tests/server/tasks/test_inmemory_task_store.py @@ -10,7 +10,7 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'type': 'task', + 'itemType': 'task', } diff --git a/tests/server/tasks/test_task_manager.py b/tests/server/tasks/test_task_manager.py index aae189fd..c8ec967c 100644 --- a/tests/server/tasks/test_task_manager.py +++ b/tests/server/tasks/test_task_manager.py @@ -22,7 +22,7 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'type': 'task', + 'itemType': 'task', } @@ -205,7 +205,7 @@ async def test_save_task_event_new_task_no_task_id( 'id': 'new-task-id', 'contextId': 'some-context', 'status': {'state': 'working'}, - 'type': 'task', + 'itemType': 'task', } task = Task(**task_data) await task_manager_without_id.save_task_event(task) From 11dabde070ea4353d186976d19bad3c19315b8bf Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Fri, 16 May 2025 13:11:18 -0700 Subject: [PATCH 4/7] Add type generation script --- development.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 development.md diff --git a/development.md b/development.md new file mode 100644 index 00000000..64bbde91 --- /dev/null +++ b/development.md @@ -0,0 +1,7 @@ +## Type generation from spec + + + +```bash +uv run datamodel-codegen --input ./spec.json --input-file-type jsonschema --output ./src/a2a/types.py --target-python-version 3.10 --output-model-type pydantic_v2.BaseModel --disable-timestamp --use-schema-description --use-union-operator --use-field-description --use-default --use-default-kwarg --use-one-literal-as-default --class-name A2A --use-standard-collections +``` \ No newline at end of file From c45c3cde09d7d07ce56bd7de2c0e0aa417198140 Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Sun, 18 May 2025 12:22:19 -0700 Subject: [PATCH 5/7] Remove final from message type --- src/a2a/types.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/a2a/types.py b/src/a2a/types.py index 05d6887e..9db99fc4 100644 --- a/src/a2a/types.py +++ b/src/a2a/types.py @@ -976,10 +976,6 @@ class Message(BaseModel): """ the context the message is associated with """ - final: bool | None = None - """ - indicates the end of the event stream - """ itemType: Literal['message'] = 'message' """ event type From be0d7086608d6bfb10786f82a1fca18ecfed65d6 Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Mon, 19 May 2025 11:37:47 -0700 Subject: [PATCH 6/7] Rename itemType to kind --- examples/helloworld/test_client.py | 2 +- examples/langgraph/test_client.py | 2 +- src/a2a/types.py | 14 ++++----- tests/client/test_client.py | 4 +-- tests/server/events/test_event_consumer.py | 2 +- tests/server/events/test_event_queue.py | 2 +- .../request_handlers/test_jsonrpc_handler.py | 2 +- .../server/tasks/test_inmemory_task_store.py | 2 +- tests/server/tasks/test_task_manager.py | 4 +-- tests/test_types.py | 30 +++++++++---------- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/examples/helloworld/test_client.py b/examples/helloworld/test_client.py index 71d18721..561784ad 100644 --- a/examples/helloworld/test_client.py +++ b/examples/helloworld/test_client.py @@ -18,7 +18,7 @@ async def main() -> None: 'message': { 'role': 'user', 'parts': [ - {'itemType': 'text', 'text': 'how much is 10 USD in INR?'} + {'kind': 'text', 'text': 'how much is 10 USD in INR?'} ], 'messageId': uuid4().hex, }, diff --git a/examples/langgraph/test_client.py b/examples/langgraph/test_client.py index b5e35045..3b1c1d03 100644 --- a/examples/langgraph/test_client.py +++ b/examples/langgraph/test_client.py @@ -26,7 +26,7 @@ def create_send_message_payload( payload: dict[str, Any] = { 'message': { 'role': 'user', - 'parts': [{'itemType': 'text', 'text': text}], + 'parts': [{'kind': 'text', 'text': text}], 'messageId': uuid4().hex, }, } diff --git a/src/a2a/types.py b/src/a2a/types.py index 9db99fc4..3587d441 100644 --- a/src/a2a/types.py +++ b/src/a2a/types.py @@ -133,7 +133,7 @@ class DataPart(BaseModel): """ Structured data content """ - itemType: Literal['data'] = 'data' + kind: Literal['data'] = 'data' """ Part type - data for DataParts """ @@ -590,7 +590,7 @@ class TextPart(BaseModel): Represents a text segment within parts. """ - itemType: Literal['text'] = 'text' + kind: Literal['text'] = 'text' """ Part type - text for TextParts """ @@ -745,7 +745,7 @@ class FilePart(BaseModel): """ File content either as url or bytes """ - itemType: Literal['file'] = 'file' + kind: Literal['file'] = 'file' """ Part type - file for FileParts """ @@ -976,7 +976,7 @@ class Message(BaseModel): """ the context the message is associated with """ - itemType: Literal['message'] = 'message' + kind: Literal['message'] = 'message' """ event type """ @@ -1095,7 +1095,7 @@ class TaskArtifactUpdateEvent(BaseModel): """ the context the task is associated with """ - itemType: Literal['artifact-update'] = 'artifact-update' + kind: Literal['artifact-update'] = 'artifact-update' """ event type """ @@ -1142,7 +1142,7 @@ class TaskStatusUpdateEvent(BaseModel): """ indicates the end of the event stream """ - itemType: Literal['status-update'] = 'status-update' + kind: Literal['status-update'] = 'status-update' """ event type """ @@ -1199,7 +1199,7 @@ class Task(BaseModel): """ unique identifier for the task """ - itemType: Literal['task'] = 'task' + kind: Literal['task'] = 'task' """ event type """ diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 8c1e5a52..efb6ca12 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -63,14 +63,14 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'working'}, - 'itemType': 'task', + 'kind': 'task', } MINIMAL_CANCELLED_TASK: dict[str, Any] = { 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'canceled'}, - 'itemType': 'task', + 'kind': 'task', } diff --git a/tests/server/events/test_event_consumer.py b/tests/server/events/test_event_consumer.py index c8906f28..08111a2b 100644 --- a/tests/server/events/test_event_consumer.py +++ b/tests/server/events/test_event_consumer.py @@ -28,7 +28,7 @@ 'id': '123', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'itemType': 'task', + 'kind': 'task', } MESSAGE_PAYLOAD: dict[str, Any] = { diff --git a/tests/server/events/test_event_queue.py b/tests/server/events/test_event_queue.py index 0c500a8b..8a9c163e 100644 --- a/tests/server/events/test_event_queue.py +++ b/tests/server/events/test_event_queue.py @@ -21,7 +21,7 @@ 'id': '123', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'itemType': 'task', + 'kind': 'task', } MESSAGE_PAYLOAD: dict[str, Any] = { 'role': 'agent', diff --git a/tests/server/request_handlers/test_jsonrpc_handler.py b/tests/server/request_handlers/test_jsonrpc_handler.py index c9ec89fa..cf3e66c8 100644 --- a/tests/server/request_handlers/test_jsonrpc_handler.py +++ b/tests/server/request_handlers/test_jsonrpc_handler.py @@ -62,7 +62,7 @@ 'id': 'task_123', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'itemType': 'task', + 'kind': 'task', } MESSAGE_PAYLOAD: dict[str, Any] = { 'role': 'agent', diff --git a/tests/server/tasks/test_inmemory_task_store.py b/tests/server/tasks/test_inmemory_task_store.py index 0e1b3c91..f5d9df1d 100644 --- a/tests/server/tasks/test_inmemory_task_store.py +++ b/tests/server/tasks/test_inmemory_task_store.py @@ -10,7 +10,7 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'itemType': 'task', + 'kind': 'task', } diff --git a/tests/server/tasks/test_task_manager.py b/tests/server/tasks/test_task_manager.py index c8ec967c..56205fab 100644 --- a/tests/server/tasks/test_task_manager.py +++ b/tests/server/tasks/test_task_manager.py @@ -22,7 +22,7 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': {'state': 'submitted'}, - 'itemType': 'task', + 'kind': 'task', } @@ -205,7 +205,7 @@ async def test_save_task_event_new_task_no_task_id( 'id': 'new-task-id', 'contextId': 'some-context', 'status': {'state': 'working'}, - 'itemType': 'task', + 'kind': 'task', } task = Task(**task_data) await task_manager_without_id.save_task_event(task) diff --git a/tests/test_types.py b/tests/test_types.py index bd757a61..ef658a19 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -106,22 +106,22 @@ 'version': '1.0', } -TEXT_PART_DATA: dict[str, Any] = {'itemType': 'text', 'text': 'Hello'} +TEXT_PART_DATA: dict[str, Any] = {'kind': 'text', 'text': 'Hello'} FILE_URI_PART_DATA: dict[str, Any] = { - 'itemType': 'file', + 'kind': 'file', 'file': {'uri': 'file:///path/to/file.txt', 'mimeType': 'text/plain'}, } FILE_BYTES_PART_DATA: dict[str, Any] = { - 'itemType': 'file', + 'kind': 'file', 'file': {'bytes': 'aGVsbG8=', 'name': 'hello.txt'}, # base64 for "hello" } -DATA_PART_DATA: dict[str, Any] = {'itemType': 'data', 'data': {'key': 'value'}} +DATA_PART_DATA: dict[str, Any] = {'kind': 'data', 'data': {'key': 'value'}} MINIMAL_MESSAGE_USER: dict[str, Any] = { 'role': 'user', 'parts': [TEXT_PART_DATA], 'messageId': 'msg-123', - 'itemType': 'message', + 'kind': 'message', } AGENT_MESSAGE_WITH_FILE: dict[str, Any] = { @@ -142,7 +142,7 @@ 'id': 'task-abc', 'contextId': 'session-xyz', 'status': MINIMAL_TASK_STATUS, - 'itemType': 'task', + 'kind': 'task', } FULL_TASK: dict[str, Any] = { 'id': 'task-abc', @@ -157,7 +157,7 @@ } ], 'metadata': {'priority': 'high'}, - 'itemType': 'task', + 'kind': 'task', } MINIMAL_TASK_ID_PARAMS: dict[str, Any] = {'id': 'task-123'} @@ -269,7 +269,7 @@ def test_agent_card_invalid(): def test_text_part(): part = TextPart(**TEXT_PART_DATA) - assert part.itemType == 'text' + assert part.kind == 'text' assert part.text == 'Hello' assert part.metadata is None @@ -277,7 +277,7 @@ def test_text_part(): TextPart(type='text') # Missing text # type: ignore with pytest.raises(ValidationError): TextPart( - itemType='file', # type: ignore + kind='file', # type: ignore text='hello', ) # Wrong type literal @@ -287,7 +287,7 @@ def test_file_part_variants(): file_uri = FileWithUri( uri='file:///path/to/file.txt', mimeType='text/plain' ) - part_uri = FilePart(itemType='file', file=file_uri) + part_uri = FilePart(kind='file', file=file_uri) assert isinstance(part_uri.file, FileWithUri) assert part_uri.file.uri == 'file:///path/to/file.txt' assert part_uri.file.mimeType == 'text/plain' @@ -295,7 +295,7 @@ def test_file_part_variants(): # Bytes variant file_bytes = FileWithBytes(bytes='aGVsbG8=', name='hello.txt') - part_bytes = FilePart(itemType='file', file=file_bytes) + part_bytes = FilePart(kind='file', file=file_bytes) assert isinstance(part_bytes.file, FileWithBytes) assert part_bytes.file.bytes == 'aGVsbG8=' assert part_bytes.file.name == 'hello.txt' @@ -312,14 +312,14 @@ def test_file_part_variants(): # Invalid - wrong type literal with pytest.raises(ValidationError): - FilePart(itemType='text', file=file_uri) # type: ignore + FilePart(kind='text', file=file_uri) # type: ignore FilePart(**FILE_URI_PART_DATA, extra='extra') # type: ignore def test_data_part(): part = DataPart(**DATA_PART_DATA) - assert part.itemType == 'data' + assert part.kind == 'data' assert part.data == {'key': 'value'} with pytest.raises(ValidationError): @@ -656,7 +656,7 @@ def test_send_message_streaming_status_update_response() -> None: 'taskId': '1', 'contextId': '2', 'final': False, - 'itemType': 'status-update', + 'kind': 'status-update', } event_data: dict[str, Any] = { @@ -716,7 +716,7 @@ def test_send_message_streaming_artifact_update_response() -> None: 'contextId': '2', 'append': False, 'lastChunk': True, - 'itemType': 'artifact-update', + 'kind': 'artifact-update', } event_data: dict[str, Any] = { 'jsonrpc': '2.0', From 4054255179aeaacb27c9e19877c4ec5dda1c93f6 Mon Sep 17 00:00:00 2001 From: Krishna Thota Date: Mon, 19 May 2025 11:43:07 -0700 Subject: [PATCH 7/7] fix linting in md --- development.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/development.md b/development.md index 64bbde91..0d9ef29c 100644 --- a/development.md +++ b/development.md @@ -1,7 +1,9 @@ +# Development + ## Type generation from spec ```bash uv run datamodel-codegen --input ./spec.json --input-file-type jsonschema --output ./src/a2a/types.py --target-python-version 3.10 --output-model-type pydantic_v2.BaseModel --disable-timestamp --use-schema-description --use-union-operator --use-field-description --use-default --use-default-kwarg --use-one-literal-as-default --class-name A2A --use-standard-collections -``` \ No newline at end of file +```