From fee347f31cba949c76e61a4b39d95f70a093e383 Mon Sep 17 00:00:00 2001 From: Emmanuel Hugonnet Date: Wed, 11 Feb 2026 12:09:49 +0100 Subject: [PATCH] chore: Migrate spec module to JSpecify annotations for null-safety - Add @NullMarked to io.a2a.spec package - Apply @Nullable annotations to optional fields and parameters - Move builder validation to build() methods using Assert.checkNotNullParam() - Fix null-safety issues in RequestContext ID generation - Add null checks for push notification configuration - Simplify REST route handling for push notification configs - Update tests to provide required MessageSendConfiguration Signed-off-by: Emmanuel Hugonnet --- common/src/main/java/io/a2a/util/Assert.java | 17 ++-- .../io/a2a/jsonrpc/common/json/JsonUtil.java | 4 +- .../server/rest/quarkus/A2AServerRoutes.java | 25 +----- .../server/agentexecution/RequestContext.java | 30 +++---- .../DefaultRequestHandler.java | 4 +- .../io/a2a/server/tasks/AgentEmitter.java | 9 ++- .../tasks/BasePushNotificationSender.java | 11 ++- .../InMemoryPushNotificationConfigStore.java | 2 +- .../agentexecution/RequestContextTest.java | 38 +++++---- .../java/io/a2a/grpc/utils/JSONRPCUtils.java | 4 +- spec/src/main/java/io/a2a/spec/A2AError.java | 13 ++- .../java/io/a2a/spec/A2AProtocolError.java | 8 +- .../io/a2a/spec/APIKeySecurityScheme.java | 39 +++++---- .../java/io/a2a/spec/AgentCapabilities.java | 5 +- spec/src/main/java/io/a2a/spec/AgentCard.java | 79 +++++++++++-------- .../java/io/a2a/spec/AgentCardSignature.java | 14 ++-- .../main/java/io/a2a/spec/AgentExtension.java | 11 +-- .../src/main/java/io/a2a/spec/AgentSkill.java | 33 +++++--- spec/src/main/java/io/a2a/spec/Artifact.java | 27 ++++--- .../spec/ContentTypeNotSupportedError.java | 4 +- spec/src/main/java/io/a2a/spec/DataPart.java | 6 +- ...eleteTaskPushNotificationConfigParams.java | 12 ++- .../ExtendedAgentCardNotConfiguredError.java | 7 +- .../spec/ExtensionSupportRequiredError.java | 7 +- .../GetTaskPushNotificationConfigParams.java | 12 ++- .../io/a2a/spec/HTTPAuthSecurityScheme.java | 15 ++-- .../main/java/io/a2a/spec/InternalError.java | 6 +- .../a2a/spec/InvalidAgentResponseError.java | 4 +- .../java/io/a2a/spec/InvalidParamsError.java | 6 +- .../java/io/a2a/spec/InvalidRequestError.java | 4 +- .../main/java/io/a2a/spec/JSONParseError.java | 7 +- .../java/io/a2a/spec/ListTasksParams.java | 24 +++--- spec/src/main/java/io/a2a/spec/Message.java | 38 +++++---- .../io/a2a/spec/MessageSendConfiguration.java | 16 ++-- .../java/io/a2a/spec/MessageSendParams.java | 25 +++--- .../java/io/a2a/spec/MethodNotFoundError.java | 7 +- .../io/a2a/spec/OAuth2SecurityScheme.java | 17 ++-- .../src/main/java/io/a2a/spec/OAuthFlows.java | 12 +-- .../a2a/spec/OpenIdConnectSecurityScheme.java | 12 ++- .../io/a2a/spec/PushNotificationConfig.java | 13 +-- .../PushNotificationNotSupportedError.java | 7 +- spec/src/main/java/io/a2a/spec/Task.java | 35 ++++---- .../io/a2a/spec/TaskArtifactUpdateEvent.java | 35 ++++---- .../io/a2a/spec/TaskNotCancelableError.java | 7 +- .../java/io/a2a/spec/TaskNotFoundError.java | 7 +- .../src/main/java/io/a2a/spec/TaskStatus.java | 31 +++----- .../io/a2a/spec/TaskStatusUpdateEvent.java | 26 +++--- .../a2a/spec/UnsupportedOperationError.java | 7 +- .../io/a2a/spec/VersionNotSupportedError.java | 7 +- .../main/java/io/a2a/spec/package-info.java | 8 ++ .../jsonrpc/handler/JSONRPCHandlerTest.java | 17 +++- .../transport/rest/handler/RestHandler.java | 2 +- 52 files changed, 442 insertions(+), 374 deletions(-) create mode 100644 spec/src/main/java/io/a2a/spec/package-info.java diff --git a/common/src/main/java/io/a2a/util/Assert.java b/common/src/main/java/io/a2a/util/Assert.java index b0077cd23..0c375a36b 100644 --- a/common/src/main/java/io/a2a/util/Assert.java +++ b/common/src/main/java/io/a2a/util/Assert.java @@ -1,5 +1,7 @@ package io.a2a.util; +import org.jspecify.annotations.Nullable; + public final class Assert { /** @@ -11,18 +13,21 @@ public final class Assert { * @return the value that was passed in * @throws IllegalArgumentException if the value is {@code null} */ - @NotNull - public static T checkNotNullParam(String name, T value) throws IllegalArgumentException { + public static @NotNull T checkNotNullParam(String name, @Nullable T value) throws IllegalArgumentException { checkNotNullParamChecked("name", name); - checkNotNullParamChecked(name, value); + if (value == null) { + throw new IllegalArgumentException("Parameter '" + name + "' may not be null"); + } return value; } - private static void checkNotNullParamChecked(final String name, final T value) { - if (value == null) throw new IllegalArgumentException("Parameter '" + name + "' may not be null"); + private static void checkNotNullParamChecked(final String name, final @Nullable T value) { + if (value == null) { + throw new IllegalArgumentException("Parameter '" + name + "' may not be null"); + } } - public static void isNullOrStringOrInteger(Object value) { + public static void isNullOrStringOrInteger(@Nullable Object value) { if (! (value == null || value instanceof String || value instanceof Integer)) { throw new IllegalArgumentException("Id must be null, a String, or an Integer"); } diff --git a/jsonrpc-common/src/main/java/io/a2a/jsonrpc/common/json/JsonUtil.java b/jsonrpc-common/src/main/java/io/a2a/jsonrpc/common/json/JsonUtil.java index 77452f93e..89fc2d392 100644 --- a/jsonrpc-common/src/main/java/io/a2a/jsonrpc/common/json/JsonUtil.java +++ b/jsonrpc-common/src/main/java/io/a2a/jsonrpc/common/json/JsonUtil.java @@ -22,7 +22,6 @@ import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.Map; import java.util.Set; import com.google.gson.Gson; @@ -30,7 +29,6 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.ToNumberPolicy; import com.google.gson.TypeAdapter; -import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; @@ -435,7 +433,7 @@ private A2AError createErrorInstance(@Nullable Integer code, @Nullable String me case INVALID_AGENT_RESPONSE_ERROR_CODE -> new InvalidAgentResponseError(code, message, data); default -> - new A2AError(code, message, data); + new A2AError(code, message == null ? "" : message, data); }; } } 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 a41f2d361..c8dc54086 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 @@ -289,7 +289,9 @@ public void getTaskPushNotificationConfiguration(RoutingContext rc) { try { if (taskId == null || taskId.isEmpty()) { response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad task id")); - } else { + } else if (configId == null || configId.isEmpty()) { + response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad configuration id")); + }else { response = jsonRestHandler.getTaskPushNotificationConfiguration(taskId, configId, extractTenant(rc), context); } } catch (Throwable t) { @@ -299,26 +301,7 @@ public void getTaskPushNotificationConfiguration(RoutingContext rc) { } } - @Route(regex = "^\\/(?[^\\/]*\\/?)tasks\\/(?[^/]+)\\/pushNotificationConfigs\\/$", order = 1, methods = {Route.HttpMethod.GET}, type = Route.HandlerType.BLOCKING) - public void getTaskPushNotificationConfigurationWithoutId(RoutingContext rc) { - String taskId = rc.pathParam("taskId"); - ServerCallContext context = createCallContext(rc, GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD); - HTTPRestResponse response = null; - try { - if (taskId == null || taskId.isEmpty()) { - response = jsonRestHandler.createErrorResponse(new InvalidParamsError("bad task id")); - } else { - // Call get with null configId - trailing slash distinguishes this from list - response = jsonRestHandler.getTaskPushNotificationConfiguration(taskId, null, extractTenant(rc), context); - } - } catch (Throwable t) { - response = jsonRestHandler.createErrorResponse(new InternalError(t.getMessage())); - } finally { - sendResponse(rc, response); - } - } - - @Route(regex = "^\\/(?[^\\/]*\\/?)tasks\\/(?[^/]+)\\/pushNotificationConfigs", order = 3, methods = {Route.HttpMethod.GET}, type = Route.HandlerType.BLOCKING) + @Route(regex = "^\\/(?[^\\/]*\\/?)tasks\\/(?[^/]+)\\/pushNotificationConfigs\\/?$", order = 3, methods = {Route.HttpMethod.GET}, type = Route.HandlerType.BLOCKING) public void listTaskPushNotificationConfigurations(RoutingContext rc) { String taskId = rc.pathParam("taskId"); ServerCallContext context = createCallContext(rc, LIST_TASK_PUSH_NOTIFICATION_CONFIG_METHOD); diff --git a/server-common/src/main/java/io/a2a/server/agentexecution/RequestContext.java b/server-common/src/main/java/io/a2a/server/agentexecution/RequestContext.java index 82a8b9e17..c82df7846 100644 --- a/server-common/src/main/java/io/a2a/server/agentexecution/RequestContext.java +++ b/server-common/src/main/java/io/a2a/server/agentexecution/RequestContext.java @@ -99,14 +99,12 @@ public RequestContext( if (params != null) { if (taskId != null && !taskId.equals(params.message().taskId())) { throw new InvalidParamsError("bad task id"); - } else { - checkOrGenerateTaskId(); } + this.taskId = checkOrGenerateTaskId(); if (contextId != null && !contextId.equals(params.message().contextId())) { throw new InvalidParamsError("bad context id"); - } else { - checkOrGenerateContextId(); } + this.contextId = checkOrGenerateContextId(); } } @@ -246,9 +244,9 @@ public void attachRelatedTask(Task task) { relatedTasks.add(task); } - private void checkOrGenerateTaskId() { + private @Nullable String checkOrGenerateTaskId() { if (params == null) { - return; + return taskId; } if (taskId == null && params.message().taskId() == null) { // Message is immutable, create new one with generated taskId @@ -257,15 +255,17 @@ private void checkOrGenerateTaskId() { .taskId(generatedTaskId) .build(); params = new MessageSendParams(updatedMessage, params.configuration(), params.metadata()); - this.taskId = generatedTaskId; - } else if (params.message().taskId() != null) { - this.taskId = params.message().taskId(); + return generatedTaskId; + } + if (params.message().taskId() != null) { + return params.message().taskId(); } + return taskId; } - private void checkOrGenerateContextId() { + private @Nullable String checkOrGenerateContextId() { if (params == null) { - return; + return contextId; } if (contextId == null && params.message().contextId() == null) { // Message is immutable, create new one with generated contextId @@ -274,10 +274,12 @@ private void checkOrGenerateContextId() { .contextId(generatedContextId) .build(); params = new MessageSendParams(updatedMessage, params.configuration(), params.metadata()); - this.contextId = generatedContextId; - } else if (params.message().contextId() != null) { - this.contextId = params.message().contextId(); + return generatedContextId; + } + if (params.message().contextId() != null) { + return params.message().contextId(); } + return contextId; } private String getMessageText(Message message, String delimiter) { diff --git a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java index 974482ce2..0319718ea 100644 --- a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java +++ b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java @@ -422,6 +422,7 @@ public Task onCancelTask(TaskIdParams params, ServerCallContext context) throws } @Override + @SuppressWarnings("NullAway") public EventKind onMessageSend(MessageSendParams params, ServerCallContext context) throws A2AError { LOGGER.debug("onMessageSend - task: {}; context {}", params.message().taskId(), params.message().contextId()); @@ -605,6 +606,7 @@ public EventKind onMessageSend(MessageSendParams params, ServerCallContext conte } @Override + @SuppressWarnings("NullAway") public Flow.Publisher onMessageSendStream( MessageSendParams params, ServerCallContext context) throws A2AError { LOGGER.debug("onMessageSendStream START - task: {}; context: {}; runningAgents: {}", @@ -1014,7 +1016,7 @@ private MessageSendSetup initMessageSend(MessageSendParams params, ServerCallCon LOGGER.debug("Found task updating with message {}", params.message()); task = taskManager.updateWithMessage(params.message(), task); - if (shouldAddPushInfo(params)) { + if (pushConfigStore != null && params.configuration() != null && params.configuration().pushNotificationConfig() != null) { LOGGER.debug("Adding push info"); pushConfigStore.setInfo(task.id(), params.configuration().pushNotificationConfig()); } diff --git a/server-common/src/main/java/io/a2a/server/tasks/AgentEmitter.java b/server-common/src/main/java/io/a2a/server/tasks/AgentEmitter.java index 57ff6372c..7c6c97f20 100644 --- a/server-common/src/main/java/io/a2a/server/tasks/AgentEmitter.java +++ b/server-common/src/main/java/io/a2a/server/tasks/AgentEmitter.java @@ -18,6 +18,7 @@ import io.a2a.spec.TaskStatus; import io.a2a.spec.TaskStatusUpdateEvent; import io.a2a.spec.TextPart; +import io.a2a.util.Assert; import org.jspecify.annotations.Nullable; /** @@ -95,8 +96,8 @@ */ public class AgentEmitter { private final EventQueue eventQueue; - private final @Nullable String taskId; - private final @Nullable String contextId; + private final String taskId; + private final String contextId; private final AtomicBoolean terminalStateReached = new AtomicBoolean(false); /** @@ -107,8 +108,8 @@ public class AgentEmitter { */ public AgentEmitter(RequestContext context, EventQueue eventQueue) { this.eventQueue = eventQueue; - this.taskId = context.getTaskId(); - this.contextId = context.getContextId(); + this.taskId = Assert.checkNotNullParam("taskId",context.getTaskId()); + this.contextId = Assert.checkNotNullParam("contextId",context.getContextId()); } private void updateStatus(TaskState taskState) { diff --git a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java index 58c03369d..a9b8093bf 100644 --- a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java +++ b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java @@ -78,7 +78,7 @@ public void sendNotification(StreamingEventKind event) { String nextPageToken = null; do { ListTaskPushNotificationConfigResult pageResult = configStore.getInfo(new ListTaskPushNotificationConfigParams(taskId, - DEFAULT_PAGE_SIZE, nextPageToken, "")); + DEFAULT_PAGE_SIZE, nextPageToken == null ? "" : nextPageToken, "")); if (!pageResult.configs().isEmpty()) { configs.addAll(pageResult.configs()); } @@ -111,11 +111,14 @@ public void sendNotification(StreamingEventKind event) { protected @Nullable String extractTaskId(StreamingEventKind event) { if (event instanceof Task task) { return task.id(); - } else if (event instanceof Message message) { + } + if (event instanceof Message message) { return message.taskId(); - } else if (event instanceof TaskStatusUpdateEvent statusUpdate) { + } + if (event instanceof TaskStatusUpdateEvent statusUpdate) { return statusUpdate.taskId(); - } else if (event instanceof TaskArtifactUpdateEvent artifactUpdate) { + } + if (event instanceof TaskArtifactUpdateEvent artifactUpdate) { return artifactUpdate.taskId(); } throw new IllegalStateException("Unknown StreamingEventKind: " + event); diff --git a/server-common/src/main/java/io/a2a/server/tasks/InMemoryPushNotificationConfigStore.java b/server-common/src/main/java/io/a2a/server/tasks/InMemoryPushNotificationConfigStore.java index 093ff910d..e67ae01eb 100644 --- a/server-common/src/main/java/io/a2a/server/tasks/InMemoryPushNotificationConfigStore.java +++ b/server-common/src/main/java/io/a2a/server/tasks/InMemoryPushNotificationConfigStore.java @@ -41,7 +41,7 @@ public PushNotificationConfig setInfo(String taskId, PushNotificationConfig noti Iterator notificationConfigIterator = notificationConfigList.iterator(); while (notificationConfigIterator.hasNext()) { PushNotificationConfig config = notificationConfigIterator.next(); - if (config.id().equals(notificationConfig.id())) { + if (config.id() != null && config.id().equals(notificationConfig.id())) { notificationConfigIterator.remove(); break; } diff --git a/server-common/src/test/java/io/a2a/server/agentexecution/RequestContextTest.java b/server-common/src/test/java/io/a2a/server/agentexecution/RequestContextTest.java index 433ebcac3..ba692d97c 100644 --- a/server-common/src/test/java/io/a2a/server/agentexecution/RequestContextTest.java +++ b/server-common/src/test/java/io/a2a/server/agentexecution/RequestContextTest.java @@ -15,6 +15,7 @@ import io.a2a.spec.InvalidParamsError; import io.a2a.spec.Message; +import io.a2a.spec.MessageSendConfiguration; import io.a2a.spec.MessageSendParams; import io.a2a.spec.Task; import io.a2a.spec.TaskState; @@ -25,6 +26,13 @@ public class RequestContextTest { + private static MessageSendConfiguration defaultConfiguration() { + return MessageSendConfiguration.builder() + .acceptedOutputModes(List.of()) + .blocking(false) + .build(); + } + @Test public void testInitWithoutParams() { RequestContext context = new RequestContext(null, null, null, null, null, null); @@ -38,7 +46,7 @@ public void testInitWithoutParams() { @Test public void testInitWithParamsNoIds() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); UUID taskId = UUID.fromString("00000000-0000-0000-0000-000000000001"); UUID contextId = UUID.fromString("00000000-0000-0000-0000-000000000002"); @@ -63,7 +71,7 @@ public void testInitWithParamsNoIds() { public void testInitWithTaskId() { String taskId = "task-123"; var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).taskId(taskId).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, taskId, null, null, null, null); @@ -75,7 +83,7 @@ public void testInitWithTaskId() { public void testInitWithContextId() { String contextId = "context-456"; var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).contextId(contextId).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, contextId, null, null, null); assertEquals(contextId, context.getContextId()); @@ -87,7 +95,7 @@ public void testInitWithBothIds() { String taskId = "task-123"; String contextId = "context-456"; var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).taskId(taskId).contextId(contextId).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, taskId, contextId, null, null, null); assertEquals(taskId, context.getTaskId()); @@ -100,7 +108,7 @@ public void testInitWithBothIds() { public void testInitWithTask() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).build(); var mockTask = Task.builder().id("task-123").contextId("context-456").status(new TaskStatus(TaskState.COMPLETED)).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, null, mockTask, null, null); @@ -134,7 +142,7 @@ public void testAttachRelatedTask() { public void testCheckOrGenerateTaskIdWithExistingTaskId() { String existingId = "existing-task-id"; var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).taskId(existingId).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, null, null, null, null); @@ -147,7 +155,7 @@ public void testCheckOrGenerateContextIdWithExistingContextId() { String existingId = "existing-context-id"; var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).contextId(existingId).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, null, null, null, null); @@ -158,7 +166,7 @@ public void testCheckOrGenerateContextIdWithExistingContextId() { @Test public void testInitRaisesErrorOnTaskIdMismatch() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).taskId("task-123").build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); var mockTask = Task.builder().id("task-123").contextId("context-456").status(new TaskStatus(TaskState.COMPLETED)).build(); InvalidParamsError error = assertThrows(InvalidParamsError.class, () -> @@ -170,7 +178,7 @@ public void testInitRaisesErrorOnTaskIdMismatch() { @Test public void testInitRaisesErrorOnContextIdMismatch() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).taskId("task-123").contextId("context-456").build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); var mockTask = Task.builder().id("task-123").contextId("context-456").status(new TaskStatus(TaskState.COMPLETED)).build(); InvalidParamsError error = assertThrows(InvalidParamsError.class, () -> @@ -202,7 +210,7 @@ public void testMessagePropertyWithoutParams() { @Test public void testMessagePropertyWithParams() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, null, null, null, null); // getMessage() returns a new Message with generated IDs, not the original @@ -218,7 +226,7 @@ public void testInitWithExistingIdsInMessage() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))) .taskId(existingTaskId).contextId(existingContextId).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, null, null, null, null); @@ -229,7 +237,7 @@ public void testInitWithExistingIdsInMessage() { @Test public void testInitWithTaskIdAndExistingTaskIdMatch() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).taskId("task-123").contextId("context-456").build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); var mockTask = Task.builder().id("task-123").contextId("context-456").status(new TaskStatus(TaskState.COMPLETED)).build(); @@ -242,7 +250,7 @@ public void testInitWithTaskIdAndExistingTaskIdMatch() { @Test public void testInitWithContextIdAndExistingContextIdMatch() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).taskId("task-123").contextId("context-456").build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); var mockTask = Task.builder().id("task-123").contextId("context-456").status(new TaskStatus(TaskState.COMPLETED)).build(); @@ -255,7 +263,7 @@ public void testInitWithContextIdAndExistingContextIdMatch() { @Test void testMessageBuilderGeneratesId() { var mockMessage = Message.builder().role(Message.Role.USER).parts(List.of(new TextPart(""))).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, null, null, null, null); assertNotNull(mockMessage.messageId()); @@ -265,7 +273,7 @@ void testMessageBuilderGeneratesId() { @Test void testMessageBuilderUsesProvidedId() { var mockMessage = Message.builder().messageId("123").role(Message.Role.USER).parts(List.of(new TextPart(""))).build(); - var mockParams = MessageSendParams.builder().message(mockMessage).build(); + var mockParams = MessageSendParams.builder().message(mockMessage).configuration(defaultConfiguration()).build(); RequestContext context = new RequestContext(mockParams, null, null, null, null, null); assertEquals("123", mockMessage.messageId()); diff --git a/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java b/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java index 2038b4773..1480ac42a 100644 --- a/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java +++ b/spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java @@ -434,10 +434,10 @@ private static A2AError processError(JsonObject error) { case TASK_NOT_FOUND_ERROR_CODE: return new TaskNotFoundError(code, message, data); default: - return new A2AError(code, message, data); + return new A2AError(code, message == null ? "": message, data); } } - return new A2AError(code, message, data); + return new A2AError(INTERNAL_ERROR_CODE, message == null ? "": message, data); } protected static void parseRequestBody(JsonElement jsonRpc, com.google.protobuf.Message.Builder builder, Object id) throws JsonProcessingException { diff --git a/spec/src/main/java/io/a2a/spec/A2AError.java b/spec/src/main/java/io/a2a/spec/A2AError.java index 4bbc8917b..c81bbbe32 100644 --- a/spec/src/main/java/io/a2a/spec/A2AError.java +++ b/spec/src/main/java/io/a2a/spec/A2AError.java @@ -1,6 +1,7 @@ package io.a2a.spec; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Marker interface for A2A Protocol error events. @@ -36,7 +37,7 @@ public class A2AError extends RuntimeException implements Event { /** * Additional error information (structure defined by the error code). */ - private final Object data; + private final @Nullable Object data; /** * Constructs a JSON-RPC error with the specified code, message, and optional data. @@ -48,11 +49,9 @@ public class A2AError extends RuntimeException implements Event { * @param data additional error information, structure defined by the error code (optional) * @throws IllegalArgumentException if code or message is null */ - public A2AError(Integer code, String message, Object data) { - super(message); - Assert.checkNotNullParam("code", code); - Assert.checkNotNullParam("message", message); - this.code = code; + public A2AError(Integer code, String message, @Nullable Object data) { + super(Assert.checkNotNullParam("message", message)); + this.code = Assert.checkNotNullParam("code", code); this.data = data; } @@ -84,7 +83,7 @@ public Integer getCode() { * * @return the error data, or null if not provided */ - public Object getData() { + public @Nullable Object getData() { return data; } } diff --git a/spec/src/main/java/io/a2a/spec/A2AProtocolError.java b/spec/src/main/java/io/a2a/spec/A2AProtocolError.java index e2655175d..b03ba4080 100644 --- a/spec/src/main/java/io/a2a/spec/A2AProtocolError.java +++ b/spec/src/main/java/io/a2a/spec/A2AProtocolError.java @@ -1,5 +1,7 @@ package io.a2a.spec; +import org.jspecify.annotations.Nullable; + /** * Represents a protocol-level error in the A2A Protocol with a reference URL. *

@@ -15,7 +17,7 @@ public class A2AProtocolError extends A2AError { /** * URL reference for additional information about this protocol error. */ - private final String url; + private final @Nullable String url; /** * Constructs a protocol error with the specified code, message, data, and reference URL. @@ -25,7 +27,7 @@ public class A2AProtocolError extends A2AError { * @param data additional error information, structure defined by the error code (optional) * @param url URL reference providing additional context about this protocol error (optional) */ - public A2AProtocolError(Integer code, String message, Object data, String url) { + public A2AProtocolError(Integer code, String message, @Nullable Object data, @Nullable String url) { super(code, message, data); this.url = url; } @@ -38,7 +40,7 @@ public A2AProtocolError(Integer code, String message, Object data, String url) { * * @return the reference URL, or null if not provided */ - public String getUrl() { + public @Nullable String getUrl() { return url; } } diff --git a/spec/src/main/java/io/a2a/spec/APIKeySecurityScheme.java b/spec/src/main/java/io/a2a/spec/APIKeySecurityScheme.java index ffa886849..676d43835 100644 --- a/spec/src/main/java/io/a2a/spec/APIKeySecurityScheme.java +++ b/spec/src/main/java/io/a2a/spec/APIKeySecurityScheme.java @@ -1,6 +1,7 @@ package io.a2a.spec; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * API key security scheme for agent authentication. @@ -17,13 +18,12 @@ * @see OpenAPI Security Scheme * @see A2A Protocol Specification */ -public record APIKeySecurityScheme( - Location location, - String name, - String description -) implements SecurityScheme { +public record APIKeySecurityScheme(Location location, String name, @Nullable + String description) implements SecurityScheme { - /** The security scheme type identifier for API key authentication. */ + /** + * The security scheme type identifier for API key authentication. + */ public static final String TYPE = "apiKeySecurityScheme"; @Override @@ -48,13 +48,17 @@ public String type() { * Represents the location of the API key. */ public enum Location { - /** API key sent in a cookie. */ + /** + * API key sent in a cookie. + */ COOKIE("cookie"), - - /** API key sent in an HTTP header. */ + /** + * API key sent in an HTTP header. + */ HEADER("header"), - - /** API key sent as a query parameter. */ + /** + * API key sent as a query parameter. + */ QUERY("query"); private final String location; @@ -90,7 +94,8 @@ public static Location fromString(String location) { case "query" -> { return QUERY; } - default -> throw new IllegalArgumentException("Invalid API key location: " + location); + default -> + throw new IllegalArgumentException("Invalid API key location: " + location); } } } @@ -117,9 +122,10 @@ public static Builder builder() { * } */ public static class Builder { - private Location location; - private String name; - private String description; + + private @Nullable Location location; + private @Nullable String name; + private @Nullable String description; /** * Creates a new Builder with all fields unset. @@ -167,7 +173,8 @@ public Builder description(String description) { * @throws IllegalArgumentException if location or name is null */ public APIKeySecurityScheme build() { - return new APIKeySecurityScheme(location, name, description); + return new APIKeySecurityScheme(Assert.checkNotNullParam("location", location), + Assert.checkNotNullParam("name", name), description); } } } diff --git a/spec/src/main/java/io/a2a/spec/AgentCapabilities.java b/spec/src/main/java/io/a2a/spec/AgentCapabilities.java index da166a4e1..e36088a42 100644 --- a/spec/src/main/java/io/a2a/spec/AgentCapabilities.java +++ b/spec/src/main/java/io/a2a/spec/AgentCapabilities.java @@ -1,6 +1,7 @@ package io.a2a.spec; import java.util.List; +import org.jspecify.annotations.Nullable; /** * Defines optional capabilities supported by an agent in the A2A Protocol. @@ -32,7 +33,7 @@ public record AgentCapabilities(boolean streaming, boolean pushNotifications, boolean extendedAgentCard, - List extensions) { + @Nullable List extensions) { /** * Create a new Builder @@ -61,7 +62,7 @@ public static class Builder { private boolean streaming; private boolean pushNotifications; private boolean extendedAgentCard; - private List extensions; + private @Nullable List extensions; /** * Creates a new Builder with all capabilities set to false by default. diff --git a/spec/src/main/java/io/a2a/spec/AgentCard.java b/spec/src/main/java/io/a2a/spec/AgentCard.java index d49dcc862..dec9ce722 100644 --- a/spec/src/main/java/io/a2a/spec/AgentCard.java +++ b/spec/src/main/java/io/a2a/spec/AgentCard.java @@ -5,6 +5,8 @@ import java.util.Map; import io.a2a.util.Assert; +import java.util.Collections; +import org.jspecify.annotations.Nullable; /** * The AgentCard is a self-describing manifest for an agent in the A2A Protocol. @@ -43,18 +45,18 @@ public record AgentCard( String name, String description, - AgentProvider provider, + @Nullable AgentProvider provider, String version, - String documentationUrl, + @Nullable String documentationUrl, AgentCapabilities capabilities, List defaultInputModes, List defaultOutputModes, List skills, - Map securitySchemes, - List>> securityRequirements, - String iconUrl, + @Nullable Map securitySchemes, + @Nullable List>> securityRequirements, + @Nullable String iconUrl, List supportedInterfaces, - List signatures) { + @Nullable List signatures) { /** * Compact constructor that validates required fields. @@ -98,7 +100,7 @@ public static Builder builder() { /** * Create a new Builder initialized with values from an existing AgentCard. *

- * This builder creates defensive copies of mutable collections to ensure + * This builder creates defensive copies of mutable collections to ensure * that modifications to the builder do not affect the original AgentCard. * * @param card the AgentCard to copy values from @@ -108,7 +110,6 @@ public static Builder builder(AgentCard card) { return new Builder(card); } - /** * Builder for constructing immutable {@link AgentCard} instances. *

@@ -140,26 +141,26 @@ public static Builder builder(AgentCard card) { * } */ public static class Builder { - private String name; - private String description; - private AgentProvider provider; - private String version; - private String documentationUrl; - private AgentCapabilities capabilities; - private List defaultInputModes; - private List defaultOutputModes; - private List skills; - private Map securitySchemes; - private List>> securityRequirements; - private String iconUrl; - private List supportedInterfaces; - private List signatures; + + private @Nullable String name; + private @Nullable String description; + private @Nullable AgentProvider provider; + private @Nullable String version; + private @Nullable String documentationUrl; + private @Nullable AgentCapabilities capabilities; + private @Nullable List defaultInputModes; + private @Nullable List defaultOutputModes; + private @Nullable List skills; + private @Nullable Map securitySchemes; + private @Nullable List>> securityRequirements; + private @Nullable String iconUrl; + private @Nullable List supportedInterfaces; + private @Nullable List signatures; /** * Creates a new Builder with all fields unset. */ private Builder() { - } /** @@ -177,13 +178,13 @@ private Builder(AgentCard card) { this.version = card.version; this.documentationUrl = card.documentationUrl; this.capabilities = card.capabilities; - this.defaultInputModes = card.defaultInputModes != null ? new ArrayList<>(card.defaultInputModes) : null; - this.defaultOutputModes = card.defaultOutputModes != null ? new ArrayList<>(card.defaultOutputModes) : null; - this.skills = card.skills != null ? new ArrayList<>(card.skills) : null; - this.securitySchemes = card.securitySchemes != null ? Map.copyOf(card.securitySchemes) : null; - this.securityRequirements = card.securityRequirements != null ? new ArrayList<>(card.securityRequirements) : null; + this.defaultInputModes = card.defaultInputModes != null ? new ArrayList<>(card.defaultInputModes) : Collections.emptyList(); + this.defaultOutputModes = card.defaultOutputModes != null ? new ArrayList<>(card.defaultOutputModes) : Collections.emptyList(); + this.skills = card.skills != null ? new ArrayList<>(card.skills) : Collections.emptyList(); + this.securitySchemes = card.securitySchemes != null ? Map.copyOf(card.securitySchemes) : Collections.emptyMap(); + this.securityRequirements = card.securityRequirements != null ? new ArrayList<>(card.securityRequirements) :Collections.emptyList(); this.iconUrl = card.iconUrl; - this.supportedInterfaces = card.supportedInterfaces != null ? new ArrayList<>(card.supportedInterfaces) : null; + this.supportedInterfaces = card.supportedInterfaces != null ? new ArrayList<>(card.supportedInterfaces) : Collections.emptyList(); this.signatures = card.signatures != null ? new ArrayList<>(card.signatures) : null; } @@ -209,7 +210,6 @@ public Builder description(String description) { return this; } - /** * Sets information about the organization or entity providing the agent. * @@ -387,10 +387,21 @@ public Builder signatures(List signatures) { * @throws IllegalArgumentException if any required field is null */ public AgentCard build() { - return new AgentCard(name, description, provider, version, documentationUrl, - capabilities, defaultInputModes, defaultOutputModes, skills, - securitySchemes, securityRequirements, iconUrl, - supportedInterfaces, signatures); + return new AgentCard( + Assert.checkNotNullParam("name", name), + Assert.checkNotNullParam("description", description), + provider, + Assert.checkNotNullParam("version", version), + documentationUrl, + Assert.checkNotNullParam("capabilities", capabilities), + Assert.checkNotNullParam("defaultInputModes", defaultInputModes), + Assert.checkNotNullParam("defaultOutputModes", defaultOutputModes), + Assert.checkNotNullParam("skills", skills), + securitySchemes, + securityRequirements, + iconUrl, + Assert.checkNotNullParam("supportedInterfaces", supportedInterfaces), + signatures); } } } diff --git a/spec/src/main/java/io/a2a/spec/AgentCardSignature.java b/spec/src/main/java/io/a2a/spec/AgentCardSignature.java index cb6bc5c77..f87a9b25a 100644 --- a/spec/src/main/java/io/a2a/spec/AgentCardSignature.java +++ b/spec/src/main/java/io/a2a/spec/AgentCardSignature.java @@ -4,6 +4,7 @@ import com.google.gson.annotations.SerializedName; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Represents a digital signature for an {@link AgentCard} using JSON Web Signature (JWS) format. @@ -31,7 +32,7 @@ * @see RFC 7515 - JSON Web Signature * @see A2A Protocol Specification */ -public record AgentCardSignature(Map header, @SerializedName("protected")String protectedHeader, +public record AgentCardSignature(@Nullable Map header, @SerializedName("protected")String protectedHeader, String signature) { /** @@ -69,9 +70,9 @@ public static Builder builder() { * } */ public static class Builder { - private Map header; - String protectedHeader; - String signature; + private @Nullable Map header; + @Nullable String protectedHeader; + @Nullable String signature; /** * Creates a new Builder with all fields unset. @@ -122,7 +123,10 @@ public Builder signature(String signature) { * @throws IllegalArgumentException if protectedHeader or signature is null */ public AgentCardSignature build() { - return new AgentCardSignature(header, protectedHeader, signature); + return new AgentCardSignature( + header, + Assert.checkNotNullParam("protectedHeader", protectedHeader), + Assert.checkNotNullParam("signature", signature)); } } } diff --git a/spec/src/main/java/io/a2a/spec/AgentExtension.java b/spec/src/main/java/io/a2a/spec/AgentExtension.java index 7faf41d8f..de454efad 100644 --- a/spec/src/main/java/io/a2a/spec/AgentExtension.java +++ b/spec/src/main/java/io/a2a/spec/AgentExtension.java @@ -3,6 +3,7 @@ import java.util.Map; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Represents a protocol extension supported by an agent. @@ -23,7 +24,7 @@ * @see AgentCard * @see A2A Protocol Specification */ -public record AgentExtension (String description, Map params, boolean required, String uri) { +public record AgentExtension (@Nullable String description, @Nullable Map params, boolean required, String uri) { /** * Compact constructor that validates required fields. @@ -61,10 +62,10 @@ public static Builder builder() { * } */ public static class Builder { - String description; - Map params; + @Nullable String description; + @Nullable Map params; boolean required; - String uri; + @Nullable String uri; /** * Creates a new Builder with all fields unset. @@ -123,7 +124,7 @@ public Builder uri(String uri) { * @throws IllegalArgumentException if uri is null */ public AgentExtension build() { - return new AgentExtension(description, params, required, uri); + return new AgentExtension(description, params, required, Assert.checkNotNullParam("uri", uri)); } } diff --git a/spec/src/main/java/io/a2a/spec/AgentSkill.java b/spec/src/main/java/io/a2a/spec/AgentSkill.java index 2ff217f5d..6d60baec4 100644 --- a/spec/src/main/java/io/a2a/spec/AgentSkill.java +++ b/spec/src/main/java/io/a2a/spec/AgentSkill.java @@ -4,6 +4,7 @@ import java.util.Map; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Represents a distinct skill or capability that an agent can perform in the A2A Protocol. @@ -40,8 +41,8 @@ * @see A2A Protocol Specification */ public record AgentSkill(String id, String name, String description, List tags, - List examples, List inputModes, List outputModes, - List>> securityRequirements) { + @Nullable List examples, @Nullable List inputModes, @Nullable List outputModes, + @Nullable List>> securityRequirements) { /** * Compact constructor that validates required fields. @@ -57,9 +58,9 @@ public record AgentSkill(String id, String name, String description, List tags; - private List examples; - private List inputModes; - private List outputModes; - private List>> securityRequirements; + private @Nullable String id; + private @Nullable String name; + private @Nullable String description; + private @Nullable List tags; + private @Nullable List examples; + private @Nullable List inputModes; + private @Nullable List outputModes; + private @Nullable List>> securityRequirements; /** * Creates a new Builder with all fields unset. @@ -231,7 +232,15 @@ public Builder securityRequirements(List>> securityRequ * @throws IllegalArgumentException if any required field (id, name, description, tags) is null */ public AgentSkill build() { - return new AgentSkill(id, name, description, tags, examples, inputModes, outputModes, securityRequirements); + return new AgentSkill( + Assert.checkNotNullParam("id", id), + Assert.checkNotNullParam("name", name), + Assert.checkNotNullParam("description", description), + Assert.checkNotNullParam("tags", tags), + examples, + inputModes, + outputModes, + securityRequirements); } } } diff --git a/spec/src/main/java/io/a2a/spec/Artifact.java b/spec/src/main/java/io/a2a/spec/Artifact.java index fb05f4b00..6f9b5dc68 100644 --- a/spec/src/main/java/io/a2a/spec/Artifact.java +++ b/spec/src/main/java/io/a2a/spec/Artifact.java @@ -4,6 +4,7 @@ import java.util.Map; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Represents a file, data structure, or other resource generated by an agent during task execution. @@ -29,8 +30,8 @@ * @param extensions protocol extensions used in this artifact (optional) * @see A2A Protocol Specification */ -public record Artifact(String artifactId, String name, String description, List> parts, Map metadata, - List extensions) { +public record Artifact(String artifactId, @Nullable String name, @Nullable String description, List> parts, @Nullable Map metadata, + @Nullable List extensions) { /** * Compact constructor that validates required fields. @@ -90,12 +91,12 @@ public static Builder builder(Artifact artifact) { * } */ public static class Builder { - private String artifactId; - private String name; - private String description; - private List> parts; - private Map metadata; - private List extensions; + private @Nullable String artifactId; + private @Nullable String name; + private @Nullable String description; + private @Nullable List> parts; + private @Nullable Map metadata; + private @Nullable List extensions; /** * Creates a new Builder with all fields unset. @@ -134,7 +135,7 @@ public Builder artifactId(String artifactId) { * @param name the artifact name (optional) * @return this builder for method chaining */ - public Builder name(String name) { + public Builder name(@Nullable String name) { this.name = name; return this; } @@ -145,7 +146,7 @@ public Builder name(String name) { * @param description the artifact description (optional) * @return this builder for method chaining */ - public Builder description(String description) { + public Builder description(@Nullable String description) { this.description = description; return this; } @@ -178,7 +179,7 @@ public Builder parts(Part... parts) { * @param metadata map of metadata key-value pairs (optional) * @return this builder for method chaining */ - public Builder metadata(Map metadata) { + public Builder metadata(@Nullable Map metadata) { this.metadata = metadata; return this; } @@ -189,7 +190,7 @@ public Builder metadata(Map metadata) { * @param extensions the list of extension identifiers (optional) * @return this builder for method chaining */ - public Builder extensions(List extensions) { + public Builder extensions(@Nullable List extensions) { this.extensions = (extensions == null) ? null : List.copyOf(extensions); return this; } @@ -201,7 +202,7 @@ public Builder extensions(List extensions) { * @throws IllegalArgumentException if required fields are missing or parts is empty */ public Artifact build() { - return new Artifact(artifactId, name, description, parts, metadata, extensions); + return new Artifact(Assert.checkNotNullParam("artifactId", artifactId), name, description, Assert.checkNotNullParam("parts", parts), metadata, extensions); } } } diff --git a/spec/src/main/java/io/a2a/spec/ContentTypeNotSupportedError.java b/spec/src/main/java/io/a2a/spec/ContentTypeNotSupportedError.java index 812d290d2..57a88c17c 100644 --- a/spec/src/main/java/io/a2a/spec/ContentTypeNotSupportedError.java +++ b/spec/src/main/java/io/a2a/spec/ContentTypeNotSupportedError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.CONTENT_TYPE_NOT_SUPPORTED_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating incompatibility between requested content types and agent capabilities. *

@@ -45,7 +47,7 @@ public class ContentTypeNotSupportedError extends A2AProtocolError { * @param message the error message * @param data additional error data */ - public ContentTypeNotSupportedError(Integer code, String message, Object data) { + public ContentTypeNotSupportedError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super(defaultIfNull(code, CONTENT_TYPE_NOT_SUPPORTED_ERROR_CODE), defaultIfNull(message, "Incompatible content types"), data, diff --git a/spec/src/main/java/io/a2a/spec/DataPart.java b/spec/src/main/java/io/a2a/spec/DataPart.java index b358d14d9..437debd8a 100644 --- a/spec/src/main/java/io/a2a/spec/DataPart.java +++ b/spec/src/main/java/io/a2a/spec/DataPart.java @@ -1,8 +1,8 @@ package io.a2a.spec; -import java.util.Map; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** @@ -77,7 +77,7 @@ public static Builder builder() { * Builder for constructing {@link DataPart} instances. */ public static class Builder { - private Object data; + private @Nullable Object data; /** * Creates a new Builder with all fields unset. @@ -103,7 +103,7 @@ public Builder data(Object data) { * @throws IllegalArgumentException if data is null */ public DataPart build() { - return new DataPart(data); + return new DataPart(Assert.checkNotNullParam("data", data)); } } } diff --git a/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java b/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java index 75faf2e54..28f438dfb 100644 --- a/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java +++ b/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java @@ -3,6 +3,7 @@ import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Parameters for deleting a push notification configuration from a task. @@ -57,9 +58,9 @@ public static Builder builder() { * Provides a fluent API for setting parameters with optional metadata. */ public static class Builder { - String id; - String pushNotificationConfigId; - String tenant; + @Nullable String id; + @Nullable String pushNotificationConfigId; + @Nullable String tenant; /** * Creates a new Builder with all fields unset. @@ -107,7 +108,10 @@ public Builder tenant(String tenant) { * @throws IllegalArgumentException if id or pushNotificationConfigId is null */ public DeleteTaskPushNotificationConfigParams build() { - return new DeleteTaskPushNotificationConfigParams(id, pushNotificationConfigId, tenant); + return new DeleteTaskPushNotificationConfigParams( + Assert.checkNotNullParam("id", id), + Assert.checkNotNullParam("pushNotificationConfigId", pushNotificationConfigId), + Assert.checkNotNullParam("tenant", tenant)); } } } diff --git a/spec/src/main/java/io/a2a/spec/ExtendedAgentCardNotConfiguredError.java b/spec/src/main/java/io/a2a/spec/ExtendedAgentCardNotConfiguredError.java index c8998fe04..5b961e2bf 100644 --- a/spec/src/main/java/io/a2a/spec/ExtendedAgentCardNotConfiguredError.java +++ b/spec/src/main/java/io/a2a/spec/ExtendedAgentCardNotConfiguredError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.EXTENDED_AGENT_CARD_NOT_CONFIGURED_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that the agent does not have an authenticated extended card configured. @@ -36,10 +38,7 @@ public class ExtendedAgentCardNotConfiguredError extends A2AProtocolError { * @param message the error message * @param data additional error data */ - public ExtendedAgentCardNotConfiguredError( - Integer code, - String message, - Object data) { + public ExtendedAgentCardNotConfiguredError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, EXTENDED_AGENT_CARD_NOT_CONFIGURED_ERROR_CODE), defaultIfNull(message, "Extended Card not configured"), diff --git a/spec/src/main/java/io/a2a/spec/ExtensionSupportRequiredError.java b/spec/src/main/java/io/a2a/spec/ExtensionSupportRequiredError.java index ac0ea574d..abb11aa9f 100644 --- a/spec/src/main/java/io/a2a/spec/ExtensionSupportRequiredError.java +++ b/spec/src/main/java/io/a2a/spec/ExtensionSupportRequiredError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.EXTENSION_SUPPORT_REQUIRED_ERROR; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that a client requested use of an extension marked as required @@ -36,10 +38,7 @@ public class ExtensionSupportRequiredError extends A2AProtocolError { * @param message the error message (defaults to standard message if null) * @param data additional error data (optional) */ - public ExtensionSupportRequiredError( - Integer code, - String message, - Object data) { + public ExtensionSupportRequiredError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, EXTENSION_SUPPORT_REQUIRED_ERROR), defaultIfNull(message, "Extension support required but not declared"), diff --git a/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java b/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java index 81537ce97..070042e40 100644 --- a/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java +++ b/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java @@ -3,6 +3,7 @@ import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Parameters for retrieving push notification configuration for a specific task. @@ -55,9 +56,9 @@ public static Builder builder() { * Builder for constructing GetTaskPushNotificationConfigParams instances. */ public static class Builder { - String id; - String pushNotificationConfigId; - String tenant; + @Nullable String id; + @Nullable String pushNotificationConfigId; + @Nullable String tenant; /** * Creates a new Builder with all fields unset. @@ -104,7 +105,10 @@ public Builder tenant(String tenant) { * @return a new GetTaskPushNotificationConfigParams */ public GetTaskPushNotificationConfigParams build() { - return new GetTaskPushNotificationConfigParams(id, pushNotificationConfigId, tenant == null ? "" : tenant); + return new GetTaskPushNotificationConfigParams( + Assert.checkNotNullParam("id", id), + Assert.checkNotNullParam("pushNotificationConfigId", pushNotificationConfigId), + Assert.checkNotNullParam("tenant", tenant)); } } } diff --git a/spec/src/main/java/io/a2a/spec/HTTPAuthSecurityScheme.java b/spec/src/main/java/io/a2a/spec/HTTPAuthSecurityScheme.java index 996a0c0a6..a34c64239 100644 --- a/spec/src/main/java/io/a2a/spec/HTTPAuthSecurityScheme.java +++ b/spec/src/main/java/io/a2a/spec/HTTPAuthSecurityScheme.java @@ -1,6 +1,7 @@ package io.a2a.spec; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * HTTP authentication security scheme for agent authentication. @@ -35,11 +36,7 @@ * @see RFC 7235 - HTTP Authentication * @see A2A Protocol Specification */ -public record HTTPAuthSecurityScheme( - String bearerFormat, - String scheme, - String description -) implements SecurityScheme { +public record HTTPAuthSecurityScheme(@Nullable String bearerFormat, String scheme, @Nullable String description) implements SecurityScheme { /** The HTTP security scheme type identifier. */ public static final String TYPE = "httpAuthSecurityScheme"; @@ -77,9 +74,9 @@ public static Builder builder() { * The {@code scheme} parameter is required and must be set before calling {@code build()}. */ public static class Builder { - private String bearerFormat; - private String scheme; - private String description; + private @Nullable String bearerFormat; + private @Nullable String scheme; + private @Nullable String description; /** * Creates a new Builder with all fields unset. @@ -127,7 +124,7 @@ public Builder description(String description) { * @throws IllegalArgumentException if required fields are missing */ public HTTPAuthSecurityScheme build() { - return new HTTPAuthSecurityScheme(bearerFormat, scheme, description); + return new HTTPAuthSecurityScheme(bearerFormat, Assert.checkNotNullParam("scheme", scheme), description); } } } diff --git a/spec/src/main/java/io/a2a/spec/InternalError.java b/spec/src/main/java/io/a2a/spec/InternalError.java index 99aa71220..5d9793972 100644 --- a/spec/src/main/java/io/a2a/spec/InternalError.java +++ b/spec/src/main/java/io/a2a/spec/InternalError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.INTERNAL_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * JSON-RPC error indicating an internal error occurred on the server. @@ -34,7 +36,7 @@ public class InternalError extends A2AError { * @param message the error message * @param data additional error data */ - public InternalError(Integer code, String message, Object data) { + public InternalError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, INTERNAL_ERROR_CODE), defaultIfNull(message, "Internal Error"), @@ -46,7 +48,7 @@ public InternalError(Integer code, String message, Object data) { * * @param message the error message */ - public InternalError(String message) { + public InternalError(@Nullable String message) { this(null, message, null); } } diff --git a/spec/src/main/java/io/a2a/spec/InvalidAgentResponseError.java b/spec/src/main/java/io/a2a/spec/InvalidAgentResponseError.java index d86fb584e..d6d8508d9 100644 --- a/spec/src/main/java/io/a2a/spec/InvalidAgentResponseError.java +++ b/spec/src/main/java/io/a2a/spec/InvalidAgentResponseError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.INVALID_AGENT_RESPONSE_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that an agent returned a response not conforming to protocol specifications. @@ -44,7 +46,7 @@ public class InvalidAgentResponseError extends A2AProtocolError { * @param message the error message * @param data additional error data */ - public InvalidAgentResponseError(Integer code, String message, Object data) { + public InvalidAgentResponseError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, INVALID_AGENT_RESPONSE_ERROR_CODE), defaultIfNull(message, "Invalid agent response"), diff --git a/spec/src/main/java/io/a2a/spec/InvalidParamsError.java b/spec/src/main/java/io/a2a/spec/InvalidParamsError.java index a7f3476c3..a56f1ac91 100644 --- a/spec/src/main/java/io/a2a/spec/InvalidParamsError.java +++ b/spec/src/main/java/io/a2a/spec/InvalidParamsError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.INVALID_PARAMS_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * JSON-RPC error indicating that method parameters are invalid or missing required fields. *

@@ -37,7 +39,7 @@ public class InvalidParamsError extends A2AError { * @param message the error message * @param data additional error data */ - public InvalidParamsError(Integer code, String message, Object data) { + public InvalidParamsError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, INVALID_PARAMS_ERROR_CODE), defaultIfNull(message, "Invalid parameters"), @@ -49,7 +51,7 @@ public InvalidParamsError(Integer code, String message, Object data) { * * @param message the error message */ - public InvalidParamsError(String message) { + public InvalidParamsError(@Nullable String message) { this(null, message, null); } diff --git a/spec/src/main/java/io/a2a/spec/InvalidRequestError.java b/spec/src/main/java/io/a2a/spec/InvalidRequestError.java index 98d0f65f7..512c21b59 100644 --- a/spec/src/main/java/io/a2a/spec/InvalidRequestError.java +++ b/spec/src/main/java/io/a2a/spec/InvalidRequestError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.INVALID_REQUEST_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * JSON-RPC error indicating that the request payload is not a valid JSON-RPC Request object. @@ -45,7 +47,7 @@ public InvalidRequestError() { * @param message the error message * @param data additional error data */ - public InvalidRequestError(Integer code, String message, Object data) { + public InvalidRequestError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, INVALID_REQUEST_ERROR_CODE), defaultIfNull(message, "Request payload validation error"), diff --git a/spec/src/main/java/io/a2a/spec/JSONParseError.java b/spec/src/main/java/io/a2a/spec/JSONParseError.java index 74da3ddf5..72e4aaf02 100644 --- a/spec/src/main/java/io/a2a/spec/JSONParseError.java +++ b/spec/src/main/java/io/a2a/spec/JSONParseError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.JSON_PARSE_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * JSON-RPC error indicating that the server received invalid JSON that could not be parsed. *

@@ -51,10 +53,7 @@ public JSONParseError(String message) { * @param message the error message * @param data additional error data */ - public JSONParseError( - Integer code, - String message, - Object data) { + public JSONParseError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, JSON_PARSE_ERROR_CODE), defaultIfNull(message, "Invalid JSON payload"), diff --git a/spec/src/main/java/io/a2a/spec/ListTasksParams.java b/spec/src/main/java/io/a2a/spec/ListTasksParams.java index d99d25bcf..906fda993 100644 --- a/spec/src/main/java/io/a2a/spec/ListTasksParams.java +++ b/spec/src/main/java/io/a2a/spec/ListTasksParams.java @@ -1,5 +1,6 @@ package io.a2a.spec; +import io.a2a.util.Assert; import java.time.Instant; import org.jspecify.annotations.Nullable; @@ -44,10 +45,7 @@ public record ListTasksParams( * @throws InvalidParamsError if tenant is null or if pageSize or historyLength are out of valid range */ public ListTasksParams { - if (tenant == null) { - throw new InvalidParamsError(null, "Parameter 'tenant' may not be null", null); - } - + Assert.checkNotNullParam("tenant", tenant); // Validate pageSize (1-100) if (pageSize != null && (pageSize < MIN_PAGE_SIZE || pageSize > MAX_PAGE_SIZE)) { throw new InvalidParamsError(null, @@ -119,14 +117,14 @@ public static Builder builder() { * Builder for constructing instances. */ public static class Builder { - private String contextId; - private TaskState status; - private Integer pageSize; - private String pageToken; - private Integer historyLength; - private Instant statusTimestampAfter; - private Boolean includeArtifacts; - private String tenant; + private @Nullable String contextId; + private @Nullable TaskState status; + private @Nullable Integer pageSize; + private @Nullable String pageToken; + private @Nullable Integer historyLength; + private @Nullable Instant statusTimestampAfter; + private @Nullable Boolean includeArtifacts; + private @Nullable String tenant; /** * Creates a new Builder with all fields unset. @@ -229,7 +227,7 @@ public Builder tenant(String tenant) { */ public ListTasksParams build() { return new ListTasksParams(contextId, status, pageSize, pageToken, historyLength, - statusTimestampAfter, includeArtifacts, tenant); + statusTimestampAfter, includeArtifacts, Assert.checkNotNullParam("tenant", tenant)); } } } diff --git a/spec/src/main/java/io/a2a/spec/Message.java b/spec/src/main/java/io/a2a/spec/Message.java index 4383b1e02..1b4c73248 100644 --- a/spec/src/main/java/io/a2a/spec/Message.java +++ b/spec/src/main/java/io/a2a/spec/Message.java @@ -5,6 +5,7 @@ import java.util.UUID; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Represents a single message in the conversation between a user and an agent in the A2A Protocol. @@ -34,9 +35,9 @@ * @see A2A Protocol Specification */ public record Message(Role role, List> parts, - String messageId, String contextId, - String taskId, List referenceTaskIds, - Map metadata, List extensions + String messageId, @Nullable String contextId, + @Nullable String taskId, @Nullable List referenceTaskIds, + @Nullable Map metadata, @Nullable List extensions ) implements EventKind, StreamingEventKind { /** @@ -66,8 +67,8 @@ public record Message(Role role, List> parts, } parts = List.copyOf(parts); referenceTaskIds = referenceTaskIds != null ? List.copyOf(referenceTaskIds) : null; - extensions = extensions != null ? List.copyOf(extensions) : null; metadata = (metadata != null) ? Map.copyOf(metadata) : null; + extensions = extensions != null ? List.copyOf(extensions) : null; } @Override @@ -144,14 +145,14 @@ public String asString() { */ public static class Builder { - private Role role; - private List> parts; - private String messageId; - private String contextId; - private String taskId; - private List referenceTaskIds; - private Map metadata; - private List extensions; + private @Nullable Role role; + private @Nullable List> parts; + private @Nullable String messageId; + private @Nullable String contextId; + private @Nullable String taskId; + private @Nullable List referenceTaskIds; + private @Nullable Map metadata; + private @Nullable List extensions; /** * Creates a new Builder with all fields unset. @@ -260,7 +261,7 @@ public Builder referenceTaskIds(List referenceTaskIds) { * @param metadata map of metadata key-value pairs (optional) * @return this builder for method chaining */ - public Builder metadata(Map metadata) { + public Builder metadata(@Nullable Map metadata) { this.metadata = metadata; return this; } @@ -285,8 +286,15 @@ public Builder extensions(List extensions) { * @throws IllegalArgumentException if required fields are missing or invalid */ public Message build() { - return new Message(role, parts, messageId == null ? UUID.randomUUID().toString() : messageId, - contextId, taskId, referenceTaskIds, metadata, extensions); + return new Message( + Assert.checkNotNullParam("role", role), + Assert.checkNotNullParam("parts", parts), + messageId == null ? UUID.randomUUID().toString() : messageId, + contextId, + taskId, + referenceTaskIds, + metadata, + extensions); } } } diff --git a/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java b/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java index 4082d2501..d8ea772a9 100644 --- a/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java +++ b/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java @@ -21,8 +21,8 @@ * @see PushNotificationConfig for push notification options * @see A2A Protocol Specification */ -public record MessageSendConfiguration(List acceptedOutputModes, Integer historyLength, - PushNotificationConfig pushNotificationConfig, Boolean blocking) { +public record MessageSendConfiguration(@Nullable List acceptedOutputModes, @Nullable Integer historyLength, + @Nullable PushNotificationConfig pushNotificationConfig, Boolean blocking) { /** * Compact constructor for validation. @@ -56,9 +56,9 @@ public static Builder builder() { */ public static class Builder { - List acceptedOutputModes; - Integer historyLength; - PushNotificationConfig pushNotificationConfig; + @Nullable List acceptedOutputModes; + @Nullable Integer historyLength; + @Nullable PushNotificationConfig pushNotificationConfig; Boolean blocking = false; /** @@ -121,7 +121,11 @@ public Builder blocking(@NonNull Boolean blocking) { * @return a new message send configuration instance */ public MessageSendConfiguration build() { - return new MessageSendConfiguration(acceptedOutputModes, historyLength, pushNotificationConfig, blocking); + return new MessageSendConfiguration( + acceptedOutputModes, + historyLength, + pushNotificationConfig, + blocking); } } } diff --git a/spec/src/main/java/io/a2a/spec/MessageSendParams.java b/spec/src/main/java/io/a2a/spec/MessageSendParams.java index 1dcb6dd61..e1c75a668 100644 --- a/spec/src/main/java/io/a2a/spec/MessageSendParams.java +++ b/spec/src/main/java/io/a2a/spec/MessageSendParams.java @@ -3,6 +3,7 @@ import java.util.Map; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Parameters for sending a message to an agent in the A2A Protocol. @@ -20,8 +21,8 @@ * @see MessageSendConfiguration for available configuration options * @see A2A Protocol Specification */ -public record MessageSendParams(Message message, MessageSendConfiguration configuration, - Map metadata, String tenant) { +public record MessageSendParams(Message message, @Nullable MessageSendConfiguration configuration, + @Nullable Map metadata, String tenant) { /** * Compact constructor for validation. @@ -44,7 +45,7 @@ public record MessageSendParams(Message message, MessageSendConfiguration config * @param configuration optional configuration for message processing * @param metadata optional metadata */ - public MessageSendParams(Message message, MessageSendConfiguration configuration, Map metadata) { + public MessageSendParams(Message message, @Nullable MessageSendConfiguration configuration, @Nullable Map metadata) { this(message, configuration, metadata, ""); } @@ -64,10 +65,10 @@ public static Builder builder() { * configuration and metadata. */ public static class Builder { - Message message; - MessageSendConfiguration configuration; - Map metadata; - String tenant; + @Nullable Message message; + @Nullable MessageSendConfiguration configuration; + @Nullable Map metadata; + @Nullable String tenant; /** * Creates a new Builder with all fields unset. @@ -92,7 +93,7 @@ public Builder message(Message message) { * @param configuration the message send configuration * @return this builder */ - public Builder configuration(MessageSendConfiguration configuration) { + public Builder configuration(@Nullable MessageSendConfiguration configuration) { this.configuration = configuration; return this; } @@ -103,7 +104,7 @@ public Builder configuration(MessageSendConfiguration configuration) { * @param metadata arbitrary key-value metadata * @return this builder */ - public Builder metadata(Map metadata) { + public Builder metadata(@Nullable Map metadata) { this.metadata = metadata; return this; } @@ -126,7 +127,11 @@ public Builder tenant(String tenant) { * @throws IllegalArgumentException if message is null */ public MessageSendParams build() { - return new MessageSendParams(message, configuration, metadata, tenant == null ? "" : tenant); + return new MessageSendParams( + Assert.checkNotNullParam("message", message), + configuration, + metadata, + tenant == null ? "" : tenant); } } } diff --git a/spec/src/main/java/io/a2a/spec/MethodNotFoundError.java b/spec/src/main/java/io/a2a/spec/MethodNotFoundError.java index e38f7a605..c69aaa572 100644 --- a/spec/src/main/java/io/a2a/spec/MethodNotFoundError.java +++ b/spec/src/main/java/io/a2a/spec/MethodNotFoundError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.METHOD_NOT_FOUND_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * JSON-RPC error indicating that the requested method does not exist or is not available. *

@@ -29,10 +31,7 @@ public class MethodNotFoundError extends A2AError { * @param message the error message (defaults to "Method not found" if null) * @param data additional error data (optional) */ - public MethodNotFoundError( - Integer code, - String message, - Object data) { + public MethodNotFoundError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, METHOD_NOT_FOUND_ERROR_CODE), defaultIfNull(message, "Method not found"), diff --git a/spec/src/main/java/io/a2a/spec/OAuth2SecurityScheme.java b/spec/src/main/java/io/a2a/spec/OAuth2SecurityScheme.java index ffaa323aa..85e0f76cb 100644 --- a/spec/src/main/java/io/a2a/spec/OAuth2SecurityScheme.java +++ b/spec/src/main/java/io/a2a/spec/OAuth2SecurityScheme.java @@ -1,6 +1,7 @@ package io.a2a.spec; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * OAuth 2.0 security scheme for agent authentication. @@ -19,11 +20,8 @@ * @see OpenAPI Security Scheme * @see A2A Protocol Specification */ -public record OAuth2SecurityScheme( - OAuthFlows flows, - String description, - String oauth2MetadataUrl -) implements SecurityScheme { +public record OAuth2SecurityScheme(OAuthFlows flows, @Nullable String description, @Nullable String oauth2MetadataUrl) + implements SecurityScheme { /** * The type identifier for OAuth 2.0 security schemes: "oauth2SecurityScheme". @@ -69,9 +67,10 @@ public static Builder builder() { * } */ public static class Builder { - private OAuthFlows flows; - private String description; - private String oauth2MetadataUrl; + + private @Nullable OAuthFlows flows; + private @Nullable String description; + private @Nullable String oauth2MetadataUrl; /** * Creates a new Builder with all fields unset. @@ -119,7 +118,7 @@ public Builder oauth2MetadataUrl(String oauth2MetadataUrl) { * @throws IllegalArgumentException if flows is null */ public OAuth2SecurityScheme build() { - return new OAuth2SecurityScheme(flows, description, oauth2MetadataUrl); + return new OAuth2SecurityScheme(Assert.checkNotNullParam("flows", flows), description, oauth2MetadataUrl); } } } diff --git a/spec/src/main/java/io/a2a/spec/OAuthFlows.java b/spec/src/main/java/io/a2a/spec/OAuthFlows.java index 5cabfcccd..4349c4906 100644 --- a/spec/src/main/java/io/a2a/spec/OAuthFlows.java +++ b/spec/src/main/java/io/a2a/spec/OAuthFlows.java @@ -1,5 +1,7 @@ package io.a2a.spec; +import org.jspecify.annotations.Nullable; + /** * Configuration for supported OAuth 2.0 authorization flows. *

@@ -15,8 +17,8 @@ * @see OpenAPI OAuth Flows Object * @see A2A Protocol Specification */ -public record OAuthFlows(AuthorizationCodeOAuthFlow authorizationCode, ClientCredentialsOAuthFlow clientCredentials, - DeviceCodeOAuthFlow deviceCode) { +public record OAuthFlows(@Nullable AuthorizationCodeOAuthFlow authorizationCode, @Nullable ClientCredentialsOAuthFlow clientCredentials, + @Nullable DeviceCodeOAuthFlow deviceCode) { /** * Create a new Builder @@ -31,9 +33,9 @@ public static Builder builder() { * Builder for constructing {@link OAuthFlows} instances. */ public static class Builder { - private AuthorizationCodeOAuthFlow authorizationCode; - private ClientCredentialsOAuthFlow clientCredentials; - private DeviceCodeOAuthFlow deviceCode; + private @Nullable AuthorizationCodeOAuthFlow authorizationCode; + private @Nullable ClientCredentialsOAuthFlow clientCredentials; + private @Nullable DeviceCodeOAuthFlow deviceCode; /** * Creates a new Builder with all fields unset. diff --git a/spec/src/main/java/io/a2a/spec/OpenIdConnectSecurityScheme.java b/spec/src/main/java/io/a2a/spec/OpenIdConnectSecurityScheme.java index 63700853a..371f18982 100644 --- a/spec/src/main/java/io/a2a/spec/OpenIdConnectSecurityScheme.java +++ b/spec/src/main/java/io/a2a/spec/OpenIdConnectSecurityScheme.java @@ -1,6 +1,7 @@ package io.a2a.spec; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * OpenID Connect security scheme for agent authentication. @@ -28,10 +29,7 @@ * @see OpenID Connect Discovery * @see A2A Protocol Specification */ -public record OpenIdConnectSecurityScheme( - String openIdConnectUrl, - String description -) implements SecurityScheme { +public record OpenIdConnectSecurityScheme(String openIdConnectUrl, @Nullable String description) implements SecurityScheme { /** * The type identifier for OpenID Connect security schemes: "openIdConnect". @@ -70,8 +68,8 @@ public static Builder builder() { * The {@code openIdConnectUrl} parameter is required. */ public static class Builder { - private String openIdConnectUrl; - private String description; + private @Nullable String openIdConnectUrl; + private @Nullable String description; /** * Creates a new Builder with all fields unset. @@ -108,7 +106,7 @@ public Builder description(String description) { * @throws IllegalArgumentException if openIdConnectUrl is null */ public OpenIdConnectSecurityScheme build() { - return new OpenIdConnectSecurityScheme(openIdConnectUrl, description); + return new OpenIdConnectSecurityScheme(Assert.checkNotNullParam("openIdConnectUrl", openIdConnectUrl), description); } } } diff --git a/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java b/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java index 6fe62ff1c..d6c88dec5 100644 --- a/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java +++ b/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java @@ -1,6 +1,7 @@ package io.a2a.spec; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Configuration for asynchronous push notifications of task updates. @@ -25,7 +26,7 @@ * @see MessageSendConfiguration for configuring push notifications on message send * @see A2A Protocol Specification */ -public record PushNotificationConfig(String url, String token, AuthenticationInfo authentication, String id) { +public record PushNotificationConfig(String url, @Nullable String token, @Nullable AuthenticationInfo authentication, @Nullable String id) { /** * Compact constructor for validation. @@ -66,10 +67,10 @@ public static Builder builder(PushNotificationConfig config) { * authentication and identification. */ public static class Builder { - private String url; - private String token; - private AuthenticationInfo authentication; - private String id; + private @Nullable String url; + private @Nullable String token; + private @Nullable AuthenticationInfo authentication; + private @Nullable String id; /** Creates an empty builder. */ private Builder() { @@ -138,7 +139,7 @@ public Builder id(String id) { * @throws IllegalArgumentException if url is null */ public PushNotificationConfig build() { - return new PushNotificationConfig(url, token, authentication, id); + return new PushNotificationConfig(Assert.checkNotNullParam("url", url), token, authentication, id); } } } diff --git a/spec/src/main/java/io/a2a/spec/PushNotificationNotSupportedError.java b/spec/src/main/java/io/a2a/spec/PushNotificationNotSupportedError.java index f8f7747b8..c84f4d79f 100644 --- a/spec/src/main/java/io/a2a/spec/PushNotificationNotSupportedError.java +++ b/spec/src/main/java/io/a2a/spec/PushNotificationNotSupportedError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that the agent does not support push notifications. *

@@ -42,10 +44,7 @@ public PushNotificationNotSupportedError() { * @param message the error message (defaults to "Push Notification is not supported" if null) * @param data additional error data (optional) */ - public PushNotificationNotSupportedError( - Integer code, - String message, - Object data) { + public PushNotificationNotSupportedError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, PUSH_NOTIFICATION_NOT_SUPPORTED_ERROR_CODE), defaultIfNull(message, "Push Notification is not supported"), diff --git a/spec/src/main/java/io/a2a/spec/Task.java b/spec/src/main/java/io/a2a/spec/Task.java index 24336dcd9..fd7cfce01 100644 --- a/spec/src/main/java/io/a2a/spec/Task.java +++ b/spec/src/main/java/io/a2a/spec/Task.java @@ -4,6 +4,7 @@ import java.util.Map; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Represents a single, stateful operation or conversation between a client and an agent in the A2A Protocol. @@ -42,14 +43,8 @@ * @see Message * @see A2A Protocol Specification */ -public record Task( - String id, - String contextId, - TaskStatus status, - List artifacts, - List history, - Map metadata -) implements EventKind, StreamingEventKind { +public record Task(String id, String contextId, TaskStatus status, @Nullable List artifacts, + @Nullable List history, @Nullable Map metadata) implements EventKind, StreamingEventKind { /** * The identifier when used in streaming responses @@ -124,12 +119,12 @@ public static Builder builder(Task task) { * } */ public static class Builder { - private String id; - private String contextId; - private TaskStatus status; - private List artifacts; - private List history; - private Map metadata; + private @Nullable String id; + private @Nullable String contextId; + private @Nullable TaskStatus status; + private @Nullable List artifacts; + private @Nullable List history; + private @Nullable Map metadata; /** * Creates a new Builder with all fields unset. @@ -203,7 +198,7 @@ public Builder status(TaskStatus status) { * @return this builder for method chaining * @see Artifact */ - public Builder artifacts(List artifacts) { + public Builder artifacts(@Nullable List artifacts) { this.artifacts = artifacts; return this; } @@ -218,7 +213,7 @@ public Builder artifacts(List artifacts) { * @return this builder for method chaining * @see Message */ - public Builder history(List history) { + public Builder history(@Nullable List history) { this.history = history; return this; } @@ -258,7 +253,13 @@ public Builder metadata(Map metadata) { * @throws IllegalArgumentException if any required field (id, contextId, status) is null */ public Task build() { - return new Task(id, contextId, status, artifacts, history, metadata); + return new Task( + Assert.checkNotNullParam("id", id), + Assert.checkNotNullParam("contextId", contextId), + Assert.checkNotNullParam("status", status), + artifacts, + history, + metadata); } } } diff --git a/spec/src/main/java/io/a2a/spec/TaskArtifactUpdateEvent.java b/spec/src/main/java/io/a2a/spec/TaskArtifactUpdateEvent.java index b2ca5265e..1d009673d 100644 --- a/spec/src/main/java/io/a2a/spec/TaskArtifactUpdateEvent.java +++ b/spec/src/main/java/io/a2a/spec/TaskArtifactUpdateEvent.java @@ -3,6 +3,7 @@ import java.util.Map; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * Event notifying that a task artifact has been created, modified, or appended to. @@ -38,14 +39,8 @@ * @see Artifact * @see Task */ -public record TaskArtifactUpdateEvent( - String taskId, - Artifact artifact, - String contextId, - Boolean append, - Boolean lastChunk, - Map metadata -) implements EventKind, StreamingEventKind, UpdateEvent { +public record TaskArtifactUpdateEvent(String taskId, Artifact artifact, String contextId, @Nullable Boolean append, + @Nullable Boolean lastChunk, @Nullable Map metadata) implements EventKind, StreamingEventKind, UpdateEvent { /** * The identifier when used in streaming responses @@ -124,12 +119,12 @@ public static Builder builder(TaskArtifactUpdateEvent event) { */ public static class Builder { - private String taskId; - private Artifact artifact; - private String contextId; - private Boolean append; - private Boolean lastChunk; - private Map metadata; + private @Nullable String taskId; + private @Nullable Artifact artifact; + private @Nullable String contextId; + private @Nullable Boolean append; + private @Nullable Boolean lastChunk; + private @Nullable Map metadata; private Builder() { } @@ -182,7 +177,7 @@ public Builder contextId(String contextId) { * @param append true to append, false or null for new artifact * @return this builder for method chaining */ - public Builder append(Boolean append) { + public Builder append(@Nullable Boolean append) { this.append = append; return this; } @@ -193,7 +188,7 @@ public Builder append(Boolean append) { * @param lastChunk true if final chunk, false or null otherwise * @return this builder for method chaining */ - public Builder lastChunk(Boolean lastChunk) { + public Builder lastChunk(@Nullable Boolean lastChunk) { this.lastChunk = lastChunk; return this; } @@ -216,7 +211,13 @@ public Builder metadata(Map metadata) { * @throws IllegalArgumentException if required fields are missing */ public TaskArtifactUpdateEvent build() { - return new TaskArtifactUpdateEvent(taskId, artifact, contextId, append, lastChunk, metadata); + return new TaskArtifactUpdateEvent( + Assert.checkNotNullParam("taskId", taskId), + Assert.checkNotNullParam("artifact", artifact), + Assert.checkNotNullParam("contextId", contextId), + append, + lastChunk, + metadata); } } } diff --git a/spec/src/main/java/io/a2a/spec/TaskNotCancelableError.java b/spec/src/main/java/io/a2a/spec/TaskNotCancelableError.java index 86213eb37..13853a16a 100644 --- a/spec/src/main/java/io/a2a/spec/TaskNotCancelableError.java +++ b/spec/src/main/java/io/a2a/spec/TaskNotCancelableError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.TASK_NOT_CANCELABLE_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that a task cannot be canceled in its current state. *

@@ -45,10 +47,7 @@ public TaskNotCancelableError() { * @param message the error message (defaults to "Task cannot be canceled" if null) * @param data additional error data (optional) */ - public TaskNotCancelableError( - Integer code, - String message, - Object data) { + public TaskNotCancelableError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, TASK_NOT_CANCELABLE_ERROR_CODE), defaultIfNull(message, "Task cannot be canceled"), diff --git a/spec/src/main/java/io/a2a/spec/TaskNotFoundError.java b/spec/src/main/java/io/a2a/spec/TaskNotFoundError.java index 252286257..8bb8eea27 100644 --- a/spec/src/main/java/io/a2a/spec/TaskNotFoundError.java +++ b/spec/src/main/java/io/a2a/spec/TaskNotFoundError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.TASK_NOT_FOUND_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that the requested task ID does not exist. *

@@ -46,10 +48,7 @@ public TaskNotFoundError() { * @param message the error message (defaults to "Task not found" if null) * @param data additional error data (optional) */ - public TaskNotFoundError( - Integer code, - String message, - Object data) { + public TaskNotFoundError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, TASK_NOT_FOUND_ERROR_CODE), defaultIfNull(message, "Task not found"), diff --git a/spec/src/main/java/io/a2a/spec/TaskStatus.java b/spec/src/main/java/io/a2a/spec/TaskStatus.java index 0ce67f3df..87055f236 100644 --- a/spec/src/main/java/io/a2a/spec/TaskStatus.java +++ b/spec/src/main/java/io/a2a/spec/TaskStatus.java @@ -1,9 +1,10 @@ package io.a2a.spec; import java.time.OffsetDateTime; -import java.time.ZoneOffset; import io.a2a.util.Assert; +import java.time.ZoneOffset; +import org.jspecify.annotations.Nullable; /** * Represents the status of a task at a specific point in time in the A2A Protocol. @@ -37,8 +38,7 @@ * @see Task * @see A2A Protocol Specification */ -public record TaskStatus(TaskState state, Message message, - OffsetDateTime timestamp) { +public record TaskStatus(TaskState state, @Nullable Message message, OffsetDateTime timestamp) { /** * Compact constructor for validation and timestamp initialization. @@ -48,11 +48,15 @@ public record TaskStatus(TaskState state, Message message, * @param message optional status message * @param timestamp the status timestamp */ - public TaskStatus { - Assert.checkNotNullParam("state", state); - timestamp = timestamp == null ? OffsetDateTime.now(ZoneOffset.UTC) : timestamp; + public TaskStatus(TaskState state, @Nullable Message message, @Nullable OffsetDateTime timestamp){ + this.state = Assert.checkNotNullParam("state", state); + this.timestamp = timestamp == null ? OffsetDateTime.now(ZoneOffset.UTC) : timestamp; + this.message = message; } + public OffsetDateTime timestamp() { + return timestamp; + } /** * Creates a TaskStatus with only a state, using the current UTC time as the timestamp. *

@@ -62,19 +66,6 @@ public record TaskStatus(TaskState state, Message message, * @param state the task state (required) */ public TaskStatus(TaskState state) { - this(state, null, null); - } - - /** - * Creates a TaskStatus with a specific timestamp, primarily for testing purposes. - *

- * This package-private constructor allows tests to create TaskStatus instances - * with deterministic timestamps for assertions. - * - * @param state the task state (required) - * @param timestamp the timestamp to use (if null, current UTC time is used) - */ - TaskStatus(TaskState state, OffsetDateTime timestamp) { - this(state, null, timestamp); + this(state, null, OffsetDateTime.now(ZoneOffset.UTC)); } } diff --git a/spec/src/main/java/io/a2a/spec/TaskStatusUpdateEvent.java b/spec/src/main/java/io/a2a/spec/TaskStatusUpdateEvent.java index 547a6e089..5cc6621cf 100644 --- a/spec/src/main/java/io/a2a/spec/TaskStatusUpdateEvent.java +++ b/spec/src/main/java/io/a2a/spec/TaskStatusUpdateEvent.java @@ -10,6 +10,7 @@ import com.google.gson.annotations.SerializedName; import io.a2a.util.Assert; +import org.jspecify.annotations.Nullable; /** * An event sent by the agent to notify the client of a change in a task's status. @@ -21,14 +22,8 @@ * @param isFinal whether this is a final status * @param metadata additional metadata (optional) */ -public record TaskStatusUpdateEvent( - String taskId, - TaskStatus status, - String contextId, - @SerializedName("final") - boolean isFinal, - Map metadata - ) implements EventKind, StreamingEventKind, UpdateEvent { +public record TaskStatusUpdateEvent(String taskId, TaskStatus status, String contextId, + @SerializedName("final") boolean isFinal, @Nullable Map metadata) implements EventKind, StreamingEventKind, UpdateEvent { /** * The identifier when used in streaming responses @@ -91,10 +86,10 @@ public static Builder builder(TaskStatusUpdateEvent event) { */ public static class Builder { - private String taskId; - private TaskStatus status; - private String contextId; - private Map metadata; + private @Nullable String taskId; + private @Nullable TaskStatus status; + private @Nullable String contextId; + private @Nullable Map metadata; private Builder() { } @@ -156,7 +151,12 @@ public Builder metadata(Map metadata) { * @return a new TaskStatusUpdateEvent instance */ public TaskStatusUpdateEvent build() { - return new TaskStatusUpdateEvent(taskId, status, contextId, status.state().isFinal(), metadata); + return new TaskStatusUpdateEvent( + Assert.checkNotNullParam("taskId", taskId), + Assert.checkNotNullParam("status", status), + Assert.checkNotNullParam("contextId", contextId), + Assert.checkNotNullParam("status", status).state().isFinal(), + metadata); } } } diff --git a/spec/src/main/java/io/a2a/spec/UnsupportedOperationError.java b/spec/src/main/java/io/a2a/spec/UnsupportedOperationError.java index ac98efa3a..65bdbed9a 100644 --- a/spec/src/main/java/io/a2a/spec/UnsupportedOperationError.java +++ b/spec/src/main/java/io/a2a/spec/UnsupportedOperationError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.UNSUPPORTED_OPERATION_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that the requested operation is not supported by the agent. *

@@ -40,10 +42,7 @@ public class UnsupportedOperationError extends A2AProtocolError { * @param message the error message (defaults to "This operation is not supported" if null) * @param data additional error data (optional) */ - public UnsupportedOperationError( - Integer code, - String message, - Object data) { + public UnsupportedOperationError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, UNSUPPORTED_OPERATION_ERROR_CODE), defaultIfNull(message, "This operation is not supported"), diff --git a/spec/src/main/java/io/a2a/spec/VersionNotSupportedError.java b/spec/src/main/java/io/a2a/spec/VersionNotSupportedError.java index 72a05bc74..f4caf5e77 100644 --- a/spec/src/main/java/io/a2a/spec/VersionNotSupportedError.java +++ b/spec/src/main/java/io/a2a/spec/VersionNotSupportedError.java @@ -3,6 +3,8 @@ import static io.a2a.spec.A2AErrorCodes.VERSION_NOT_SUPPORTED_ERROR_CODE; import static io.a2a.util.Utils.defaultIfNull; +import org.jspecify.annotations.Nullable; + /** * A2A Protocol error indicating that the A2A protocol version specified in the request @@ -35,10 +37,7 @@ public class VersionNotSupportedError extends A2AProtocolError { * @param message the error message (defaults to standard message if null) * @param data additional error data (optional) */ - public VersionNotSupportedError( - Integer code, - String message, - Object data) { + public VersionNotSupportedError(@Nullable Integer code, @Nullable String message, @Nullable Object data) { super( defaultIfNull(code, VERSION_NOT_SUPPORTED_ERROR_CODE), defaultIfNull(message, "Protocol version not supported"), diff --git a/spec/src/main/java/io/a2a/spec/package-info.java b/spec/src/main/java/io/a2a/spec/package-info.java new file mode 100644 index 000000000..f91b3a08c --- /dev/null +++ b/spec/src/main/java/io/a2a/spec/package-info.java @@ -0,0 +1,8 @@ +/** + * JSON processing exceptions for the A2A Java SDK. + *

+ * This package provides custom exceptions that replace Jackson's JSON processing exceptions, + * allowing the SDK to be independent of any specific JSON library implementation. + */ +@org.jspecify.annotations.NullMarked +package io.a2a.spec; diff --git a/transport/jsonrpc/src/test/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandlerTest.java b/transport/jsonrpc/src/test/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandlerTest.java index ea58beba4..67ac7a2a9 100644 --- a/transport/jsonrpc/src/test/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandlerTest.java +++ b/transport/jsonrpc/src/test/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandlerTest.java @@ -63,6 +63,7 @@ import io.a2a.spec.ListTaskPushNotificationConfigParams; import io.a2a.spec.ListTasksParams; import io.a2a.spec.Message; +import io.a2a.spec.MessageSendConfiguration; import io.a2a.spec.MessageSendParams; import io.a2a.spec.PushNotificationConfig; import io.a2a.spec.PushNotificationNotSupportedError; @@ -92,6 +93,13 @@ public class JSONRPCHandlerTest extends AbstractA2ARequestHandlerTest { private final ServerCallContext callContext = new ServerCallContext(UnauthenticatedUser.INSTANCE, Map.of("foo", "bar"), new HashSet<>()); + private static MessageSendConfiguration defaultConfiguration() { + return MessageSendConfiguration.builder() + .acceptedOutputModes(List.of()) + .blocking(false) + .build(); + } + @Test public void testOnGetTaskSuccess() throws Exception { JSONRPCHandler handler = new JSONRPCHandler(CARD, requestHandler, internalExecutor); @@ -1008,6 +1016,7 @@ public void testStreamingNotSupportedError() { .id("1") .params(MessageSendParams.builder() .message(MESSAGE) + .configuration(defaultConfiguration()) .build()) .build(); Flow.Publisher response = handler.onMessageSendStream(request, callContext); @@ -1171,7 +1180,7 @@ public void testOnMessageSendInternalError() { JSONRPCHandler handler = new JSONRPCHandler(CARD, mocked, internalExecutor); - SendMessageRequest request = new SendMessageRequest("1", new MessageSendParams(MESSAGE, null, null)); + SendMessageRequest request = new SendMessageRequest("1", new MessageSendParams(MESSAGE, defaultConfiguration(), null)); SendMessageResponse response = handler.onMessageSend(request, callContext); assertInstanceOf(InternalError.class, response.getError()); @@ -1185,7 +1194,7 @@ public void testOnMessageStreamInternalError() { JSONRPCHandler handler = new JSONRPCHandler(CARD, mocked, internalExecutor); - SendStreamingMessageRequest request = new SendStreamingMessageRequest("1", new MessageSendParams(MESSAGE, null, null)); + SendStreamingMessageRequest request = new SendStreamingMessageRequest("1", new MessageSendParams(MESSAGE, defaultConfiguration(), null)); Flow.Publisher response = handler.onMessageSendStream(request, callContext); List results = new ArrayList<>(); @@ -1270,7 +1279,7 @@ public void testOnMessageSendTaskIdMismatch() { agentEmitter.emitEvent(MINIMAL_TASK); }); SendMessageRequest request = new SendMessageRequest("1", - new MessageSendParams(MESSAGE, null, null)); + new MessageSendParams(MESSAGE, defaultConfiguration(), null)); SendMessageResponse response = handler.onMessageSend(request, callContext); assertInstanceOf(InternalError.class, response.getError()); @@ -1286,7 +1295,7 @@ public void testOnMessageStreamTaskIdMismatch() throws InterruptedException { agentEmitter.emitEvent(MINIMAL_TASK); }); - SendStreamingMessageRequest request = new SendStreamingMessageRequest("1", new MessageSendParams(MESSAGE, null, null)); + SendStreamingMessageRequest request = new SendStreamingMessageRequest("1", new MessageSendParams(MESSAGE, defaultConfiguration(), null)); Flow.Publisher response = handler.onMessageSendStream(request, callContext); CompletableFuture future = new CompletableFuture<>(); 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 609c4e0a9..7f206d336 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 @@ -290,7 +290,7 @@ public HTTPRestResponse listTasks(@Nullable String contextId, @Nullable String s } } - public HTTPRestResponse getTaskPushNotificationConfiguration(String taskId, @Nullable String configId, String tenant, ServerCallContext context) { + public HTTPRestResponse getTaskPushNotificationConfiguration(String taskId, String configId, String tenant, ServerCallContext context) { try { if (!agentCard.capabilities().pushNotifications()) { throw new PushNotificationNotSupportedError();