From b9623c7b6fc306ea2f3daaf6b2f424e01635f737 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 10:09:08 -0500 Subject: [PATCH 01/30] chore: Formatting/Lint fixes --- .../google_adk/birthday_planner/__main__.py | 13 +++++--- .../birthday_planner/adk_agent_executor.py | 32 +++++++++---------- .../default_request_handler.py | 8 ++--- src/a2a/server/tasks/result_aggregator.py | 21 ++++-------- src/a2a/utils/helpers.py | 2 +- 5 files changed, 36 insertions(+), 40 deletions(-) diff --git a/examples/google_adk/birthday_planner/__main__.py b/examples/google_adk/birthday_planner/__main__.py index d4a0343d..16609b3e 100644 --- a/examples/google_adk/birthday_planner/__main__.py +++ b/examples/google_adk/birthday_planner/__main__.py @@ -5,6 +5,7 @@ import click import uvicorn + from adk_agent_executor import ADKAgentExecutor from dotenv import load_dotenv @@ -18,6 +19,7 @@ AgentSkill, ) + load_dotenv() logging.basicConfig() @@ -39,11 +41,12 @@ def wrapper(*args, **kwargs): ) def main(host: str, port: int, calendar_agent: str): # Verify an API key is set. Not required if using Vertex AI APIs, since those can use gcloud credentials. - if not os.getenv('GOOGLE_GENAI_USE_VERTEXAI') == 'TRUE': - if not os.getenv('GOOGLE_API_KEY'): - raise Exception( - 'GOOGLE_API_KEY environment variable not set and GOOGLE_GENAI_USE_VERTEXAI is not TRUE.' - ) + if os.getenv('GOOGLE_GENAI_USE_VERTEXAI') != 'TRUE' and not os.getenv( + 'GOOGLE_API_KEY' + ): + raise ValueError( + 'GOOGLE_API_KEY environment variable not set and GOOGLE_GENAI_USE_VERTEXAI is not TRUE.' + ) skill = AgentSkill( id='plan_parties', diff --git a/examples/google_adk/birthday_planner/adk_agent_executor.py b/examples/google_adk/birthday_planner/adk_agent_executor.py index 49f13e96..de6708cf 100644 --- a/examples/google_adk/birthday_planner/adk_agent_executor.py +++ b/examples/google_adk/birthday_planner/adk_agent_executor.py @@ -1,10 +1,12 @@ import asyncio import logging -from collections.abc import AsyncGenerator -from typing import Any, AsyncIterable + +from collections.abc import AsyncGenerator, AsyncIterable +from typing import Any from uuid import uuid4 import httpx + from google.adk import Runner from google.adk.agents import LlmAgent, RunConfig from google.adk.artifacts import InMemoryArtifactService @@ -42,6 +44,7 @@ from a2a.utils import get_text_parts from a2a.utils.errors import ServerError + logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -49,7 +52,7 @@ class A2ARunConfig(RunConfig): - """Custom override of ADK RunConfig to smuggle extra data through the event loop""" + """Custom override of ADK RunConfig to smuggle extra data through the event loop.""" model_config = ConfigDict( arbitrary_types_allowed=True, @@ -66,7 +69,7 @@ def __init__(self, calendar_agent_url): name='birthday_planner_agent', description='An agent that helps manage birthday parties.', after_tool_callback=self._handle_auth_required_task, - instruction=f""" + instruction=""" You are an agent that helps plan birthday parties. Your job as a party planner is to act as a sounding board and idea generator for @@ -111,7 +114,7 @@ async def _handle_auth_required_task( tool_context: ToolContext, tool_response: dict, ) -> dict | None: - """Handle requests that return auth-required""" + """Handle requests that return auth-required.""" if tool.name != 'message_calendar_agent': return None if not tool_context.state.get('task_suspended'): @@ -165,7 +168,7 @@ async def _process_request( task_updater.add_artifact(response) task_updater.complete() break - elif calls := event.get_function_calls(): + if calls := event.get_function_calls(): for call in calls: # Provide an update on what we're doing. if call.name == 'message_calendar_agent': @@ -314,23 +317,21 @@ def convert_a2a_part_to_genai(part: Part) -> types.Part: part = part.root if isinstance(part, TextPart): return types.Part(text=part.text) - elif isinstance(part, FilePart): + if isinstance(part, FilePart): if isinstance(part.file, FileWithUri): return types.Part( file_data=types.FileData( file_uri=part.file.uri, mime_type=part.file.mime_type ) ) - elif isinstance(part.file, FileWithBytes): + if isinstance(part.file, FileWithBytes): return types.Part( inline_data=types.Blob( data=part.file.bytes, mime_type=part.file.mime_type ) ) - else: - raise ValueError(f'Unsupported file type: {type(part.file)}') - else: - raise ValueError(f'Unsupported part type: {type(part)}') + raise ValueError(f'Unsupported file type: {type(part.file)}') + raise ValueError(f'Unsupported part type: {type(part)}') def convert_genai_parts_to_a2a(parts: list[types.Part]) -> list[Part]: @@ -346,14 +347,14 @@ def convert_genai_part_to_a2a(part: types.Part) -> Part: """Convert a single Google GenAI Part type into an A2A Part type.""" if part.text: return TextPart(text=part.text) - elif part.file_data: + if part.file_data: return FilePart( file=FileWithUri( uri=part.file_data.file_uri, mime_type=part.file_data.mime_type, ) ) - elif part.inline_data: + if part.inline_data: return Part( root=FilePart( file=FileWithBytes( @@ -362,5 +363,4 @@ def convert_genai_part_to_a2a(part: types.Part) -> Part: ) ) ) - else: - raise ValueError(f'Unsupported part type: {part}') + raise ValueError(f'Unsupported part type: {part}') diff --git a/src/a2a/server/request_handlers/default_request_handler.py b/src/a2a/server/request_handlers/default_request_handler.py index f656e414..88e8850e 100644 --- a/src/a2a/server/request_handlers/default_request_handler.py +++ b/src/a2a/server/request_handlers/default_request_handler.py @@ -1,6 +1,6 @@ import asyncio -import contextlib import logging + from collections.abc import AsyncGenerator from typing import cast @@ -10,7 +10,6 @@ EventConsumer, EventQueue, InMemoryQueueManager, - NoTaskQueue, QueueManager, TaskQueueExists, ) @@ -29,6 +28,7 @@ ) from a2a.utils.errors import ServerError + logger = logging.getLogger(__name__) @@ -206,11 +206,11 @@ async def on_message_send_stream( finally: await self._cleanup_producer(producer_task, task_id) - async def _register_producer(self, task_id, producer_task): + async def _register_producer(self, task_id, producer_task) -> None: async with self._running_agents_lock: self._running_agents[task_id] = producer_task - async def _cleanup_producer(self, producer_task, task_id): + async def _cleanup_producer(self, producer_task, task_id) -> None: await producer_task await self._queue_manager.close(task_id) async with self._running_agents_lock: diff --git a/src/a2a/server/tasks/result_aggregator.py b/src/a2a/server/tasks/result_aggregator.py index 9220fc86..c1cafb8b 100644 --- a/src/a2a/server/tasks/result_aggregator.py +++ b/src/a2a/server/tasks/result_aggregator.py @@ -1,12 +1,13 @@ import asyncio import logging + from collections.abc import AsyncGenerator, AsyncIterator -from typing import Tuple from a2a.server.events import Event, EventConsumer from a2a.server.tasks.task_manager import TaskManager from a2a.types import Message, Task, TaskState, TaskStatusUpdateEvent + logger = logging.getLogger(__name__) @@ -54,7 +55,7 @@ async def consume_all( async def consume_and_break_on_interrupt( self, consumer: EventConsumer - ) -> Tuple[Task | Message | None, bool]: + ) -> tuple[Task | Message | None, bool]: """Process the event stream until completion or an interruptable state is encountered.""" event_stream = consumer.consume_all() interrupted = False @@ -64,7 +65,7 @@ async def consume_and_break_on_interrupt( return event, False await self.task_manager.process(event) if ( - isinstance(event, (Task, TaskStatusUpdateEvent)) + isinstance(event, Task | TaskStatusUpdateEvent) and event.status.state == TaskState.auth_required ): # auth-required is a special state: the message should be @@ -82,16 +83,8 @@ async def consume_and_break_on_interrupt( break return await self.task_manager.get_task(), interrupted - async def _continue_consuming(self, event_stream: AsyncIterator[Event]): + async def _continue_consuming( + self, event_stream: AsyncIterator[Event] + ) -> None: async for event in event_stream: await self.task_manager.process(event) - - # async def consume_and_emit_task( - # self, consumer: EventConsumer - # ) -> AsyncGenerator[Event, None]: - # """Processes the event stream and emits the current state of the task.""" - # async for event in consumer.consume_all(): - # if isinstance(event, Message): - # self._current_task_or_message = event - # break - # yield await self.task_manager.process(event) diff --git a/src/a2a/utils/helpers.py b/src/a2a/utils/helpers.py index 2b5d2fe5..a0fe3cec 100644 --- a/src/a2a/utils/helpers.py +++ b/src/a2a/utils/helpers.py @@ -92,7 +92,7 @@ def decorator(function): def wrapper(self, *args, **kwargs): if not expression(self): if not error_message: - message = str(expression) + error_message = str(expression) logger.error(f'Unsupported Operation: {error_message}') raise ServerError( UnsupportedOperationError(message=error_message) From 6fb5d9144c1dd89563f3d923996dffebf68b14d4 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 10:21:22 -0500 Subject: [PATCH 02/30] Fix markdown lint errors --- .github/linters/.markdown-lint.yaml | 1 + .gitignore | 187 +++++++++++++++++- .prettierrc | 6 + .../google_adk/birthday_planner/README.md | 14 +- examples/google_adk/calendar_agent/README.md | 16 +- 5 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 .github/linters/.markdown-lint.yaml create mode 100644 .prettierrc diff --git a/.github/linters/.markdown-lint.yaml b/.github/linters/.markdown-lint.yaml new file mode 100644 index 00000000..20de1609 --- /dev/null +++ b/.github/linters/.markdown-lint.yaml @@ -0,0 +1 @@ +MD034: false diff --git a/.gitignore b/.gitignore index 67bf01f1..c10627bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,181 @@ -.DS_Store -__pycache__ -.env +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*.pyc +*$py.class +**/dist +/tmp +/out-tsc +/bazel-out + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ .coverage -.mypy_cache -.pytest_cache -.ruff_cache -.venv +.coverage.* +.cache +nosetests.xml coverage.xml -spec.json \ No newline at end of file +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +Pipfile.lock +Pipfile + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +.venv* +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# macOS +.DS_Store + +# PyCharm +.idea + +# User-specific files +language/examples/prompt-design/train.csv +README-TOC*.md + +# Terraform +terraform.tfstate** +.terraform* +.Terraform* + +tmp* + +# Node +**/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Miscellaneous +**/.angular/* +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..a415f1dd --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 2, + "useTabs": false, + "trailingComma": "es5", + "bracketSameLine": true +} diff --git a/examples/google_adk/birthday_planner/README.md b/examples/google_adk/birthday_planner/README.md index 8567f795..fb927193 100644 --- a/examples/google_adk/birthday_planner/README.md +++ b/examples/google_adk/birthday_planner/README.md @@ -12,16 +12,16 @@ This agent helps plan birthday parties. It has access to a Calendar Agent that i ## Running the example -1. Create the .env file with your API Key +1. Create the `.env` file with your API Key -```bash -echo "GOOGLE_API_KEY=your_api_key_here" > .env -``` + ```bash + echo "GOOGLE_API_KEY=your_api_key_here" > .env + ``` 2. Run the Calendar Agent. See examples/google_adk/calendar_agent. 3. Run the example -``` -uv run . -``` \ No newline at end of file + ```sh + uv run . + ``` diff --git a/examples/google_adk/calendar_agent/README.md b/examples/google_adk/calendar_agent/README.md index 7e2f8e7b..25b71513 100644 --- a/examples/google_adk/calendar_agent/README.md +++ b/examples/google_adk/calendar_agent/README.md @@ -14,14 +14,14 @@ This example shows how to create an A2A Server that uses an ADK-based Agent that 1. Create the .env file with your API Key and OAuth2.0 Client details -```bash -echo "GOOGLE_API_KEY=your_api_key_here" > .env -echo "GOOGLE_CLIENT_ID=your_client_id_here" >> .env -echo "GOOGLE_CLIENT_SECRET=your_client_secret_here" >> .env -``` + ```bash + echo "GOOGLE_API_KEY=your_api_key_here" > .env + echo "GOOGLE_CLIENT_ID=your_client_id_here" >> .env + echo "GOOGLE_CLIENT_SECRET=your_client_secret_here" >> .env + ``` 2. Run the example -``` -uv run . -``` + ```bash + uv run . + ``` From b701b0cac971df06e0a24493775a63c957c1659d Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 10:27:41 -0500 Subject: [PATCH 03/30] Further markdown linting --- .prettierrc | 6 ------ CODE_OF_CONDUCT.md | 4 ++-- examples/google_adk/birthday_planner/README.md | 12 ++++++------ examples/langgraph/README.md | 8 +++----- 4 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index a415f1dd..00000000 --- a/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "tabWidth": 2, - "useTabs": false, - "trailingComma": "es5", - "bracketSameLine": true -} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 9feb80f7..257e8a0c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -47,7 +47,7 @@ offensive, or harmful. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail +representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. @@ -93,4 +93,4 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html Note: A version of this file is also available in the -[New Project repo](https://github.com/google/new-project/blob/master/docs/code-of-conduct.md). +[New Project repository](https://github.com/google/new-project/blob/master/docs/code-of-conduct.md). diff --git a/examples/google_adk/birthday_planner/README.md b/examples/google_adk/birthday_planner/README.md index fb927193..52edb373 100644 --- a/examples/google_adk/birthday_planner/README.md +++ b/examples/google_adk/birthday_planner/README.md @@ -14,14 +14,14 @@ This agent helps plan birthday parties. It has access to a Calendar Agent that i 1. Create the `.env` file with your API Key - ```bash - echo "GOOGLE_API_KEY=your_api_key_here" > .env - ``` + ```bash + echo "GOOGLE_API_KEY=your_api_key_here" > .env + ``` 2. Run the Calendar Agent. See examples/google_adk/calendar_agent. 3. Run the example - ```sh - uv run . - ``` + ```sh + uv run . + ``` diff --git a/examples/langgraph/README.md b/examples/langgraph/README.md index f66e9808..cdd4ba1f 100644 --- a/examples/langgraph/README.md +++ b/examples/langgraph/README.md @@ -4,21 +4,19 @@ An example LangGraph agent that helps with currency conversion. ## Getting started -1. Extract the zip file and cd to examples folder - -2. Create an environment file with your API key: +1. Create an environment file with your API key: ```bash echo "GOOGLE_API_KEY=your_api_key_here" > .env ``` -3. Start the server +2. Start the server ```bash uv run main.py ``` -4. Run the test client +3. Run the test client ```bash uv run test_client.py From c78879401a1d3a3fe6b8af60bff6d1019e129537 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 10:31:50 -0500 Subject: [PATCH 04/30] Update gitignore and spelling --- .github/actions/spelling/allow.txt | 5 + .gitignore | 187 +----------------- .../birthday_planner/adk_agent_executor.py | 8 +- 3 files changed, 17 insertions(+), 183 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index d73260da..33479008 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -1,7 +1,10 @@ +AClient AError ARequest +ARun AServer AStarlette +Llm adk codegen datamodel @@ -14,3 +17,5 @@ opensource socio sse tagwords +AClient +ARun diff --git a/.gitignore b/.gitignore index c10627bf..5e87de5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,181 +1,10 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*.pyc -*$py.class -**/dist -/tmp -/out-tsc -/bazel-out - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -Pipfile.lock -Pipfile - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments +.DS_Store +__pycache__ .env +.coverage +.mypy_cache +.pytest_cache +.ruff_cache .venv -.venv* -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# macOS -.DS_Store - -# PyCharm -.idea - -# User-specific files -language/examples/prompt-design/train.csv -README-TOC*.md - -# Terraform -terraform.tfstate** -.terraform* -.Terraform* - -tmp* - -# Node -**/node_modules -npm-debug.log -yarn-error.log - -# IDEs and editors -.idea/ -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# Miscellaneous -**/.angular/* -/.angular/cache -.sass-cache/ -/connect.lock -/coverage -/libpeerconnection.log -testem.log -/typings - -# System files -.DS_Store -Thumbs.db +coverage.xml +spec.json diff --git a/examples/google_adk/birthday_planner/adk_agent_executor.py b/examples/google_adk/birthday_planner/adk_agent_executor.py index de6708cf..2d821931 100644 --- a/examples/google_adk/birthday_planner/adk_agent_executor.py +++ b/examples/google_adk/birthday_planner/adk_agent_executor.py @@ -308,12 +308,12 @@ async def _get_agent_task(self, task_id) -> Task: def convert_a2a_parts_to_genai(parts: list[Part]) -> list[types.Part]: - """Convert a list of A2A Part types into a list of Google GenAI Part types.""" + """Convert a list of A2A Part types into a list of Google Gen AI Part types.""" return [convert_a2a_part_to_genai(part) for part in parts] def convert_a2a_part_to_genai(part: Part) -> types.Part: - """Convert a single A2A Part type into a Google GenAI Part type.""" + """Convert a single A2A Part type into a Google Gen AI Part type.""" part = part.root if isinstance(part, TextPart): return types.Part(text=part.text) @@ -335,7 +335,7 @@ def convert_a2a_part_to_genai(part: Part) -> types.Part: def convert_genai_parts_to_a2a(parts: list[types.Part]) -> list[Part]: - """Convert a list of Google GenAI Part types into a list of A2A Part types.""" + """Convert a list of Google Gen AI Part types into a list of A2A Part types.""" return [ convert_genai_part_to_a2a(part) for part in parts @@ -344,7 +344,7 @@ def convert_genai_parts_to_a2a(parts: list[types.Part]) -> list[Part]: def convert_genai_part_to_a2a(part: types.Part) -> Part: - """Convert a single Google GenAI Part type into an A2A Part type.""" + """Convert a single Google Gen AI Part type into an A2A Part type.""" if part.text: return TextPart(text=part.text) if part.file_data: From 364532251975f166afe83da9082b551ab7cd564a Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 10:35:56 -0500 Subject: [PATCH 05/30] Move markdown lint --- .github/linters/.markdown-lint.yaml => .markdown-lint.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/linters/.markdown-lint.yaml => .markdown-lint.yaml (100%) diff --git a/.github/linters/.markdown-lint.yaml b/.markdown-lint.yaml similarity index 100% rename from .github/linters/.markdown-lint.yaml rename to .markdown-lint.yaml From 54e7d6fc10d55ae1cddb66d61ab7111be28339f3 Mon Sep 17 00:00:00 2001 From: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Date: Thu, 15 May 2025 11:16:59 -0500 Subject: [PATCH 06/30] Update .github/actions/spelling/allow.txt --- .github/actions/spelling/allow.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 33479008..01db42eb 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -17,5 +17,3 @@ opensource socio sse tagwords -AClient -ARun From 673cb112dba2543f0b658284b28de89869bbf398 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:17:55 -0500 Subject: [PATCH 07/30] Add examples to jscpd ignore --- .github/linters/.jscpd.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json index 5e86d6d8..fb0f3b60 100644 --- a/.github/linters/.jscpd.json +++ b/.github/linters/.jscpd.json @@ -1,5 +1,5 @@ { - "ignore": ["**/.github/**", "**/.git/**", "**/tests/**"], + "ignore": ["**/.github/**", "**/.git/**", "**/tests/**", "**/examples/**"], "threshold": 3, "reporters": ["html", "markdown"] } From bf8c6b2c227a6e8b823b2d5665a4661a58118cce Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:27:23 -0500 Subject: [PATCH 08/30] Fix ruff lint errors --- src/a2a/utils/helpers.py | 7 ++- .../request_handlers/test_jsonrpc_handler.py | 52 ++++++++++--------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/a2a/utils/helpers.py b/src/a2a/utils/helpers.py index a0fe3cec..6d838d98 100644 --- a/src/a2a/utils/helpers.py +++ b/src/a2a/utils/helpers.py @@ -91,11 +91,10 @@ def validate(expression, error_message=None): def decorator(function): def wrapper(self, *args, **kwargs): if not expression(self): - if not error_message: - error_message = str(expression) - logger.error(f'Unsupported Operation: {error_message}') + final_message = error_message or str(expression) + logger.error(f'Unsupported Operation: {final_message}') raise ServerError( - UnsupportedOperationError(message=error_message) + UnsupportedOperationError(message=final_message) ) return function(self, *args, **kwargs) diff --git a/tests/server/request_handlers/test_jsonrpc_handler.py b/tests/server/request_handlers/test_jsonrpc_handler.py index c482fcf7..8adb41db 100644 --- a/tests/server/request_handlers/test_jsonrpc_handler.py +++ b/tests/server/request_handlers/test_jsonrpc_handler.py @@ -1,49 +1,53 @@ import unittest import unittest.async_case -from unittest.mock import AsyncMock, patch, MagicMock + +from collections.abc import AsyncGenerator +from typing import Any +from unittest.mock import AsyncMock, MagicMock, patch + import pytest -from a2a.server.events.event_queue import EventQueue + from a2a.server.agent_execution import AgentExecutor -from a2a.utils.errors import ServerError +from a2a.server.events import ( + QueueManager, +) +from a2a.server.events.event_queue import EventQueue from a2a.server.request_handlers import ( DefaultRequestHandler, JSONRPCHandler, ) -from a2a.server.events import ( - QueueManager, -) from a2a.server.tasks import TaskStore from a2a.types import ( - AgentCard, AgentCapabilities, + AgentCard, + Artifact, + CancelTaskRequest, + CancelTaskSuccessResponse, GetTaskRequest, GetTaskResponse, GetTaskSuccessResponse, - Task, - TaskQueryParams, JSONRPCErrorResponse, - TaskNotFoundError, - TaskIdParams, - CancelTaskRequest, - CancelTaskSuccessResponse, - UnsupportedOperationError, - SendMessageRequest, Message, MessageSendParams, + Part, + SendMessageRequest, SendMessageSuccessResponse, SendStreamingMessageRequest, SendStreamingMessageSuccessResponse, + Task, TaskArtifactUpdateEvent, + TaskIdParams, + TaskNotFoundError, + TaskQueryParams, + TaskResubscriptionRequest, + TaskState, + TaskStatus, TaskStatusUpdateEvent, - Artifact, - Part, TextPart, - TaskStatus, - TaskState, - TaskResubscriptionRequest, + UnsupportedOperationError, ) -from collections.abc import AsyncGenerator -from typing import Any +from a2a.utils.errors import ServerError + MINIMAL_TASK: dict[str, Any] = { 'id': 'task_123', @@ -316,7 +320,7 @@ async def streaming_coro(): assert isinstance( event.root, SendStreamingMessageSuccessResponse ) - assert collected_events[i].root.result == events[i] + assert event.root.result == events[i] mock_agent_executor.execute.assert_called_once() async def test_on_message_stream_new_message_existing_task_success( @@ -387,7 +391,7 @@ async def test_on_resubscribe_existing_task_success( request_handler = DefaultRequestHandler( mock_agent_executor, mock_task_store, mock_queue_manager ) - mock_agent_card = MagicMock(spec=AgentCard) + self.mock_agent_card = MagicMock(spec=AgentCard) handler = JSONRPCHandler(self.mock_agent_card, request_handler) mock_task = Task(**MINIMAL_TASK, history=[]) events: list[Any] = [ From 5c102ae08a44586321538eadcc086d5b9f9a0f65 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:44:51 -0500 Subject: [PATCH 09/30] Rename markdown lint config --- .github/workflows/linter.yaml | 1 + .markdown-lint.yaml | 1 - .markdownlint.json | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 .markdown-lint.yaml create mode 100644 .markdownlint.json diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 152340bc..2a2e61ac 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -61,3 +61,4 @@ jobs: VALIDATE_CHECKOV: false VALIDATE_JAVASCRIPT_STANDARD: false VALIDATE_TYPESCRIPT_STANDARD: false + MARKDOWN_CONFIG_FILE: .markdownlint.json diff --git a/.markdown-lint.yaml b/.markdown-lint.yaml deleted file mode 100644 index 20de1609..00000000 --- a/.markdown-lint.yaml +++ /dev/null @@ -1 +0,0 @@ -MD034: false diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000..85a0d4a6 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,3 @@ +{ + "MD034": false +} From e6743aeb5d7ed1670f1271608444518c23bbf79f Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:45:28 -0500 Subject: [PATCH 10/30] Spelling --- .github/actions/spelling/allow.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 01db42eb..5a426731 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -4,9 +4,12 @@ ARequest ARun AServer AStarlette +JSONRPCt Llm adk +autouse codegen +coro datamodel genai inmemory From 383a3719e71065cbfdd626ab9410f94331668009 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:46:48 -0500 Subject: [PATCH 11/30] Update JSCPD to 0 --- .github/linters/.jscpd.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json index fb0f3b60..f8ac4cca 100644 --- a/.github/linters/.jscpd.json +++ b/.github/linters/.jscpd.json @@ -1,5 +1,5 @@ { "ignore": ["**/.github/**", "**/.git/**", "**/tests/**", "**/examples/**"], - "threshold": 3, + "threshold": 0, "reporters": ["html", "markdown"] } From d206962cb1963c831b0dba133b80bd3a9f5d1284 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:48:24 -0500 Subject: [PATCH 12/30] Add init.py --- examples/google_adk/__init__.py | 0 examples/google_adk/birthday_planner/__init__.py | 0 examples/google_adk/calendar_agent/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/google_adk/__init__.py create mode 100644 examples/google_adk/birthday_planner/__init__.py create mode 100644 examples/google_adk/calendar_agent/__init__.py diff --git a/examples/google_adk/__init__.py b/examples/google_adk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/google_adk/birthday_planner/__init__.py b/examples/google_adk/birthday_planner/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/google_adk/calendar_agent/__init__.py b/examples/google_adk/calendar_agent/__init__.py new file mode 100644 index 00000000..e69de29b From 9c0d11095bf74baa134073da8f3fa857b138064d Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:50:06 -0500 Subject: [PATCH 13/30] Ignore commitlint --- .github/workflows/linter.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linter.yaml b/.github/workflows/linter.yaml index 2a2e61ac..2fcf37ae 100644 --- a/.github/workflows/linter.yaml +++ b/.github/workflows/linter.yaml @@ -61,4 +61,5 @@ jobs: VALIDATE_CHECKOV: false VALIDATE_JAVASCRIPT_STANDARD: false VALIDATE_TYPESCRIPT_STANDARD: false + VALIDATE_GIT_COMMITLINT: false MARKDOWN_CONFIG_FILE: .markdownlint.json From 65b5aa290b8d847165832a395127ba158d074498 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:50:56 -0500 Subject: [PATCH 14/30] move markdown lint file --- .markdownlint.json => .github/linters/.markdownlint.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .markdownlint.json => .github/linters/.markdownlint.json (100%) diff --git a/.markdownlint.json b/.github/linters/.markdownlint.json similarity index 100% rename from .markdownlint.json rename to .github/linters/.markdownlint.json From bf4f21ffec0fb2c40ee53d6ebf3ed7a34090b51d Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:54:49 -0500 Subject: [PATCH 15/30] Add conventional-commit-lint.yaml --- .github/conventional-commit-lint.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/conventional-commit-lint.yaml diff --git a/.github/conventional-commit-lint.yaml b/.github/conventional-commit-lint.yaml new file mode 100644 index 00000000..c967ffa6 --- /dev/null +++ b/.github/conventional-commit-lint.yaml @@ -0,0 +1,2 @@ +enabled: true +always_check_pr_title: true From 3028fc6c7180b567948aba5a665175df07b12c47 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 11:59:39 -0500 Subject: [PATCH 16/30] Fix markdown/MyPy issues --- .github/linters/.markdownlint.json | 3 ++- examples/langgraph/__init__.py | 0 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 examples/langgraph/__init__.py diff --git a/.github/linters/.markdownlint.json b/.github/linters/.markdownlint.json index 85a0d4a6..940cb359 100644 --- a/.github/linters/.markdownlint.json +++ b/.github/linters/.markdownlint.json @@ -1,3 +1,4 @@ { - "MD034": false + "MD034": false, + "MD013": false } diff --git a/examples/langgraph/__init__.py b/examples/langgraph/__init__.py new file mode 100644 index 00000000..e69de29b From db064cfa2ae87fe403d5fd5ca66bb5cfe384b4fa Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:00:05 -0500 Subject: [PATCH 17/30] Prettier --- .github/linters/.markdownlint.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/linters/.markdownlint.json b/.github/linters/.markdownlint.json index 940cb359..5c6dcb9d 100644 --- a/.github/linters/.markdownlint.json +++ b/.github/linters/.markdownlint.json @@ -1,4 +1,4 @@ { - "MD034": false, - "MD013": false + "MD034": false, + "MD013": false } From e308305a02f5e99cc1525b711000b96d74aa5aa3 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:10:02 -0500 Subject: [PATCH 18/30] Fixed Copy/paste error --- examples/langgraph/agent.py | 3 +- src/a2a/client/client.py | 75 ++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/examples/langgraph/agent.py b/examples/langgraph/agent.py index 409995cc..41323427 100644 --- a/examples/langgraph/agent.py +++ b/examples/langgraph/agent.py @@ -11,9 +11,10 @@ ) from langchain_core.tools import tool # type: ignore from langchain_google_genai import ChatGoogleGenerativeAI +from pydantic import BaseModel + from langgraph.checkpoint.memory import MemorySaver from langgraph.prebuilt import create_react_agent # type: ignore -from pydantic import BaseModel logger = logging.getLogger(__name__) diff --git a/src/a2a/client/client.py b/src/a2a/client/client.py index 2f032707..f575fb67 100644 --- a/src/a2a/client/client.py +++ b/src/a2a/client/client.py @@ -26,6 +26,36 @@ ) +async def _make_httpx_request( + client: httpx.AsyncClient, + method: str, + url: str, + json_payload: dict[str, Any] | None = None, + http_kwargs: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Makes an HTTP request and handles common errors, returning parsed JSON.""" + try: + if method.upper() == 'GET': + response = await client.get(url, **(http_kwargs or {})) + elif method.upper() == 'POST': + response = await client.post( + url, json=json_payload, **(http_kwargs or {}) + ) + else: + raise ValueError(f'Unsupported HTTP method: {method}') + + response.raise_for_status() + return response.json() + except httpx.HTTPStatusError as e: + raise A2AClientHTTPError(e.response.status_code, str(e)) from e + except json.JSONDecodeError as e: + raise A2AClientJSONError(str(e)) from e + except httpx.RequestError as e: + raise A2AClientHTTPError( + 503, f'Network communication error: {e}' + ) from e + + class A2ACardResolver: """Agent Card resolver.""" @@ -42,21 +72,13 @@ def __init__( async def get_agent_card( self, http_kwargs: dict[str, Any] | None = None ) -> AgentCard: - try: - response = await self.httpx_client.get( - f'{self.base_url}/{self.agent_card_path}', - **(http_kwargs or {}), - ) - response.raise_for_status() - return AgentCard.model_validate(response.json()) - except httpx.HTTPStatusError as e: - raise A2AClientHTTPError(e.response.status_code, str(e)) from e - except json.JSONDecodeError as e: - raise A2AClientJSONError(str(e)) from e - except httpx.RequestError as e: - raise A2AClientHTTPError( - 503, f'Network communication error: {e}' - ) from e + response_json = await _make_httpx_request( + client=self.httpx_client, + method='GET', + url=f'{self.base_url}/{self.agent_card_path}', + http_kwargs=http_kwargs, + ) + return AgentCard.model_validate(response_json) class A2AClient: @@ -152,22 +174,15 @@ async def _send_request( Args: rpc_request_payload: JSON RPC payload for sending the request - **kwargs: Additional keyword arguments to pass to the httpx client. + http_kwargs: Additional keyword arguments to pass to the httpx client. """ - try: - response = await self.httpx_client.post( - self.url, json=rpc_request_payload, **(http_kwargs or {}) - ) - response.raise_for_status() - return response.json() - except httpx.HTTPStatusError as e: - raise A2AClientHTTPError(e.response.status_code, str(e)) from e - except json.JSONDecodeError as e: - raise A2AClientJSONError(str(e)) from e - except httpx.RequestError as e: - raise A2AClientHTTPError( - 503, f'Network communication error: {e}' - ) from e + return await _make_httpx_request( + client=self.httpx_client, + method='POST', + url=self.url, + json_payload=rpc_request_payload, + http_kwargs=http_kwargs, + ) async def get_task( self, From e0a34afe1550e8c62870f4546b625fda54b47a85 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:12:23 -0500 Subject: [PATCH 19/30] Add .nox to .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5e87de5a..4da52568 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ __pycache__ .ruff_cache .venv coverage.xml -spec.json +.nox From 81073fe0c41eb44fd4586ef20ac2ba036d09960d Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:17:35 -0500 Subject: [PATCH 20/30] Adjust spelling --- .github/actions/spelling/allow.txt | 3 +++ .github/workflows/spelling.yaml | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 5a426731..1feeede6 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -1,11 +1,14 @@ +ACard AClient AError ARequest ARun AServer AStarlette +EUR JSONRPCt Llm +aconnect adk autouse codegen diff --git a/.github/workflows/spelling.yaml b/.github/workflows/spelling.yaml index 72516608..94f87aab 100644 --- a/.github/workflows/spelling.yaml +++ b/.github/workflows/spelling.yaml @@ -3,14 +3,14 @@ name: Check Spelling on: pull_request: branches: - - "**" + - '**' types: - - "opened" - - "reopened" - - "synchronize" + - 'opened' + - 'reopened' + - 'synchronize' issue_comment: types: - - "created" + - 'created' jobs: spelling: @@ -80,6 +80,6 @@ jobs: cspell:sql/src/tsql.txt cspell:terraform/dict/terraform.txt cspell:typescript/dict/typescript.txt - check_extra_dictionaries: "" - only_check_changed_files: true - longest_word: "10" + check_extra_dictionaries: '' + only_check_changed_files: false + longest_word: '10' From a2a51bf1ac0ab75f633304f7580ecfa962ec6884 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:17:40 -0500 Subject: [PATCH 21/30] Formatting --- src/a2a/server/events/event_consumer.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/a2a/server/events/event_consumer.py b/src/a2a/server/events/event_consumer.py index e1109402..d70a3d12 100644 --- a/src/a2a/server/events/event_consumer.py +++ b/src/a2a/server/events/event_consumer.py @@ -51,11 +51,13 @@ async def consume_all(self) -> AsyncGenerator[Event]: raise self._exception try: # We use a timeout when waiting for an event from the queue. - # This is required because it allows the loop to check if - # `self._exception` has been set by the `agent_task_callback`. + # This is required because it allows the loop to check if + # `self._exception` has been set by the `agent_task_callback`. # Without the timeout, loop might hang indefinitely if no events are # enqueued by the agent and the agent simply threw an exception - event = await asyncio.wait_for(self.queue.dequeue_event(), timeout=self._timeout) + event = await asyncio.wait_for( + self.queue.dequeue_event(), timeout=self._timeout + ) logger.debug( f'Dequeued event of type: {type(event)} in consume_all.' ) @@ -88,11 +90,7 @@ async def consume_all(self) -> AsyncGenerator[Event]: continue except asyncio.QueueShutDown: break - - - - def agent_task_callback(self, agent_task: asyncio.Task[None]): - if agent_task.exception() is not None: - self._exception = agent_task.exception() \ No newline at end of file + if agent_task.exception() is not None: + self._exception = agent_task.exception() From 71d8dd4b7979571eff6e78d0fe7d59c09db16205 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:18:02 -0500 Subject: [PATCH 22/30] Change back only check changed --- .github/workflows/spelling.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spelling.yaml b/.github/workflows/spelling.yaml index 94f87aab..cada5d5a 100644 --- a/.github/workflows/spelling.yaml +++ b/.github/workflows/spelling.yaml @@ -81,5 +81,5 @@ jobs: cspell:terraform/dict/terraform.txt cspell:typescript/dict/typescript.txt check_extra_dictionaries: '' - only_check_changed_files: false + only_check_changed_files: true longest_word: '10' From aed5652535e6b21005c0e2d2b73700fd1e92c395 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:51:08 -0500 Subject: [PATCH 23/30] Undo JSCPD fix --- src/a2a/client/client.py | 75 ++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/src/a2a/client/client.py b/src/a2a/client/client.py index f575fb67..2f032707 100644 --- a/src/a2a/client/client.py +++ b/src/a2a/client/client.py @@ -26,36 +26,6 @@ ) -async def _make_httpx_request( - client: httpx.AsyncClient, - method: str, - url: str, - json_payload: dict[str, Any] | None = None, - http_kwargs: dict[str, Any] | None = None, -) -> dict[str, Any]: - """Makes an HTTP request and handles common errors, returning parsed JSON.""" - try: - if method.upper() == 'GET': - response = await client.get(url, **(http_kwargs or {})) - elif method.upper() == 'POST': - response = await client.post( - url, json=json_payload, **(http_kwargs or {}) - ) - else: - raise ValueError(f'Unsupported HTTP method: {method}') - - response.raise_for_status() - return response.json() - except httpx.HTTPStatusError as e: - raise A2AClientHTTPError(e.response.status_code, str(e)) from e - except json.JSONDecodeError as e: - raise A2AClientJSONError(str(e)) from e - except httpx.RequestError as e: - raise A2AClientHTTPError( - 503, f'Network communication error: {e}' - ) from e - - class A2ACardResolver: """Agent Card resolver.""" @@ -72,13 +42,21 @@ def __init__( async def get_agent_card( self, http_kwargs: dict[str, Any] | None = None ) -> AgentCard: - response_json = await _make_httpx_request( - client=self.httpx_client, - method='GET', - url=f'{self.base_url}/{self.agent_card_path}', - http_kwargs=http_kwargs, - ) - return AgentCard.model_validate(response_json) + try: + response = await self.httpx_client.get( + f'{self.base_url}/{self.agent_card_path}', + **(http_kwargs or {}), + ) + response.raise_for_status() + return AgentCard.model_validate(response.json()) + except httpx.HTTPStatusError as e: + raise A2AClientHTTPError(e.response.status_code, str(e)) from e + except json.JSONDecodeError as e: + raise A2AClientJSONError(str(e)) from e + except httpx.RequestError as e: + raise A2AClientHTTPError( + 503, f'Network communication error: {e}' + ) from e class A2AClient: @@ -174,15 +152,22 @@ async def _send_request( Args: rpc_request_payload: JSON RPC payload for sending the request - http_kwargs: Additional keyword arguments to pass to the httpx client. + **kwargs: Additional keyword arguments to pass to the httpx client. """ - return await _make_httpx_request( - client=self.httpx_client, - method='POST', - url=self.url, - json_payload=rpc_request_payload, - http_kwargs=http_kwargs, - ) + try: + response = await self.httpx_client.post( + self.url, json=rpc_request_payload, **(http_kwargs or {}) + ) + response.raise_for_status() + return response.json() + except httpx.HTTPStatusError as e: + raise A2AClientHTTPError(e.response.status_code, str(e)) from e + except json.JSONDecodeError as e: + raise A2AClientJSONError(str(e)) from e + except httpx.RequestError as e: + raise A2AClientHTTPError( + 503, f'Network communication error: {e}' + ) from e async def get_task( self, From 771ffd142bc11d0a11821830c76a67622a88867a Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:53:33 -0500 Subject: [PATCH 24/30] Formatting --- examples/google_adk/birthday_planner/adk_agent_executor.py | 2 +- src/a2a/server/events/event_consumer.py | 2 +- src/a2a/server/request_handlers/default_request_handler.py | 4 ++-- src/a2a/server/tasks/result_aggregator.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/google_adk/birthday_planner/adk_agent_executor.py b/examples/google_adk/birthday_planner/adk_agent_executor.py index 2d821931..4cf6a569 100644 --- a/examples/google_adk/birthday_planner/adk_agent_executor.py +++ b/examples/google_adk/birthday_planner/adk_agent_executor.py @@ -99,7 +99,7 @@ def _run_agent( session_id, new_message: types.Content, task_updater: TaskUpdater, - ) -> AsyncGenerator[Event, None]: + ) -> AsyncGenerator[Event]: return self.runner.run_async( session_id=session_id, user_id='self', diff --git a/src/a2a/server/events/event_consumer.py b/src/a2a/server/events/event_consumer.py index d70a3d12..73cc9c75 100644 --- a/src/a2a/server/events/event_consumer.py +++ b/src/a2a/server/events/event_consumer.py @@ -85,7 +85,7 @@ async def consume_all(self) -> AsyncGenerator[Event]: logger.debug('Stopping event consumption in consume_all.') self.queue.close() break - except asyncio.TimeoutError: + except TimeoutError: # continue polling until there is a final event continue except asyncio.QueueShutDown: diff --git a/src/a2a/server/request_handlers/default_request_handler.py b/src/a2a/server/request_handlers/default_request_handler.py index f3749d15..bf616c0c 100644 --- a/src/a2a/server/request_handlers/default_request_handler.py +++ b/src/a2a/server/request_handlers/default_request_handler.py @@ -161,7 +161,7 @@ async def on_message_send( async def on_message_send_stream( self, params: MessageSendParams - ) -> AsyncGenerator[Event, None]: + ) -> AsyncGenerator[Event]: """Default handler for 'message/stream'.""" task_manager = TaskManager( task_id=params.message.taskId, @@ -232,7 +232,7 @@ async def on_get_task_push_notification_config( async def on_resubscribe_to_task( self, params: TaskIdParams - ) -> AsyncGenerator[Event, None]: + ) -> AsyncGenerator[Event]: """Default handler for 'tasks/resubscribe'.""" task: Task | None = await self.task_store.get(params.id) if not task: diff --git a/src/a2a/server/tasks/result_aggregator.py b/src/a2a/server/tasks/result_aggregator.py index c1cafb8b..94f27403 100644 --- a/src/a2a/server/tasks/result_aggregator.py +++ b/src/a2a/server/tasks/result_aggregator.py @@ -36,7 +36,7 @@ async def current_result(self) -> Task | Message | None: async def consume_and_emit( self, consumer: EventConsumer - ) -> AsyncGenerator[Event, None]: + ) -> AsyncGenerator[Event]: """Processes the event stream and emits the same event stream out.""" async for event in consumer.consume_all(): await self.task_manager.process(event) From 803958e413eed46499c989d25d57749fb4e21795 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:53:40 -0500 Subject: [PATCH 25/30] Update noxfile to autoflake python 3.13 --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 4d8b3165..fd9569fb 100644 --- a/noxfile.py +++ b/noxfile.py @@ -121,7 +121,7 @@ def format(session): session.run( 'pyupgrade', '--exit-zero-even-if-changed', - '--py311-plus', + '--py313-plus', *lint_paths_py, ) session.run( From 12ee89dfdac1c55c9a896bd2444b66e96c0a60bd Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:55:14 -0500 Subject: [PATCH 26/30] Add noxfile to spelling ignore --- .github/actions/spelling/excludes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 0d6f82d4..a2267ef9 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -85,3 +85,4 @@ ^\Q.github/workflows/linter.yaml\E$ \.gitignore\E$ \.vscode/ +/noxfile.py$ From 0e1475eda489a02de635e2b92211c1d99f881d2c Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:58:19 -0500 Subject: [PATCH 27/30] Spelling --- .github/actions/spelling/allow.txt | 10 ++++++++++ .github/actions/spelling/excludes.txt | 1 + 2 files changed, 11 insertions(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 1feeede6..20747808 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -6,20 +6,30 @@ ARun AServer AStarlette EUR +GBP +INR +JPY JSONRPCt Llm aconnect adk autouse +cla +cls +coc codegen coro datamodel +dunders genai +gle inmemory langgraph lifecycles +linting oauthoidc opensource socio sse tagwords +vulnz diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index a2267ef9..5211b963 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -86,3 +86,4 @@ \.gitignore\E$ \.vscode/ /noxfile.py$ +\.ruff.toml$ From 22c664652d70034d7304fbf75939bcbe9b591677 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 12:59:18 -0500 Subject: [PATCH 28/30] Prettier yaml --- .github/workflows/spelling.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/spelling.yaml b/.github/workflows/spelling.yaml index cada5d5a..72516608 100644 --- a/.github/workflows/spelling.yaml +++ b/.github/workflows/spelling.yaml @@ -3,14 +3,14 @@ name: Check Spelling on: pull_request: branches: - - '**' + - "**" types: - - 'opened' - - 'reopened' - - 'synchronize' + - "opened" + - "reopened" + - "synchronize" issue_comment: types: - - 'created' + - "created" jobs: spelling: @@ -80,6 +80,6 @@ jobs: cspell:sql/src/tsql.txt cspell:terraform/dict/terraform.txt cspell:typescript/dict/typescript.txt - check_extra_dictionaries: '' + check_extra_dictionaries: "" only_check_changed_files: true - longest_word: '10' + longest_word: "10" From 6492448887bf5c0658f5cee43aa592105619e05b Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 13:00:28 -0500 Subject: [PATCH 29/30] Fix spelling excludes --- .github/actions/spelling/excludes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 5211b963..7b4de3ec 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -85,5 +85,5 @@ ^\Q.github/workflows/linter.yaml\E$ \.gitignore\E$ \.vscode/ -/noxfile.py$ +noxfile.py \.ruff.toml$ From be187974490a55a127b255ff26edcf9b876e3494 Mon Sep 17 00:00:00 2001 From: Holt Skinner Date: Thu, 15 May 2025 13:11:35 -0500 Subject: [PATCH 30/30] Set JSCPD to 3 --- .github/linters/.jscpd.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json index f8ac4cca..fb0f3b60 100644 --- a/.github/linters/.jscpd.json +++ b/.github/linters/.jscpd.json @@ -1,5 +1,5 @@ { "ignore": ["**/.github/**", "**/.git/**", "**/tests/**", "**/examples/**"], - "threshold": 0, + "threshold": 3, "reporters": ["html", "markdown"] }