Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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