Skip to content

Conversation

@jmesnil
Copy link
Collaborator

@jmesnil jmesnil commented Nov 27, 2025

The payload of push notifications is encapsulated by the kind of payload as specified in § 4.3.3. Push Notification Payload

This commit only works for task payload and will required more work to support additional payloads (tracked by #490).

This fixes #491

Description

Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Follow the CONTRIBUTING Guide.
  • Make your Pull Request title in the https://www.conventionalcommits.org/ specification.
    • Important Prefixes for release-please:
      • fix: which represents bug fixes, and correlates to a SemVer patch.
      • feat: represents a new feature, and correlates to a SemVer minor.
      • feat!:, or fix!:, refactor!:, etc., which represent a breaking change (indicated by the !) and will result in a SemVer major.
  • Ensure the tests pass
  • Appropriate READMEs were updated (if necessary)

Fixes #491🦕

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jmesnil, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors how push notification payloads are serialized and deserialized to conform with the A2A protocol's specification for payload encapsulation. It introduces a new utility method to wrap StreamingEventKind objects, starting with task payloads, inside a JSON object keyed by their kind. This ensures that push notifications are sent in a standardized, structured format, and corresponding test cases have been updated to reflect this change.

Highlights

  • Payload Encapsulation: Implements the encapsulation of push notification payloads within a "kind" field, adhering to the A2A protocol specification (§ 4.3.3).
  • New Serialization Utility: Introduces a Utils.marshalFrom(StreamingEventKind kind) method to handle the structured serialization of StreamingEventKind objects, wrapping them in a JSON object with their kind as the key.
  • Task Payload Focus: This change specifically applies to task payloads, with future work planned to support additional payload types (tracked by [Feat]: Push Notification Payload can also be message, statusUpdate or artifactUpdate #490).
  • Test Updates: Adjusts existing tests to correctly parse and handle the newly encapsulated push notification payload format using JsonFactory and JsonParser.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request encapsulates the push notification payload with a kind field, aligning with the A2A protocol specification. The implementation introduces a Utils.marshalFrom method to handle the serialization, and updates the server and test code to accommodate this new structure. My review focuses on improving the test code's maintainability by addressing duplicated logic and suggesting a more robust parsing strategy. I've also pointed out a few unused imports that can be cleaned up.

@jmesnil jmesnil force-pushed the 491_push_notification_payload branch from 8d20f65 to 101ddf3 Compare November 27, 2025 12:33
@jmesnil jmesnil changed the title [#491] Encapsulate the payload of Push notifications bug: Encapsulate the payload of Push notifications Nov 27, 2025
@jmesnil jmesnil force-pushed the 491_push_notification_payload branch 2 times, most recently from 1d36bdc to be38665 Compare November 27, 2025 12:37
@jmesnil jmesnil changed the title bug: Encapsulate the payload of Push notifications fix: Encapsulate the payload of Push notifications Nov 27, 2025
@jmesnil jmesnil force-pushed the 491_push_notification_payload branch from be38665 to 084f2f2 Compare November 27, 2025 12:38
@jmesnil
Copy link
Collaborator Author

jmesnil commented Nov 27, 2025

/gemini review

The payload of push notifications is encapsulated by the
`kind` of payload as specified in [§ 4.3.3. Push Notification Payload](https://a2a-protocol.org/latest/specification/#433-push-notification-payload)

This commit only works for `task` payload and will required more work to support additional payloads (tracked by a2aproject#490).

This fixes a2aproject#491
Signed-off-by: Jeff Mesnil <[email protected]>
@jmesnil jmesnil force-pushed the 491_push_notification_payload branch from 084f2f2 to aa41de5 Compare November 27, 2025 12:48
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly encapsulates the push notification payload by wrapping it in a JSON object with a kind field, as required by the specification. The introduction of Utils.marshalFrom centralizes this serialization logic effectively. My review includes suggestions to improve the test code by refactoring the duplicated payload-unwrapping logic into a new, more robust utility method. This will enhance maintainability and make the test deserialization logic less brittle.

Comment on lines 201 to +206
@Override
public A2AHttpResponse post() throws IOException, InterruptedException {
tasks.add(Utils.OBJECT_MAPPER.readValue(body, Task.TYPE_REFERENCE));
JsonNode root = Utils.OBJECT_MAPPER.readTree(body);
// This will need to be updated for #490 to unmarshall based on the kind of payload
JsonNode taskNode = root.elements().next();
Task task = Utils.OBJECT_MAPPER.treeToValue(taskNode, Task.TYPE_REFERENCE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code for deserializing the wrapped task has a couple of areas for improvement:

  1. Robustness: root.elements().next() will throw a NoSuchElementException if the JSON object is empty. It's better to validate that the object contains exactly one field, as per the specification, for example by checking root.size().

  2. Code Duplication: This logic is duplicated in PushNotificationSenderTest.java.

To address both points and improve maintainability, you could extract this logic into a new, more robust helper method in io.a2a.util.Utils. This method would be the inverse of marshalFrom. For example:

// In Utils.java
public static <T> T unmarshalWrappedFrom(String data, TypeReference<T> typeRef) throws JsonProcessingException {
    JsonNode root = OBJECT_MAPPER.readTree(data);
    if (root.size() != 1) {
        throw new com.fasterxml.jackson.databind.exc.MismatchedInputException(null, "Expected a single field in the JSON payload, but found " + root.size());
    }
    JsonNode payloadNode = root.elements().next();
    return OBJECT_MAPPER.treeToValue(payloadNode, typeRef);
}

Then you could simplify this block to just:

tasks.add(Utils.unmarshalWrappedFrom(body, Task.TYPE_REFERENCE));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code will be refined for #490 when additional kind of payload will be supported and will follow these guidelines

Comment on lines 80 to +85
try {
Task task = Utils.OBJECT_MAPPER.readValue(body, Task.TYPE_REFERENCE);
JsonNode root = Utils.OBJECT_MAPPER.readTree(body);
// This assumes there is always one field in the outer JSON object.
// This will need to be updated for #490 to unmarshall based on the kind of payload
JsonNode taskNode = root.elements().next();
Task task = Utils.OBJECT_MAPPER.treeToValue(taskNode, Task.TYPE_REFERENCE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code for deserializing the wrapped task has a couple of areas for improvement:

  1. Robustness: root.elements().next() will throw a NoSuchElementException if the JSON object is empty. It's better to validate that the object contains exactly one field, as per the specification, for example by checking root.size().

  2. Code Duplication: This logic is duplicated in AbstractA2ARequestHandlerTest.java.

To address both points and improve maintainability, you could extract this logic into a new, more robust helper method in io.a2a.util.Utils. This method would be the inverse of marshalFrom. For example:

// In Utils.java
public static <T> T unmarshalWrappedFrom(String data, TypeReference<T> typeRef) throws JsonProcessingException {
    JsonNode root = OBJECT_MAPPER.readTree(data);
    if (root.size() != 1) {
        throw new com.fasterxml.jackson.databind.exc.MismatchedInputException(null, "Expected a single field in the JSON payload, but found " + root.size());
    }
    JsonNode payloadNode = root.elements().next();
    return OBJECT_MAPPER.treeToValue(payloadNode, typeRef);
}

Then you could simplify this block to just:

tasks.add(Utils.unmarshalWrappedFrom(body, Task.TYPE_REFERENCE));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem, will be refined for #490

@jmesnil
Copy link
Collaborator Author

jmesnil commented Nov 27, 2025

/gemini summary

@gemini-code-assist
Copy link
Contributor

Summary of Changes

This pull request implements a crucial step towards standardizing push notification payloads by encapsulating them according to the A2A protocol specification. It introduces a new utility for structured serialization of StreamingEventKind objects, initially focusing on task payloads, and updates existing test cases to correctly handle this new format. This change ensures that notifications are sent and received in a consistent, specified structure.

Highlights

  • Payload Encapsulation: Push notification payloads are now encapsulated within a "kind" field, aligning with the A2A protocol specification (§ 4.3.3).
  • New Serialization Utility: A new Utils.marshalFrom(StreamingEventKind kind) method has been introduced to serialize StreamingEventKind objects by wrapping them in a JSON object with their kind as the key.
  • Task Payload Specific: This change currently applies only to task payloads, with further work planned to support other payload types (tracked by [Feat]: Push Notification Payload can also be message, statusUpdate or artifactUpdate #490).
  • Updated Deserialization in Tests: Test files (AbstractA2ARequestHandlerTest.java and PushNotificationSenderTest.java) have been updated to correctly deserialize the newly encapsulated task payloads using JsonNode.
Changelog
  • server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java
    • Reordered imports for better organization.
    • Updated the serialization of Task objects for push notifications to use the new Utils.marshalFrom method, ensuring proper encapsulation.
  • server-common/src/test/java/io/a2a/server/requesthandlers/AbstractA2ARequestHandlerTest.java
    • Added necessary imports for java.util.Map, JsonNode, and JUnit lifecycle annotations.
    • Modified the mock HTTP client's post() method to correctly parse the encapsulated Task payload using JsonNode for deserialization.
  • server-common/src/test/java/io/a2a/server/tasks/PushNotificationSenderTest.java
    • Reordered imports and added JsonNode import.
    • Updated the mock HTTP client's post() method to deserialize the encapsulated Task payload using JsonNode.
  • spec/src/main/java/io/a2a/util/Utils.java
    • Added java.util.Map and StreamingEventKind imports.
    • Introduced a new static method public static String marshalFrom(StreamingEventKind kind) which serializes a StreamingEventKind object by wrapping it in a JSON object with its kind as the key.
Activity
  • gemini-code-assist[bot] provided an initial summary of the PR.
  • gemini-code-assist[bot] posted several review comments, highlighting code duplication and suggesting improvements for JSON deserialization robustness in test files, proposing a shared utility method.
  • jmesnil acknowledged the suggestions for improving deserialization logic, noting that these refinements are planned for a future task ([Feat]: Push Notification Payload can also be message, statusUpdate or artifactUpdate #490) when additional payload kinds are supported.
  • jmesnil also requested additional /gemini review and /gemini summary commands.

@jmesnil
Copy link
Collaborator Author

jmesnil commented Nov 28, 2025

I'm not sure that the TCK tests run by the GitHub action is correctly reporting its status.

When I look at the logs for this PR, I have failures (eg https://github.com/a2aproject/a2a-java/actions/runs/19736796539/job/56550560615#step:11:28) but thetck-tests is reporting success.

I'll have a look but it seems that the TCK is expecting to get directly the payload from the push notifications and is not following the spec that mandates to have it wrapped in its kind (task, message, etc.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Payload of push notifications is not properly encapsulated

1 participant