Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies = [
"pydantic>=2.11.3",
"protobuf>=5.29.5",
"google-api-core>=1.26.0",
"jsonschema>=4.23.0",
]

classifiers = [
Expand All @@ -37,6 +38,8 @@ postgresql = ["sqlalchemy[asyncio,postgresql-asyncpg]>=2.0.0"]
mysql = ["sqlalchemy[asyncio,aiomysql]>=2.0.0"]
signing = ["PyJWT>=2.0.0"]
sqlite = ["sqlalchemy[asyncio,aiosqlite]>=2.0.0"]
performance = ["uvloop>=0.21.0"]
tls = ["cryptography>=43.0.0"]

sql = ["a2a-sdk[postgresql,mysql,sqlite]"]

Expand All @@ -47,6 +50,8 @@ all = [
"a2a-sdk[grpc]",
"a2a-sdk[telemetry]",
"a2a-sdk[signing]",
"a2a-sdk[performance]",
"a2a-sdk[tls]",
]

[project.urls]
Expand Down
33 changes: 32 additions & 1 deletion src/a2a/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
"""The A2A Python SDK."""
"""The A2A Python SDK.

This SDK provides tools for building A2A (Agent-to-Agent) protocol clients
and servers with support for:

- TLS/SSL secure communication (see a2a.client.TLSConfig)
- JSON Schema message validation (see a2a.validation)
- High-performance async operations via uvloop (see a2a.performance)

Example usage:

from a2a.client import Client, ClientConfig, TLSConfig
from a2a.validation import validate_message
from a2a.performance import install_uvloop

# Enable uvloop for better performance
install_uvloop()

# Configure TLS
tls = TLSConfig(
enabled=True,
verify='/path/to/ca.pem',
cert=('/path/to/client.pem', '/path/to/key.pem'),
)

config = ClientConfig(tls_config=tls)
"""

from a2a import client, performance, types, validation


__all__ = ['client', 'performance', 'types', 'validation']
8 changes: 8 additions & 0 deletions src/a2a/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
from a2a.client.helpers import create_text_message_object
from a2a.client.legacy import A2AClient
from a2a.client.middleware import ClientCallContext, ClientCallInterceptor
from a2a.client.tls import (
TLSConfig,
create_grpc_channel_factory,
create_server_ssl_context,
)


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -62,6 +67,9 @@ def __init__(self, *args, **kwargs):
'Consumer',
'CredentialService',
'InMemoryContextCredentialStore',
'TLSConfig',
'create_grpc_channel_factory',
'create_server_ssl_context',
'create_text_message_object',
'minimal_agent_card',
]
46 changes: 45 additions & 1 deletion src/a2a/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Callable, Coroutine
from typing import Any
from typing import TYPE_CHECKING, Any

import httpx

Expand All @@ -24,6 +24,10 @@
)


if TYPE_CHECKING:
from a2a.client.tls import TLSConfig


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -70,6 +74,46 @@ class ClientConfig:
extensions: list[str] = dataclasses.field(default_factory=list)
"""A list of extension URIs the client supports."""

tls_config: 'TLSConfig | None' = None
"""TLS/SSL configuration for secure communication. If provided, this
will be used to configure secure connections for HTTP and gRPC
transports. Ignored if httpx_client or grpc_channel_factory is
explicitly provided."""

validate_messages: bool = False
"""Whether to validate messages against JSON Schema before sending
and after receiving. Useful for protocol compliance testing."""

def get_httpx_client(self) -> httpx.AsyncClient:
"""Get or create an httpx client with TLS configuration.

Returns:
Configured httpx.AsyncClient instance.
"""
if self.httpx_client is not None:
return self.httpx_client

if self.tls_config is not None:
return self.tls_config.create_httpx_client()

return httpx.AsyncClient()

def get_grpc_channel_factory(self) -> Callable[[str], Channel] | None:
"""Get or create a gRPC channel factory with TLS configuration.

Returns:
A callable that creates gRPC channels, or None.
"""
if self.grpc_channel_factory is not None:
return self.grpc_channel_factory

if self.tls_config is not None:
from a2a.client.tls import create_grpc_channel_factory

return create_grpc_channel_factory(self.tls_config)

return None


UpdateEvent = TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None
# Alias for emitted events from client
Expand Down
11 changes: 8 additions & 3 deletions src/a2a/client/client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,17 @@ def __init__(
def _register_defaults(
self, supported: list[str | TransportProtocol]
) -> None:
# Empty support list implies JSON-RPC only.
httpx_client = self._config.httpx_client
if httpx_client is None and self._config.tls_config is not None:
httpx_client = self._config.tls_config.create_httpx_client()
elif httpx_client is None:
httpx_client = httpx.AsyncClient()

if TransportProtocol.jsonrpc in supported or not supported:
self.register(
TransportProtocol.jsonrpc,
lambda card, url, config, interceptors: JsonRpcTransport(
config.httpx_client or httpx.AsyncClient(),
httpx_client,
card,
url,
interceptors,
Expand All @@ -87,7 +92,7 @@ def _register_defaults(
self.register(
TransportProtocol.http_json,
lambda card, url, config, interceptors: RestTransport(
config.httpx_client or httpx.AsyncClient(),
httpx_client,
card,
url,
interceptors,
Expand Down
Loading
Loading