diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index e2fb7253d..69cc8a178 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -18,7 +18,7 @@ jobs:
matrix:
java-version: ['17', '21', '25']
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
@@ -29,7 +29,7 @@ jobs:
run: mvn -B package --file pom.xml -fae
- name: Upload Test Reports
if: failure()
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: surefire-reports-java-${{ matrix.java-version }}
path: |
@@ -39,7 +39,7 @@ jobs:
if-no-files-found: warn
- name: Upload Build Logs
if: failure()
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: build-logs-java-${{ matrix.java-version }}
path: |
diff --git a/.github/workflows/build-with-release-profile.yml b/.github/workflows/build-with-release-profile.yml
index 129833307..70737fca9 100644
--- a/.github/workflows/build-with-release-profile.yml
+++ b/.github/workflows/build-with-release-profile.yml
@@ -1,12 +1,13 @@
-name: Build with '-Prelease'
-
-# Simply runs the build with -Prelease to avoid nasty surprises when running the release-to-maven-central workflow.
+name: Build with '-Prelease' (Trigger)
+# Trigger workflow for release profile build verification.
+# This workflow runs on PRs and uploads the PR info for the workflow_run job.
+# The actual build with secrets happens in build-with-release-profile-run.yml
+# See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests
on:
- # Handle all branches for now
+ pull_request: # Changed from pull_request_target for security
push:
- pull_request_target:
workflow_dispatch:
# Only run the latest job
@@ -15,7 +16,7 @@ concurrency:
cancel-in-progress: true
jobs:
- build:
+ trigger:
# Only run this job for the main repository, not for forks
if: github.repository == 'a2aproject/a2a-java'
runs-on: ubuntu-latest
@@ -23,39 +24,27 @@ jobs:
contents: read
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- java-version: '17'
- distribution: 'temurin'
- cache: maven
-
- # Use secrets to import GPG key
- - name: Import GPG key
- uses: crazy-max/ghaction-import-gpg@v6
- with:
- gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }}
- passphrase: ${{ secrets.GPG_SIGNING_PASSPHRASE }}
-
- # Create settings.xml for Maven since it needs the 'central-a2asdk-temp' server.
- # Populate wqith username and password from secrets
- - name: Create settings.xml
+ - name: Prepare PR info
run: |
- mkdir -p ~/.m2
- echo "central-a2asdk-temp${{ secrets.CENTRAL_TOKEN_USERNAME }}${{ secrets.CENTRAL_TOKEN_PASSWORD }}" > ~/.m2/settings.xml
-
- # Build with the same settings as the deploy job
- # -s uses the settings file we created.
- - name: Build with same arguments as deploy job
- run: >
- mvn -B install
- -s ~/.m2/settings.xml
- -P release
- -DskipTests
- -Drelease.auto.publish=true
- env:
- # GPG passphrase is set as an environment variable for the gpg plugin to use
- GPG_PASSPHRASE: ${{ secrets.GPG_SIGNING_PASSPHRASE }}
\ No newline at end of file
+ mkdir -p pr_info
+
+ # Store PR number for workflow_run job
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
+ echo ${{ github.event.number }} > pr_info/pr_number
+ echo ${{ github.event.pull_request.head.sha }} > pr_info/pr_sha
+ echo ${{ github.event.pull_request.head.ref }} > pr_info/pr_ref
+ else
+ # For push events, store the commit sha
+ echo ${{ github.sha }} > pr_info/pr_sha
+ echo ${{ github.ref }} > pr_info/pr_ref
+ fi
+
+ echo "Event: ${{ github.event_name }}"
+ cat pr_info/*
+
+ - name: Upload PR info
+ uses: actions/upload-artifact@v6
+ with:
+ name: pr-info
+ path: pr_info/
+ retention-days: 1
diff --git a/.github/workflows/cloud-deployment-example.yml b/.github/workflows/cloud-deployment-example.yml
index f52ea5111..57a97a638 100644
--- a/.github/workflows/cloud-deployment-example.yml
+++ b/.github/workflows/cloud-deployment-example.yml
@@ -16,8 +16,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
- uses: actions/checkout@v4
-
+ uses: actions/checkout@v6
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
@@ -27,7 +26,7 @@ jobs:
- name: Install Kind
run: |
- curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64
+ curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
kind version
@@ -58,7 +57,8 @@ jobs:
mvn test-compile exec:java \
-Dexec.mainClass="io.a2a.examples.cloud.A2ACloudExampleClient" \
-Dexec.classpathScope=test \
- -Dagent.url=http://localhost:8080
+ -Dagent.url=http://localhost:8080 \
+ -Dci.mode=true
- name: Show diagnostics on failure
if: failure()
diff --git a/examples/cloud-deployment/k8s/02-kafka.yaml b/examples/cloud-deployment/k8s/02-kafka.yaml
index 044aeb1ac..a4ad8eb0f 100644
--- a/examples/cloud-deployment/k8s/02-kafka.yaml
+++ b/examples/cloud-deployment/k8s/02-kafka.yaml
@@ -33,8 +33,8 @@ metadata:
strimzi.io/kraft: enabled
spec:
kafka:
- version: 4.0.0
- metadataVersion: 4.0-IV0
+ version: 4.2.0
+ metadataVersion: 4.2-IV0
listeners:
- name: plain
port: 9092
diff --git a/examples/cloud-deployment/scripts/deploy.sh b/examples/cloud-deployment/scripts/deploy.sh
index e267f3302..448457221 100755
--- a/examples/cloud-deployment/scripts/deploy.sh
+++ b/examples/cloud-deployment/scripts/deploy.sh
@@ -177,6 +177,11 @@ if ! kubectl get namespace kafka > /dev/null 2>&1; then
fi
if ! kubectl get crd kafkas.kafka.strimzi.io > /dev/null 2>&1; then
+# Keep this around in case we need to hardcode operator version again in the future
+# echo "Installing Strimzi operator... at https://github.com/strimzi/strimzi-kafka-operator/releases/download/0.50.1/strimzi-cluster-operator-0.50.1.yaml"
+# curl -sL 'https://github.com/strimzi/strimzi-kafka-operator/releases/download/0.50.1/strimzi-cluster-operator-0.50.1.yaml' \
+# | sed 's/namespace: .*/namespace: kafka/' \
+# | kubectl apply -f - -n kafka
echo "Installing Strimzi operator..."
kubectl create -f 'https://strimzi.io/install/latest?namespace=kafka' -n kafka
@@ -212,6 +217,22 @@ echo ""
echo "Deploying PostgreSQL..."
kubectl apply -f ../k8s/01-postgres.yaml
echo "Waiting for PostgreSQL to be ready..."
+
+# Wait for pod to be created (StatefulSet takes time to create pod)
+for i in {1..30}; do
+ if kubectl get pod -l app=postgres -n a2a-demo 2>/dev/null | grep -q postgres; then
+ echo "PostgreSQL pod found, waiting for ready state..."
+ break
+ fi
+ if [ $i -eq 30 ]; then
+ echo -e "${RED}ERROR: PostgreSQL pod not created after 30 seconds${NC}"
+ kubectl get statefulset -n a2a-demo
+ exit 1
+ fi
+ sleep 1
+done
+
+# Now wait for pod to be ready
kubectl wait --for=condition=Ready pod -l app=postgres -n a2a-demo --timeout=120s
echo -e "${GREEN}✓ PostgreSQL deployed${NC}"
diff --git a/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java b/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java
index 7a4f8e76f..1f14a61ae 100644
--- a/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java
+++ b/reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java
@@ -100,14 +100,7 @@ public void invokeJSONRPCHandler(@Body String body, RoutingContext rc) {
throw new JSONParseError(e.getMessage());
}
- // Validate jsonrpc field
- com.google.gson.JsonElement jsonrpcElement = node.get("jsonrpc");
- if (jsonrpcElement == null || !jsonrpcElement.isJsonPrimitive()
- || !JSONRPCMessage.JSONRPC_VERSION.equals(jsonrpcElement.getAsString())) {
- throw new InvalidRequestError("Invalid JSON-RPC request: missing or invalid 'jsonrpc' field");
- }
-
- // Validate id field (must be string, number, or null — not an object or array)
+ // Extract id field early so error responses can include it
com.google.gson.JsonElement idElement = node.get("id");
if (idElement != null && !idElement.isJsonNull() && !idElement.isJsonPrimitive()) {
throw new InvalidRequestError("Invalid JSON-RPC request: 'id' must be a string, number, or null");
@@ -117,6 +110,13 @@ public void invokeJSONRPCHandler(@Body String body, RoutingContext rc) {
requestId = idPrimitive.isNumber() ? idPrimitive.getAsLong() : idPrimitive.getAsString();
}
+ // Validate jsonrpc field
+ com.google.gson.JsonElement jsonrpcElement = node.get("jsonrpc");
+ if (jsonrpcElement == null || !jsonrpcElement.isJsonPrimitive()
+ || !JSONRPCMessage.JSONRPC_VERSION.equals(jsonrpcElement.getAsString())) {
+ throw new InvalidRequestError("Invalid JSON-RPC request: missing or invalid 'jsonrpc' field");
+ }
+
// Validate method field
com.google.gson.JsonElement methodElement = node.get("method");
if (methodElement == null || !methodElement.isJsonPrimitive()) {
diff --git a/spec/src/main/java/io/a2a/json/JsonUtil.java b/spec/src/main/java/io/a2a/json/JsonUtil.java
index 56dd3f310..ab8a67f84 100644
--- a/spec/src/main/java/io/a2a/json/JsonUtil.java
+++ b/spec/src/main/java/io/a2a/json/JsonUtil.java
@@ -23,10 +23,13 @@
import com.google.gson.JsonSyntaxException;
import com.google.gson.ToNumberPolicy;
import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import io.a2a.spec.APIKeySecurityScheme;
import io.a2a.spec.EventKind;
+import io.a2a.spec.JSONRPCResponse;
import io.a2a.spec.ContentTypeNotSupportedError;
import io.a2a.spec.DataPart;
import io.a2a.spec.FileContent;
@@ -76,7 +79,10 @@ private static GsonBuilder createBaseGsonBuilder() {
.registerTypeAdapter(Message.Role.class, new RoleTypeAdapter())
.registerTypeAdapter(Part.Kind.class, new PartKindTypeAdapter())
.registerTypeHierarchyAdapter(FileContent.class, new FileContentTypeAdapter())
- .registerTypeHierarchyAdapter(SecurityScheme.class, new SecuritySchemeTypeAdapter());
+ .registerTypeHierarchyAdapter(SecurityScheme.class, new SecuritySchemeTypeAdapter())
+ .registerTypeAdapter(void.class, new VoidTypeAdapter())
+ .registerTypeAdapter(Void.class, new VoidTypeAdapter())
+ .registerTypeAdapterFactory(new JSONRPCResponseTypeAdapterFactory());
}
/**
@@ -87,7 +93,6 @@ private static GsonBuilder createBaseGsonBuilder() {
*
* Used throughout the SDK for consistent JSON serialization and deserialization.
*
- * @see GsonFactory#createGson()
*/
public static final Gson OBJECT_MAPPER = createBaseGsonBuilder()
.registerTypeHierarchyAdapter(Part.class, new PartTypeAdapter())
@@ -725,8 +730,7 @@ public void write(JsonWriter out, StreamingEventKind value) throws java.io.IOExc
}
@Override
- public @Nullable
- StreamingEventKind read(JsonReader in) throws java.io.IOException {
+ public @Nullable StreamingEventKind read(JsonReader in) throws java.io.IOException {
if (in.peek() == com.google.gson.stream.JsonToken.NULL) {
in.nextNull();
return null;
@@ -875,8 +879,7 @@ public void write(JsonWriter out, FileContent value) throws java.io.IOException
}
@Override
- public @Nullable
- FileContent read(JsonReader in) throws java.io.IOException {
+ public @Nullable FileContent read(JsonReader in) throws java.io.IOException {
if (in.peek() == com.google.gson.stream.JsonToken.NULL) {
in.nextNull();
return null;
@@ -901,4 +904,98 @@ FileContent read(JsonReader in) throws java.io.IOException {
}
}
+ static class VoidTypeAdapter extends TypeAdapter {
+
+
+ @Override
+ @SuppressWarnings("resource")
+ public void write(final JsonWriter out, final Void value) throws java.io.IOException {
+ out.nullValue();
+ }
+
+ @Override
+ public @Nullable Void read(final JsonReader in) throws java.io.IOException {
+ in.skipValue();
+ return null;
+ }
+
+ }
+
+ /**
+ * Gson TypeAdapterFactory for serializing {@link JSONRPCResponse} subclasses.
+ *
+ * JSON-RPC 2.0 requires that:
+ *
+ * - {@code result} MUST be present (possibly null) on success, and MUST NOT be present on error
+ * - {@code error} MUST be present on error, and MUST NOT be present on success
+ *
+ * Gson's default null-field-skipping behavior would omit {@code "result": null} for Void responses,
+ * so this factory writes the fields explicitly to comply with the spec.
+ */
+ static class JSONRPCResponseTypeAdapterFactory implements TypeAdapterFactory {
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public @Nullable TypeAdapter create(Gson gson, TypeToken type) {
+ if (!JSONRPCResponse.class.isAssignableFrom(type.getRawType())) {
+ return null;
+ }
+
+ TypeAdapter delegateAdapter = gson.getDelegateAdapter(this, type);
+ TypeAdapter errorAdapter = gson.getAdapter(JSONRPCError.class);
+
+ return new TypeAdapter() {
+ @Override
+ public void write(JsonWriter out, T value) throws java.io.IOException {
+ if (value == null) {
+ out.nullValue();
+ return;
+ }
+
+ JSONRPCResponse> response = (JSONRPCResponse>) value;
+
+ out.beginObject();
+ out.name("jsonrpc").value(response.getJsonrpc());
+
+ Object id = response.getId();
+ out.name("id");
+ if (id == null) {
+ out.nullValue();
+ } else if (id instanceof Number n) {
+ out.value(n.longValue());
+ } else {
+ out.value(id.toString());
+ }
+
+ JSONRPCError error = response.getError();
+ if (error != null) {
+ out.name("error");
+ errorAdapter.write(out, error);
+ } else {
+ out.name("result");
+ Object result = response.getResult();
+ if (result == null) {
+ // JsonWriter.nullValue() skips both name+value when serializeNulls=false,
+ // so we must temporarily enable it to write "result":null as required
+ // by JSON-RPC 2.0.
+ boolean prev = out.getSerializeNulls();
+ out.setSerializeNulls(true);
+ out.nullValue();
+ out.setSerializeNulls(prev);
+ } else {
+ TypeAdapter resultAdapter = gson.getAdapter(result.getClass());
+ resultAdapter.write(out, result);
+ }
+ }
+
+ out.endObject();
+ }
+
+ @Override
+ public T read(JsonReader in) throws java.io.IOException {
+ return delegateAdapter.read(in);
+ }
+ };
+ }
+ }
}
diff --git a/spec/src/main/java/io/a2a/spec/AgentCapabilities.java b/spec/src/main/java/io/a2a/spec/AgentCapabilities.java
index 1de51d5f9..2b4fbcee5 100644
--- a/spec/src/main/java/io/a2a/spec/AgentCapabilities.java
+++ b/spec/src/main/java/io/a2a/spec/AgentCapabilities.java
@@ -4,6 +4,11 @@
/**
* Defines optional capabilities supported by an agent.
+ *
+ * @param streaming whether the agent supports streaming responses
+ * @param pushNotifications whether the agent supports push notifications
+ * @param stateTransitionHistory whether the agent supports state transition history
+ * @param extensions optional list of protocol extensions supported by the agent
*/
public record AgentCapabilities(boolean streaming, boolean pushNotifications, boolean stateTransitionHistory,
List extensions) {
diff --git a/spec/src/main/java/io/a2a/spec/AgentCard.java b/spec/src/main/java/io/a2a/spec/AgentCard.java
index 2574f5425..7963f6c57 100644
--- a/spec/src/main/java/io/a2a/spec/AgentCard.java
+++ b/spec/src/main/java/io/a2a/spec/AgentCard.java
@@ -10,6 +10,25 @@
* The AgentCard is a self-describing manifest for an agent. It provides essential
* metadata including the agent's identity, capabilities, skills, supported
* communication methods, and security requirements.
+ *
+ * @param name the human-readable name of the agent
+ * @param description a human-readable description of the agent
+ * @param url the URL of the agent's primary endpoint
+ * @param provider the organization or individual providing the agent
+ * @param version the version of the agent
+ * @param documentationUrl optional URL to the agent's documentation
+ * @param capabilities the capabilities supported by the agent
+ * @param defaultInputModes the default input content modes supported by the agent
+ * @param defaultOutputModes the default output content modes supported by the agent
+ * @param skills the list of skills the agent can perform
+ * @param supportsAuthenticatedExtendedCard whether the agent supports an authenticated extended card
+ * @param securitySchemes the security scheme definitions available for this agent
+ * @param security the security requirements for accessing the agent
+ * @param iconUrl optional URL to the agent's icon
+ * @param additionalInterfaces additional transport/URL combinations for interacting with the agent
+ * @param preferredTransport the preferred transport protocol
+ * @param protocolVersion the A2A protocol version supported by the agent
+ * @param signatures optional JWS signatures of the agent card
*/
public record AgentCard(String name, String description, String url, AgentProvider provider,
String version, String documentationUrl, AgentCapabilities capabilities,
diff --git a/spec/src/main/java/io/a2a/spec/AgentCardSignature.java b/spec/src/main/java/io/a2a/spec/AgentCardSignature.java
index 70a92cd57..35714554e 100644
--- a/spec/src/main/java/io/a2a/spec/AgentCardSignature.java
+++ b/spec/src/main/java/io/a2a/spec/AgentCardSignature.java
@@ -8,6 +8,10 @@
/**
* Represents a JWS signature of an AgentCard.
* This follows the JSON format of an RFC 7515 JSON Web Signature (JWS).
+ *
+ * @param header the JWS unprotected header
+ * @param protectedHeader the JWS protected header (base64url-encoded)
+ * @param signature the JWS signature value (base64url-encoded)
*/
public record AgentCardSignature(Map header, @SerializedName("protected")String protectedHeader,
String signature) {
diff --git a/spec/src/main/java/io/a2a/spec/AgentExtension.java b/spec/src/main/java/io/a2a/spec/AgentExtension.java
index 053855976..76f1c9579 100644
--- a/spec/src/main/java/io/a2a/spec/AgentExtension.java
+++ b/spec/src/main/java/io/a2a/spec/AgentExtension.java
@@ -6,6 +6,11 @@
/**
* A declaration of a protocol extension supported by an Agent.
+ *
+ * @param description a human-readable description of the extension
+ * @param params optional parameters for configuring the extension
+ * @param required whether the extension is required for the agent to function
+ * @param uri the URI identifying the extension
*/
public record AgentExtension (String description, Map params, boolean required, String uri) {
diff --git a/spec/src/main/java/io/a2a/spec/AgentInterface.java b/spec/src/main/java/io/a2a/spec/AgentInterface.java
index 0b2e8d8b0..ff30c913d 100644
--- a/spec/src/main/java/io/a2a/spec/AgentInterface.java
+++ b/spec/src/main/java/io/a2a/spec/AgentInterface.java
@@ -5,8 +5,10 @@
/**
* Declares a combination of a target URL and a transport protocol for interacting with the agent.
+ *
+ * @param transport the transport protocol identifier (e.g., "jsonrpc", "grpc")
+ * @param url the endpoint URL for this transport
*/
-
public record AgentInterface(String transport, String url) {
public AgentInterface {
Assert.checkNotNullParam("transport", transport);
diff --git a/spec/src/main/java/io/a2a/spec/AgentProvider.java b/spec/src/main/java/io/a2a/spec/AgentProvider.java
index 1d50b699e..eea07b24e 100644
--- a/spec/src/main/java/io/a2a/spec/AgentProvider.java
+++ b/spec/src/main/java/io/a2a/spec/AgentProvider.java
@@ -4,6 +4,9 @@
/**
* Represents the service provider of an agent.
+ *
+ * @param organization the name of the organization providing the agent
+ * @param url the URL of the provider's website or documentation
*/
public record AgentProvider(String organization, String url) {
diff --git a/spec/src/main/java/io/a2a/spec/AgentSkill.java b/spec/src/main/java/io/a2a/spec/AgentSkill.java
index b397f6248..4ec0c6911 100644
--- a/spec/src/main/java/io/a2a/spec/AgentSkill.java
+++ b/spec/src/main/java/io/a2a/spec/AgentSkill.java
@@ -7,6 +7,15 @@
/**
* The set of skills, or distinct capabilities, that the agent can perform.
+ *
+ * @param id a unique identifier for the skill
+ * @param name the human-readable name of the skill
+ * @param description a human-readable description of the skill
+ * @param tags tags for categorizing or discovering the skill
+ * @param examples example prompts or use cases for the skill
+ * @param inputModes the content modes accepted as input by the skill
+ * @param outputModes the content modes produced as output by the skill
+ * @param security optional security requirements specific to this skill
*/
public record AgentSkill(String id, String name, String description, List tags,
List examples, List inputModes, List outputModes,
diff --git a/spec/src/main/java/io/a2a/spec/Artifact.java b/spec/src/main/java/io/a2a/spec/Artifact.java
index 69d2f0581..bda976da3 100644
--- a/spec/src/main/java/io/a2a/spec/Artifact.java
+++ b/spec/src/main/java/io/a2a/spec/Artifact.java
@@ -7,6 +7,13 @@
/**
* Represents a file, data structure, or other resource generated by an agent during a task.
+ *
+ * @param artifactId a unique identifier for the artifact within the task
+ * @param name optional human-readable name of the artifact
+ * @param description optional human-readable description of the artifact
+ * @param parts the content parts that make up the artifact
+ * @param metadata optional additional metadata associated with the artifact
+ * @param extensions optional list of protocol extension identifiers
*/
public record Artifact(String artifactId, String name, String description, List> parts, Map metadata,
List extensions) {
diff --git a/spec/src/main/java/io/a2a/spec/AuthenticationInfo.java b/spec/src/main/java/io/a2a/spec/AuthenticationInfo.java
index 4f24e3c4c..3ecc368a4 100644
--- a/spec/src/main/java/io/a2a/spec/AuthenticationInfo.java
+++ b/spec/src/main/java/io/a2a/spec/AuthenticationInfo.java
@@ -6,6 +6,9 @@
/**
* The authentication info for an agent.
+ *
+ * @param schemes the list of authentication scheme identifiers
+ * @param credentials optional credentials string for the authentication scheme
*/
public record AuthenticationInfo(List schemes, String credentials) {
diff --git a/spec/src/main/java/io/a2a/spec/AuthorizationCodeOAuthFlow.java b/spec/src/main/java/io/a2a/spec/AuthorizationCodeOAuthFlow.java
index cc5e0ee54..886360b7a 100644
--- a/spec/src/main/java/io/a2a/spec/AuthorizationCodeOAuthFlow.java
+++ b/spec/src/main/java/io/a2a/spec/AuthorizationCodeOAuthFlow.java
@@ -7,6 +7,11 @@
/**
* Defines configuration details for the OAuth 2.0 Authorization Code flow.
+ *
+ * @param authorizationUrl the URL for the authorization endpoint
+ * @param refreshUrl optional URL for obtaining refresh tokens
+ * @param scopes the available scopes mapped to their descriptions
+ * @param tokenUrl the URL for the token endpoint
*/
public record AuthorizationCodeOAuthFlow(String authorizationUrl, String refreshUrl, Map scopes,
String tokenUrl) {
diff --git a/spec/src/main/java/io/a2a/spec/ClientCredentialsOAuthFlow.java b/spec/src/main/java/io/a2a/spec/ClientCredentialsOAuthFlow.java
index 18056681f..9577649e0 100644
--- a/spec/src/main/java/io/a2a/spec/ClientCredentialsOAuthFlow.java
+++ b/spec/src/main/java/io/a2a/spec/ClientCredentialsOAuthFlow.java
@@ -8,6 +8,10 @@
/**
* Defines configuration details for the OAuth 2.0 Client Credentials flow.
+ *
+ * @param refreshUrl optional URL for obtaining refresh tokens
+ * @param scopes the available scopes mapped to their descriptions
+ * @param tokenUrl the URL for the token endpoint
*/
public record ClientCredentialsOAuthFlow(String refreshUrl, Map scopes, String tokenUrl) {
diff --git a/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java b/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java
index 0cb34a38d..4e300a59f 100644
--- a/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java
+++ b/spec/src/main/java/io/a2a/spec/DeleteTaskPushNotificationConfigParams.java
@@ -7,6 +7,10 @@
/**
* Parameters for removing pushNotificationConfiguration associated with a Task.
+ *
+ * @param id the task ID
+ * @param pushNotificationConfigId the ID of the push notification configuration to delete
+ * @param metadata optional additional metadata
*/
public record DeleteTaskPushNotificationConfigParams(String id, String pushNotificationConfigId, Map metadata) {
diff --git a/spec/src/main/java/io/a2a/spec/FileWithBytes.java b/spec/src/main/java/io/a2a/spec/FileWithBytes.java
index 01ccef127..0a5df369b 100644
--- a/spec/src/main/java/io/a2a/spec/FileWithBytes.java
+++ b/spec/src/main/java/io/a2a/spec/FileWithBytes.java
@@ -2,6 +2,10 @@
/**
* Represents a file with its content provided directly as a base64-encoded string.
+ *
+ * @param mimeType the MIME type of the file content
+ * @param name optional name of the file
+ * @param bytes the base64-encoded file content
*/
public record FileWithBytes(String mimeType, String name, String bytes) implements FileContent {
}
diff --git a/spec/src/main/java/io/a2a/spec/FileWithUri.java b/spec/src/main/java/io/a2a/spec/FileWithUri.java
index e1edd4bd2..45514ae04 100644
--- a/spec/src/main/java/io/a2a/spec/FileWithUri.java
+++ b/spec/src/main/java/io/a2a/spec/FileWithUri.java
@@ -2,6 +2,10 @@
/**
* Represents a file with its content located at a specific URI.
+ *
+ * @param mimeType the MIME type of the file content
+ * @param name optional name of the file
+ * @param uri the URI pointing to the file content
*/
public record FileWithUri(String mimeType, String name, String uri) implements FileContent {
}
diff --git a/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java b/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java
index 2836e2065..200a3c3d5 100644
--- a/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java
+++ b/spec/src/main/java/io/a2a/spec/GetTaskPushNotificationConfigParams.java
@@ -8,6 +8,10 @@
/**
* Parameters for fetching a pushNotificationConfiguration associated with a Task.
+ *
+ * @param id the task ID
+ * @param pushNotificationConfigId optional ID of a specific push notification configuration to retrieve
+ * @param metadata optional additional metadata
*/
public record GetTaskPushNotificationConfigParams(String id, @Nullable String pushNotificationConfigId, @Nullable Map metadata) {
diff --git a/spec/src/main/java/io/a2a/spec/ImplicitOAuthFlow.java b/spec/src/main/java/io/a2a/spec/ImplicitOAuthFlow.java
index cd2ef6235..ec76ab318 100644
--- a/spec/src/main/java/io/a2a/spec/ImplicitOAuthFlow.java
+++ b/spec/src/main/java/io/a2a/spec/ImplicitOAuthFlow.java
@@ -7,6 +7,10 @@
/**
* Defines configuration details for the OAuth 2.0 Implicit flow.
+ *
+ * @param authorizationUrl the URL for the authorization endpoint
+ * @param refreshUrl optional URL for obtaining refresh tokens
+ * @param scopes the available scopes mapped to their descriptions
*/
public record ImplicitOAuthFlow(String authorizationUrl, String refreshUrl, Map scopes) {
diff --git a/spec/src/main/java/io/a2a/spec/JSONRPCRequest.java b/spec/src/main/java/io/a2a/spec/JSONRPCRequest.java
index 45e2b6883..69683a764 100644
--- a/spec/src/main/java/io/a2a/spec/JSONRPCRequest.java
+++ b/spec/src/main/java/io/a2a/spec/JSONRPCRequest.java
@@ -6,6 +6,8 @@
/**
* Represents a JSONRPC request.
+ *
+ * @param the type of the request parameters
*/
public abstract sealed class JSONRPCRequest implements JSONRPCMessage permits NonStreamingJSONRPCRequest, StreamingJSONRPCRequest {
diff --git a/spec/src/main/java/io/a2a/spec/JSONRPCResponse.java b/spec/src/main/java/io/a2a/spec/JSONRPCResponse.java
index d4330d843..395483596 100644
--- a/spec/src/main/java/io/a2a/spec/JSONRPCResponse.java
+++ b/spec/src/main/java/io/a2a/spec/JSONRPCResponse.java
@@ -6,6 +6,8 @@
/**
* Represents a JSONRPC response.
+ *
+ * @param the type of the response result
*/
public abstract sealed class JSONRPCResponse implements JSONRPCMessage permits SendStreamingMessageResponse,
GetTaskResponse, CancelTaskResponse, SetTaskPushNotificationConfigResponse, GetTaskPushNotificationConfigResponse,
diff --git a/spec/src/main/java/io/a2a/spec/ListTaskPushNotificationConfigParams.java b/spec/src/main/java/io/a2a/spec/ListTaskPushNotificationConfigParams.java
index 2d04f36ce..179e86172 100644
--- a/spec/src/main/java/io/a2a/spec/ListTaskPushNotificationConfigParams.java
+++ b/spec/src/main/java/io/a2a/spec/ListTaskPushNotificationConfigParams.java
@@ -6,6 +6,9 @@
/**
* Parameters for getting list of pushNotificationConfigurations associated with a Task.
+ *
+ * @param id the task ID
+ * @param metadata optional additional metadata
*/
public record ListTaskPushNotificationConfigParams(String id, Map metadata) {
diff --git a/spec/src/main/java/io/a2a/spec/Message.java b/spec/src/main/java/io/a2a/spec/Message.java
index 9d08cb56c..050e44c53 100644
--- a/spec/src/main/java/io/a2a/spec/Message.java
+++ b/spec/src/main/java/io/a2a/spec/Message.java
@@ -55,6 +55,19 @@ public Message(Role role, List> parts,
this.kind = kind;
}
+ public void check() {
+ Assert.checkNotNullParam("kind", kind);
+ Assert.checkNotNullParam("parts", parts);
+ if (parts.isEmpty()) {
+ throw new IllegalArgumentException("Parts cannot be empty");
+ }
+ Assert.checkNotNullParam("role", role);
+ if (!kind.equals(MESSAGE)) {
+ throw new IllegalArgumentException("Invalid Message");
+ }
+ Assert.checkNotNullParam("messageId", messageId);
+ }
+
public Role getRole() {
return role;
}
diff --git a/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java b/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java
index d44ce494f..cd4888ff4 100644
--- a/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java
+++ b/spec/src/main/java/io/a2a/spec/MessageSendConfiguration.java
@@ -6,7 +6,12 @@
import org.jspecify.annotations.Nullable;
/**
- * Defines configuration options for a `message/send` or `message/stream` request.
+ * Defines configuration options for a {@code message/send} or {@code message/stream} request.
+ *
+ * @param acceptedOutputModes the output content modes the client accepts
+ * @param historyLength optional maximum number of history messages to include
+ * @param pushNotificationConfig optional push notification configuration for task updates
+ * @param blocking whether the request should block until the task completes
*/
public record MessageSendConfiguration(List acceptedOutputModes, Integer historyLength,
PushNotificationConfig pushNotificationConfig, Boolean blocking) {
diff --git a/spec/src/main/java/io/a2a/spec/MessageSendParams.java b/spec/src/main/java/io/a2a/spec/MessageSendParams.java
index 5914ef462..de7e4bfc7 100644
--- a/spec/src/main/java/io/a2a/spec/MessageSendParams.java
+++ b/spec/src/main/java/io/a2a/spec/MessageSendParams.java
@@ -1,5 +1,6 @@
package io.a2a.spec;
+
import java.util.Map;
import io.a2a.util.Assert;
@@ -7,6 +8,10 @@
/**
* Defines the parameters for a request to send a message to an agent. This can be used
* to create a new task, continue an existing one, or restart a task.
+ *
+ * @param message the message to send to the agent
+ * @param configuration optional configuration options for this send request
+ * @param metadata optional additional metadata
*/
public record MessageSendParams(Message message, MessageSendConfiguration configuration,
Map metadata) {
@@ -15,6 +20,11 @@ public record MessageSendParams(Message message, MessageSendConfiguration config
Assert.checkNotNullParam("message", message);
}
+ public void check() {
+ Assert.checkNotNullParam("message", message);
+ message.check();
+ }
+
public static class Builder {
Message message;
MessageSendConfiguration configuration;
diff --git a/spec/src/main/java/io/a2a/spec/NonStreamingJSONRPCRequest.java b/spec/src/main/java/io/a2a/spec/NonStreamingJSONRPCRequest.java
index e969ce08e..66562662d 100644
--- a/spec/src/main/java/io/a2a/spec/NonStreamingJSONRPCRequest.java
+++ b/spec/src/main/java/io/a2a/spec/NonStreamingJSONRPCRequest.java
@@ -2,6 +2,8 @@
/**
* Represents a non-streaming JSON-RPC request.
+ *
+ * @param the type of the request parameters
*/
public abstract sealed class NonStreamingJSONRPCRequest extends JSONRPCRequest permits GetTaskRequest,
CancelTaskRequest, SetTaskPushNotificationConfigRequest, GetTaskPushNotificationConfigRequest,
diff --git a/spec/src/main/java/io/a2a/spec/OAuthFlows.java b/spec/src/main/java/io/a2a/spec/OAuthFlows.java
index 849f84d41..31312b41b 100644
--- a/spec/src/main/java/io/a2a/spec/OAuthFlows.java
+++ b/spec/src/main/java/io/a2a/spec/OAuthFlows.java
@@ -2,6 +2,11 @@
/**
* Defines the configuration for the supported OAuth 2.0 flows.
+ *
+ * @param authorizationCode configuration for the Authorization Code flow
+ * @param clientCredentials configuration for the Client Credentials flow
+ * @param implicit configuration for the Implicit flow
+ * @param password configuration for the Resource Owner Password flow
*/
public record OAuthFlows(AuthorizationCodeOAuthFlow authorizationCode, ClientCredentialsOAuthFlow clientCredentials,
ImplicitOAuthFlow implicit, PasswordOAuthFlow password) {
diff --git a/spec/src/main/java/io/a2a/spec/PasswordOAuthFlow.java b/spec/src/main/java/io/a2a/spec/PasswordOAuthFlow.java
index e5de924cb..58d4b81dd 100644
--- a/spec/src/main/java/io/a2a/spec/PasswordOAuthFlow.java
+++ b/spec/src/main/java/io/a2a/spec/PasswordOAuthFlow.java
@@ -6,6 +6,10 @@
/**
* Defines configuration details for the OAuth 2.0 Resource Owner Password flow.
+ *
+ * @param refreshUrl optional URL for obtaining refresh tokens
+ * @param scopes the available scopes mapped to their descriptions
+ * @param tokenUrl the URL for the token endpoint
*/
public record PasswordOAuthFlow(String refreshUrl, Map scopes, String tokenUrl) {
diff --git a/spec/src/main/java/io/a2a/spec/PushNotificationAuthenticationInfo.java b/spec/src/main/java/io/a2a/spec/PushNotificationAuthenticationInfo.java
index 6263ac990..66d01e523 100644
--- a/spec/src/main/java/io/a2a/spec/PushNotificationAuthenticationInfo.java
+++ b/spec/src/main/java/io/a2a/spec/PushNotificationAuthenticationInfo.java
@@ -5,6 +5,9 @@
/**
* Defines authentication details for a push notification endpoint.
+ *
+ * @param schemes the list of authentication scheme identifiers
+ * @param credentials optional credentials string for the authentication scheme
*/
public record PushNotificationAuthenticationInfo(List schemes, String credentials) {
diff --git a/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java b/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java
index b5a9e1131..18eda5f08 100644
--- a/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java
+++ b/spec/src/main/java/io/a2a/spec/PushNotificationConfig.java
@@ -4,6 +4,11 @@
/**
* Defines the configuration for setting up push notifications for task updates.
+ *
+ * @param url the URL of the push notification endpoint
+ * @param token optional authentication token for the push notification endpoint
+ * @param authentication optional authentication details for the push notification endpoint
+ * @param id optional identifier for this push notification configuration
*/
public record PushNotificationConfig(String url, String token, PushNotificationAuthenticationInfo authentication, String id) {
diff --git a/spec/src/main/java/io/a2a/spec/SendMessageRequest.java b/spec/src/main/java/io/a2a/spec/SendMessageRequest.java
index a58ce0890..a8ec457e5 100644
--- a/spec/src/main/java/io/a2a/spec/SendMessageRequest.java
+++ b/spec/src/main/java/io/a2a/spec/SendMessageRequest.java
@@ -44,6 +44,22 @@ public SendMessageRequest(String jsonrpc, Object id, String method, MessageSendP
this.params = params;
}
+ public void check() {
+ if (jsonrpc == null || jsonrpc.isEmpty()) {
+ throw new IllegalArgumentException("JSON-RPC protocol version cannot be null or empty");
+ }
+ if (jsonrpc != null && !jsonrpc.equals(JSONRPC_VERSION)) {
+ throw new IllegalArgumentException("Invalid JSON-RPC protocol version");
+ }
+ Assert.checkNotNullParam("method", method);
+ if (!method.equals(METHOD)) {
+ throw new IllegalArgumentException("Invalid SendMessageRequest method");
+ }
+ Assert.checkNotNullParam("params", params);
+ Assert.isNullOrStringOrInteger(id);
+ params.check();
+ }
+
public SendMessageRequest(Object id, MessageSendParams params) {
this(JSONRPC_VERSION, id, METHOD, params);
}
diff --git a/spec/src/main/java/io/a2a/spec/SendStreamingMessageRequest.java b/spec/src/main/java/io/a2a/spec/SendStreamingMessageRequest.java
index de3abf950..d0eba2dbc 100644
--- a/spec/src/main/java/io/a2a/spec/SendStreamingMessageRequest.java
+++ b/spec/src/main/java/io/a2a/spec/SendStreamingMessageRequest.java
@@ -1,5 +1,6 @@
package io.a2a.spec;
+import static io.a2a.spec.JSONRPCMessage.JSONRPC_VERSION;
import static io.a2a.util.Utils.defaultIfNull;
import io.a2a.util.Assert;
@@ -33,6 +34,19 @@ public SendStreamingMessageRequest(Object id, MessageSendParams params) {
this(null, id, METHOD, params);
}
+ public void check() {
+ if (jsonrpc != null && !jsonrpc.equals(JSONRPC_VERSION)) {
+ throw new IllegalArgumentException("Invalid JSON-RPC protocol version");
+ }
+ Assert.checkNotNullParam("method", method);
+ if (!method.equals(METHOD)) {
+ throw new IllegalArgumentException("Invalid SendStreamingMessageRequest method");
+ }
+ Assert.checkNotNullParam("params", params);
+ Assert.isNullOrStringOrInteger(id);
+ params.check();
+ }
+
public static class Builder {
private String jsonrpc;
private Object id;
diff --git a/spec/src/main/java/io/a2a/spec/StreamingJSONRPCRequest.java b/spec/src/main/java/io/a2a/spec/StreamingJSONRPCRequest.java
index 9cbb5e4c6..e0b2a6255 100644
--- a/spec/src/main/java/io/a2a/spec/StreamingJSONRPCRequest.java
+++ b/spec/src/main/java/io/a2a/spec/StreamingJSONRPCRequest.java
@@ -2,8 +2,9 @@
/**
* Represents a streaming JSON-RPC request.
+ *
+ * @param the type of the request parameters
*/
-
public abstract sealed class StreamingJSONRPCRequest extends JSONRPCRequest permits TaskResubscriptionRequest,
SendStreamingMessageRequest {
diff --git a/spec/src/main/java/io/a2a/spec/TaskIdParams.java b/spec/src/main/java/io/a2a/spec/TaskIdParams.java
index 096c9a8a0..7a9e9b159 100644
--- a/spec/src/main/java/io/a2a/spec/TaskIdParams.java
+++ b/spec/src/main/java/io/a2a/spec/TaskIdParams.java
@@ -6,6 +6,9 @@
/**
* Defines parameters containing a task ID, used for simple task operations.
+ *
+ * @param id the task ID
+ * @param metadata optional additional metadata
*/
public record TaskIdParams(String id, Map metadata) {
diff --git a/spec/src/main/java/io/a2a/spec/TaskPushNotificationConfig.java b/spec/src/main/java/io/a2a/spec/TaskPushNotificationConfig.java
index 23a7fc0c4..5dfdede6c 100644
--- a/spec/src/main/java/io/a2a/spec/TaskPushNotificationConfig.java
+++ b/spec/src/main/java/io/a2a/spec/TaskPushNotificationConfig.java
@@ -4,6 +4,9 @@
/**
* A container associating a push notification configuration with a specific task.
+ *
+ * @param taskId the ID of the task this configuration is associated with
+ * @param pushNotificationConfig the push notification configuration for the task
*/
public record TaskPushNotificationConfig(String taskId, PushNotificationConfig pushNotificationConfig) {
diff --git a/spec/src/main/java/io/a2a/util/Utils.java b/spec/src/main/java/io/a2a/util/Utils.java
index f374f302e..004b1cdc4 100644
--- a/spec/src/main/java/io/a2a/util/Utils.java
+++ b/spec/src/main/java/io/a2a/util/Utils.java
@@ -24,13 +24,12 @@
*
* Key capabilities:
*
- * - JSON processing with pre-configured {@link Gson}
+ * - JSON processing with pre-configured {@link io.a2a.json.JsonUtil#OBJECT_MAPPER}
* - Null-safe value defaults via {@link #defaultIfNull(Object, Object)}
* - Artifact streaming support via {@link #appendArtifactToTask(Task, TaskArtifactUpdateEvent, String)}
* - Type-safe exception rethrowing via {@link #rethrow(Throwable)}
*
*
- * @see Gson for JSON processing
* @see TaskArtifactUpdateEvent for streaming artifact updates
*/
public class Utils {
@@ -41,7 +40,7 @@ public class Utils {
/**
* Deserializes JSON string into a typed object using Gson.
*
- * This method uses the pre-configured {@link #OBJECT_MAPPER} to parse JSON.
+ * This method uses the pre-configured {@link io.a2a.json.JsonUtil#OBJECT_MAPPER} to parse JSON.
*
* @param the target type
* @param data JSON string to deserialize
diff --git a/spec/src/test/java/io/a2a/spec/JSONRPCErrorSerializationTest.java b/spec/src/test/java/io/a2a/spec/JSONRPCErrorSerializationTest.java
index 9c83f0806..e95be5a5a 100644
--- a/spec/src/test/java/io/a2a/spec/JSONRPCErrorSerializationTest.java
+++ b/spec/src/test/java/io/a2a/spec/JSONRPCErrorSerializationTest.java
@@ -3,9 +3,12 @@
import org.junit.jupiter.api.Test;
import java.util.List;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import io.a2a.json.JsonProcessingException;
import io.a2a.json.JsonUtil;
@@ -44,5 +47,36 @@ record ErrorCase(int code, Class extends JSONRPCError> clazz) {}
}
}
+ @Test
+ @SuppressWarnings("unchecked")
+ void deleteTaskPushNotificationConfigSuccessResponseSerializesResultAsNull() throws JsonProcessingException {
+ DeleteTaskPushNotificationConfigResponse response =
+ new DeleteTaskPushNotificationConfigResponse("req-123");
+
+ String json = JsonUtil.toJson(response);
+ Map map = JsonUtil.fromJson(json, Map.class);
+
+ assertEquals("2.0", map.get("jsonrpc"));
+ assertEquals("req-123", map.get("id"));
+ assertTrue(map.containsKey("result"), "result field must be present in success response");
+ assertEquals(null, map.get("result"), "result must be null for delete response");
+ assertFalse(map.containsKey("error"), "error field must not be present in success response");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void deleteTaskPushNotificationConfigErrorResponseSerializesErrorWithoutResult() throws JsonProcessingException {
+ DeleteTaskPushNotificationConfigResponse response =
+ new DeleteTaskPushNotificationConfigResponse("req-456", new TaskNotFoundError());
+
+ String json = JsonUtil.toJson(response);
+ Map map = JsonUtil.fromJson(json, Map.class);
+
+ assertEquals("2.0", map.get("jsonrpc"));
+ assertEquals("req-456", map.get("id"));
+ assertTrue(map.containsKey("error"), "error field must be present in error response");
+ assertFalse(map.containsKey("result"), "result field must not be present in error response");
+ }
+
}
diff --git a/tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java b/tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java
index 5c085f981..560c4d7f2 100644
--- a/tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java
+++ b/tck/src/main/java/io/a2a/tck/server/AgentExecutorProducer.java
@@ -39,7 +39,7 @@ public void execute(RequestContext context, EventQueue eventQueue) throws JSONRP
}
// Sleep to allow task state persistence before TCK resubscribe test
- if (context.getMessage().getMessageId().startsWith("test-resubscribe-message-id")) {
+ if (context.getMessage().getMessageId() != null && context.getMessage().getMessageId().startsWith("test-resubscribe-message-id")) {
int timeoutMs = Integer.parseInt(System.getenv().getOrDefault("RESUBSCRIBE_TIMEOUT_MS", "3000"));
System.out.println("====> task id starts with test-resubscribe-message-id, sleeping for " + timeoutMs + " ms");
try {
diff --git a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java
index ef577aaa5..853d6978b 100644
--- a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java
+++ b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java
@@ -1,6 +1,7 @@
package io.a2a.transport.jsonrpc.handler;
import static io.a2a.server.util.async.AsyncUtils.createTubeConfig;
+
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
@@ -46,6 +47,7 @@
import io.a2a.spec.TaskPushNotificationConfig;
import io.a2a.spec.TaskResubscriptionRequest;
import io.a2a.server.util.async.Internal;
+import io.a2a.spec.InvalidParamsError;
import mutiny.zero.ZeroPublisher;
@ApplicationScoped
@@ -78,11 +80,14 @@ public JSONRPCHandler(@PublicAgentCard AgentCard agentCard, RequestHandler reque
public SendMessageResponse onMessageSend(SendMessageRequest request, ServerCallContext context) {
try {
+ request.check();
EventKind taskOrMessage = requestHandler.onMessageSend(request.getParams(), context);
return new SendMessageResponse(request.getId(), taskOrMessage);
} catch (JSONRPCError e) {
return new SendMessageResponse(request.getId(), e);
- } catch (Throwable t) {
+ } catch (IllegalArgumentException t) {
+ return new SendMessageResponse(request.getId(), new InvalidParamsError(t.getMessage()));
+ }catch (Throwable t) {
return new SendMessageResponse(request.getId(), new InternalError(t.getMessage()));
}
}
@@ -96,8 +101,8 @@ public Flow.Publisher onMessageSendStream(
request.getId(),
new InvalidRequestError("Streaming is not supported by the agent")));
}
-
try {
+ request.check();
Flow.Publisher publisher =
requestHandler.onMessageSendStream(request.getParams(), context);
// We can't use the convertingProcessor convenience method since that propagates any errors as an error handled