Skip to content
4 changes: 2 additions & 2 deletions docs/migrations/v1_0/database/zero_downtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Enable the v0.3 conversion utilities in your application entry point (e.g., `mai

```python
from a2a.server.tasks import DatabaseTaskStore, DatabasePushNotificationConfigStore
from a2a.compat.v0_3.conversions import (
from a2a.compat.v0_3.model_conversions import (
core_to_compat_task_model,
core_to_compat_push_notification_config_model,
)
Expand Down Expand Up @@ -126,7 +126,7 @@ This allows v1.0 instances to read *all* existing data regardless of when it was

## 🧩 Resources
- **[a2a-db CLI](../../../../src/a2a/migrations/README.md)**: The primary tool for executing schema migrations.
- **[Compatibility Conversions](../../../../src/a2a/compat/v0_3/conversions.py)**: Source for classes like `core_to_compat_task_model` used in Step 2.
- **[Compatibility Conversions](../../../../src/a2a/compat/v0_3/model_conversions.py)**: Source for model conversion functions `core_to_compat_task_model` and `core_to_compat_push_notification_config_model` used in Step 2.
- **[Task Store Implementation](../../../../src/a2a/server/tasks/database_task_store.py)**: The `DatabaseTaskStore` which handles the version-aware read/write logic.
- **[Push Notification Store Implementation](../../../../src/a2a/server/tasks/database_push_notification_config_store.py)**: The `DatabasePushNotificationConfigStore` which handles the version-aware read/write logic.

81 changes: 1 addition & 80 deletions src/a2a/compat/v0_3/conversions.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import base64

from typing import TYPE_CHECKING, Any


if TYPE_CHECKING:
from cryptography.fernet import Fernet
from typing import Any

from google.protobuf.json_format import MessageToDict, ParseDict

from a2a.compat.v0_3 import types as types_v03
from a2a.compat.v0_3.versions import is_legacy_version
from a2a.server.models import PushNotificationConfigModel, TaskModel
from a2a.types import a2a_pb2 as pb2_v10
from a2a.utils import constants, errors

Expand Down Expand Up @@ -1378,77 +1373,3 @@ def to_compat_get_extended_agent_card_request(
) -> types_v03.GetAuthenticatedExtendedCardRequest:
"""Convert get extended agent card request to v0.3 compat type."""
return types_v03.GetAuthenticatedExtendedCardRequest(id=request_id)


def core_to_compat_task_model(task: pb2_v10.Task, owner: str) -> TaskModel:
"""Converts a 1.0 core Task to a TaskModel using v0.3 JSON structure."""
compat_task = to_compat_task(task)
data = compat_task.model_dump(mode='json')

return TaskModel(
id=task.id,
context_id=task.context_id,
owner=owner,
status=data.get('status'),
history=data.get('history'),
artifacts=data.get('artifacts'),
task_metadata=data.get('metadata'),
protocol_version='0.3',
)


def compat_task_model_to_core(task_model: TaskModel) -> pb2_v10.Task:
"""Converts a TaskModel with v0.3 structure to a 1.0 core Task."""
compat_task = types_v03.Task(
id=task_model.id,
context_id=task_model.context_id,
status=types_v03.TaskStatus.model_validate(task_model.status),
artifacts=(
[types_v03.Artifact.model_validate(a) for a in task_model.artifacts]
if task_model.artifacts
else []
),
history=(
[types_v03.Message.model_validate(h) for h in task_model.history]
if task_model.history
else []
),
metadata=task_model.task_metadata,
)
return to_core_task(compat_task)


def core_to_compat_push_notification_config_model(
task_id: str,
config: pb2_v10.TaskPushNotificationConfig,
owner: str,
fernet: 'Fernet | None' = None,
) -> PushNotificationConfigModel:
"""Converts a 1.0 core TaskPushNotificationConfig to a PushNotificationConfigModel using v0.3 JSON structure."""
compat_config = to_compat_push_notification_config(config)

json_payload = compat_config.model_dump_json().encode('utf-8')
data_to_store = fernet.encrypt(json_payload) if fernet else json_payload

return PushNotificationConfigModel(
task_id=task_id,
config_id=config.id,
owner=owner,
config_data=data_to_store,
protocol_version='0.3',
)


def compat_push_notification_config_model_to_core(
model_instance: str, task_id: str
) -> pb2_v10.TaskPushNotificationConfig:
"""Converts a PushNotificationConfigModel with v0.3 structure back to a 1.0 core TaskPushNotificationConfig."""
inner_config = types_v03.PushNotificationConfig.model_validate_json(
model_instance
)
return to_core_task_push_notification_config(
types_v03.TaskPushNotificationConfig(
task_id=task_id,
push_notification_config=inner_config,
)
)
92 changes: 92 additions & 0 deletions src/a2a/compat/v0_3/model_conversions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Database model conversions for v0.3 compatibility."""

from typing import TYPE_CHECKING


if TYPE_CHECKING:
from cryptography.fernet import Fernet


from a2a.compat.v0_3 import types as types_v03
from a2a.compat.v0_3.conversions import (
to_compat_push_notification_config,
to_compat_task,
to_core_task,
to_core_task_push_notification_config,
)
from a2a.server.models import PushNotificationConfigModel, TaskModel
from a2a.types import a2a_pb2 as pb2_v10


def core_to_compat_task_model(task: pb2_v10.Task, owner: str) -> TaskModel:
"""Converts a 1.0 core Task to a TaskModel using v0.3 JSON structure."""
compat_task = to_compat_task(task)
data = compat_task.model_dump(mode='json')

return TaskModel(
id=task.id,
context_id=task.context_id,
owner=owner,
status=data.get('status'),
history=data.get('history'),
artifacts=data.get('artifacts'),
task_metadata=data.get('metadata'),
protocol_version='0.3',
)


def compat_task_model_to_core(task_model: TaskModel) -> pb2_v10.Task:
"""Converts a TaskModel with v0.3 structure to a 1.0 core Task."""
compat_task = types_v03.Task(
id=task_model.id,
context_id=task_model.context_id,
status=types_v03.TaskStatus.model_validate(task_model.status),
artifacts=(
[types_v03.Artifact.model_validate(a) for a in task_model.artifacts]
if task_model.artifacts
else []
),
history=(
[types_v03.Message.model_validate(h) for h in task_model.history]
if task_model.history
else []
),
metadata=task_model.task_metadata,
)
return to_core_task(compat_task)


def core_to_compat_push_notification_config_model(
task_id: str,
config: pb2_v10.TaskPushNotificationConfig,
owner: str,
fernet: 'Fernet | None' = None,
) -> PushNotificationConfigModel:
"""Converts a 1.0 core TaskPushNotificationConfig to a PushNotificationConfigModel using v0.3 JSON structure."""
compat_config = to_compat_push_notification_config(config)

json_payload = compat_config.model_dump_json().encode('utf-8')
data_to_store = fernet.encrypt(json_payload) if fernet else json_payload

return PushNotificationConfigModel(
task_id=task_id,
config_id=config.id,
owner=owner,
config_data=data_to_store,
protocol_version='0.3',
)


def compat_push_notification_config_model_to_core(
model_instance: str, task_id: str
) -> pb2_v10.TaskPushNotificationConfig:
"""Converts a PushNotificationConfigModel with v0.3 structure back to a 1.0 core TaskPushNotificationConfig."""
inner_config = types_v03.PushNotificationConfig.model_validate_json(
model_instance
)
return to_core_task_push_notification_config(
types_v03.TaskPushNotificationConfig(
task_id=task_id,
push_notification_config=inner_config,
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

from collections.abc import Callable

from a2a.compat.v0_3.conversions import (
from a2a.compat.v0_3.model_conversions import (
compat_push_notification_config_model_to_core,
)
from a2a.server.context import ServerCallContext
Expand Down
2 changes: 1 addition & 1 deletion src/a2a/server/tasks/database_task_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
) from e
from google.protobuf.json_format import MessageToDict, ParseDict

from a2a.compat.v0_3.conversions import (
from a2a.compat.v0_3.model_conversions import (
compat_task_model_to_core,
)
from a2a.server.context import ServerCallContext
Expand Down
2 changes: 2 additions & 0 deletions tests/compat/v0_3/test_conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
to_core_task_push_notification_config,
to_core_task_status,
to_core_task_status_update_event,
)
from a2a.compat.v0_3.model_conversions import (
core_to_compat_task_model,
compat_task_model_to_core,
core_to_compat_push_notification_config_model,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
TaskState,
TaskStatus,
)
from a2a.compat.v0_3.conversions import (
from a2a.compat.v0_3.model_conversions import (
core_to_compat_push_notification_config_model,
)

Expand Down
2 changes: 1 addition & 1 deletion tests/server/tasks/test_database_task_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from a2a.server.models import Base, TaskModel # Important: To get Base.metadata
from a2a.server.tasks.database_task_store import DatabaseTaskStore
from a2a.compat.v0_3.conversions import core_to_compat_task_model
from a2a.compat.v0_3.model_conversions import core_to_compat_task_model
from a2a.types.a2a_pb2 import (
Artifact,
ListTasksRequest,
Expand Down
Loading