From cbde8b7b1e10cc93a6d5e59d1cf823a9ab4cc78f Mon Sep 17 00:00:00 2001 From: Emmanuel Hugonnet Date: Wed, 3 Jun 2026 12:25:41 +0200 Subject: [PATCH] refactor: use @Nullable String for tenant across all spec params records The tenant field was semantically optional but typed as non-nullable String with "" as an absent-value sentinel in most params records, while GetExtendedAgentCardParams and TaskPushNotificationConfig correctly used @Nullable String. This standardizes all params records to @Nullable (null = no tenant), removes the Assert.checkNotNullParam assertions for tenant, and updates gRPC MapStruct mappers to skip setTenant when null (conditionExpression) and convert proto "" back to null on fromProto (emptyToNull). Fixes #844 Signed-off-by: Emmanuel Hugonnet --- .../sdk/client/ClientBuilderTest.java | 4 ++-- .../sdk/grpc/mapper/AgentInterfaceMapper.java | 5 ++++- ...askPushNotificationConfigParamsMapper.java | 6 +++--- ...askPushNotificationConfigParamsMapper.java | 6 +++--- ...skPushNotificationConfigsParamsMapper.java | 6 +++--- .../grpc/mapper/MessageSendParamsMapper.java | 2 ++ .../sdk/grpc/mapper/TaskIdParamsMapper.java | 5 ++++- .../grpc/mapper/TaskQueryParamsMapper.java | 4 ++-- .../a2aproject/sdk/spec/AgentInterface.java | 8 ++++---- .../a2aproject/sdk/spec/CancelTaskParams.java | 10 ++++------ ...eleteTaskPushNotificationConfigParams.java | 10 ++++------ .../GetTaskPushNotificationConfigParams.java | 10 ++++------ ...ListTaskPushNotificationConfigsParams.java | 10 ++++------ .../a2aproject/sdk/spec/ListTasksParams.java | 13 ++++++------ .../sdk/spec/MessageSendParams.java | 9 ++++----- .../org/a2aproject/sdk/spec/TaskIdParams.java | 10 ++++------ .../a2aproject/sdk/spec/TaskQueryParams.java | 12 +++++------ .../java/org/a2aproject/sdk/util/Utils.java | 20 +++++++++++-------- 18 files changed, 74 insertions(+), 76 deletions(-) diff --git a/client/base/src/test/java/org/a2aproject/sdk/client/ClientBuilderTest.java b/client/base/src/test/java/org/a2aproject/sdk/client/ClientBuilderTest.java index 8df595122..3f1c120cc 100644 --- a/client/base/src/test/java/org/a2aproject/sdk/client/ClientBuilderTest.java +++ b/client/base/src/test/java/org/a2aproject/sdk/client/ClientBuilderTest.java @@ -163,13 +163,13 @@ public void shouldSelectCorrectInterfaceWithClientPreference() throws A2AClientE } @Test - public void shouldPreserveEmptyTenant() throws A2AClientException { + public void shouldHaveNullTenantWhenNotSet() throws A2AClientException { ClientBuilder builder = Client .builder(card) .withTransport(JSONRPCTransport.class, new JSONRPCTransportConfigBuilder()); AgentInterface selectedInterface = builder.findBestClientTransport(); - Assertions.assertEquals("", selectedInterface.tenant()); + Assertions.assertNull(selectedInterface.tenant()); } } diff --git a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/AgentInterfaceMapper.java b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/AgentInterfaceMapper.java index 81b476088..d7b51224f 100644 --- a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/AgentInterfaceMapper.java +++ b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/AgentInterfaceMapper.java @@ -8,12 +8,15 @@ * Mapper between {@link org.a2aproject.sdk.spec.AgentInterface} and {@link org.a2aproject.sdk.grpc.AgentInterface}. */ @Mapper(config = A2AProtoMapperConfig.class, - collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) + collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, + uses = A2ACommonFieldMapper.class) public interface AgentInterfaceMapper { AgentInterfaceMapper INSTANCE = A2AMappers.getMapper(AgentInterfaceMapper.class); + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.AgentInterface toProto(org.a2aproject.sdk.spec.AgentInterface domain); + @Mapping(target = "tenant", source = "tenant", qualifiedByName = "emptyToNull") org.a2aproject.sdk.spec.AgentInterface fromProto(org.a2aproject.sdk.grpc.AgentInterface proto); } diff --git a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/DeleteTaskPushNotificationConfigParamsMapper.java b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/DeleteTaskPushNotificationConfigParamsMapper.java index 18fc3ae9d..a464dfab2 100644 --- a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/DeleteTaskPushNotificationConfigParamsMapper.java +++ b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/DeleteTaskPushNotificationConfigParamsMapper.java @@ -9,7 +9,7 @@ /** * Mapper between {@link org.a2aproject.sdk.grpc.DeleteTaskPushNotificationConfigRequest} and {@link org.a2aproject.sdk.spec.DeleteTaskPushNotificationConfigParams}. */ -@Mapper(config = A2AProtoMapperConfig.class) +@Mapper(config = A2AProtoMapperConfig.class, uses = A2ACommonFieldMapper.class) public interface DeleteTaskPushNotificationConfigParamsMapper { DeleteTaskPushNotificationConfigParamsMapper INSTANCE = A2AMappers.getMapper(DeleteTaskPushNotificationConfigParamsMapper.class); @@ -20,7 +20,7 @@ public interface DeleteTaskPushNotificationConfigParamsMapper { @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "taskId", source = "taskId") @Mapping(target = "id", source = "id") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", qualifiedByName = "emptyToNull") DeleteTaskPushNotificationConfigParams fromProto(org.a2aproject.sdk.grpc.DeleteTaskPushNotificationConfigRequest proto); /** @@ -28,6 +28,6 @@ public interface DeleteTaskPushNotificationConfigParamsMapper { */ @Mapping(target = "taskId", source = "taskId") @Mapping(target = "id", source = "id") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.DeleteTaskPushNotificationConfigRequest toProto(DeleteTaskPushNotificationConfigParams domain); } diff --git a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/GetTaskPushNotificationConfigParamsMapper.java b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/GetTaskPushNotificationConfigParamsMapper.java index 9febadf56..e277b7ce0 100644 --- a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/GetTaskPushNotificationConfigParamsMapper.java +++ b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/GetTaskPushNotificationConfigParamsMapper.java @@ -9,7 +9,7 @@ /** * Mapper between {@link org.a2aproject.sdk.grpc.GetTaskPushNotificationConfigRequest} and {@link org.a2aproject.sdk.spec.GetTaskPushNotificationConfigParams}. */ -@Mapper(config = A2AProtoMapperConfig.class) +@Mapper(config = A2AProtoMapperConfig.class, uses = A2ACommonFieldMapper.class) public interface GetTaskPushNotificationConfigParamsMapper { GetTaskPushNotificationConfigParamsMapper INSTANCE = A2AMappers.getMapper(GetTaskPushNotificationConfigParamsMapper.class); @@ -20,7 +20,7 @@ public interface GetTaskPushNotificationConfigParamsMapper { @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "taskId", source = "taskId") @Mapping(target = "id", source = "id") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", qualifiedByName = "emptyToNull") GetTaskPushNotificationConfigParams fromProto(org.a2aproject.sdk.grpc.GetTaskPushNotificationConfigRequest proto); /** @@ -28,6 +28,6 @@ public interface GetTaskPushNotificationConfigParamsMapper { */ @Mapping(target = "taskId", source = "taskId") @Mapping(target = "id", source = "id", conditionExpression = "java(domain.id() != null)") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.GetTaskPushNotificationConfigRequest toProto(GetTaskPushNotificationConfigParams domain); } diff --git a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/ListTaskPushNotificationConfigsParamsMapper.java b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/ListTaskPushNotificationConfigsParamsMapper.java index 635600bf2..c69fa1925 100644 --- a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/ListTaskPushNotificationConfigsParamsMapper.java +++ b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/ListTaskPushNotificationConfigsParamsMapper.java @@ -9,7 +9,7 @@ /** * Mapper between {@link org.a2aproject.sdk.grpc.ListTaskPushNotificationConfigsRequest} and {@link org.a2aproject.sdk.spec.ListTaskPushNotificationConfigsParams}. */ -@Mapper(config = A2AProtoMapperConfig.class) +@Mapper(config = A2AProtoMapperConfig.class, uses = A2ACommonFieldMapper.class) public interface ListTaskPushNotificationConfigsParamsMapper { ListTaskPushNotificationConfigsParamsMapper INSTANCE = A2AMappers.getMapper(ListTaskPushNotificationConfigsParamsMapper.class); @@ -19,13 +19,13 @@ public interface ListTaskPushNotificationConfigsParamsMapper { */ @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "id", source = "taskId") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", qualifiedByName = "emptyToNull") ListTaskPushNotificationConfigsParams fromProto(org.a2aproject.sdk.grpc.ListTaskPushNotificationConfigsRequest proto); /** * Converts domain ListTaskPushNotificationConfigsParams to proto ListTaskPushNotificationConfigsRequest. */ @Mapping(target = "taskId", source = "id") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.ListTaskPushNotificationConfigsRequest toProto(ListTaskPushNotificationConfigsParams domain); } diff --git a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/MessageSendParamsMapper.java b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/MessageSendParamsMapper.java index 78bec7580..0325ca527 100644 --- a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/MessageSendParamsMapper.java +++ b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/MessageSendParamsMapper.java @@ -22,6 +22,7 @@ public interface MessageSendParamsMapper { */ @Mapping(target = "configuration", source = "configuration", conditionExpression = "java(domain.configuration() != null)") @Mapping(target = "metadata", source = "metadata", qualifiedByName = "metadataToProto") + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.SendMessageRequest toProto(MessageSendParams domain); /** @@ -31,5 +32,6 @@ public interface MessageSendParamsMapper { */ @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "metadata", source = "metadata", qualifiedByName = "metadataFromProto") + @Mapping(target = "tenant", source = "tenant", qualifiedByName = "emptyToNull") MessageSendParams fromProto(org.a2aproject.sdk.grpc.SendMessageRequest proto); } diff --git a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskIdParamsMapper.java b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskIdParamsMapper.java index b42d9379a..d6f3a913d 100644 --- a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskIdParamsMapper.java +++ b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskIdParamsMapper.java @@ -24,6 +24,7 @@ public interface TaskIdParamsMapper { @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "id", source = "id") @Mapping(target = "metadata", source = "metadata", qualifiedByName = "metadataFromProto") + @Mapping(target = "tenant", source = "tenant", qualifiedByName = "emptyToNull") CancelTaskParams fromProtoCancelTaskRequest(org.a2aproject.sdk.grpc.CancelTaskRequest proto); /** @@ -33,6 +34,7 @@ public interface TaskIdParamsMapper { @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "id", source = "id") @Mapping(target = "metadata", source = "metadata", qualifiedByName = "metadataToProto") + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.CancelTaskRequest toProtoCancelTaskRequest(CancelTaskParams domain); @@ -42,7 +44,7 @@ public interface TaskIdParamsMapper { */ @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "id", source = "id") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", qualifiedByName = "emptyToNull") TaskIdParams fromProtoSubscribeToTaskRequest(org.a2aproject.sdk.grpc.SubscribeToTaskRequest proto); /** @@ -51,5 +53,6 @@ public interface TaskIdParamsMapper { */ @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "id", source = "id") + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.SubscribeToTaskRequest toProtoSubscribeToTaskRequest(TaskIdParams domain); } diff --git a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskQueryParamsMapper.java b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskQueryParamsMapper.java index fe5916a47..d95147b75 100644 --- a/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskQueryParamsMapper.java +++ b/spec-grpc/src/main/java/org/a2aproject/sdk/grpc/mapper/TaskQueryParamsMapper.java @@ -11,7 +11,7 @@ *

