Skip to content

Commit b2bb57f

Browse files
Merge branch 'main' into ae_routes
2 parents 2c57faf + 2ddf585 commit b2bb57f

File tree

21 files changed

+987
-351
lines changed

21 files changed

+987
-351
lines changed

.github/actions/spelling/allow.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ agentic
99
AGrpc
1010
aio
1111
aiomysql
12+
amannn
1213
aproject
1314
ARequest
1415
ARun
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: "Conventional Commits"
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- edited
8+
- synchronize
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
main:
15+
permissions:
16+
pull-requests: read
17+
statuses: write
18+
name: Validate PR Title
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: semantic-pull-request
22+
uses: amannn/action-semantic-pull-request@v5.5.3
23+
env:
24+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25+
with:
26+
validateSingleCommit: false

.github/workflows/unit-tests.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,21 @@ jobs:
4040
steps:
4141
- name: Checkout code
4242
uses: actions/checkout@v4
43-
- name: Set up Python ${{ matrix.python-version }}
44-
uses: actions/setup-python@v5
45-
with:
46-
python-version: ${{ matrix.python-version }}
4743
- name: Set up test environment variables
4844
run: |
4945
echo "POSTGRES_TEST_DSN=postgresql+asyncpg://a2a:a2a_password@localhost:5432/a2a_test" >> $GITHUB_ENV
5046
echo "MYSQL_TEST_DSN=mysql+aiomysql://a2a:a2a_password@localhost:3306/a2a_test" >> $GITHUB_ENV
5147
52-
- name: Install uv
48+
- name: Install uv for Python ${{ matrix.python-version }}
5349
uses: astral-sh/setup-uv@v6
50+
with:
51+
python-version: ${{ matrix.python-version }}
5452
- name: Add uv to PATH
5553
run: |
5654
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
5755
- name: Install dependencies
5856
run: uv sync --dev --extra sql --extra encryption --extra grpc --extra telemetry
5957
- name: Run tests and check coverage
60-
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=89
58+
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=88
6159
- name: Show coverage summary in log
6260
run: uv run coverage report

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ When you're working within a uv project or a virtual environment managed by uv,
3333
uv add a2a-sdk
3434
```
3535

36+
To include the optional HTTP server components (FastAPI, Starlette), install the `http-server` extra:
37+
38+
```bash
39+
uv add a2a-sdk[http-server]
40+
```
41+
3642
To install with gRPC support:
3743

3844
```bash
@@ -69,6 +75,12 @@ If you prefer to use pip, the standard Python package installer, you can install
6975
pip install a2a-sdk
7076
```
7177

78+
To include the optional HTTP server components (FastAPI, Starlette), install the `http-server` extra:
79+
80+
```bash
81+
pip install a2a-sdk[http-server]
82+
```
83+
7284
To install with gRPC support:
7385

7486
```bash

error_handlers.py

Whitespace-only changes.

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
88
requires-python = ">=3.10"
99
keywords = ["A2A", "A2A SDK", "A2A Protocol", "Agent2Agent", "Agent 2 Agent"]
1010
dependencies = [
11-
"fastapi>=0.95.0",
1211
"httpx>=0.28.1",
1312
"httpx-sse>=0.4.0",
1413
"pydantic>=2.11.3",
15-
"sse-starlette",
16-
"starlette",
1714
"protobuf>=5.29.5",
1815
"google-api-core>=1.26.0",
1916
]
@@ -32,6 +29,7 @@ classifiers = [
3229
]
3330

