turul-a2a-client
Independent, typed client library for the A2A Protocol v1.0.
This crate has no dependency on the server framework. It ships separately
so you can call any A2A v1.0 agent — built with turul-a2a or any other
compliant server.
Features
- Discover agent cards via
/.well-known/agent-card.json. - Send messages (blocking and streaming SSE).
- Get, list, cancel, and subscribe to tasks.
- Push notification config CRUD.
- Tenant scoping, API-key and Bearer auth.
- Wrapper-first request/response types with typed streaming events.
- Optional gRPC transport via the
grpcfeature.
Quick start
use ;
let client = discover.await?;
let response = client
.send_message
.await?;
println!;
discover performs GET /.well-known/agent-card.json and caches the
agent card. Skip discovery with A2aClient::new(base_url) if you
already know the routes.
Streaming
use StreamExt;
let mut stream = client
.send_streaming_message
.await?;
while let Some = stream.next.await
subscribe_to_task(task_id) reattaches to an existing non-terminal task
and replays missed events from the durable event store. Subscribing to
an already-terminal task returns UnsupportedOperationError; use
get_task for the final state.
Push notification config
Register a webhook against an existing task — the 80% case takes two inputs, no proto types in sight:
let task_id = response.task.unwrap.id;
client
.create_push_config
.await?;
For inline registration on the same send_message call (server assigns
the task id during atomic creation), use the MessageBuilder helper:
let request = new
.text
.inline_push_config
.build;
client.send_message.await?;
For configs that need HTTP Authorization headers on the webhook
delivery, build a PushConfig and use create_push_config_with:
use ;
let cfg = new
.authentication
.build;
client.create_push_config_with.await?;
The server delivers a POST to the registered URL when the task reaches
a terminal state, with the token echoed in the
X-Turul-Push-Token header so the receiver can validate the call.
Auth
use ;
// API key (the header name is server-configured; default `X-API-Key`).
let client = new
.with_auth;
// Bearer JWT.
let client = new
.with_auth;
Tenant scoping
Multi-tenant agents route per-tenant via path prefix
(/{tenant}/message:send). Set the tenant once on the client:
let client = new.with_tenant;
All subsequent calls automatically include the tenant prefix.
Errors
All client methods return Result<T, A2aClientError>. Common variants:
| Variant | Cause |
|---|---|
Http { status, message } |
Non-2xx HTTP response without an A2A error envelope. |
A2aError { status, message, reason } |
A2A-typed error (TaskNotFound, TaskNotCancelable, UnsupportedOperation, …) with the ErrorInfo.reason from the server. Use .reason() to read it. |
Request(_) |
reqwest/transport error before a response arrived. |
Json(_) |
Response payload didn't parse as expected JSON. |
Conversion(_) |
Proto → wrapper type conversion failed. |
Sse(_) / StreamClosed |
SSE-specific errors during streaming. |
A2aClientError is #[non_exhaustive]. With the grpc feature it
also carries Grpc and GrpcTransport variants.
Examples
examples/echo-agent— round-trip a message, blocking.examples/conversation-agent— multi-turn refinement withreferenceTaskIds.examples/interrupting-agent— pause + resume viaINPUT_REQUIRED.examples/callback-agent— register a push-notification webhook.examples/grpc-agent— gRPC transport (--features grpc).
See the workspace README for the project overview and crate map.
License
Licensed under either MIT or Apache 2.0 at your option.