Skip to content
Closed
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
1 change: 0 additions & 1 deletion .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
a2a

Check warning on line 1 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
A2A

Check warning on line 2 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
A2AFastAPI

Check warning on line 3 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
AAgent
Expand Down Expand Up @@ -29,7 +29,7 @@
AUser
autouse
backticks
base64url

Check warning on line 32 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
buf
bufbuild
cla
Expand All @@ -49,7 +49,7 @@
dsn
DSNs
dunders
ES256

Check warning on line 52 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
euo
EUR
evt
Expand All @@ -66,8 +66,8 @@
gowebpki
GVsb
hazmat
HS256

Check warning on line 69 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
HS384

Check warning on line 70 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
ietf
importlib
initdb
Expand Down Expand Up @@ -110,11 +110,11 @@
Oneof
OpenAPI
openapiv
openapiv2

Check warning on line 113 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
opensource
otherurl
outerjoin
pb2

Check warning on line 117 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
podman
Podman
poolclass
Expand All @@ -135,10 +135,9 @@
pyupgrade
pyversions
redef
respx
resub
rmi
RS256

Check warning on line 140 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Ignoring entry because it contains non-alpha characters (non-alpha-in-dictionary)
RUF
Rundgren
SECP
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ spec.json
docker-compose.yaml
.geminiignore
docs/ai/ai_learnings.md
.idea

# ITK Integration Test Artifacts
itk/a2a-itk/
Expand Down
16 changes: 8 additions & 8 deletions itk/acts_client_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

from typing import Any

import httpx
import httpx2

from google.protobuf.json_format import MessageToDict

Expand Down Expand Up @@ -70,7 +70,7 @@ def _is_enveloped(payload: Any) -> bool:
)


def _transport(payload: Any) -> httpx.MockTransport:
def _transport(payload: Any) -> httpx2.MockTransport:
"""An HTTP transport that answers every request with ``payload``.

The response's JSON-RPC ``id`` is rewritten to echo the request's, which
Expand All @@ -82,7 +82,7 @@ def _transport(payload: Any) -> httpx.MockTransport:
express.
"""

def handler(request: httpx.Request) -> httpx.Response:
def handler(request: httpx2.Request) -> httpx2.Response:
body = payload
if _is_enveloped(payload):
try:
Expand All @@ -91,9 +91,9 @@ def handler(request: httpx.Request) -> httpx.Response:
sent = None
if isinstance(sent, dict) and 'id' in sent:
body = {**payload, 'id': sent['id']}
return httpx.Response(200, json=body)
return httpx2.Response(200, json=body)

return httpx.MockTransport(handler)
return httpx2.MockTransport(handler)


def _scaffold_card() -> AgentCard:
Expand Down Expand Up @@ -129,7 +129,7 @@ async def _client(payload: Any) -> Any:
config = ClientConfig()
config.streaming = False
config.supported_protocol_bindings = [TransportProtocol.JSONRPC]
config.httpx_client = httpx.AsyncClient(transport=_transport(payload))
config.httpx_client = httpx2.AsyncClient(transport=_transport(payload))
return await create_client(_scaffold_card(), client_config=config)