3431
[project.optional-dependencies]
32+
http-server = ["fastapi>=0.115.2", "sse-starlette", "starlette"]
3533
postgresql = ["sqlalchemy[asyncio,postgresql-asyncpg]>=2.0.0"]
3634
mysql = ["sqlalchemy[asyncio,aiomysql]>=2.0.0"]
3735
sqlite = ["sqlalchemy[asyncio,aiosqlite]>=2.0.0"]
@@ -89,6 +87,9 @@ dev = [
8987
"types-protobuf",
9088
"types-requests",
9189
"pre-commit",
90+
"fastapi>=0.115.2",
91+
"sse-starlette",
92+
"starlette",
9293
"pyupgrade",
9394
"autoflake",
9495
"no_implicit_optional",

src/a2a/grpc/a2a_pb2_grpc.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ class A2AServiceStub(object):
1717
- TaskPushNotificationConfig are a resource whose parent is a task.
1818
They have get, list and create methods.
1919
- AgentCard is a static resource with only a get method.
20-
fields are not present as they don't comply with AIP rules, and the
21-
optional history_length on the get task method is not present as it also
22-
violates AIP-127 and AIP-131.
2320
"""
2421

2522
def __init__(self, channel):
@@ -91,9 +88,6 @@ class A2AServiceServicer(object):
9188
- TaskPushNotificationConfig are a resource whose parent is a task.
9289
They have get, list and create methods.
9390
- AgentCard is a static resource with only a get method.
94-
fields are not present as they don't comply with AIP rules, and the
95-
optional history_length on the get task method is not present as it also
96-
violates AIP-127 and AIP-131.
9791
"""
9892

9993
def SendMessage(self, request, context):
@@ -244,9 +238,6 @@ class A2AService(object):
244238
- TaskPushNotificationConfig are a resource whose parent is a task.
245239
They have get, list and create methods.
246240
- AgentCard is a static resource with only a get method.
247-
fields are not present as they don't comply with AIP rules, and the
248-
optional history_length on the get task method is not present as it also
249-
violates AIP-127 and AIP-131.
250241
"""
251242

252243
@staticmethod

src/a2a/server/apps/jsonrpc/fastapi_app.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
import logging
22

3-
from typing import Any
3+
from collections.abc import Callable
4+
from typing import TYPE_CHECKING, Any
45

5-
from fastapi import FastAPI
6+
7+
if TYPE_CHECKING:
8+
from fastapi import FastAPI
9+
10+
_package_fastapi_installed = True
11+
else:
12+
try:
13+
from fastapi import FastAPI
14+
15+
_package_fastapi_installed = True
16+
except ImportError:
17+
FastAPI = Any
18+
19+
_package_fastapi_installed = False
620

721
from a2a.server.apps.jsonrpc.jsonrpc_app import (
22+
CallContextBuilder,
823
JSONRPCApplication,
924
)
10-
from a2a.types import A2ARequest
25+
from a2a.server.context import ServerCallContext
26+
from a2a.server.request_handlers.jsonrpc_handler import RequestHandler
27+
from a2a.types import A2ARequest, AgentCard
1128
from a2a.utils.constants import (
1229
AGENT_CARD_WELL_KNOWN_PATH,
1330
DEFAULT_RPC_URL,
@@ -49,6 +66,50 @@ class A2AFastAPIApplication(JSONRPCApplication):
4966
(SSE).
5067
"""
5168

69+
def __init__( # noqa: PLR0913
70+
self,
71+
agent_card: AgentCard,
72+
http_handler: RequestHandler,
73+
extended_agent_card: AgentCard | None = None,
74+
context_builder: CallContextBuilder | None = None,
75+
card_modifier: Callable[[AgentCard], AgentCard] | None = None,
76+
extended_card_modifier: Callable[
77+
[AgentCard, ServerCallContext], AgentCard
78+
]
79+
| None = None,
80+
) -> None:
81+
"""Initializes the A2AFastAPIApplication.
82+
83+
Args:
84+
agent_card: The AgentCard describing the agent's capabilities.
85+
http_handler: The handler instance responsible for processing A2A
86+
requests via http.
87+
extended_agent_card: An optional, distinct AgentCard to be served
88+
at the authenticated extended card endpoint.
89+
context_builder: The CallContextBuilder used to construct the
90+
ServerCallContext passed to the http_handler. If None, no
91+
ServerCallContext is passed.
92+
card_modifier: An optional callback to dynamically modify the public
93+
agent card before it is served.
94+
extended_card_modifier: An optional callback to dynamically modify
95+
the extended agent card before it is served. It receives the
96+
call context.
97+
"""
98+
if not _package_fastapi_installed:
99+
raise ImportError(
100+
'The `fastapi` package is required to use the `A2AFastAPIApplication`.'
101+
' It can be added as a part of `a2a-sdk` optional dependencies,'
102+
' `a2a-sdk[http-server]`.'
103+
)
104+
super().__init__(
105+
agent_card=agent_card,
106+
http_handler=http_handler,
107+
extended_agent_card=extended_agent_card,
108+
context_builder=context_builder,
109+
card_modifier=card_modifier,
110+
extended_card_modifier=extended_card_modifier,
111+
)
112+
52113
def add_routes_to_app(
53114
self,
54115
app: FastAPI,

src/a2a/server/apps/jsonrpc/jsonrpc_app.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,9 @@
55

66
from abc import ABC, abstractmethod
77
from collections.abc import AsyncGenerator, Callable
8-
from typing import Any
8+
from typing import TYPE_CHECKING, Any
99

10-
from fastapi import FastAPI
1110
from pydantic import ValidationError
12-
from sse_starlette.sse import EventSourceResponse
13-
from starlette.applications import Starlette
14-
from starlette.authentication import BaseUser
15-
from starlette.exceptions import HTTPException
16-
from starlette.requests import Request
17-
from starlette.responses import JSONResponse, Response
18-
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE
1911

2012
from a2a.auth.user import UnauthenticatedUser
2113
from a2a.auth.user import User as A2AUser
@@ -61,6 +53,42 @@
6153

6254
logger = logging.getLogger(__name__)
6355

56+
if TYPE_CHECKING:
57+
from fastapi import FastAPI
58+
from sse_starlette.sse import EventSourceResponse
59+
from starlette.applications import Starlette
60+
from starlette.authentication import BaseUser
61+
from starlette.exceptions import HTTPException
62+
from starlette.requests import Request
63+
from starlette.responses import JSONResponse, Response
64+
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE
65+
66+
_package_starlette_installed = True
67+
else:
68+
FastAPI = Any
69+
try:
70+
from sse_starlette.sse import EventSourceResponse
71+
from starlette.applications import Starlette
72+
from starlette.authentication import BaseUser
73+
from starlette.exceptions import HTTPException
74+
from starlette.requests import Request
75+
from starlette.responses import JSONResponse, Response
76+
from starlette.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE
77+
78+
_package_starlette_installed = True
79+
except ImportError:
80+
_package_starlette_installed = False
81+
# Provide placeholder types for runtime type hinting when dependencies are not installed.
82+
# These will not be used if the code path that needs them is guarded by _http_server_installed.
83+
EventSourceResponse = Any
84+
Starlette = Any
85+
BaseUser = Any
86+
HTTPException = Any
87+
Request = Any
88+
JSONResponse = Any
89+
Response = Any
90+
HTTP_413_REQUEST_ENTITY_TOO_LARGE = Any
91+
6492

6593
class StarletteUserProxy(A2AUser):
6694
"""Adapts the Starlette User class to the A2A user representation."""
@@ -135,7 +163,7 @@ def __init__( # noqa: PLR0913
135163
]
136164
| None = None,
137165
) -> None:
138-
"""Initializes the A2AStarletteApplication.
166+
"""Initializes the JSONRPCApplication.
139167
140168
Args:
141169
agent_card: The AgentCard describing the agent's capabilities.
@@ -152,6 +180,12 @@ def __init__( # noqa: PLR0913
152180
the extended agent card before it is served. It receives the
153181
call context.
154182
"""
183+
if not _package_starlette_installed:
184+
raise ImportError(
185+
'Packages `starlette` and `sse-starlette` are required to use the'
186+
' `JSONRPCApplication`. They can be added as a part of `a2a-sdk`'
187+
' optional dependencies, `a2a-sdk[http-server]`.'
188+
)
155189
self.agent_card = agent_card
156190
self.extended_agent_card = extended_agent_card
157191
self.card_modifier = card_modifier

0 commit comments

Comments
 (0)