diff --git a/reference/grpc/src/test/java/io/a2a/server/grpc/quarkus/QuarkusA2AGrpcTest.java b/reference/grpc/src/test/java/io/a2a/server/grpc/quarkus/QuarkusA2AGrpcTest.java
index 24320b94d..7423edda0 100644
--- a/reference/grpc/src/test/java/io/a2a/server/grpc/quarkus/QuarkusA2AGrpcTest.java
+++ b/reference/grpc/src/test/java/io/a2a/server/grpc/quarkus/QuarkusA2AGrpcTest.java
@@ -50,4 +50,10 @@ public static void closeChannel() {
Thread.currentThread().interrupt();
}
}
+
+ @Override
+ public void testAgentCardHeaders() {
+ // Skip - gRPC doesn't use HTTP caching headers for Agent Card
+ // The A2A spec section 8.6 caching requirements apply only to HTTP endpoints
+ }
}
\ No newline at end of file
diff --git a/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java b/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java
index 6914dc5f9..63a88f355 100644
--- a/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java
+++ b/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java
@@ -173,6 +173,9 @@ public class A2AServerRoutes {
@Inject
JSONRPCHandler jsonRpcHandler;
+ @Inject
+ io.a2a.server.AgentCardCacheMetadata cacheMetadata;
+
// Hook so testing can wait until the MultiSseSupport is subscribed.
// Without this we get intermittent failures
private static volatile Runnable streamingMultiSseSupportSubscribedRunnable;
@@ -322,6 +325,13 @@ public void invokeJSONRPCHandler(@Body String body, RoutingContext rc) {
*
Returns the agent's capabilities and metadata in JSON format according to the
* A2A protocol specification. This endpoint is publicly accessible (no authentication).
*
+ *
Includes HTTP caching headers per A2A specification section 8.6:
+ *
+ * - {@code Cache-Control} - with max-age directive
+ * - {@code ETag} - content hash for validation
+ * - {@code Last-Modified} - timestamp when agent card was initialized
+ *
+ *
* Request:
*
{@code
* GET /.well-known/agent-card.json
@@ -331,6 +341,9 @@ public void invokeJSONRPCHandler(@Body String body, RoutingContext rc) {
* {@code
* HTTP/1.1 200 OK
* Content-Type: application/json
+ * Cache-Control: public, max-age=3600
+ * ETag: "a1b2c3d4..."
+ * Last-Modified: Mon, 17 Mar 2025 10:00:00 GMT
*
* {
* "name": "My Agent",
@@ -343,12 +356,15 @@ public void invokeJSONRPCHandler(@Body String body, RoutingContext rc) {
* }
* }
*
+ * @param rc the Vert.x routing context
* @return the agent card as a JSON string
* @throws JsonProcessingException if serialization fails
* @see JSONRPCHandler#getAgentCard()
*/
@Route(path = "/.well-known/agent-card.json", methods = Route.HttpMethod.GET, produces = APPLICATION_JSON)
- public String getAgentCard() throws JsonProcessingException {
+ public String getAgentCard(RoutingContext rc) throws JsonProcessingException {
+ // Add caching headers per A2A specification section 8.6
+ cacheMetadata.getHttpHeadersMap().forEach((k, v) -> rc.response().putHeader(k, v));
return JsonUtil.toJson(jsonRpcHandler.getAgentCard());
}
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 19399c18a..7abbb934d 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
@@ -380,10 +380,14 @@ public void cancelTask(@Body String body, RoutingContext rc) {
*/
private void sendResponse(RoutingContext rc, @Nullable HTTPRestResponse response) {
if (response != null) {
- rc.response()
+ var httpResponse = rc.response()
.setStatusCode(response.getStatusCode())
- .putHeader(CONTENT_TYPE, response.getContentType())
- .end(response.getBody());
+ .putHeader(CONTENT_TYPE, response.getContentType());
+
+ // Add any additional headers from the response
+ response.getHeaders().forEach(httpResponse::putHeader);
+
+ httpResponse.end(response.getBody());
} else {
rc.response().end();
}
diff --git a/server-common/src/main/java/io/a2a/server/AgentCardCacheMetadata.java b/server-common/src/main/java/io/a2a/server/AgentCardCacheMetadata.java
new file mode 100644
index 000000000..573ac9384
--- /dev/null
+++ b/server-common/src/main/java/io/a2a/server/AgentCardCacheMetadata.java
@@ -0,0 +1,190 @@
+package io.a2a.server;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.HexFormat;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Instance;
+import jakarta.inject.Inject;
+
+import org.jspecify.annotations.Nullable;
+
+import io.a2a.jsonrpc.common.json.JsonProcessingException;
+import io.a2a.jsonrpc.common.json.JsonUtil;
+import io.a2a.server.config.A2AConfigProvider;
+import io.a2a.spec.AgentCard;
+
+/**
+ * Provides HTTP caching metadata for Agent Card responses.
+ *
+ * This bean computes and caches HTTP caching headers (Cache-Control, ETag, Last-Modified)
+ * for the Agent Card endpoint as specified in the A2A protocol specification section 8.6.
+ *
+ *
The metadata is computed once at initialization:
+ *
+ * - Cache-Control: Configured via {@code a2a.agent-card.cache.max-age} (default: 3600 seconds)
+ * - ETag: MD5 hash of the serialized Agent Card JSON
+ * - Last-Modified: Timestamp when the bean was initialized (RFC 1123 format)
+ *
+ *
+ * Since the Agent Card is {@code @ApplicationScoped}, these values remain stable
+ * throughout the application lifecycle unless the application is restarted.
+ *
+ * @see A2A Specification - Agent Card Caching
+ */
+@ApplicationScoped
+public class AgentCardCacheMetadata {
+
+ private static final String CONFIG_KEY_MAX_AGE = "a2a.agent-card.cache.max-age";
+ private static final String DEFAULT_MAX_AGE = "3600"; // 1 hour
+ private static final DateTimeFormatter RFC_1123_FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME;
+
+ @Inject
+ @PublicAgentCard
+ Instance agentCardInstance;
+
+ @Inject
+ Instance configInstance;
+
+ private @Nullable AgentCard agentCard;
+ private @Nullable A2AConfigProvider config;
+
+ @SuppressWarnings("NullAway") // Initialized in @PostConstruct when agentCard is available
+ private String etag;
+ @SuppressWarnings("NullAway") // Initialized in @PostConstruct when agentCard is available
+ private String lastModified;
+ @SuppressWarnings("NullAway") // Initialized in @PostConstruct when agentCard is available
+ private String cacheControl;
+
+ /**
+ * Package-private no-arg constructor for CDI.
+ */
+ AgentCardCacheMetadata() {
+ // For CDI
+ }
+
+ /**
+ * Public constructor for testing purposes.
+ *
+ * @param agentCard the agent card
+ * @param config the configuration provider
+ */
+ public AgentCardCacheMetadata(AgentCard agentCard, A2AConfigProvider config) {
+ this.agentCard = agentCard;
+ this.config = config;
+ init();
+ }
+
+ @PostConstruct
+ @SuppressWarnings("NullAway") // agentCard and config are guaranteed non-null in both paths
+ void init() {
+ // Handle two initialization paths:
+ // 1. CDI injection: get beans from Instance if available
+ // 2. Direct constructor: agentCard and config already set
+
+ if (agentCard == null && agentCardInstance != null) {
+ // CDI path - only initialize if AgentCard bean is available
+ if (agentCardInstance.isUnsatisfied() || configInstance.isUnsatisfied()) {
+ return;
+ }
+ this.agentCard = agentCardInstance.get();
+ this.config = configInstance.get();
+ }
+
+ // At this point, agentCard and config should be set (either via CDI or constructor)
+ if (agentCard == null || config == null) {
+ return;
+ }
+
+ // Calculate ETag from the serialized JSON representation
+ this.etag = calculateETag(agentCard);
+
+ // Set Last-Modified to the initialization time
+ this.lastModified = RFC_1123_FORMATTER.format(Instant.now().atZone(ZoneOffset.UTC));
+
+ // Configure Cache-Control with max-age directive
+ String maxAge = config.getOptionalValue(CONFIG_KEY_MAX_AGE).orElse(DEFAULT_MAX_AGE);
+ this.cacheControl = "public, max-age=" + maxAge;
+ }
+
+ /**
+ * Returns the ETag header value for the Agent Card.
+ *
+ * The ETag is an MD5 hash of the serialized Agent Card JSON, quoted per HTTP specification.
+ *
+ * @return the ETag header value (e.g., {@code "a1b2c3d4..."})
+ */
+ public String getETag() {
+ return etag;
+ }
+
+ /**
+ * Returns the Last-Modified header value for the Agent Card.
+ *
+ *
The timestamp represents when the bean was initialized, in RFC 1123 format.
+ *
+ * @return the Last-Modified header value (e.g., {@code "Mon, 17 Mar 2025 10:00:00 GMT"})
+ */
+ public String getLastModified() {
+ return lastModified;
+ }
+
+ /**
+ * Returns the Cache-Control header value for the Agent Card.
+ *
+ *
The value includes {@code public} and a {@code max-age} directive configured
+ * via {@code a2a.agent-card.cache.max-age} (default: 3600 seconds).
+ *
+ * @return the Cache-Control header value (e.g., {@code "public, max-age=3600"})
+ */
+ public String getCacheControl() {
+ return cacheControl;
+ }
+
+ /**
+ * Calculates an MD5 hash of the Agent Card JSON for use as an ETag.
+ *
+ * @param card the agent card to hash
+ * @return the hex-encoded MD5 hash, quoted per HTTP specification
+ */
+ private String calculateETag(AgentCard card) {
+ try {
+ String json = JsonUtil.toJson(card);
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] hash = md.digest(json.getBytes(StandardCharsets.UTF_8));
+ return "\"" + HexFormat.of().formatHex(hash) + "\"";
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("MD5 algorithm not available", e);
+ } catch (JsonProcessingException e) {
+ throw new IllegalStateException("Failed to serialize Agent Card for ETag calculation", e);
+ }
+ }
+
+ /**
+ * Populates a map with header names and header values stored in this instance.
+ *
+ * @return a map of the headers
+ */
+ public Map getHttpHeadersMap() {
+ Map headers = new HashMap<>();
+ if (cacheControl != null) {
+ headers.put("Cache-Control", cacheControl);
+ }
+ if (lastModified != null) {
+ headers.put("Last-Modified", lastModified);
+ }
+ if (etag != null) {
+ headers.put("ETag", etag);
+ }
+ return headers;
+ }
+}
diff --git a/tests/server-common/src/test/java/io/a2a/server/apps/common/AbstractA2AServerTest.java b/tests/server-common/src/test/java/io/a2a/server/apps/common/AbstractA2AServerTest.java
index f7cdacc61..90f226790 100644
--- a/tests/server-common/src/test/java/io/a2a/server/apps/common/AbstractA2AServerTest.java
+++ b/tests/server-common/src/test/java/io/a2a/server/apps/common/AbstractA2AServerTest.java
@@ -636,6 +636,48 @@ public void testGetExtendedAgentCard() throws A2AClientException {
assertTrue(agentCard.skills().isEmpty());
}
+ /**
+ * Tests that the Agent Card endpoint returns HTTP caching headers.
+ *
+ * Per A2A specification section 8.6, Agent Card HTTP endpoints SHOULD include:
+ *
+ * - Cache-Control header with max-age directive (CARD-CACHE-001)
+ * - ETag header for conditional request support (CARD-CACHE-002)
+ * - Last-Modified header (CARD-CACHE-003, MAY requirement)
+ *
+ *
+ * @throws Exception if HTTP request fails
+ */
+ @Test
+ public void testAgentCardHeaders() throws Exception {
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:" + serverPort + "/.well-known/agent-card.json"))
+ .GET()
+ .build();
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+ assertEquals(200, response.statusCode());
+
+ // Verify Cache-Control header with max-age directive (CARD-CACHE-001)
+ Optional cacheControl = response.headers().firstValue("Cache-Control");
+ assertTrue(cacheControl.isPresent(), "Cache-Control header should be present");
+ assertTrue(cacheControl.get().contains("max-age"),
+ "Cache-Control should contain max-age directive, got: " + cacheControl.get());
+
+ // Verify ETag header (CARD-CACHE-002)
+ Optional etag = response.headers().firstValue("ETag");
+ assertTrue(etag.isPresent(), "ETag header should be present");
+ assertTrue(etag.get().startsWith("\"") && etag.get().endsWith("\""),
+ "ETag should be quoted per HTTP specification, got: " + etag.get());
+
+ // Verify Last-Modified header in RFC 1123 format (CARD-CACHE-003)
+ Optional lastModified = response.headers().firstValue("Last-Modified");
+ assertTrue(lastModified.isPresent(), "Last-Modified header should be present");
+ assertTrue(lastModified.get().contains("GMT"),
+ "Last-Modified should be in RFC 1123 format (containing GMT), got: " + lastModified.get());
+ }
+
@Test
public void testSendMessageStreamNewMessageSuccess() throws Exception {
testSendStreamingMessage(false);
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 8491d9216..b7a7f692f 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
@@ -29,6 +29,7 @@
import io.a2a.jsonrpc.common.json.JsonProcessingException;
import io.a2a.jsonrpc.common.json.JsonUtil;
import io.a2a.jsonrpc.common.wrappers.ListTasksResult;
+import io.a2a.server.AgentCardCacheMetadata;
import io.a2a.server.AgentCardValidator;
import io.a2a.server.ExtendedAgentCard;
import io.a2a.server.PublicAgentCard;
@@ -127,6 +128,7 @@ public class RestHandler {
// final, is not proxyable in all runtimes
private AgentCard agentCard;
private @Nullable Instance extendedAgentCard;
+ private AgentCardCacheMetadata cacheMetadata;
private RequestHandler requestHandler;
private Executor executor;
@@ -146,14 +148,16 @@ protected RestHandler() {
*
* @param agentCard the public agent card containing agent capabilities
* @param extendedAgentCard optional extended agent card instance
+ * @param cacheMetadata the agent card caching metadata
* @param requestHandler the handler for processing A2A requests
* @param executor the executor for asynchronous operations
*/
@Inject
public RestHandler(@PublicAgentCard AgentCard agentCard, @ExtendedAgentCard Instance extendedAgentCard,
- RequestHandler requestHandler, @Internal Executor executor) {
+ AgentCardCacheMetadata cacheMetadata, RequestHandler requestHandler, @Internal Executor executor) {
this.agentCard = agentCard;
this.extendedAgentCard = extendedAgentCard;
+ this.cacheMetadata = cacheMetadata;
this.requestHandler = requestHandler;
this.executor = executor;
@@ -165,11 +169,14 @@ public RestHandler(@PublicAgentCard AgentCard agentCard, @ExtendedAgentCard Inst
* Creates a REST handler with basic dependencies.
*
* @param agentCard the agent card containing agent capabilities
+ * @param cacheMetadata the agent card caching metadata
* @param requestHandler the handler for processing A2A requests
* @param executor the executor for asynchronous operations
*/
- public RestHandler(AgentCard agentCard, RequestHandler requestHandler, Executor executor) {
+ public RestHandler(AgentCard agentCard, AgentCardCacheMetadata cacheMetadata,
+ RequestHandler requestHandler, Executor executor) {
this.agentCard = agentCard;
+ this.cacheMetadata = cacheMetadata;
this.requestHandler = requestHandler;
this.executor = executor;
}
@@ -929,7 +936,8 @@ public HTTPRestResponse getExtendedAgentCard(ServerCallContext context, String t
*/
public HTTPRestResponse getAgentCard() {
try {
- return new HTTPRestResponse(200, APPLICATION_JSON, JsonUtil.toJson(agentCard));
+ return new HTTPRestResponse(200, APPLICATION_JSON, JsonUtil.toJson(agentCard),
+ cacheMetadata.getHttpHeadersMap());
} catch (Throwable t) {
return createErrorResponse(500, new InternalError(t.getMessage()));
}
@@ -943,6 +951,7 @@ public static class HTTPRestResponse {
private final int statusCode;
private final String contentType;
private final String body;
+ private final Map headers;
/**
* Creates an HTTP REST response.
@@ -952,9 +961,22 @@ public static class HTTPRestResponse {
* @param body the response body
*/
public HTTPRestResponse(int statusCode, String contentType, String body) {
+ this(statusCode, contentType, body, Map.of());
+ }
+
+ /**
+ * Creates an HTTP REST response with custom headers.
+ *
+ * @param statusCode the HTTP status code
+ * @param contentType the content type of the response
+ * @param body the response body
+ * @param headers additional HTTP headers
+ */
+ public HTTPRestResponse(int statusCode, String contentType, String body, Map headers) {
this.statusCode = statusCode;
this.contentType = contentType;
this.body = body;
+ this.headers = Map.copyOf(headers);
}
/**
@@ -984,9 +1006,18 @@ public String getBody() {
return body;
}
+ /**
+ * Returns additional HTTP headers.
+ *
+ * @return the headers map
+ */
+ public Map getHeaders() {
+ return headers;
+ }
+
@Override
public String toString() {
- return "HTTPRestResponse{" + "statusCode=" + statusCode + ", contentType=" + contentType + ", body=" + body + '}';
+ return "HTTPRestResponse{" + "statusCode=" + statusCode + ", contentType=" + contentType + ", body=" + body + ", headers=" + headers + '}';
}
}
diff --git a/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java b/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java
index 738b027e7..f6085bb46 100644
--- a/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java
+++ b/transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java
@@ -14,8 +14,10 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.protobuf.InvalidProtocolBufferException;
+import io.a2a.server.AgentCardCacheMetadata;
import io.a2a.server.ServerCallContext;
import io.a2a.server.auth.UnauthenticatedUser;
+import io.a2a.server.config.DefaultValuesConfigProvider;
import io.a2a.server.requesthandlers.AbstractA2ARequestHandlerTest;
import io.a2a.spec.AgentCapabilities;
import io.a2a.spec.AgentCard;
@@ -31,9 +33,17 @@ public class RestHandlerTest extends AbstractA2ARequestHandlerTest {
private final ServerCallContext callContext = new ServerCallContext(UnauthenticatedUser.INSTANCE, Map.of("foo", "bar"), new HashSet<>());
+ private static AgentCardCacheMetadata createCacheMetadata() {
+ return createCacheMetadata(CARD);
+ }
+
+ private static AgentCardCacheMetadata createCacheMetadata(AgentCard card) {
+ return new AgentCardCacheMetadata(card, new DefaultValuesConfigProvider());
+ }
+
@Test
public void testGetTaskSuccess() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
RestHandler.HTTPRestResponse response = handler.getTask(callContext, "", MINIMAL_TASK.id(), 0);
@@ -51,7 +61,7 @@ public void testGetTaskSuccess() {
@Test
public void testGetTaskNotFound() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
RestHandler.HTTPRestResponse response = handler.getTask(callContext, "", "nonexistent", 0);
@@ -61,7 +71,7 @@ public void testGetTaskNotFound() {
@Test
public void testGetTaskNegativeHistoryLengthReturns422() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
RestHandler.HTTPRestResponse response = handler.getTask(callContext, "", MINIMAL_TASK.id(), -1);
@@ -71,7 +81,7 @@ public void testGetTaskNegativeHistoryLengthReturns422() {
@Test
public void testListTasksStatusWireString() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
RestHandler.HTTPRestResponse response = handler.listTasks(callContext, "", null, "TASK_STATE_SUBMITTED", null, null,
@@ -84,7 +94,7 @@ public void testListTasksStatusWireString() {
@Test
public void testListTasksInvalidStatus() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
RestHandler.HTTPRestResponse response = handler.listTasks(callContext, "", null, "not-a-status", null, null,
null, null, null);
@@ -95,7 +105,7 @@ public void testListTasksInvalidStatus() {
@Test
public void testSendMessage() throws InvalidProtocolBufferException {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
agentExecutorExecute = (context, agentEmitter) -> {
agentEmitter.sendMessage(context.getMessage());
};
@@ -126,7 +136,7 @@ public void testSendMessage() throws InvalidProtocolBufferException {
@Test
public void testSendMessageInvalidBody() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
String invalidBody = "invalid json";
RestHandler.HTTPRestResponse response = handler.sendMessage(callContext, "", invalidBody);
@@ -137,7 +147,7 @@ public void testSendMessageInvalidBody() {
@Test
public void testSendMessageWrongValueBody() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
String requestBody = """
{
"message":
@@ -165,7 +175,7 @@ public void testSendMessageWrongValueBody() {
@Test
public void testSendMessageEmptyBody() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
RestHandler.HTTPRestResponse response = handler.sendMessage(callContext, "", "");
@@ -175,7 +185,7 @@ public void testSendMessageEmptyBody() {
@Test
public void testCancelTaskSuccess() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
agentExecutorCancel = (context, agentEmitter) -> {
@@ -196,7 +206,7 @@ public void testCancelTaskSuccess() {
@Test
public void testCancelTaskNotFound() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
String requestBody = "{\"id\":\"nonexistent\"}";
RestHandler.HTTPRestResponse response = handler.cancelTask(callContext, "", requestBody, "nonexistent");
@@ -207,7 +217,7 @@ public void testCancelTaskNotFound() {
@Test
public void testCancelTaskWithMetadata() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
agentExecutorCancel = (context, agentEmitter) -> {
@@ -238,7 +248,7 @@ public void testCancelTaskWithMetadata() {
@Test
public void testCancelTaskWithEmptyMetadata() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
agentExecutorCancel = (context, agentEmitter) -> {
@@ -262,7 +272,7 @@ public void testCancelTaskWithEmptyMetadata() {
@Test
public void testCancelTaskWithNoMetadata() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
agentExecutorCancel = (context, agentEmitter) -> {
@@ -282,7 +292,7 @@ public void testCancelTaskWithNoMetadata() {
@Test
public void testCancelTaskWithNullBody() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
agentExecutorCancel = (context, agentEmitter) -> {
@@ -300,7 +310,7 @@ public void testCancelTaskWithNullBody() {
@Test
public void testSendStreamingMessageSuccess() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
agentExecutorExecute = (context, agentEmitter) -> {
agentEmitter.sendMessage(context.getMessage());
};
@@ -332,7 +342,7 @@ public void testSendStreamingMessageSuccess() {
@Test
public void testSendStreamingMessageNotSupported() {
AgentCard card = createAgentCard(false, true);
- RestHandler handler = new RestHandler(card, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(card, createCacheMetadata(card), requestHandler, internalExecutor);
String requestBody = """
{
@@ -353,7 +363,7 @@ public void testSendStreamingMessageNotSupported() {
@Test
public void testPushNotificationConfigSuccess() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
String requestBody = """
@@ -376,7 +386,7 @@ public void testPushNotificationConfigSuccess() {
@Test
public void testPushNotificationConfigNotSupported() {
AgentCard card = createAgentCard(true, false);
- RestHandler handler = new RestHandler(card, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(card, createCacheMetadata(card), requestHandler, internalExecutor);
String requestBody = """
{
@@ -395,7 +405,7 @@ public void testPushNotificationConfigNotSupported() {
@Test
public void testGetPushNotificationConfig() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
// First, create a push notification config
@@ -419,7 +429,7 @@ public void testGetPushNotificationConfig() {
@Test
public void testDeletePushNotificationConfig() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
RestHandler.HTTPRestResponse response = handler.deleteTaskPushNotificationConfiguration(callContext, "", MINIMAL_TASK.id(), "default-config-id");
Assertions.assertEquals(204, response.getStatusCode());
@@ -427,7 +437,7 @@ public void testDeletePushNotificationConfig() {
@Test
public void testListPushNotificationConfigs() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
RestHandler.HTTPRestResponse response = handler.listTaskPushNotificationConfigurations(callContext, "", MINIMAL_TASK.id(), 0, "");
@@ -439,7 +449,7 @@ public void testListPushNotificationConfigs() {
@Test
public void testHttpStatusCodeMapping() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
// Test 400 for invalid request
RestHandler.HTTPRestResponse response = handler.sendMessage(callContext, "", "");
@@ -452,7 +462,7 @@ public void testHttpStatusCodeMapping() {
@Test
public void testStreamingDoesNotBlockMainThread() throws Exception {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
// Track if the main thread gets blocked during streaming
AtomicBoolean eventReceived = new AtomicBoolean(false);
@@ -556,7 +566,7 @@ public void testExtensionSupportRequiredErrorOnSendMessage() {
.skills(List.of())
.build();
- RestHandler handler = new RestHandler(cardWithExtension, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(cardWithExtension, createCacheMetadata(cardWithExtension), requestHandler, internalExecutor);
String requestBody = """
{
@@ -604,7 +614,7 @@ public void testExtensionSupportRequiredErrorOnSendStreamingMessage() {
.skills(List.of())
.build();
- RestHandler handler = new RestHandler(cardWithExtension, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(cardWithExtension, createCacheMetadata(cardWithExtension), requestHandler, internalExecutor);
String requestBody = """
{
@@ -693,7 +703,7 @@ public void testRequiredExtensionProvidedSuccess() {
.skills(List.of())
.build();
- RestHandler handler = new RestHandler(cardWithExtension, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(cardWithExtension, createCacheMetadata(cardWithExtension), requestHandler, internalExecutor);
// Create context WITH the required extension
Set requestedExtensions = new HashSet<>();
@@ -749,7 +759,7 @@ public void testVersionNotSupportedErrorOnSendMessage() {
.skills(List.of())
.build();
- RestHandler handler = new RestHandler(agentCard, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(agentCard, createCacheMetadata(agentCard), requestHandler, internalExecutor);
// Create context with incompatible version 2.0
ServerCallContext contextWithVersion = new ServerCallContext(
@@ -799,7 +809,7 @@ public void testVersionNotSupportedErrorOnSendStreamingMessage() {
.skills(List.of())
.build();
- RestHandler handler = new RestHandler(agentCard, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(agentCard, createCacheMetadata(agentCard), requestHandler, internalExecutor);
// Create context with incompatible version 2.0
ServerCallContext contextWithVersion = new ServerCallContext(
@@ -889,7 +899,7 @@ public void testCompatibleVersionSuccess() {
.skills(List.of())
.build();
- RestHandler handler = new RestHandler(agentCard, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(agentCard, createCacheMetadata(agentCard), requestHandler, internalExecutor);
// Create context with compatible version 1.1
ServerCallContext contextWithVersion = new ServerCallContext(
@@ -944,7 +954,7 @@ public void testNoVersionDefaultsToCurrentVersionSuccess() {
.skills(List.of())
.build();
- RestHandler handler = new RestHandler(agentCard, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(agentCard, createCacheMetadata(agentCard), requestHandler, internalExecutor);
// Use default callContext (no version - should default to 1.0)
agentExecutorExecute = (context, agentEmitter) -> {
@@ -977,7 +987,7 @@ public void testNoVersionDefaultsToCurrentVersionSuccess() {
@Test
public void testListTasksNegativeTimestampReturns422() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
// Negative timestamp should return 422 (Invalid params)
RestHandler.HTTPRestResponse response = handler.listTasks(callContext, "", null, null, null, null,
@@ -989,7 +999,7 @@ public void testListTasksNegativeTimestampReturns422() {
@Test
public void testListTasksUnixMillisecondsTimestamp() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
// Unix milliseconds timestamp are no longer accepted
RestHandler.HTTPRestResponse response = handler.listTasks(callContext, "", null, null, null, null,
null, "1234567", null);
@@ -998,7 +1008,7 @@ public void testListTasksUnixMillisecondsTimestamp() {
@Test
public void testListTasksProtobufEnumStatus() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
// Protobuf enum format (TASK_STATE_SUBMITTED) should be accepted
@@ -1012,7 +1022,7 @@ public void testListTasksProtobufEnumStatus() {
@Test
public void testListTasksEnumConstantStatus() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
taskStore.save(MINIMAL_TASK, false);
// Enum constant format (TASK_STATE_SUBMITTED) should be accepted
@@ -1026,7 +1036,7 @@ public void testListTasksEnumConstantStatus() {
@Test
public void testListTasksEmptyResultIncludesAllFields() {
- RestHandler handler = new RestHandler(CARD, requestHandler, internalExecutor);
+ RestHandler handler = new RestHandler(CARD, createCacheMetadata(), requestHandler, internalExecutor);
// Query for a context that doesn't exist - should return empty result with all fields
RestHandler.HTTPRestResponse response = handler.listTasks(callContext, "", "nonexistent-context-id", null, null, null,