Expand All @@ -141,15 +141,15 @@ async def _parse_card(payload: Any, path: str) -> dict[str, Any]:
plain HTTP on every binding, so there is no envelope to unwrap. The two
differ only in the path they are served from.
"""
async with httpx.AsyncClient(transport=_transport(payload)) as http:
async with httpx2.AsyncClient(transport=_transport(payload)) as http:
resolver = A2ACardResolver(httpx_client=http, base_url=_BASE_URL)
card = await resolver.get_agent_card(relative_card_path=path)
return MessageToDict(card)


async def _parse_extended_card_envelope(payload: Any) -> dict[str, Any]:
"""The enveloped form of `get_extended_agent_card`, via the RPC client."""
async with httpx.AsyncClient(transport=_transport(payload)) as http:
async with httpx2.AsyncClient(transport=_transport(payload)) as http:
config = ClientConfig()
config.streaming = False
config.supported_protocol_bindings = [TransportProtocol.JSONRPC]
Expand Down
6 changes: 3 additions & 3 deletions itk/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import uuid

import grpc
import httpx
import httpx2
import uvicorn

from fastapi import FastAPI
Expand Down Expand Up @@ -300,7 +300,7 @@ async def handle_call_agent(
token='itk-token', # noqa: S106
)

async with httpx.AsyncClient(timeout=30.0) as httpx_client:
async with httpx2.AsyncClient(timeout=30.0) as httpx_client:
config.httpx_client = httpx_client
try:
client = await create_client(
Expand Down Expand Up @@ -738,7 +738,7 @@ async def main_async(http_port: int, grpc_port: int) -> None:

task_store = InMemoryTaskStore()
push_config_store = InMemoryPushNotificationConfigStore()
httpx_client = httpx.AsyncClient()
httpx_client = httpx2.AsyncClient()
push_sender = BasePushNotificationSender(
httpx_client=httpx_client,
config_store=push_config_store,
Expand Down
1 change: 0 additions & 1 deletion itk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ dependencies = [
"grpcio-tools",
"protobuf",
"sse-starlette",
"httpx-sse",
"packaging",
]

Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
requires-python = ">=3.10"
keywords = ["A2A", "A2A SDK", "A2A Protocol", "Agent2Agent", "Agent 2 Agent"]
dependencies = [
"httpx>=0.28.1",
"httpx2>=2.14.0",
"pydantic>=2.11.3",
"protobuf>=5.29.5",
"google-api-core>=1.26.0",
Expand Down Expand Up @@ -113,7 +113,6 @@ dev = [
"pytest-cov>=6.1.1",
"pytest-mock>=3.14.0",
"pytest-xdist>=3.6.1",
"respx>=0.20.2",
# Test-only oracle. The RFC 8785 conformance tests check the canonicalizer
# against an independent implementation, so a mistake shared between the
# implementation and its tests still shows up. Not a runtime dependency.
Expand Down
12 changes: 6 additions & 6 deletions samples/agent_card_signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
from collections.abc import Callable, Mapping
from typing import Any

import httpx
import httpx2
import uvicorn

from cryptography.hazmat.primitives.asymmetric import ec
Expand Down Expand Up @@ -275,7 +275,7 @@ def __init__(self, jwks_by_url: Mapping[str, PyJWKSet]) -> None:

@classmethod
async def fetch(
cls, trusted_jku_urls: set[str], http_client: httpx.AsyncClient
cls, trusted_jku_urls: set[str], http_client: httpx2.AsyncClient
) -> 'JwksKeyProvider':
"""Fetches every trusted JWKS and builds a provider from them.

Expand Down Expand Up @@ -324,7 +324,7 @@ def __call__(self, kid: str | None, jku: str | None) -> PyJWK:


