← back to A2a Lab
client_sdk.py
69 lines
"""SDK-native A2A client — uses a2a-sdk's own client stack (not raw JSON-RPC).
Contrast with client.py (hand-rolled JSON-RPC over httpx): this drives the official
a2a-sdk 1.1.2 client — A2ACardResolver to fetch the agent card, ClientFactory.create to
build a transport-appropriate Client, and the async send_message() streaming iterator.
The SDK handles the proto SendMessageRequest, the SendMessage method name, and response
aggregation; we still supply the A2A-Version:1.0 header via the httpx client.
Run (server up): . .venv/bin/activate && A2A_BASE=http://127.0.0.1:41241 python client_sdk.py "hello"
"""
from __future__ import annotations
import asyncio
import os
import sys
import uuid
import httpx
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import Message, Part, Role, SendMessageRequest
BASE = os.environ.get("A2A_BASE", "http://127.0.0.1:41241")
def _text_of(resp) -> str | None:
"""Pull text out of a StreamResponse (oneof message/task/status_update/artifact_update)."""
if resp.HasField("message"):
return "".join(p.text for p in resp.message.parts if p.text)
if resp.HasField("task"):
st = resp.task.status
if st and st.message:
return "".join(p.text for p in st.message.parts if p.text)
return f"[task {resp.task.id} state={resp.task.status.state}]"
return None
async def run(base: str, text: str) -> int:
async with httpx.AsyncClient(headers={"A2A-Version": "1.0"}, timeout=30) as hx:
card = await A2ACardResolver(hx, base).get_agent_card()
print(f"① DISCOVERED (SDK): {card.name} v{card.version}")
client = ClientFactory(ClientConfig(httpx_client=hx, streaming=False)).create(card)
req = SendMessageRequest(
message=Message(
message_id=str(uuid.uuid4()),
role=Role.ROLE_USER,
parts=[Part(text=text)],
)
)
print(f"② SEND (SDK send_message): {text!r}")
got = False
async for resp in client.send_message(req):
out = _text_of(resp)
if out is not None:
print("③ RESPONSE:\n" + out)
got = True
if not got:
print("③ (no message payload in stream)")
close = getattr(client, "close", None)
if close:
res = close()
if asyncio.iscoroutine(res):
await res
return 0
if __name__ == "__main__":
text = sys.argv[1] if len(sys.argv) > 1 else "hello"
raise SystemExit(asyncio.run(run(BASE, text)))