diff --git a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java index 9b6bf510a..350bc29bb 100644 --- a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java +++ b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/A2AServerRoutes.java @@ -63,6 +63,61 @@ import static io.a2a.transport.rest.context.RestContextKeys.TENANT_KEY; +/** + * Quarkus reactive routes for A2A protocol REST endpoints. + * + *
This class defines all HTTP routes for the A2A protocol using Quarkus Reactive Routes + * (Vert.x web). Each method is annotated with {@code @Route} to map HTTP requests to + * A2A operations, delegating to {@link RestHandler} for processing. + * + *
Routes support optional tenant prefixing and use regex patterns for flexible matching: + *
+ * POST /{tenant}/message:send → sendMessage()
+ * POST /{tenant}/message:stream → sendMessageStreaming()
+ * GET /{tenant}/tasks → listTasks()
+ * GET /{tenant}/tasks/{taskId} → getTask()
+ * POST /{tenant}/tasks/{taskId}:cancel → cancelTask()
+ * POST /{tenant}/tasks/{taskId}:subscribe → subscribeToTask()
+ * GET /.well-known/agent-card.json → getAgentCard()
+ * GET /{tenant}/extendedAgentCard → getExtendedAgentCard()
+ *
+ *
+ * Most endpoints require authentication via {@code @Authenticated}, except: + *
Streaming endpoints ({@code message:stream}, {@code subscribe}) use Server-Sent Events (SSE) + * via the inner {@link MultiSseSupport} class. SSE responses are handled by: + *
All errors are caught and converted to HTTP responses via {@link RestHandler#createErrorResponse(A2AError)}, + * ensuring consistent error format and status codes across all endpoints. + * + *
Each request creates a {@link ServerCallContext} via {@link #createCallContext(RoutingContext, String)}, + * extracting: + *
Custom context creation is supported via CDI-provided {@link CallContextFactory}.
+ *
+ * @see RestHandler
+ * @see ServerCallContext
+ * @see CallContextFactory
+ */
@Singleton
@Authenticated
public class A2AServerRoutes {
@@ -87,6 +142,29 @@ public class A2AServerRoutes {
@Inject
Instance Maps {@code POST /{tenant}/message:send} to {@link RestHandler#sendMessage}.
+ * The request body must be JSON containing a message with parts.
+ *
+ * URL Pattern: {@code /message:send} or {@code /{tenant}/message:send}
+ *
+ * Example:
+ * Maps {@code POST /{tenant}/message:stream} to {@link RestHandler#sendStreamingMessage}.
+ * Returns a stream of task updates and artifacts as SSE events.
+ *
+ * URL Pattern: {@code /message:stream} or {@code /{tenant}/message:stream}
+ *
+ * Response Format: {@code text/event-stream} with JSON events:
+ * Maps {@code GET /{tenant}/tasks} to {@link RestHandler#listTasks}.
+ * Supports query parameters for filtering and pagination.
+ *
+ * URL Pattern: {@code /tasks?status=COMPLETED&pageSize=10}
+ *
+ * Query Parameters:
+ * Maps {@code GET /{tenant}/tasks/{taskId}} to {@link RestHandler#getTask}.
+ * Optionally includes task history via query parameter.
+ *
+ * URL Pattern: {@code /tasks/{taskId}?historyLength=10}
+ *
+ * @param rc the Vert.x routing context (taskId extracted from path)
+ */
@Route(regex = "^\\/(? Maps {@code POST /{tenant}/tasks/{taskId}:cancel} to {@link RestHandler#cancelTask}.
+ * Signals the agent executor to stop processing and transition to CANCELED state.
+ *
+ * URL Pattern: {@code /tasks/{taskId}:cancel}
+ *
+ * @param rc the Vert.x routing context (taskId extracted from path)
+ */
@Route(regex = "^\\/(? Helper method that sets status code, content type header, and body
+ * from the response object. Used by all blocking endpoints.
+ *
+ * @param rc the Vert.x routing context
+ * @param response the response to send, or null to end without body
+ */
private void sendResponse(RoutingContext rc, @Nullable HTTPRestResponse response) {
if (response != null) {
rc.response()
@@ -231,6 +379,23 @@ private void sendResponse(RoutingContext rc, @Nullable HTTPRestResponse response
}
}
+ /**
+ * Subscribes to task updates via Server-Sent Events.
+ *
+ * Maps {@code POST /{tenant}/tasks/{taskId}:subscribe} to {@link RestHandler#subscribeToTask}.
+ * Returns a stream of task events allowing clients to reconnect to ongoing tasks.
+ *
+ * URL Pattern: {@code /tasks/{taskId}:subscribe}
+ *
+ * Use Cases:
+ * Maps {@code POST /{tenant}/tasks/{taskId}/pushNotificationConfigs} to
+ * {@link RestHandler#createTaskPushNotificationConfiguration}.
+ *
+ * URL Pattern: {@code /tasks/{taskId}/pushNotificationConfigs}
+ *
+ * Request Body: JSON containing webhook URL and event filters
+ *
+ * @param body the JSON request body with notification configuration
+ * @param rc the Vert.x routing context (taskId extracted from path)
+ */
@Route(regex = "^\\/(? Maps {@code GET /{tenant}/tasks/{taskId}/pushNotificationConfigs/{configId}} to
+ * {@link RestHandler#getTaskPushNotificationConfiguration}.
+ *
+ * URL Pattern: {@code /tasks/{taskId}/pushNotificationConfigs/{configId}}
+ *
+ * @param rc the Vert.x routing context (taskId and configId extracted from path)
+ */
@Route(regex = "^\\/(? Maps {@code GET /{tenant}/tasks/{taskId}/pushNotificationConfigs} to
+ * {@link RestHandler#listTaskPushNotificationConfigurations}.
+ * Supports pagination via query parameters.
+ *
+ * URL Pattern: {@code /tasks/{taskId}/pushNotificationConfigs?pageSize=10}
+ *
+ * Query Parameters:
+ * Maps {@code DELETE /{tenant}/tasks/{taskId}/pushNotificationConfigs/{configId}} to
+ * {@link RestHandler#deleteTaskPushNotificationConfiguration}.
+ *
+ * URL Pattern: {@code /tasks/{taskId}/pushNotificationConfigs/{configId}}
+ *
+ * Response: HTTP 204 No Content on success
+ *
+ * @param rc the Vert.x routing context (taskId and configId extracted from path)
+ */
@Route(regex = "^\\/(? Handles optional tenant prefixes in URL paths, normalizing:
+ * Maps {@code GET /.well-known/agent-card.json} to {@link RestHandler#getAgentCard}.
+ * This is the primary discovery endpoint that clients use to understand agent capabilities,
+ * supported skills, and communication methods.
*
- * @param rc the routing context
+ * URL Pattern: {@code /.well-known/agent-card.json} (well-known URI)
+ *
+ * Authentication: {@code @PermitAll} - Public endpoint requiring no authentication
+ *
+ * Response: JSON containing {@link io.a2a.spec.AgentCard} with:
+ * Maps {@code GET /{tenant}/extendedAgentCard} to {@link RestHandler#getExtendedAgentCard}.
+ * Provides tenant-specific or private capabilities beyond the public agent card.
+ *
+ * URL Pattern: {@code /extendedAgentCard} or {@code /{tenant}/extendedAgentCard}
+ *
+ * Authentication: Required (inherits {@code @Authenticated} from class)
+ *
+ * @param rc the Vert.x routing context
+ */
@Route(regex = "^\\/(? Handles all HTTP methods on unmatched paths with order=100 (lowest priority).
+ * Returns a {@link io.a2a.spec.MethodNotFoundError} with HTTP 404 status.
+ *
+ * Purpose: Provides consistent error responses for invalid API calls
+ * instead of generic 404 HTML pages.
+ *
+ * @param rc the Vert.x routing context
+ */
@Route(path = "^/.*", order = 100, methods = {Route.HttpMethod.DELETE, Route.HttpMethod.GET, Route.HttpMethod.HEAD, Route.HttpMethod.OPTIONS, Route.HttpMethod.POST, Route.HttpMethod.PUT}, produces = APPLICATION_JSON)
public void methodNotFoundMessage(RoutingContext rc) {
HTTPRestResponse response = jsonRestHandler.createErrorResponse(new MethodNotFoundError());
@@ -397,6 +664,36 @@ static void setStreamingMultiSseSupportSubscribedRunnable(Runnable runnable) {
streamingMultiSseSupportSubscribedRunnable = runnable;
}
+ /**
+ * Creates a {@link ServerCallContext} from Vert.x routing context.
+ *
+ * This method extracts authentication, headers, and protocol metadata from the
+ * HTTP request and builds a context object that flows through the request processing
+ * pipeline to the agent executor.
+ *
+ * The created context includes:
+ * If a CDI bean implementing {@link CallContextFactory} is provided, it will be
+ * used instead of the default implementation. This allows applications to add custom
+ * context data or modify the extraction logic.
+ *
+ * @param rc the Vert.x routing context containing request data
+ * @param jsonRpcMethodName the A2A method name (e.g., "sendMessage", "cancelTask")
+ * @return a new ServerCallContext with extracted request metadata
+ * @see CallContextFactory
+ * @see ServerCallContext
+ */
private ServerCallContext createCallContext(RoutingContext rc, String jsonRpcMethodName) {
if (callContextFactory.isUnsatisfied()) {
User user;
@@ -449,10 +746,45 @@ public String getUsername() {
}
/**
- * Simplified SSE support for Vert.x/Quarkus.
- *
- * This class only handles HTTP-specific concerns (writing to response, backpressure, disconnect).
- * SSE formatting and JSON serialization are handled by {@link SseFormatter}.
+ * Server-Sent Events (SSE) streaming support for Vert.x/Quarkus.
+ *
+ * This inner class handles the HTTP-specific aspects of SSE streaming:
+ * Events are formatted by {@link SseFormatter} before being passed to this class.
+ * Each event follows the SSE specification:
+ * The subscriber requests one event at a time ({@code request(1)}) and only
+ * requests the next event after the previous write completes. This ensures the
+ * HTTP connection doesn't buffer excessive data if the client is slow.
+ *
+ * When the client closes the connection, this class:
+ * Critical: Sets {@code setWriteQueueMaxSize(1)} to force immediate flushing
+ * of each event. Without this, Vert.x buffers writes, causing delays in SSE delivery.
+ *
+ * @see SseFormatter
+ * @see ServerCallContext#invokeEventConsumerCancelCallback()
*/
private static class MultiSseSupport {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MultiSseSupport.class);
@@ -462,11 +794,32 @@ private MultiSseSupport() {
}
/**
- * Write SSE-formatted strings to HTTP response.
+ * Writes SSE-formatted event strings to the HTTP response with backpressure control.
+ *
+ * This method subscribes to the event stream and writes each SSE-formatted string
+ * to the Vert.x HTTP response. It implements reactive backpressure by requesting
+ * events one at a time and only requesting the next after the previous write completes.
+ *
+ * This interface provides an extension point for customizing how {@link ServerCallContext}
+ * instances are created in Quarkus REST applications. The default implementation in
+ * {@link A2AServerRoutes} extracts standard information (user, headers, tenant, protocol version),
+ * but applications can provide their own implementation to add custom context data.
+ *
+ * When no CDI bean implementing this interface is provided, {@link A2AServerRoutes}
+ * creates contexts with:
+ * This method is called for each incoming HTTP request to create the context
+ * that will be passed to the {@link io.a2a.server.requesthandlers.RequestHandler}
+ * and eventually to the {@link io.a2a.server.agentexecution.AgentExecutor}.
+ *
+ * @param rc the Vert.x routing context containing request data
+ * @return a new ServerCallContext with extracted authentication, headers, and metadata
+ */
ServerCallContext build(RoutingContext rc);
}
diff --git a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/QuarkusRestTransportMetadata.java b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/QuarkusRestTransportMetadata.java
index ee9d3ae98..65ab017d7 100644
--- a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/QuarkusRestTransportMetadata.java
+++ b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/QuarkusRestTransportMetadata.java
@@ -3,7 +3,30 @@
import io.a2a.server.TransportMetadata;
import io.a2a.spec.TransportProtocol;
+/**
+ * Transport metadata implementation for Quarkus REST.
+ *
+ * This class provides transport protocol identification for the Quarkus REST
+ * reference implementation. It reports {@link TransportProtocol#HTTP_JSON} as
+ * the transport protocol, indicating that this implementation uses HTTP with
+ * JSON payloads for the A2A protocol.
+ *
+ * The transport metadata is used by the framework for:
+ * This package provides a ready-to-use Quarkus-based REST transport implementation
+ * that maps HTTP endpoints to A2A protocol operations. It serves as both a reference
+ * implementation and a production-ready solution for deploying A2A agents over HTTP/REST.
+ *
+ * All endpoints support optional tenant prefixes in the URL path:
+ * Add this module as a dependency to your Quarkus project:
+ * Provide CDI beans for {@link io.a2a.spec.AgentCard} and
+ * {@link io.a2a.server.agentexecution.AgentExecutor}, and the REST endpoints
+ * will be automatically registered.
+ *
+ * @see io.a2a.transport.rest.handler
+ * @see io.a2a.server.requesthandlers
+ */
@NullMarked
package io.a2a.server.rest.quarkus;
diff --git a/transport/rest/src/main/java/io/a2a/transport/rest/context/RestContextKeys.java b/transport/rest/src/main/java/io/a2a/transport/rest/context/RestContextKeys.java
index f822607ef..86e78e1ad 100644
--- a/transport/rest/src/main/java/io/a2a/transport/rest/context/RestContextKeys.java
+++ b/transport/rest/src/main/java/io/a2a/transport/rest/context/RestContextKeys.java
@@ -3,8 +3,20 @@
/**
* Shared REST context keys for A2A protocol data.
*
- * These keys provide access to REST context information,
- * enabling rich context access in service method implementations.
+ * These keys provide access to REST context information stored in
+ * {@link io.a2a.server.ServerCallContext}, enabling rich context access
+ * in service method implementations and middleware.
+ *
+ * This handler converts HTTP REST requests into A2A protocol operations and
+ * manages the lifecycle of agent interactions including message sending, task
+ * management, and push notification configurations.
+ *
+ * HTTP REST requests flow through this handler to the underlying {@link RequestHandler},
+ * which coordinates with the agent executor and event queue system:
+ * All A2A protocol errors are caught and converted to appropriate HTTP status codes
+ * via {@link #mapErrorToHttpStatus(A2AError)}. Protocol version and required extensions
+ * are validated before processing requests.
+ *
+ * This handler is an {@code @ApplicationScoped} CDI bean that requires:
+ * This method processes an HTTP POST request containing a message to be sent to the agent.
+ * The request is validated for protocol version and required extensions before being forwarded
+ * to the {@link RequestHandler}. The method blocks until the agent produces a terminal event
+ * or requires authentication/input.
+ *
+ * Example Request: Example Response: This method processes an HTTP POST request for streaming responses from the agent.
+ * The response is returned as Server-Sent Events (SSE) via {@link HTTPRestStreamingResponse},
+ * allowing clients to receive task updates and artifacts as they are produced by the agent.
+ *
+ * This method requires the agent card to have {@code capabilities.streaming = true}.
+ *
+ * Example Request: Example Streaming Response: Attempts to cancel a running task identified by the task ID. The cancellation
+ * request is forwarded to the {@link RequestHandler}, which signals the agent executor
+ * to stop processing. The agent should transition the task to {@code CANCELED} state.
+ *
+ * Example Request: Creates a Server-Sent Events (SSE) stream that delivers real-time updates for an
+ * existing task. This allows clients to reconnect to ongoing or completed tasks and
+ * receive their event history and future updates.
+ *
+ * This method requires the agent card to have {@code capabilities.streaming = true}.
+ *
+ * Example Request: Use Cases: Retrieves a list of tasks with support for filtering by context, status, and timestamp,
+ * along with pagination controls. This method is useful for task management dashboards,
+ * monitoring systems, and task history retrieval.
+ *
+ * Example Request: Query Parameters: This method ensures consistent HTTP status code mapping for all A2A errors:
+ * The extended agent card provides additional metadata beyond the public agent card,
+ * such as tenant-specific configurations or private capabilities. This endpoint requires
+ * the agent card to have {@code capabilities.extendedAgentCard = true} and a CDI-produced
+ * {@code @ExtendedAgentCard} instance.
+ *
+ * Example Request: The agent card is a self-describing manifest that provides essential metadata about
+ * the agent, including its capabilities, supported skills, communication methods, and
+ * security requirements. This is the primary discovery endpoint for clients to understand
+ * what the agent can do and how to interact with it.
+ *
+ * Example Request: Example Response: This package contains the core REST handler that processes HTTP requests
+ * and translates them to A2A protocol operations. It includes support for:
+ * {@code
+ * POST /message:send
+ * Content-Type: application/json
+ *
+ * {
+ * "message": {
+ * "parts": [{"text": "Hello"}]
+ * }
+ * }
+ * }
+ *
+ * @param body the JSON request body
+ * @param rc the Vert.x routing context
+ */
@Route(regex = "^\\/(?{@code
+ * data: {"taskStatusUpdate":{"task":{"status":{"state":"WORKING"}}}}
+ *
+ * data: {"taskArtifactUpdate":{"artifacts":[...]}}
+ *
+ * data: {"taskStatusUpdate":{"task":{"status":{"state":"COMPLETED"}}}}
+ * }
+ *
+ * @param body the JSON request body
+ * @param rc the Vert.x routing context
+ */
@Route(regex = "^\\/(?
+ *
+ *
+ * @param rc the Vert.x routing context
+ */
@Route(regex = "^\\/(?
+ *
+ *
+ * @param rc the Vert.x routing context (taskId extracted from path)
+ */
@Route(regex = "^\\/(?
+ *
+ *
+ * @param rc the Vert.x routing context (taskId extracted from path)
+ */
@Route(regex = "^\\/(?
+ *
+ *
+ * @param rc the Vert.x routing context
+ * @return the extracted tenant ID, or empty string if not specified
+ */
private String extractTenant(RoutingContext rc) {
String tenantPath = rc.pathParam("tenant");
if (tenantPath == null || tenantPath.isBlank()) {
@@ -368,11 +598,25 @@ private String extractTenant(RoutingContext rc) {
}
/**
- * /**
- * Handles incoming GET requests to the agent card endpoint.
- * Returns the agent card in JSON format.
+ * Retrieves the public agent card for agent discovery.
+ *
+ *
+ *
+ *
+ * @param rc the Vert.x routing context
*/
@Route(path = "/.well-known/agent-card.json", order = 1, methods = Route.HttpMethod.GET, produces = APPLICATION_JSON)
@PermitAll
@@ -381,12 +625,35 @@ public void getAgentCard(RoutingContext rc) {
sendResponse(rc, response);
}
+ /**
+ * Retrieves the extended agent card with additional metadata.
+ *
+ * Context Contents
+ *
+ *
+ *
+ * Custom Context Factory
+ *
+ *
+ *
+ * SSE Format
+ *
+ * id: 0
+ * data: {"taskStatusUpdate":{...}}
+ *
+ * id: 1
+ * data: {"taskArtifactUpdate":{...}}
+ *
+ *
+ * Backpressure Handling
+ * Disconnect Detection
+ *
+ *
+ *
+ * Write Queue Configuration
+ * Execution Flow
+ *
+ *
+ *
+ * Headers Set
+ *
+ *
*
- * @param sseStrings Multi stream of SSE-formatted strings (from SseFormatter)
- * @param rc Vert.x routing context
- * @param context A2A server call context (for EventConsumer cancellation)
+ * @param sseStrings Multi stream of SSE-formatted strings (from {@link SseFormatter})
+ * @param rc Vert.x routing context providing HTTP response
+ * @param context A2A server call context (for EventConsumer cancellation on disconnect)
*/
public static void writeSseStrings(MultiDefault Behavior
+ *
+ *
+ *
+ * Custom Implementation Example
+ * {@code
+ * @ApplicationScoped
+ * public class CustomCallContextFactory implements CallContextFactory {
+ * @Override
+ * public ServerCallContext build(RoutingContext rc) {
+ * // Extract custom data from routing context
+ * String orgId = rc.request().getHeader("X-Organization-ID");
+ *
+ * Map
+ *
+ * @see ServerCallContext
+ * @see A2AServerRoutes#createCallContext(RoutingContext, String)
+ */
public interface CallContextFactory {
+ /**
+ * Builds a {@link ServerCallContext} from a Vert.x routing context.
+ *
+ *
+ *
+ *
+ * @see TransportMetadata
+ * @see TransportProtocol
+ */
public class QuarkusRestTransportMetadata implements TransportMetadata {
+ /**
+ * Returns the transport protocol identifier.
+ *
+ * @return {@code "http+json"} indicating HTTP transport with JSON encoding
+ */
@Override
public String getTransportProtocol() {
return TransportProtocol.HTTP_JSON.asString();
diff --git a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/package-info.java b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/package-info.java
index 00ca87bd6..2e76b40eb 100644
--- a/reference/rest/src/main/java/io/a2a/server/rest/quarkus/package-info.java
+++ b/reference/rest/src/main/java/io/a2a/server/rest/quarkus/package-info.java
@@ -1,3 +1,65 @@
+/**
+ * Quarkus REST reference implementation for the A2A protocol.
+ *
+ * Key Components
+ *
+ *
+ *
+ * Architecture
+ *
+ * HTTP Request (Quarkus/Vert.x)
+ * ↓
+ * A2AServerRoutes (@Route methods)
+ * ↓
+ * RestHandler (transport/rest)
+ * ↓
+ * RequestHandler (server-common)
+ * ↓
+ * AgentExecutor (user implementation)
+ *
+ *
+ * Supported Endpoints
+ *
+ *
+ *
+ * Multi-tenancy Support
+ *
+ *
+ *
+ * Usage
+ * {@code
+ *
+ *
+ * Usage Example
+ * {@code
+ * public void processRequest(ServerCallContext context) {
+ * String tenant = context.get(RestContextKeys.TENANT_KEY);
+ * String method = context.get(RestContextKeys.METHOD_NAME_KEY);
+ * Map
+ *
+ * @see io.a2a.server.ServerCallContext
*/
public final class RestContextKeys {
diff --git a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java
index 7312824c2..e743c4cc3 100644
--- a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java
+++ b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java
@@ -65,6 +65,49 @@
import mutiny.zero.ZeroPublisher;
import org.jspecify.annotations.Nullable;
+/**
+ * REST transport handler for processing A2A protocol requests over HTTP.
+ *
+ * Request Flow
+ *
+ * HTTP Request → RestHandler → RequestHandler → AgentExecutor
+ * ↓ ↓
+ * Validation EventQueue → Response
+ *
+ *
+ * Supported Operations
+ *
+ *
+ *
+ * Error Handling
+ * CDI Integration
+ *
+ *
+ *
+ * @see RequestHandler
+ * @see io.a2a.server.requesthandlers.DefaultRequestHandler
+ * @see io.a2a.spec.AgentCard
+ * @see ServerCallContext
+ */
@ApplicationScoped
public class RestHandler {
@@ -90,6 +133,14 @@ protected RestHandler() {
this.executor = null;
}
+ /**
+ * Creates a REST handler with full CDI injection support.
+ *
+ * @param agentCard the public agent card containing agent capabilities
+ * @param extendedAgentCard optional extended agent card instance
+ * @param requestHandler the handler for processing A2A requests
+ * @param executor the executor for asynchronous operations
+ */
@Inject
public RestHandler(@PublicAgentCard AgentCard agentCard, @ExtendedAgentCard Instance{@code
+ * POST /v1/tenants/{tenant}/messages
+ * Content-Type: application/json
+ *
+ * {
+ * "message": {
+ * "parts": [
+ * {"text": "What is the weather in San Francisco?"}
+ * ]
+ * }
+ * }
+ * }
+ *
+ * {@code
+ * HTTP/1.1 200 OK
+ * Content-Type: application/json
+ *
+ * {
+ * "task": {
+ * "id": "task-123",
+ * "status": {"state": "COMPLETED"},
+ * "artifacts": [...]
+ * }
+ * }
+ * }
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param body the JSON request body containing the message to send
+ * @return the HTTP response containing the task or message result
+ * @see #sendStreamingMessage(ServerCallContext, String, String)
+ * @see RequestHandler#onMessageSend(io.a2a.spec.MessageSendParams, ServerCallContext)
+ */
public HTTPRestResponse sendMessage(ServerCallContext context, String tenant, String body) {
try {
@@ -125,6 +226,51 @@ public HTTPRestResponse sendMessage(ServerCallContext context, String tenant, St
}
}
+ /**
+ * Handles a streaming message send request.
+ *
+ * {@code
+ * POST /v1/tenants/{tenant}/messages/stream
+ * Content-Type: application/json
+ *
+ * {
+ * "message": {
+ * "parts": [
+ * {"text": "Generate a long story"}
+ * ]
+ * }
+ * }
+ * }
+ *
+ * {@code
+ * HTTP/1.1 200 OK
+ * Content-Type: text/event-stream
+ *
+ * data: {"taskStatusUpdate":{"task":{"id":"task-123","status":{"state":"WORKING"}}}}
+ *
+ * data: {"taskArtifactUpdate":{"taskId":"task-123","artifacts":[{"parts":[{"text":"Once upon"}]}]}}
+ *
+ * data: {"taskArtifactUpdate":{"taskId":"task-123","artifacts":[{"parts":[{"text":" a time..."}]}]}}
+ *
+ * data: {"taskStatusUpdate":{"task":{"id":"task-123","status":{"state":"COMPLETED"}}}}
+ * }
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param body the JSON request body containing the message to send
+ * @return the streaming HTTP response containing a publisher of events
+ * @see #sendMessage(ServerCallContext, String, String)
+ * @see RequestHandler#onMessageSendStream(io.a2a.spec.MessageSendParams, ServerCallContext)
+ * @see HTTPRestStreamingResponse
+ */
public HTTPRestResponse sendStreamingMessage(ServerCallContext context, String tenant, String body) {
try {
if (!agentCard.capabilities().streaming()) {
@@ -144,6 +290,26 @@ public HTTPRestResponse sendStreamingMessage(ServerCallContext context, String t
}
}
+ /**
+ * Handles a task cancellation request.
+ *
+ * {@code
+ * POST /v1/tenants/{tenant}/tasks/{taskId}/cancel
+ * }
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param taskId the ID of the task to cancel
+ * @return the HTTP response containing the cancelled task
+ * @throws InvalidParamsError if taskId is null or empty
+ * @see RequestHandler#onCancelTask(TaskIdParams, ServerCallContext)
+ * @see io.a2a.server.agentexecution.AgentExecutor#cancel
+ */
public HTTPRestResponse cancelTask(ServerCallContext context, String tenant, String taskId) {
try {
if (taskId == null || taskId.isEmpty()) {
@@ -162,6 +328,15 @@ public HTTPRestResponse cancelTask(ServerCallContext context, String tenant, Str
}
}
+ /**
+ * Creates a push notification configuration for a task.
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param body the JSON request body containing the configuration
+ * @param taskId the ID of the task
+ * @return the HTTP response containing the created configuration
+ */
public HTTPRestResponse createTaskPushNotificationConfiguration(ServerCallContext context, String tenant, String body, String taskId) {
try {
if (!agentCard.capabilities().pushNotifications()) {
@@ -179,6 +354,34 @@ public HTTPRestResponse createTaskPushNotificationConfiguration(ServerCallContex
}
}
+ /**
+ * Subscribes to task updates via a streaming connection.
+ *
+ * {@code
+ * GET /v1/tenants/{tenant}/tasks/{taskId}/subscribe
+ * }
+ *
+ *
+ *
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param taskId the ID of the task to subscribe to
+ * @return the streaming HTTP response containing task updates
+ * @see RequestHandler#onSubscribeToTask(TaskIdParams, ServerCallContext)
+ * @see #sendStreamingMessage(ServerCallContext, String, String)
+ */
public HTTPRestResponse subscribeToTask(ServerCallContext context, String tenant, String taskId) {
try {
if (!agentCard.capabilities().streaming()) {
@@ -194,6 +397,15 @@ public HTTPRestResponse subscribeToTask(ServerCallContext context, String tenant
}
}
+ /**
+ * Retrieves a task by ID.
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param taskId the ID of the task to retrieve
+ * @param historyLength the maximum number of history entries to include
+ * @return the HTTP response containing the task
+ */
public HTTPRestResponse getTask(ServerCallContext context, String tenant, String taskId, @Nullable Integer historyLength) {
try {
TaskQueryParams params = new TaskQueryParams(taskId, historyLength, tenant);
@@ -209,6 +421,43 @@ public HTTPRestResponse getTask(ServerCallContext context, String tenant, String
}
}
+ /**
+ * Lists tasks with optional filtering and pagination.
+ *
+ * {@code
+ * GET /v1/tenants/{tenant}/tasks?status=COMPLETED&pageSize=10&includeArtifacts=true
+ * }
+ *
+ *
+ *
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param contextId optional context ID to filter by
+ * @param status optional task status to filter by (must be valid {@link TaskState} value)
+ * @param pageSize optional maximum number of tasks to return
+ * @param pageToken optional token for pagination
+ * @param historyLength optional maximum number of history entries per task
+ * @param statusTimestampAfter optional ISO-8601 timestamp to filter tasks updated after
+ * @param includeArtifacts optional flag to include task artifacts
+ * @return the HTTP response containing the list of tasks
+ * @throws InvalidParamsError if status is not a valid TaskState or timestamp is malformed
+ * @see RequestHandler#onListTasks(ListTasksParams, ServerCallContext)
+ * @see TaskState
+ */
public HTTPRestResponse listTasks(ServerCallContext context, String tenant,
@Nullable String contextId, @Nullable String status,
@Nullable Integer pageSize, @Nullable String pageToken,
@@ -271,6 +520,15 @@ public HTTPRestResponse listTasks(ServerCallContext context, String tenant,
}
}
+ /**
+ * Retrieves a specific push notification configuration for a task.
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param taskId the ID of the task
+ * @param configId the ID of the configuration to retrieve
+ * @return the HTTP response containing the configuration
+ */
public HTTPRestResponse getTaskPushNotificationConfiguration(ServerCallContext context, String tenant, String taskId, String configId) {
try {
if (!agentCard.capabilities().pushNotifications()) {
@@ -286,6 +544,16 @@ public HTTPRestResponse getTaskPushNotificationConfiguration(ServerCallContext c
}
}
+ /**
+ * Lists push notification configurations for a task.
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param taskId the ID of the task
+ * @param pageSize the maximum number of configurations to return
+ * @param pageToken the token for pagination
+ * @return the HTTP response containing the list of configurations
+ */
public HTTPRestResponse listTaskPushNotificationConfigurations(ServerCallContext context, String tenant, String taskId, int pageSize, String pageToken) {
try {
if (!agentCard.capabilities().pushNotifications()) {
@@ -301,6 +569,15 @@ public HTTPRestResponse listTaskPushNotificationConfigurations(ServerCallContext
}
}
+ /**
+ * Deletes a push notification configuration for a task.
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @param taskId the ID of the task
+ * @param configId the ID of the configuration to delete
+ * @return the HTTP response with no content on success
+ */
public HTTPRestResponse deleteTaskPushNotificationConfiguration(ServerCallContext context, String tenant, String taskId, String configId) {
try {
if (!agentCard.capabilities().pushNotifications()) {
@@ -348,6 +625,12 @@ private HTTPRestResponse createSuccessResponse(int statusCode, com.google.protob
}
}
+ /**
+ * Creates an HTTP error response from an A2A error.
+ *
+ * @param error the A2A error to convert
+ * @return the HTTP response with appropriate status code and error details
+ */
public HTTPRestResponse createErrorResponse(A2AError error) {
int statusCode = mapErrorToHttpStatus(error);
return createErrorResponse(statusCode, error);
@@ -425,6 +708,24 @@ public void onComplete() {
});
}
+ /**
+ * Maps A2A protocol errors to HTTP status codes.
+ *
+ *
+ *
+ *
+ * @param error the A2A error to map
+ * @return the corresponding HTTP status code
+ */
private int mapErrorToHttpStatus(A2AError error) {
if (error instanceof InvalidRequestError || error instanceof JSONParseError) {
return 400;
@@ -459,6 +760,26 @@ private int mapErrorToHttpStatus(A2AError error) {
return 500;
}
+ /**
+ * Retrieves the extended agent card if configured.
+ *
+ * {@code
+ * GET /v1/tenants/{tenant}/extended-agent-card
+ * }
+ *
+ * @param context the server call context containing authentication and metadata
+ * @param tenant the tenant identifier
+ * @return the HTTP response containing the extended agent card
+ * @throws ExtendedAgentCardNotConfiguredError if extended agent card is not available
+ * @see #getAgentCard()
+ * @see AgentCard
+ */
public HTTPRestResponse getExtendedAgentCard(ServerCallContext context, String tenant) {
try {
if (!agentCard.capabilities().extendedAgentCard() || extendedAgentCard == null || !extendedAgentCard.isResolvable()) {
@@ -472,6 +793,38 @@ public HTTPRestResponse getExtendedAgentCard(ServerCallContext context, String t
}
}
+ /**
+ * Retrieves the public agent card.
+ *
+ * {@code
+ * GET /v1/agent-card
+ * }
+ *
+ * {@code
+ * {
+ * "name": "Weather Agent",
+ * "description": "Provides weather information",
+ * "version": "1.0.0",
+ * "capabilities": {
+ * "streaming": true,
+ * "pushNotifications": false
+ * },
+ * "skills": [...],
+ * "supportedInterfaces": [...]
+ * }
+ * }
+ *
+ * @return the HTTP response containing the agent card
+ * @see AgentCard
+ * @see #getExtendedAgentCard(ServerCallContext, String)
+ */
public HTTPRestResponse getAgentCard() {
try {
return new HTTPRestResponse(200, "application/json", JsonUtil.toJson(agentCard));
@@ -480,26 +833,51 @@ public HTTPRestResponse getAgentCard() {
}
}
+ /**
+ * Represents an HTTP REST response with status code, content type, and body.
+ */
public static class HTTPRestResponse {
private final int statusCode;
private final String contentType;
private final String body;
+ /**
+ * Creates an HTTP REST response.
+ *
+ * @param statusCode the HTTP status code
+ * @param contentType the content type of the response
+ * @param body the response body
+ */
public HTTPRestResponse(int statusCode, String contentType, String body) {
this.statusCode = statusCode;
this.contentType = contentType;
this.body = body;
}
+ /**
+ * Returns the HTTP status code.
+ *
+ * @return the status code
+ */
public int getStatusCode() {
return statusCode;
}
+ /**
+ * Returns the content type.
+ *
+ * @return the content type
+ */
public String getContentType() {
return contentType;
}
+ /**
+ * Returns the response body.
+ *
+ * @return the body
+ */
public String getBody() {
return body;
}
@@ -510,26 +888,47 @@ public String toString() {
}
}
+ /**
+ * Represents an HTTP streaming response with Server-Sent Events.
+ */
public static class HTTPRestStreamingResponse extends HTTPRestResponse {
private final Flow.Publisher
+ *
+ */
@NullMarked
package io.a2a.transport.rest.handler;
diff --git a/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestTestTransportMetadata.java b/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestTestTransportMetadata.java
index 68aad41bb..d9401d68e 100644
--- a/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestTestTransportMetadata.java
+++ b/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestTestTransportMetadata.java
@@ -3,7 +3,16 @@
import io.a2a.server.TransportMetadata;
import io.a2a.spec.TransportProtocol;
+/**
+ * Test implementation of TransportMetadata for REST transport testing.
+ */
public class RestTestTransportMetadata implements TransportMetadata {
+
+ /**
+ * Returns the transport protocol used for REST communication.
+ *
+ * @return the HTTP JSON transport protocol identifier
+ */
@Override
public String getTransportProtocol() {
return TransportProtocol.HTTP_JSON.asString();