← back to A2a Lab

client.py

65 lines

"""Minimal A2A (Agent2Agent) demo client — spec-compliant raw JSON-RPC over httpx.

Deliberately does NOT use the SDK client, so it self-documents the A2A wire protocol:
  1. DISCOVER  — GET the agent card from /.well-known/agent-card.json
  2. INVOKE    — POST a JSON-RPC 2.0 `message/send` with a text message
  3. READ      — print the agent's reply

Run (server must be up): . .venv/bin/activate && python client.py
"""
from __future__ import annotations

import json
import os
import sys
import uuid

import httpx

# Override with A2A_BASE to point at another agent (e.g. the tk bridge on :41242).
BASE = os.environ.get("A2A_BASE", "http://127.0.0.1:41241")


def discover(base: str) -> dict:
    card = httpx.get(f"{base}/.well-known/agent-card.json", timeout=10).json()
    print(f"① DISCOVERED agent card → name={card.get('name')!r} version={card.get('version')!r}")
    ifaces = card.get("supportedInterfaces") or card.get("supported_interfaces") or []
    if ifaces:
        print(f"   interface → {ifaces[0]}")
    return card


def send_message(base: str, text: str) -> dict:
    # a2a-sdk 1.1.2 uses gRPC-style method names (SendMessage) and parses params
    # as a protobuf SendMessageRequest (camelCase fields, enum-name roles).
    payload = {
        "jsonrpc": "2.0",
        "id": str(uuid.uuid4()),
        "method": "SendMessage",
        "params": {
            "message": {
                "messageId": str(uuid.uuid4()),
                "role": "ROLE_USER",
                "parts": [{"text": text}],
            }
        },
    }
    print(f"② SEND SendMessage → {text!r}")
    # The server defaults a missing A2A-Version header to legacy '0.3'; signal 1.0.
    headers = {"A2A-Version": "1.0"}
    resp = httpx.post(f"{base}/", json=payload, headers=headers, timeout=30)
    return resp.json()


def main() -> int:
    text = sys.argv[1] if len(sys.argv) > 1 else "hello a2a"
    discover(BASE)
    result = send_message(BASE, text)
    print("③ RESPONSE:")
    print(json.dumps(result, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())