async def create_card_verifier(
trusted_jku_urls: set[str], http_client: httpx.AsyncClient
trusted_jku_urls: set[str], http_client: httpx2.AsyncClient
) -> Callable[[AgentCard], None]:
"""Builds a verifier that accepts cards signed by the trusted keys.

Expand Down Expand Up @@ -356,7 +356,7 @@ async def verify_remote_card(base_url: str) -> AgentCard:
SignatureVerificationError: If the card carries no signature, or none of
its signatures can be verified.
"""
async with httpx.AsyncClient() as http_client:
async with httpx2.AsyncClient() as http_client:
verifier = await create_card_verifier(
trusted_jku_urls={f'{base_url}{JWKS_WELL_KNOWN_PATH}'},
http_client=http_client,
Expand All @@ -373,7 +373,7 @@ async def verify_remote_card(base_url: str) -> AgentCard:


async def _fetch_card_json(
base_url: str, http_client: httpx.AsyncClient
base_url: str, http_client: httpx2.AsyncClient
) -> dict[str, Any]:
"""Fetches the raw Agent Card JSON, bypassing verification."""
response = await http_client.get(f'{base_url}{AGENT_CARD_WELL_KNOWN_PATH}')
Expand All @@ -384,7 +384,7 @@ async def _fetch_card_json(

async def run_demo(base_url: str) -> None:
"""Verifies a good card, then two cards a verifier should reject."""
async with httpx.AsyncClient() as http_client:
async with httpx2.AsyncClient() as http_client:
verifier = await create_card_verifier(
trusted_jku_urls={f'{base_url}{JWKS_WELL_KNOWN_PATH}'},
http_client=http_client,
Expand Down
6 changes: 3 additions & 3 deletions samples/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Any

import grpc
import httpx
import httpx2

from a2a.client import A2ACardResolver, ClientConfig, create_client
from a2a.helpers import get_artifact_text, get_message_text
Expand Down Expand Up @@ -83,7 +83,7 @@ async def main() -> None:
f'Connecting to {args.url} (preferred transport: {args.transport or "Any"})'
)

async with httpx.AsyncClient() as httpx_client:
async with httpx2.AsyncClient() as httpx_client:
resolver = A2ACardResolver(httpx_client, args.url)
card = await resolver.get_agent_card()
print('\n✓ Agent Card Found:')
Expand Down Expand Up @@ -124,7 +124,7 @@ async def main() -> None:
try:
stream = client.send_message(request)
current_task_id = await _handle_stream(stream, current_task_id)
except (httpx.RequestError, grpc.RpcError) as e:
except (httpx2.RequestError, grpc.RpcError) as e:
print(f'Error communicating with agent: {e}')

await client.close()
Expand Down
10 changes: 5 additions & 5 deletions samples/clustermode/exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from typing import Any

import httpx
import httpx2

from google.protobuf.json_format import MessageToDict, ParseDict
from google.protobuf.message import Message as ProtoMessage
Expand Down Expand Up @@ -46,7 +46,7 @@ def _endpoint(host: str, port: int) -> str:


async def _rpc(
client: httpx.AsyncClient,
client: httpx2.AsyncClient,
url: str,
method: str,
params_msg: ProtoMessage,
Expand All @@ -69,7 +69,7 @@ async def _rpc(
return data['result']


async def _get_task(client: httpx.AsyncClient, url: str, task_id: str) -> Task:
async def _get_task(client: httpx2.AsyncClient, url: str, task_id: str) -> Task:
"""Fetches a task via GetTask from the given replica."""
result = await _rpc(client, url, 'GetTask', GetTaskRequest(id=task_id))
task = Task()
Expand All @@ -78,7 +78,7 @@ async def _get_task(client: httpx.AsyncClient, url: str, task_id: str) -> Task:


async def _start_streaming(
client: httpx.AsyncClient, url: str, send: SendMessageRequest
client: httpx2.AsyncClient, url: str, send: SendMessageRequest
) -> tuple[str, asyncio.Task[None]]:
"""Starts a streaming send; returns (task_id, drainer task).

Expand Down Expand Up @@ -128,7 +128,7 @@ async def run(host: str, ports: list[int]) -> None:

urls = [_endpoint(host, p) for p in ports]

async with httpx.AsyncClient() as client:
async with httpx2.AsyncClient() as client:
# 1) Start a streaming task on replica 0. The stream stays open (agent
# runs) while we observe and cancel from other replicas.
print(f'[send] -> replica 0 ({urls[0]}) (streaming)')
Expand Down
12 changes: 6 additions & 6 deletions src/a2a/client/card_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections.abc import Callable
from typing import Any

import httpx
import httpx2

from google.protobuf.json_format import ParseDict, ParseError

Expand Down Expand Up @@ -133,14 +133,14 @@ class A2ACardResolver:

def __init__(
self,
httpx_client: httpx.AsyncClient,
httpx_client: httpx2.AsyncClient,
base_url: str,
agent_card_path: str = AGENT_CARD_WELL_KNOWN_PATH,
) -> None:
"""Initializes the A2ACardResolver.

Args:
httpx_client: An async HTTP client instance (e.g., httpx.AsyncClient).
httpx_client: An async HTTP client instance (e.g., httpx2.AsyncClient).
base_url: The base URL of the agent's host.
agent_card_path: The path to the agent card endpoint, relative to the base URL.
"""
Expand All @@ -164,7 +164,7 @@ async def get_agent_card(
relative to the base URL. If None, uses the default public
agent card path. Use `'/'` for an empty path.
http_kwargs: Optional dictionary of keyword arguments to pass to the
underlying httpx.get request.
underlying httpx2.get request.
signature_verifier: A callable used to verify the agent card's signatures.

Returns:
Expand Down Expand Up @@ -200,7 +200,7 @@ async def get_agent_card(
agent_card = parse_agent_card(agent_card_data)
if signature_verifier:
signature_verifier(agent_card)
except httpx.HTTPStatusError as e:
except httpx2.HTTPStatusError as e:
raise AgentCardResolutionError(
f'Failed to fetch agent card from {target_url} (HTTP {e.response.status_code}): {e}',
status_code=e.response.status_code,
Expand All @@ -209,7 +209,7 @@ async def get_agent_card(
raise AgentCardResolutionError(
f'Failed to parse JSON for agent card from {target_url}: {e}'
) from e
except httpx.RequestError as e:
except httpx2.RequestError as e:
raise AgentCardResolutionError(
f'Network communication error fetching agent card from {target_url}: {e}',
) from e
Expand Down
4 changes: 2 additions & 2 deletions src/a2a/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from types import TracebackType
from typing import Any

import httpx
import httpx2

from pydantic import BaseModel, Field
from typing_extensions import Self
Expand Down Expand Up @@ -48,7 +48,7 @@ class ClientConfig:
the callers job to check if the response is completed and if not run a
polling loop."""

httpx_client: httpx.AsyncClient | None = None
httpx_client: httpx2.AsyncClient | None = None
"""Http client to use to connect to agent."""

grpc_channel_factory: Callable[[str], Channel] | None = None
Expand Down
8 changes: 4 additions & 4 deletions src/a2a/client/client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from collections.abc import Callable
from typing import TYPE_CHECKING, Any

import httpx
import httpx2

from packaging.version import InvalidVersion, Version

Expand Down Expand Up @@ -38,13 +38,13 @@
try:
from a2a.client.transports.grpc import GrpcTransport
except ImportError:
GrpcTransport = None # type: ignore # pyright: ignore

Check warning on line 41 in src/a2a/client/client_factory.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ty (unused-type-ignore-comment)

src/a2a/client/client_factory.py:41:27: unused-type-ignore-comment: Unused blanket `type: ignore` directive help: Remove the unused suppression comment


try:
from a2a.compat.v0_3.grpc_transport import CompatGrpcTransport
except ImportError:
CompatGrpcTransport = None # type: ignore # pyright: ignore

Check warning on line 47 in src/a2a/client/client_factory.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ty (unused-type-ignore-comment)

src/a2a/client/client_factory.py:47:33: unused-type-ignore-comment: Unused blanket `type: ignore` directive help: Remove the unused suppression comment

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -78,7 +78,7 @@
The client can be used consistently regardless of the transport. This
aligns the client configuration with the server's capabilities.

Each created client gets its own `httpx.AsyncClient` and closes it on
Each created client gets its own `httpx2.AsyncClient` and closes it on
`close()`, unless `ClientConfig.httpx_client` is set.
"""

Expand All @@ -96,11 +96,11 @@
self._registry: dict[str, TransportProducer] = {}
self._register_defaults(config.supported_protocol_bindings)

def _resolve_http_client(self) -> tuple[httpx.AsyncClient, bool]:
def _resolve_http_client(self) -> tuple[httpx2.AsyncClient, bool]:
"""Returns an HTTP client and whether it is shared across clients."""
if self._config.httpx_client:
return self._config.httpx_client, True
httpx_client = httpx.AsyncClient()
httpx_client = httpx2.AsyncClient()
httpx_client.headers.setdefault(
VERSION_HEADER, PROTOCOL_VERSION_CURRENT
)
Expand Down
Loading
Loading