Skip to content

Commit be01a4e

Browse files
committed
Merge remote-tracking branch 'origin/1.0-dev' into ishymko/845-validation
2 parents 2075a24 + 405be3f commit be01a4e

38 files changed

Lines changed: 1066 additions & 1545 deletions

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Always check @./GEMINI.md for the full instruction list.
2+
3+
This file exists for compatibility with tools that look for AGENTS.md.

GEMINI.md

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
**A2A specification:** https://a2a-protocol.org/latest/specification/
1+
# Agent Command Center
22

3-
## Project frameworks
4-
- uv as package manager
3+
## 1. Project Overview & Purpose
4+
**Primary Goal**: This is the Python SDK for the Agent2Agent (A2A) Protocol. It allows developers to build and run agentic applications as A2A-compliant servers. It handles complex messaging, task management, and communication across different transports (REST, gRPC, JSON-RPC).
5+
**Specification**: [A2A-Protocol](https://a2a-protocol.org/latest/specification/)
56

6-
## Code style and mandatory checks
7-
1. Whenever writing python code, write types as well.
8-
2. After making the changes run ruff to check and fix the formatting issues
9-
```
10-
uv run ruff check --fix
11-
uv run ruff format
12-
```
13-
3. Run mypy type checkers to check for type errors
14-
```
15-
uv run mypy src
16-
```
17-
4. Run the unit tests to make sure that none of the unit tests are broken.
18-
```
19-
uv run pytest
20-
```
7+
## 2. Technology Stack & Architecture
8+
9+
- **Language**: Python 3.10+
10+
- **Package Manager**: `uv`
11+
- **Lead Transports**: FastAPI (REST/JSON-RPC), gRPC
12+
- **Data Layer**: SQLAlchemy (SQL), Pydantic (Logic/Legacy), Protobuf (Modern Messaging)
13+
- **Key Directories**:
14+
- `/src`: Core implementation logic.
15+
- `/tests`: Comprehensive test suite.
16+
- `/docs`: AI guides.
17+
18+
## 3. Style Guidelines & Mandatory Checks
19+
- **Style Guidelines**: Follow the rules in @./docs/ai/coding_conventions.md for every response involving code.
20+
- **Mandatory Checks**: Run the commands in @./docs/ai/mandatory_checks.md after making any changes to the code and before committing.
21+
22+
## 4. Mandatory AI Workflow for Coding Tasks
23+
1. **Required Reading**: You MUST read the contents of @./docs/ai/coding_conventions.md and @./docs/ai/mandatory_checks.md at the very beginning of EVERY coding task.
24+
2. **Initial Checklist**: Every `task.md` you create MUST include a section for **Mandatory Checks** from @./docs/ai/mandatory_checks.md.
25+
3. **Verification Requirement**: You MUST run all mandatory checks before declaring any task finished.

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636

3737
## 🧩 Compatibility
3838

39-
This SDK implements the A2A Protocol Specification [`v0.3.0`](https://a2a-protocol.org/v0.3.0/specification).
39+
This SDK implements the A2A Protocol Specification [`0.3`](https://a2a-protocol.org/v0.3.0/specification).
40+
41+
> [!IMPORTANT]
42+
> There is an [**alpha version**](https://github.com/a2aproject/a2a-python/releases?q=%22v1.0.0-alpha%22&expanded=true) available with support for both [`1.0`](https://a2a-protocol.org/v1.0.0/specification/) and [`0.3`](https://a2a-protocol.org/v0.3.0/specification) versions. Development for this version is taking place in the [`1.0-dev`](https://github.com/a2aproject/a2a-python/tree/1.0-dev) branch, tracked in [#701](https://github.com/a2aproject/a2a-python/issues/701).
4043
4144
| Transport | Client | Server |
4245
| :--- | :---: | :---: |
@@ -65,6 +68,7 @@ Install the core SDK and any desired extras using your preferred package manager
6568
| **gRPC Support** | `uv add "a2a-sdk[grpc]"` | `pip install "a2a-sdk[grpc]"` |
6669
| **OpenTelemetry Tracing**| `uv add "a2a-sdk[telemetry]"` | `pip install "a2a-sdk[telemetry]"` |
6770
| **Encryption** | `uv add "a2a-sdk[encryption]"` | `pip install "a2a-sdk[encryption]"` |
71+
| **Vertex AI Task Store** | `uv add "a2a-sdk[vertex]"` | `pip install "a2a-sdk[vertex]"` |
6872
| | | |
6973
| **Database Drivers** | | |
7074
| **PostgreSQL** | `uv add "a2a-sdk[postgresql]"` | `pip install "a2a-sdk[postgresql]"` |

docs/ai/coding_conventions.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
### Coding Conventions & Style Guide
2+
3+
Non-negotiable rules for code quality and style.
4+
5+
1. **Python Types**: All Python code MUST include type hints. All function definitions MUST include return types.
6+
2. **Type Safety**: All code MUST pass `mypy` and `pyright` checks.
7+
3. **Formatting & Linting**: All code MUST be formatted with `ruff`.
8+
9+
#### Examples:
10+
11+
**Correct Typing:**
12+
```python
13+
async def get_task_status(task: Task) -> TaskStatus:
14+
return task.status
15+
```
16+
17+
**Incorrect (Do NOT do this):**
18+
```python
19+
async def get_task_status(task): # Missing type hints for argument and return value
20+
return task.status
21+
```

docs/ai/mandatory_checks.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
### Test and Fix Commands
2+
3+
Exact shell commands required to test the project and fix formatting issues.
4+
5+
1. **Formatting & Linting**:
6+
```bash
7+
uv run ruff check --fix
8+
uv run ruff format
9+
```
10+
11+
2. **Type Checking**:
12+
```bash
13+
uv run mypy src
14+
uv run pyright src
15+
```
16+
17+
3. **Testing**:
18+
```bash
19+
uv run pytest
20+
```
21+
22+
4. **Coverage**:
23+
Only run this command after adding new source code and before committing.
24+
```bash
25+
uv run pytest --cov=src --cov-report=term-missing
26+
```

samples/hello_world_agent.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
from a2a.compat.v0_3.grpc_handler import CompatGrpcHandler
1212
from a2a.server.agent_execution.agent_executor import AgentExecutor
1313
from a2a.server.agent_execution.context import RequestContext
14-
from a2a.server.apps import A2AFastAPIApplication, A2ARESTFastAPIApplication
14+
from a2a.server.apps import A2ARESTFastAPIApplication
1515
from a2a.server.events.event_queue import EventQueue
1616
from a2a.server.request_handlers import GrpcHandler
1717
from a2a.server.request_handlers.default_request_handler import (
1818
DefaultRequestHandler,
1919
)
20+
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
2021
from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore
2122
from a2a.server.tasks.task_updater import TaskUpdater
2223
from a2a.types import (
@@ -197,14 +198,17 @@ async def serve(
197198
)
198199
rest_app = rest_app_builder.build()
199200

200-
jsonrpc_app_builder = A2AFastAPIApplication(
201+
jsonrpc_routes = create_jsonrpc_routes(
202+
agent_card=agent_card,
203+
request_handler=request_handler,
204+
rpc_url='/a2a/jsonrpc/',
205+
)
206+
agent_card_routes = create_agent_card_routes(
201207
agent_card=agent_card,
202-
http_handler=request_handler,
203-
enable_v0_3_compat=True,
204208
)
205-
206209
app = FastAPI()
207-
jsonrpc_app_builder.add_routes_to_app(app, rpc_url='/a2a/jsonrpc/')
210+
app.routes.extend(jsonrpc_routes)
211+
app.routes.extend(agent_card_routes)
208212
app.mount('/a2a/rest', rest_app)
209213

210214
grpc_server = grpc.aio.server()

src/a2a/compat/v0_3/jsonrpc_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
if TYPE_CHECKING:
1111
from starlette.requests import Request
1212

13-
from a2a.server.apps.jsonrpc.jsonrpc_app import CallContextBuilder
1413
from a2a.server.request_handlers.request_handler import RequestHandler
14+
from a2a.server.routes import CallContextBuilder
1515
from a2a.types.a2a_pb2 import AgentCard
1616

1717
_package_starlette_installed = True

src/a2a/compat/v0_3/rest_adapter.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,9 @@
3333

3434
from a2a.compat.v0_3 import conversions
3535
from a2a.compat.v0_3.rest_handler import REST03Handler
36-
from a2a.server.apps.jsonrpc.jsonrpc_app import (
37-
CallContextBuilder,
38-
DefaultCallContextBuilder,
39-
)
4036
from a2a.server.apps.rest.rest_adapter import RESTAdapterInterface
4137
from a2a.server.context import ServerCallContext
38+
from a2a.server.routes import CallContextBuilder, DefaultCallContextBuilder
4239
from a2a.utils.error_handlers import (
4340
rest_error_handler,
4441
rest_stream_error_handler,

src/a2a/contrib/tasks/vertex_task_converter.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
try:
2+
from google.genai import types as genai_types
23
from vertexai import types as vertexai_types
34
except ImportError as e:
45
raise ImportError(
@@ -25,70 +26,70 @@
2526

2627

2728
_TO_SDK_TASK_STATE = {
28-
vertexai_types.State.STATE_UNSPECIFIED: TaskState.unknown,
29-
vertexai_types.State.SUBMITTED: TaskState.submitted,
30-
vertexai_types.State.WORKING: TaskState.working,
31-
vertexai_types.State.COMPLETED: TaskState.completed,
32-
vertexai_types.State.CANCELLED: TaskState.canceled,
33-
vertexai_types.State.FAILED: TaskState.failed,
34-
vertexai_types.State.REJECTED: TaskState.rejected,
35-
vertexai_types.State.INPUT_REQUIRED: TaskState.input_required,
36-
vertexai_types.State.AUTH_REQUIRED: TaskState.auth_required,
29+
vertexai_types.A2aTaskState.STATE_UNSPECIFIED: TaskState.unknown,
30+
vertexai_types.A2aTaskState.SUBMITTED: TaskState.submitted,
31+
vertexai_types.A2aTaskState.WORKING: TaskState.working,
32+
vertexai_types.A2aTaskState.COMPLETED: TaskState.completed,
33+
vertexai_types.A2aTaskState.CANCELLED: TaskState.canceled,
34+
vertexai_types.A2aTaskState.FAILED: TaskState.failed,
35+
vertexai_types.A2aTaskState.REJECTED: TaskState.rejected,
36+
vertexai_types.A2aTaskState.INPUT_REQUIRED: TaskState.input_required,
37+
vertexai_types.A2aTaskState.AUTH_REQUIRED: TaskState.auth_required,
3738
}
3839

3940
_SDK_TO_STORED_TASK_STATE = {v: k for k, v in _TO_SDK_TASK_STATE.items()}
4041

4142

42-
def to_sdk_task_state(stored_state: vertexai_types.State) -> TaskState:
43+
def to_sdk_task_state(stored_state: vertexai_types.A2aTaskState) -> TaskState:
4344
"""Converts a proto A2aTask.State to a TaskState enum."""
4445
return _TO_SDK_TASK_STATE.get(stored_state, TaskState.unknown)
4546

4647

47-
def to_stored_task_state(task_state: TaskState) -> vertexai_types.State:
48+
def to_stored_task_state(task_state: TaskState) -> vertexai_types.A2aTaskState:
4849
"""Converts a TaskState enum to a proto A2aTask.State enum value."""
4950
return _SDK_TO_STORED_TASK_STATE.get(
50-
task_state, vertexai_types.State.STATE_UNSPECIFIED
51+
task_state, vertexai_types.A2aTaskState.STATE_UNSPECIFIED
5152
)
5253

5354

54-
def to_stored_part(part: Part) -> vertexai_types.Part:
55+
def to_stored_part(part: Part) -> genai_types.Part:
5556
"""Converts a SDK Part to a proto Part."""
5657
if isinstance(part.root, TextPart):
57-
return vertexai_types.Part(text=part.root.text)
58+
return genai_types.Part(text=part.root.text)
5859
if isinstance(part.root, DataPart):
5960
data_bytes = json.dumps(part.root.data).encode('utf-8')
60-
return vertexai_types.Part(
61-
inline_data=vertexai_types.Blob(
61+
return genai_types.Part(
62+
inline_data=genai_types.Blob(
6263
mime_type='application/json', data=data_bytes
6364
)
6465
)
6566
if isinstance(part.root, FilePart):
6667
file_content = part.root.file
6768
if isinstance(file_content, FileWithBytes):
6869
decoded_bytes = base64.b64decode(file_content.bytes)
69-
return vertexai_types.Part(
70-
inline_data=vertexai_types.Blob(
70+
return genai_types.Part(
71+
inline_data=genai_types.Blob(
7172
mime_type=file_content.mime_type or '', data=decoded_bytes
7273
)
7374
)
7475
if isinstance(file_content, FileWithUri):
75-
return vertexai_types.Part(
76-
file_data=vertexai_types.FileData(
76+
return genai_types.Part(
77+
file_data=genai_types.FileData(
7778
mime_type=file_content.mime_type or '',
7879
file_uri=file_content.uri,
7980
)
8081
)
8182
raise ValueError(f'Unsupported part type: {type(part.root)}')
8283

8384

84-
def to_sdk_part(stored_part: vertexai_types.Part) -> Part:
85+
def to_sdk_part(stored_part: genai_types.Part) -> Part:
8586
"""Converts a proto Part to a SDK Part."""
8687
if stored_part.text:
8788
return Part(root=TextPart(text=stored_part.text))
8889
if stored_part.inline_data:
89-
encoded_bytes = base64.b64encode(stored_part.inline_data.data).decode(
90-
'utf-8'
91-
)
90+
encoded_bytes = base64.b64encode(
91+
stored_part.inline_data.data or b''
92+
).decode('utf-8')
9293
return Part(
9394
root=FilePart(
9495
file=FileWithBytes(
@@ -97,7 +98,7 @@ def to_sdk_part(stored_part: vertexai_types.Part) -> Part:
9798
)
9899
)
99100
)
100-
if stored_part.file_data:
101+
if stored_part.file_data and stored_part.file_data.file_uri:
101102
return Part(
102103
root=FilePart(
103104
file=FileWithUri(

src/a2a/server/apps/__init__.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
"""HTTP application components for the A2A server."""
22

3-
from a2a.server.apps.jsonrpc import (
4-
A2AFastAPIApplication,
5-
A2AStarletteApplication,
6-
CallContextBuilder,
7-
JSONRPCApplication,
8-
)
93
from a2a.server.apps.rest import A2ARESTFastAPIApplication
104

115

126
__all__ = [
13-
'A2AFastAPIApplication',
147
'A2ARESTFastAPIApplication',
15-
'A2AStarletteApplication',
16-
'CallContextBuilder',
17-
'JSONRPCApplication',
188
]

0 commit comments

Comments
 (0)