| Crates.io | a2a-client |
| lib.rs | a2a-client |
| version | 0.1.2 |
| created_at | 2025-10-06 10:49:15.541168+00 |
| updated_at | 2025-11-13 10:23:23.150916+00 |
| description | A2A Protocol Client - HTTP client for calling remote A2A agents |
| homepage | https://radkit.dev |
| repository | https://github.com/microagents/radkit |
| max_upload_size | |
| id | 1870092 |
| size | 101,713 |
A Rust HTTP client for calling remote A2A (Agent-to-Agent) protocol compliant agents.
| Crate Version | A2A Protocol Version | Notes |
|---|---|---|
| 0.1.0 | 0.3.0 | Initial implementation with full protocol support |
See the A2A Protocol Releases for the specification.
Add this to your Cargo.toml:
[dependencies]
a2a-client = "0.1.0"
a2a-types = "0.1.1"
use a2a_client::A2AClient;
use a2a_types::{Message, MessageRole, MessageSendParams, Part};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create client from agent card URL
let client = A2AClient::from_card_url("https://agent.example.com")
.await?
.with_auth_token("your_api_key");
// Create a message
let message = Message {
kind: "message".to_string(),
message_id: "msg_123".to_string(),
role: MessageRole::User,
parts: vec![Part::Text {
text: "Hello, agent!".to_string(),
metadata: None,
}],
context_id: None,
task_id: None,
reference_task_ids: vec![],
extensions: vec![],
metadata: None,
};
// Send message
let result = client
.send_message(MessageSendParams {
message,
configuration: None,
metadata: None,
})
.await?;
println!("Response: {:?}", result);
Ok(())
}
use futures_util::StreamExt;
use a2a_client::A2AClient;
use a2a_types::{Message, MessageSendParams, SendStreamingMessageResult};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = A2AClient::from_card_url("https://agent.example.com").await?;
let mut stream = client
.send_streaming_message(MessageSendParams { /* ... */ })
.await?;
while let Some(result) = stream.next().await {
match result? {
SendStreamingMessageResult::Task(task) => {
println!("Task: {:?}", task);
}
SendStreamingMessageResult::Message(msg) => {
println!("Message: {:?}", msg);
}
SendStreamingMessageResult::TaskStatusUpdate(update) => {
println!("Status: {:?}", update);
}
SendStreamingMessageResult::TaskArtifactUpdate(artifact) => {
println!("Artifact: {:?}", artifact);
}
}
}
Ok(())
}
For advanced use cases like custom timeouts, proxies, retry logic, or auth flows, provide your own reqwest::Client:
use a2a_client::A2AClient;
use reqwest::{Client, header};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Configure custom HTTP client
let mut headers = header::HeaderMap::new();
headers.insert(
header::AUTHORIZATION,
header::HeaderValue::from_str("Bearer your_token")?,
);
headers.insert(
"X-Custom-Header",
header::HeaderValue::from_static("custom-value"),
);
let http_client = Client::builder()
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(10))
.default_headers(headers)
.user_agent("my-app/1.0")
.build()?;
// Create A2A client with custom HTTP client
let client = A2AClient::from_card_url_with_client(
"https://agent.example.com",
http_client
).await?;
// Use client as normal...
Ok(())
}
A2AClient::from_card_url(base_url)Create a client by fetching the agent card from {base_url}/.well-known/agent-card.json:
let client = A2AClient::from_card_url("https://agent.example.com").await?;
A2AClient::from_card_url_with_client(base_url, http_client)Create a client with a custom reqwest::Client:
let http_client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let client = A2AClient::from_card_url_with_client(
"https://agent.example.com",
http_client
).await?;
A2AClient::from_card(agent_card)Create a client directly from a pre-fetched agent card:
let agent_card = /* ... fetch or construct AgentCard ... */;
let client = A2AClient::from_card(agent_card)?;
A2AClient::from_card_with_client(agent_card, http_client)Create a client from an agent card with a custom HTTP client:
let http_client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let client = A2AClient::from_card_with_client(agent_card, http_client)?;
with_auth_token(token)Add bearer token authentication (builder pattern):
let client = A2AClient::from_card_url("https://agent.example.com")
.await?
.with_auth_token("your_api_key");
// Non-streaming
let response = client.send_message(params).await?;
// Streaming (returns a Stream)
let stream = client.send_streaming_message(params).await?;
// Get a specific task
let task = client.get_task(TaskQueryParams {
id: "task_123".to_string(),
history_length: Some(10),
metadata: None,
}).await?;
// Cancel a task
let cancelled_task = client.cancel_task(TaskIdParams {
id: "task_123".to_string(),
metadata: None,
}).await?;
// Resubscribe to a task's event stream (for reconnection)
let stream = client.resubscribe_task(TaskIdParams {
id: "task_123".to_string(),
metadata: None,
}).await?;
// List tasks (commonly implemented, not official A2A spec)
let tasks = client.list_tasks(Some("context_id".to_string())).await?;
use a2a_types::{TaskPushNotificationConfig, PushNotificationConfig};
// Set push notification config
let config = client.set_task_push_notification_config(TaskPushNotificationConfig {
task_id: "task_123".to_string(),
push_notification_config: PushNotificationConfig {
url: "https://my-app.com/webhook".to_string(),
token: Some("webhook_token".to_string()),
id: None,
authentication: None,
},
}).await?;
// Get push notification config
let config = client.get_task_push_notification_config(TaskIdParams {
id: "task_123".to_string(),
metadata: None,
}).await?;
// List all push notification configs for a task
let configs = client.list_task_push_notification_config(
ListTaskPushNotificationConfigParams {
id: "task_123".to_string(),
metadata: None,
}
).await?;
// Delete a push notification config
client.delete_task_push_notification_config(
DeleteTaskPushNotificationConfigParams {
id: "task_123".to_string(),
push_notification_config_id: "config_id".to_string(),
metadata: None,
}
).await?;
Call custom agent extension methods:
#[derive(serde::Serialize)]
struct CustomParams {
query: String,
limit: usize,
}
#[derive(serde::Deserialize)]
struct CustomResponse {
results: Vec<String>,
}
let response: CustomResponse = client
.call_extension_method("custom/search", CustomParams {
query: "hello".to_string(),
limit: 10,
})
.await?;
// Get cached agent card
let card = client.agent_card();
// Fetch a fresh agent card
let card = client.fetch_agent_card("https://agent.example.com").await?;
The client uses A2AError for error reporting:
use a2a_client::{A2AError, A2AResult};
match client.send_message(params).await {
Ok(response) => { /* handle success */ }
Err(A2AError::NetworkError { message }) => {
eprintln!("Network error: {}", message);
}
Err(A2AError::RemoteAgentError { message, code }) => {
eprintln!("Agent error {}: {}", code.unwrap_or(0), message);
}
Err(A2AError::SerializationError { message }) => {
eprintln!("Serialization error: {}", message);
}
Err(e) => {
eprintln!("Other error: {:?}", e);
}
}
# Run unit tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_client_requires_valid_card_url
Contributions are welcome! Please ensure:
MIT