* Extracts task ID from resource name format "tasks/{id}" using {@link ResourceNameParser}. */ -@Mapper(config = A2AProtoMapperConfig.class) +@Mapper(config = A2AProtoMapperConfig.class, uses = A2ACommonFieldMapper.class) public interface TaskQueryParamsMapper { TaskQueryParamsMapper INSTANCE = A2AMappers.getMapper(TaskQueryParamsMapper.class); @@ -28,6 +28,6 @@ public interface TaskQueryParamsMapper { @BeanMapping(builder = @Builder(buildMethod = "build")) @Mapping(target = "id", source = "id") @Mapping(target = "historyLength", source = "historyLength") - @Mapping(target = "tenant", source = "tenant") + @Mapping(target = "tenant", source = "tenant", conditionExpression = "java(domain.tenant() != null)") org.a2aproject.sdk.grpc.GetTaskRequest toProto(TaskQueryParams domain); } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/AgentInterface.java b/spec/src/main/java/org/a2aproject/sdk/spec/AgentInterface.java index c14147eee..3ad2d4717 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/AgentInterface.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/AgentInterface.java @@ -1,6 +1,7 @@ package org.a2aproject.sdk.spec; import org.a2aproject.sdk.util.Assert; +import org.jspecify.annotations.Nullable; /** * Declares a combination of a target URL and protocol binding for accessing an agent. @@ -19,13 +20,13 @@ * @param protocolBinding the protocol binding supported at this URL (e.g., "JSONRPC", "GRPC", "HTTP+JSON") (required) * @param url the endpoint URL where this interface is available; must be a valid absolute HTTPS URL in production * (required) - * @param tenant the tenant to be set in the request when calling the agent. + * @param tenant optional tenant to be set in the request when calling the agent. * @param protocolVersion the version of the A2A protocol this interface exposes (e.g., "1.0", "0.3") (required) * @see AgentCard * @see TransportProtocol * @see A2A Protocol Specification */ -public record AgentInterface(String protocolBinding, String url, String tenant, String protocolVersion) { +public record AgentInterface(String protocolBinding, String url, @Nullable String tenant, String protocolVersion) { /** The default A2A Protocol version used when not explicitly specified. */ public static final String CURRENT_PROTOCOL_VERSION = "1.0"; @@ -42,7 +43,6 @@ public record AgentInterface(String protocolBinding, String url, String tenant, public AgentInterface { Assert.checkNotNullParam("protocolBinding", protocolBinding); Assert.checkNotNullParam("url", url); - Assert.checkNotNullParam("tenant", tenant); if (protocolVersion == null || protocolVersion.isEmpty()) { protocolVersion = CURRENT_PROTOCOL_VERSION; @@ -67,6 +67,6 @@ public AgentInterface(String protocolBinding, String url, String tenant) { * @param url the endpoint URL (see class-level JavaDoc) */ public AgentInterface(String protocolBinding, String url) { - this(protocolBinding, url, "", CURRENT_PROTOCOL_VERSION); + this(protocolBinding, url, null, CURRENT_PROTOCOL_VERSION); } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/CancelTaskParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/CancelTaskParams.java index d950f1793..e823af312 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/CancelTaskParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/CancelTaskParams.java @@ -1,7 +1,6 @@ package org.a2aproject.sdk.spec; import org.a2aproject.sdk.util.Assert; -import org.a2aproject.sdk.util.Utils; import java.util.Collections; import java.util.Map; import org.jspecify.annotations.Nullable; @@ -17,7 +16,7 @@ * @param metadata optional arbitrary key-value metadata (e.g. cancellation reason) * @see A2A Protocol Specification */ -public record CancelTaskParams(String id, String tenant, Map metadata) { +public record CancelTaskParams(String id, @Nullable String tenant, Map metadata) { /** * Compact constructor for validation. @@ -28,7 +27,6 @@ public record CancelTaskParams(String id, String tenant, Map met */ public CancelTaskParams { Assert.checkNotNullParam("id", id); - Assert.checkNotNullParam("tenant", tenant); } /** @@ -37,7 +35,7 @@ public record CancelTaskParams(String id, String tenant, Map met * @param id the task identifier (required) */ public CancelTaskParams(String id) { - this(id, "", Collections.emptyMap()); + this(id, null, Collections.emptyMap()); } /** @@ -80,7 +78,7 @@ public Builder id(String id) { * @param tenant the tenant identifier * @return this builder for method chaining */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -105,7 +103,7 @@ public Builder metadata(Map metadata) { public CancelTaskParams build() { return new CancelTaskParams( Assert.checkNotNullParam("id", id), - Utils.defaultIfNull(tenant,""), + tenant, metadata ); } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/DeleteTaskPushNotificationConfigParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/DeleteTaskPushNotificationConfigParams.java index 96728e8fe..517cc7379 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/DeleteTaskPushNotificationConfigParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/DeleteTaskPushNotificationConfigParams.java @@ -3,7 +3,6 @@ import org.a2aproject.sdk.util.Assert; -import org.a2aproject.sdk.util.Utils; import org.jspecify.annotations.Nullable; /** @@ -17,7 +16,7 @@ * @param tenant optional tenant, provided as a path parameter. * @see A2A Protocol Specification */ -public record DeleteTaskPushNotificationConfigParams(String taskId, String id, String tenant) { +public record DeleteTaskPushNotificationConfigParams(String taskId, String id, @Nullable String tenant) { /** * Compact constructor that validates required fields. @@ -30,7 +29,6 @@ public record DeleteTaskPushNotificationConfigParams(String taskId, String id, S public DeleteTaskPushNotificationConfigParams { Assert.checkNotNullParam("taskId", taskId); Assert.checkNotNullParam("id", id); - Assert.checkNotNullParam("tenant", tenant); } /** @@ -41,7 +39,7 @@ public record DeleteTaskPushNotificationConfigParams(String taskId, String id, S * @throws IllegalArgumentException if taskId or id is null */ public DeleteTaskPushNotificationConfigParams(String taskId, String id) { - this(taskId, id, ""); + this(taskId, id, null); } /** @@ -97,7 +95,7 @@ public Builder id(String id) { * @param tenant arbitrary tenant (optional) * @return this builder for method chaining */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -112,7 +110,7 @@ public DeleteTaskPushNotificationConfigParams build() { return new DeleteTaskPushNotificationConfigParams( Assert.checkNotNullParam("taskId", taskId), Assert.checkNotNullParam("id", id), - Utils.defaultIfNull(tenant,"")); + tenant); } } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java index 0d2bfc453..d405c21c7 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java @@ -3,7 +3,6 @@ import org.a2aproject.sdk.util.Assert; -import org.a2aproject.sdk.util.Utils; import org.jspecify.annotations.Nullable; /** @@ -18,7 +17,7 @@ * @see TaskPushNotificationConfig for the returned configuration structure * @see A2A Protocol Specification */ -public record GetTaskPushNotificationConfigParams(String taskId, String id, String tenant) { +public record GetTaskPushNotificationConfigParams(String taskId, String id, @Nullable String tenant) { /** * Compact constructor that validates required fields. @@ -31,7 +30,6 @@ public record GetTaskPushNotificationConfigParams(String taskId, String id, Stri public GetTaskPushNotificationConfigParams { Assert.checkNotNullParam("taskId", taskId); Assert.checkNotNullParam("id", id); - Assert.checkNotNullParam("tenant", tenant); } /** @@ -41,7 +39,7 @@ public record GetTaskPushNotificationConfigParams(String taskId, String id, Stri * @param id optional configuration ID to retrieve */ public GetTaskPushNotificationConfigParams(String taskId, String id) { - this(taskId, id, ""); + this(taskId, id, null); } /** @@ -95,7 +93,7 @@ public Builder id(String id) { * @param tenant the tenant * @return this builder for method chaining */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -109,7 +107,7 @@ public GetTaskPushNotificationConfigParams build() { return new GetTaskPushNotificationConfigParams( Assert.checkNotNullParam("taskId", taskId), Assert.checkNotNullParam("id", id), - Utils.defaultIfNull(tenant,"")); + tenant); } } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/ListTaskPushNotificationConfigsParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/ListTaskPushNotificationConfigsParams.java index 1e2389b16..36d0f58fb 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/ListTaskPushNotificationConfigsParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/ListTaskPushNotificationConfigsParams.java @@ -1,7 +1,6 @@ package org.a2aproject.sdk.spec; import org.a2aproject.sdk.util.Assert; -import org.a2aproject.sdk.util.Utils; import org.jspecify.annotations.Nullable; /** @@ -17,7 +16,7 @@ * @see TaskPushNotificationConfig for the configuration structure * @see A2A Protocol Specification */ -public record ListTaskPushNotificationConfigsParams(String id, int pageSize, String pageToken, String tenant) { +public record ListTaskPushNotificationConfigsParams(String id, int pageSize, String pageToken, @Nullable String tenant) { /** * Compact constructor for validation. @@ -30,7 +29,6 @@ public record ListTaskPushNotificationConfigsParams(String id, int pageSize, Str */ public ListTaskPushNotificationConfigsParams { Assert.checkNotNullParam("id", id); - Assert.checkNotNullParam("tenant", tenant); } /** @@ -39,7 +37,7 @@ public record ListTaskPushNotificationConfigsParams(String id, int pageSize, Str * @param id the task identifier (required) */ public ListTaskPushNotificationConfigsParams(String id) { - this(id, 0, "", ""); + this(id, 0, "", null); } /** @@ -117,7 +115,7 @@ public Builder pageToken(String pageToken) { * @param tenant the tenant identifier * @return this builder for method chaining */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -133,7 +131,7 @@ public ListTaskPushNotificationConfigsParams build() { Assert.checkNotNullParam("id", id), pageSize != null ? pageSize : 0, pageToken != null ? pageToken : "", - Utils.defaultIfNull(tenant,"") + tenant ); } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/ListTasksParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/ListTasksParams.java index ee4387d26..20d3d70b6 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/ListTasksParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/ListTasksParams.java @@ -25,7 +25,7 @@ public record ListTasksParams( @Nullable Integer historyLength, @Nullable Instant statusTimestampAfter, @Nullable Boolean includeArtifacts, - String tenant + @Nullable String tenant ) { private static final int MIN_PAGE_SIZE = 1; private static final int MAX_PAGE_SIZE = 100; @@ -42,10 +42,9 @@ public record ListTasksParams( * @param statusTimestampAfter filter by status timestamp * @param includeArtifacts whether to include artifacts * @param tenant the tenant identifier - * @throws InvalidParamsError if tenant is null or if pageSize or historyLength are out of valid range + * @throws InvalidParamsError if pageSize or historyLength are out of valid range */ public ListTasksParams { - Assert.checkNotNullParam("tenant", tenant); // Validate pageSize (1-100) if (pageSize != null && (pageSize < MIN_PAGE_SIZE || pageSize > MAX_PAGE_SIZE)) { throw new InvalidParamsError(null, @@ -62,7 +61,7 @@ public record ListTasksParams( * Default constructor for listing all tasks. */ public ListTasksParams() { - this(null, null, null, null, null, null, null, ""); + this(null, null, null, null, null, null, null, null); } /** @@ -72,7 +71,7 @@ public ListTasksParams() { * @param pageToken Token for pagination */ public ListTasksParams(Integer pageSize, String pageToken) { - this(null, null, pageSize, pageToken, null, null, null, ""); + this(null, null, pageSize, pageToken, null, null, null, null); } /** @@ -215,7 +214,7 @@ public Builder includeArtifacts(Boolean includeArtifacts) { * @param tenant the tenant * @return this builder for method chaining */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -227,7 +226,7 @@ public Builder tenant(String tenant) { */ public ListTasksParams build() { return new ListTasksParams(contextId, status, pageSize, pageToken, historyLength, - statusTimestampAfter, includeArtifacts, tenant == null ? "" : tenant); + statusTimestampAfter, includeArtifacts, tenant); } } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/MessageSendParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/MessageSendParams.java index bb0156d99..0f4bafe5f 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/MessageSendParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/MessageSendParams.java @@ -22,7 +22,7 @@ * @see A2A Protocol Specification */ public record MessageSendParams(Message message, @Nullable MessageSendConfiguration configuration, - @Nullable Map metadata, String tenant) { + @Nullable Map metadata, @Nullable String tenant) { /** * Compact constructor for validation. @@ -35,7 +35,6 @@ public record MessageSendParams(Message message, @Nullable MessageSendConfigurat */ public MessageSendParams { Assert.checkNotNullParam("message", message); - Assert.checkNotNullParam("tenant", tenant); } /** @@ -46,7 +45,7 @@ public record MessageSendParams(Message message, @Nullable MessageSendConfigurat * @param metadata optional metadata */ public MessageSendParams(Message message, @Nullable MessageSendConfiguration configuration, @Nullable Map metadata) { - this(message, configuration, metadata, ""); + this(message, configuration, metadata, null); } /** @@ -115,7 +114,7 @@ public Builder metadata(@Nullable Map metadata) { * @param tenant arbitrary key-value metadata * @return this builder */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -131,7 +130,7 @@ public MessageSendParams build() { Assert.checkNotNullParam("message", message), configuration, metadata, - tenant == null ? "" : tenant); + tenant); } } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/TaskIdParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/TaskIdParams.java index 1bef6d7a9..e92ce222a 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/TaskIdParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/TaskIdParams.java @@ -1,7 +1,6 @@ package org.a2aproject.sdk.spec; import org.a2aproject.sdk.util.Assert; -import org.a2aproject.sdk.util.Utils; import org.jspecify.annotations.Nullable; /** @@ -14,7 +13,7 @@ * @param tenant optional tenant, provided as a path parameter. * @see A2A Protocol Specification */ -public record TaskIdParams(String id, String tenant) { +public record TaskIdParams(String id, @Nullable String tenant) { /** * Compact constructor for validation. @@ -25,7 +24,6 @@ public record TaskIdParams(String id, String tenant) { */ public TaskIdParams { Assert.checkNotNullParam("id", id); - Assert.checkNotNullParam("tenant", tenant); } /** @@ -34,7 +32,7 @@ public record TaskIdParams(String id, String tenant) { * @param id the task identifier (required) */ public TaskIdParams(String id) { - this(id, ""); + this(id, null); } /** @@ -76,7 +74,7 @@ public Builder id(String id) { * @param tenant the tenant identifier * @return this builder for method chaining */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -91,7 +89,7 @@ public Builder tenant(String tenant) { public TaskIdParams build() { return new TaskIdParams( Assert.checkNotNullParam("id", id), - Utils.defaultIfNull(tenant,"") + tenant ); } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/TaskQueryParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/TaskQueryParams.java index 281357929..56c636371 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/TaskQueryParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/TaskQueryParams.java @@ -1,7 +1,6 @@ package org.a2aproject.sdk.spec; import org.a2aproject.sdk.util.Assert; -import org.a2aproject.sdk.util.Utils; import org.jspecify.annotations.Nullable; /** @@ -11,7 +10,7 @@ * @param historyLength the maximum number of items of history for the task to include in the response * @param tenant optional tenant, provided as a path parameter. */ -public record TaskQueryParams(String id, @Nullable Integer historyLength, String tenant) { +public record TaskQueryParams(String id, @Nullable Integer historyLength, @Nullable String tenant) { /** * Compact constructor for validation. @@ -24,7 +23,6 @@ public record TaskQueryParams(String id, @Nullable Integer historyLength, String */ public TaskQueryParams { Assert.checkNotNullParam("id", id); - Assert.checkNotNullParam("tenant", tenant); if (historyLength != null && historyLength < 0) { throw new IllegalArgumentException("Invalid history length"); } @@ -37,7 +35,7 @@ public record TaskQueryParams(String id, @Nullable Integer historyLength, String * @param historyLength maximum number of history items to include (optional) */ public TaskQueryParams(String id, @Nullable Integer historyLength) { - this(id, historyLength, ""); + this(id, historyLength, null); } /** @@ -46,7 +44,7 @@ public TaskQueryParams(String id, @Nullable Integer historyLength) { * @param id the task identifier (required) */ public TaskQueryParams(String id) { - this(id, null, ""); + this(id, null, null); } /** @@ -100,7 +98,7 @@ public Builder historyLength(Integer historyLength) { * @param tenant the tenant identifier * @return this builder for method chaining */ - public Builder tenant(String tenant) { + public Builder tenant(@Nullable String tenant) { this.tenant = tenant; return this; } @@ -115,7 +113,7 @@ public TaskQueryParams build() { return new TaskQueryParams( Assert.checkNotNullParam("id", id), historyLength, - Utils.defaultIfNull(tenant,"") + tenant ); } } diff --git a/spec/src/main/java/org/a2aproject/sdk/util/Utils.java b/spec/src/main/java/org/a2aproject/sdk/util/Utils.java index ce50dc30f..d605f6f14 100644 --- a/spec/src/main/java/org/a2aproject/sdk/util/Utils.java +++ b/spec/src/main/java/org/a2aproject/sdk/util/Utils.java @@ -189,7 +189,8 @@ public static void validateAbsoluteUrl(String url) throws URISyntaxException { /** * Normalizes {@code baseUrl} and {@code cardPath} and concatenates them into a full card URL. * - *

Strips any trailing slash from {@code baseUrl} and ensures {@code cardPath} starts with + *

+ * Strips any trailing slash from {@code baseUrl} and ensures {@code cardPath} starts with * a leading slash before concatenating, so both {@code http://host/base/} and * {@code http://host/base} produce the same result. * @@ -206,7 +207,8 @@ public static String buildCardUrl(String baseUrl, String cardPath) { * Strips any trailing slash and the standard well-known suffix from {@code baseUrl} so that * {@link #buildCardUrl} can append the desired path without doubling it. * - *

Only {@link #DEFAULT_AGENT_CARD_PATH} is stripped; custom paths are never inferred + *

+ * Only {@link #DEFAULT_AGENT_CARD_PATH} is stripped; custom paths are never inferred * from the URL structure. * * @param baseUrl the URL to strip @@ -222,7 +224,8 @@ public static String stripWellKnownSuffix(String baseUrl) { /** * Builds a base URL by combining a raw base URL string with an optional tenant path. * - *

Normalizes trailing slashes on the base URL and validates/normalizes the tenant path. + *

+ * Normalizes trailing slashes on the base URL and validates/normalizes the tenant path. * * @param baseUrl the base URL string, must not be null * @param tenant the tenant path override, may be null for no tenant @@ -299,17 +302,18 @@ private static void validateTenant(String tenant) { *

* If the provided {@code tenant} parameter is null or blank, the {@code agentTenant} is returned instead. * - * @param agentTenant the default tenant from the agent card, must not be null + * @param agentTenant the default tenant from the agent card, may be null or blank * @param tenant the tenant override from the request, may be null or blank * @return the normalized tenant path * @throws IllegalArgumentException if the tenant is invalid or unsafe */ - private static String extractTenant(String agentTenant, @Nullable String tenant) { - checkNotNullParam("agentTenant", agentTenant); - + private static String extractTenant(@Nullable String agentTenant, @Nullable String tenant) { String tenantPath = tenant; if (tenantPath == null || tenantPath.isBlank()) { - return agentTenant; + tenantPath = agentTenant; + } + if (tenantPath == null || tenantPath.isBlank()) { + return ""; } // Normalize slashes