Skip to content

Commit 26835ad

Browse files
authored
feat(compat): legacy v0.3 protocol models, conversion logic and utilities (#754)
* Isolate legacy v0.3 Pydantic models in `src/a2a/compat/v0_3/types.py`. * Add dynamic generation script `scripts/gen_proto.sh` for pulling legacy v0.3 Protobuf specifications directly from GitHub, ensuring no `DescriptorPool` namespace collisions. * Introduce strict conversion boundaries (`proto_utils.py` and `conversions.py`) to elegantly translate between legacy byte-formats, intermediate Pydantic models, and the modern v1.0 SDK architecture. * Add comprehensive round-trip tests to guarantee zero data loss during conversion bridging. # Description Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [X] Follow the [`CONTRIBUTING` Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md). - [X] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [X] Ensure the tests and linter pass (Run `bash scripts/format.sh` from the repository root to format) - [X] Appropriate docs were updated (if necessary)
1 parent e2ef540 commit 26835ad

17 files changed

Lines changed: 6857 additions & 0 deletions

File tree

.github/actions/spelling/allow.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ notif
8686
npx
8787
oauthoidc
8888
oidc
89+
Oneof
8990
OpenAPI
9091
openapiv
9192
openapiv2

buf.compat.gen.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Protobuf generation for legacy v0.3 A2A protocol buffer modules.
2+
---
3+
version: v2
4+
managed:
5+
enabled: true
6+
plugins:
7+
- remote: buf.build/protocolbuffers/python:v29.3
8+
out: src/a2a/compat/v0_3
9+
- remote: buf.build/grpc/python
10+
out: src/a2a/compat/v0_3
11+
- remote: buf.build/protocolbuffers/pyi
12+
out: src/a2a/compat/v0_3

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ exclude = [
162162
"**/venv",
163163
"**/.venv",
164164
"src/a2a/types",
165+
"src/a2a/compat/v0_3/*_pb2*.py",
166+
"src/a2a/compat/v0_3/proto_utils.py",
165167
]
166168
venvPath = "."
167169
venv = ".venv"

scripts/gen_proto.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,16 @@ fi
1919
# Fix imports in generated grpc file
2020
echo "Fixing imports in src/a2a/types/a2a_pb2_grpc.py"
2121
sed 's/import a2a_pb2 as a2a__pb2/from . import a2a_pb2 as a2a__pb2/g' src/a2a/types/a2a_pb2_grpc.py > src/a2a/types/a2a_pb2_grpc.py.tmp && mv src/a2a/types/a2a_pb2_grpc.py.tmp src/a2a/types/a2a_pb2_grpc.py
22+
23+
# Download legacy v0.3 compatibility protobuf code
24+
echo "Downloading legacy v0.3 proto file..."
25+
# Commit hash was selected as a2a.proto version from 0.3 branch with latests fixes.
26+
curl -o src/a2a/compat/v0_3/a2a_v0_3.proto https://github.com/a2aproject/A2A/b3b266d127dde3d1000ec103b252d1de81289e83/specification/grpc/a2a.proto
27+
28+
# Generate legacy v0.3 compatibility protobuf code
29+
echo "Generating legacy v0.3 compatibility protobuf code"
30+
npx --yes @bufbuild/buf generate src/a2a/compat/v0_3 --template buf.compat.gen.yaml
31+
32+
# Fix imports in legacy generated grpc file
33+
echo "Fixing imports in src/a2a/compat/v0_3/a2a_v0_3_pb2_grpc.py"
34+
sed 's/import a2a_v0_3_pb2 as a2a__v0__3__pb2/from . import a2a_v0_3_pb2 as a2a__v0__3__pb2/g' src/a2a/compat/v0_3/a2a_v0_3_pb2_grpc.py > src/a2a/compat/v0_3/a2a_v0_3_pb2_grpc.py.tmp && mv src/a2a/compat/v0_3/a2a_v0_3_pb2_grpc.py.tmp src/a2a/compat/v0_3/a2a_v0_3_pb2_grpc.py

src/a2a/compat/__init__.py

Whitespace-only changes.

src/a2a/compat/v0_3/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*_pb2.py
2+
*_pb2_grpc.py
3+
*_pb2.pyi
4+
a2a_v0_3.proto

src/a2a/compat/v0_3/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# A2A Protocol Backward Compatibility (v0.3)
2+
3+
This directory (`src/a2a/compat/v0_3/`) provides the foundational types and translation layers necessary for modern `v1.0` clients and servers to interoperate with legacy `v0.3` A2A systems.
4+
5+
## Data Representations
6+
7+
To support cross-version compatibility across JSON, REST, and gRPC, this directory manages three distinct data representations:
8+
9+
### 1. Legacy v0.3 Pydantic Models (`types.py`)
10+
This file contains Python [Pydantic](https://docs.pydantic.dev/) models generated from the legacy v0.3 JSON schema.
11+
* **Purpose**: This is the "pivot" format. Legacy JSON-RPC and REST implementations natively serialize to/from these models. It acts as the intermediary between old wire formats and the modern SDK.
12+
13+
### 2. Legacy v0.3 Protobuf Bindings (`a2a_v0_3_pb2.py`)
14+
This module contains the native Protobuf bindings for the legacy v0.3 gRPC protocol.
15+
* **Purpose**: To decode incoming bytes from legacy gRPC clients or encode outbound bytes to legacy gRPC servers.
16+
* **Note**: It is generated into the `a2a.v1` package namespace.
17+
18+
### 3. Current v1.0 Protobuf Bindings (`a2a.types.a2a_pb2`)
19+
This is the central source of truth for the modern SDK (`v1.0`). All legacy payloads must ultimately be translated into these `v1.0` core objects to be processed by the modern `AgentExecutor`.
20+
* **Note**: It is generated into the `lf.a2a.v1` package namespace.
21+
---
22+
23+
## Transformation Utilities
24+
25+
Payloads arriving from legacy clients undergo a phased transformation to bridge the gap between versions.
26+
27+
### Legacy gRPC ↔ Legacy Pydantic: `proto_utils.py`
28+
This module handles the mapping between legacy `v0.3` gRPC Protobuf objects and legacy `v0.3` Pydantic models.
29+
This is a copy of the `a2a.types.proto_utils` module from 0.3 release.
30+
31+
```python
32+
from a2a.compat.v0_3 import a2a_v0_3_pb2
33+
from a2a.compat.v0_3 import types as types_v03
34+
from a2a.compat.v0_3 import proto_utils
35+
36+
# 1. Receive legacy bytes over the wire
37+
legacy_pb_msg = a2a_v0_3_pb2.Message()
38+
legacy_pb_msg.ParseFromString(wire_bytes)
39+
40+
# 2. Convert to intermediate Pydantic representation
41+
pydantic_msg: types_v03.Message = proto_utils.FromProto.message(legacy_pb_msg)
42+
```
43+
44+
### Legacy Pydantic ↔ Modern v1.0 Protobuf: `conversions.py`
45+
This module structurally translates between legacy `v0.3` Pydantic objects and modern `v1.0` Core Protobufs.
46+
47+
```python
48+
from a2a.types import a2a_pb2 as pb2_v10
49+
from a2a.compat.v0_3 import conversions
50+
51+
# 3. Convert the legacy Pydantic object into a modern v1.0 Protobuf
52+
core_pb_msg: pb2_v10.Message = conversions.to_core_message(pydantic_msg)
53+
54+
```

src/a2a/compat/v0_3/__init__.py

Whitespace-only changes.

src/a2a/compat/v0_3/buf.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Generated by buf. DO NOT EDIT.
2+
version: v2
3+
deps:
4+
- name: buf.build/googleapis/googleapis
5+
commit: 004180b77378443887d3b55cabc00384
6+
digest: b5:e8f475fe3330f31f5fd86ac689093bcd274e19611a09db91f41d637cb9197881ce89882b94d13a58738e53c91c6e4bae7dc1feba85f590164c975a89e25115dc

src/a2a/compat/v0_3/buf.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version: v2
2+
deps:
3+
- buf.build/googleapis/googleapis

0 commit comments

Comments
 (0)