pub mod grpc_service;
pub mod handler;
pub mod jsonrpc_router;
pub mod rest_router;
pub mod sse_response;
pub use handler::A2aHandler;
#[cfg(feature = "grpc-server")]
pub use grpc_service::GrpcBridge;
use std::net::SocketAddr;
use std::sync::Arc;
use crate::error::A2aError;
use crate::jsonrpc::{JsonRpcRequest, METHOD_MESSAGE_STREAM, METHOD_TASKS_RESUBSCRIBE, RequestId};
use crate::params::{SendMessageRequest, SubscribeToTaskRequest};
const MAX_REQUEST_BODY_SIZE: usize = 10 * 1024 * 1024;
pub struct A2aServer<H: A2aHandler> {
handler: Arc<H>,
addr: SocketAddr,
#[cfg(feature = "grpc-server")]
grpc_addr: Option<SocketAddr>,
shutdown: Option<tokio::sync::watch::Receiver<()>>,
}
impl<H: A2aHandler> A2aServer<H> {
pub fn new(handler: H, addr: SocketAddr) -> Self {
Self {
handler: Arc::new(handler),
addr,
#[cfg(feature = "grpc-server")]
grpc_addr: None,
shutdown: None,
}
}
#[cfg(feature = "grpc-server")]
pub fn with_grpc(mut self, grpc_addr: SocketAddr) -> Self {
self.grpc_addr = Some(grpc_addr);
self
}
pub fn with_shutdown(mut self, rx: tokio::sync::watch::Receiver<()>) -> Self {
self.shutdown = Some(rx);
self
}
pub async fn run(self) -> Result<(), A2aError> {
use hyper::body::Incoming;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
let handler = self.handler.clone();
let listener = tokio::net::TcpListener::bind(self.addr)
.await
.map_err(|e| A2aError::internal(format!("Failed to bind: {e}")))?;
tracing::info!("A2A server listening on {}", self.addr);
#[cfg(feature = "grpc-server")]
if let Some(grpc_addr) = self.grpc_addr {
let grpc_handler = self.handler.clone();
let shutdown_rx = self.shutdown.clone();
let (bind_tx, mut bind_rx) = tokio::sync::oneshot::channel::<Result<(), String>>();
tokio::spawn(async move {
let bridge = GrpcBridge::new(grpc_handler);
let svc =
crate::proto::lf_a2a_v1::a2a_service_server::A2aServiceServer::new(bridge);
tracing::info!("A2A gRPC server listening on {grpc_addr}");
let builder = tonic::transport::Server::builder().add_service(svc);
let result = if let Some(mut rx) = shutdown_rx {
builder
.serve_with_shutdown(grpc_addr, async move {
let _ = rx.changed().await;
})
.await
} else {
builder.serve(grpc_addr).await
};
match result {
Ok(()) => {
let _ = bind_tx.send(Ok(()));
}
Err(e) => {
let msg = format!("gRPC server error: {e}");
tracing::error!("{msg}");
let _ = bind_tx.send(Err(msg));
}
}
});
tokio::task::yield_now().await;
if let Ok(Err(msg)) = bind_rx.try_recv() {
return Err(A2aError::internal(msg));
}
}
let mut shutdown = self.shutdown;
loop {
let accept_result = if let Some(ref mut rx) = shutdown {
tokio::select! {
result = listener.accept() => Some(result),
_ = rx.changed() => None,
}
} else {
Some(listener.accept().await)
};
let (stream, _peer) = match accept_result {
None => {
tracing::info!("A2A server shutting down");
return Ok(());
}
Some(Ok(conn)) => conn,
Some(Err(e)) => {
tracing::warn!("Accept error (continuing): {e}");
continue;
}
};
let handler = handler.clone();
tokio::spawn(async move {
let io = TokioIo::new(stream);
let svc = service_fn(move |req: hyper::Request<Incoming>| {
let handler = handler.clone();
async move { handle_http_request(handler, req).await }
});
if let Err(e) = hyper_util::server::conn::auto::Builder::new(
hyper_util::rt::TokioExecutor::new(),
)
.serve_connection(io, svc)
.await
{
tracing::debug!("Connection error: {e}");
}
});
}
}
}
#[cfg(feature = "server")]
type BoxBody = http_body_util::Either<
http_body_util::Full<bytes::Bytes>,
http_body_util::StreamBody<
std::pin::Pin<
Box<
dyn futures::Stream<Item = Result<http_body::Frame<bytes::Bytes>, std::io::Error>>
+ Send,
>,
>,
>,
>;
#[cfg(feature = "server")]
fn json_response(status: u16, body: String) -> hyper::Response<BoxBody> {
let mut resp = hyper::Response::builder()
.status(status)
.header("Content-Type", "application/json")
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
.header(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
)
.body(http_body_util::Either::Left(http_body_util::Full::new(
bytes::Bytes::from(body),
)))
.expect("response builder with valid status and headers cannot fail");
let _ = &mut resp;
resp
}
#[cfg(feature = "server")]
fn sse_response(
stream: std::pin::Pin<
Box<
dyn futures::Stream<Item = Result<http_body::Frame<bytes::Bytes>, std::io::Error>>
+ Send,
>,
>,
) -> hyper::Response<BoxBody> {
hyper::Response::builder()
.status(200)
.header("Content-Type", "text/event-stream")
.header("Cache-Control", "no-cache")
.header("Connection", "keep-alive")
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
.header(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
)
.body(http_body_util::Either::Right(
http_body_util::StreamBody::new(stream),
))
.expect("response builder with valid status and headers cannot fail")
}
#[cfg(feature = "server")]
async fn handle_http_request<H: A2aHandler>(
handler: Arc<H>,
req: hyper::Request<hyper::body::Incoming>,
) -> Result<hyper::Response<BoxBody>, hyper::Error> {
use http_body_util::BodyExt;
let method = req.method().clone();
let path = req.uri().path().to_string();
if method == hyper::Method::OPTIONS {
return Ok(json_response(204, String::new()));
}
if method == hyper::Method::GET && path == "/.well-known/agent-card.json" {
let card = handler.agent_card();
let body = serde_json::to_string(card).unwrap_or_default();
return Ok(json_response(200, body));
}
let limited = http_body_util::Limited::new(req.into_body(), MAX_REQUEST_BODY_SIZE);
let body_bytes = match limited.collect().await {
Ok(c) => c.to_bytes(),
Err(_) => {
let err = A2aError::invalid_request("Request body too large");
let body = serde_json::to_string(&err).unwrap_or_default();
return Ok(json_response(413, body));
}
};
if method == hyper::Method::POST && path == "/" {
return handle_jsonrpc(&handler, &body_bytes).await;
}
let method_str = method.as_str();
match rest_router::dispatch_rest(&handler, method_str, &path, &body_bytes).await {
Ok(rest_router::RestResult::Json(val)) => {
let body = serde_json::to_string(&val).unwrap_or_default();
Ok(json_response(200, body))
}
Ok(rest_router::RestResult::Stream(stream)) => {
let sse_stream = sse_response::stream_to_sse_rest(stream);
Ok(sse_response(sse_stream))
}
Err(e) => {
let body = serde_json::to_string(&e).unwrap_or_default();
Ok(json_response(404, body))
}
}
}
#[cfg(feature = "server")]
async fn handle_jsonrpc<H: A2aHandler>(
handler: &Arc<H>,
body: &bytes::Bytes,
) -> Result<hyper::Response<BoxBody>, hyper::Error> {
let request: JsonRpcRequest = match serde_json::from_slice(body) {
Ok(r) => r,
Err(e) => {
let resp = crate::jsonrpc::JsonRpcResponse::error(
RequestId::Number(0),
A2aError::parse_error(e.to_string()),
);
let body = serde_json::to_string(&resp).unwrap_or_default();
return Ok(json_response(200, body));
}
};
if request.method == METHOD_MESSAGE_STREAM {
let id = request.id.clone();
let params = request.params.clone().unwrap_or(serde_json::Value::Null);
let req: SendMessageRequest = match serde_json::from_value(params) {
Ok(r) => r,
Err(e) => {
let resp = crate::jsonrpc::JsonRpcResponse::error(id, A2aError::from(e));
let body = serde_json::to_string(&resp).unwrap_or_default();
return Ok(json_response(200, body));
}
};
match handler.on_send_streaming_message(req).await {
Ok(stream) => {
let sse_stream = sse_response::stream_to_sse(id, stream);
return Ok(sse_response(sse_stream));
}
Err(e) => {
let resp = crate::jsonrpc::JsonRpcResponse::error(id, e);
let body = serde_json::to_string(&resp).unwrap_or_default();
return Ok(json_response(200, body));
}
}
}
if request.method == METHOD_TASKS_RESUBSCRIBE {
let id = request.id.clone();
let params = request.params.clone().unwrap_or(serde_json::Value::Null);
let req: SubscribeToTaskRequest = match serde_json::from_value(params) {
Ok(r) => r,
Err(e) => {
let resp = crate::jsonrpc::JsonRpcResponse::error(id, A2aError::from(e));
let body = serde_json::to_string(&resp).unwrap_or_default();
return Ok(json_response(200, body));
}
};
match handler.on_subscribe_to_task(req).await {
Ok(stream) => {
let sse_stream = sse_response::stream_to_sse(id, stream);
return Ok(sse_response(sse_stream));
}
Err(e) => {
let resp = crate::jsonrpc::JsonRpcResponse::error(id, e);
let body = serde_json::to_string(&resp).unwrap_or_default();
return Ok(json_response(200, body));
}
}
}
let response = match jsonrpc_router::dispatch(handler, &request).await {
Ok(Some(resp)) => resp,
Ok(None) => {
crate::jsonrpc::JsonRpcResponse::error(
request.id,
A2aError::internal("Unexpected routing state"),
)
}
Err(resp) => resp,
};
let body = serde_json::to_string(&response).unwrap_or_default();
Ok(json_response(200, body))
}