fix: relax A2A.V0_3.AgentMessage kind/messageId requirements - #435
fix: relax A2A.V0_3.AgentMessage kind/messageId requirements#435chopmob-cloud wants to merge 5 commits into
Conversation
…ization A2AEvent.Kind was marked [JsonRequired], but presence of the discriminator is already enforced independently by BaseKindDiscriminatorConverter.Read for polymorphic deserialization (through A2AEvent or A2AResponse). The attribute only mattered for concretely-typed deserialization, where the converter never runs, e.g. AgentTaskStatus.Message : AgentMessage. Some v0.3 producers omit kind there, and deserialization failed even though the property already has a correct default from the constructor. Part of a2aproject#396.
Some v0.3 producers return status.message without messageId (a discrepancy between the .proto and JSON Schema definitions of v0.3). [JsonRequired] rejected the whole response as a result. Defaults to a freshly generated id, matching messageId being a stable identifier rather than an empty placeholder. Fixes a2aproject#396.
…ageId) Deserializes the exact payload shape from the issue report: a v0.3 task response whose status.message omits both kind and messageId. Asserts both fields fall back to sensible defaults instead of failing deserialization.
There was a problem hiding this comment.
Code Review
This pull request removes the [JsonRequired] attribute from the Kind property in A2AEvent and the MessageId property in AgentMessage to prevent deserialization failures when these properties are omitted by some producers. Additionally, MessageId now defaults to a newly generated GUID, and a unit test has been added to verify the changes. The feedback suggests optimizing the MessageId property using lazy initialization to avoid unnecessary allocations when the property is provided in the JSON payload.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Per review: generating the default in the auto-property initializer ran on every AgentMessage construction, including deserialization where the JSON already carries a messageId, discarding the generated id immediately. Uses a backing field and a lazy getter instead, so the default is only generated if the property is actually read without having been set.
Adds MessageId_WhenNotSet_IsLazyAndUniquePerInstance (two separately constructed messages get distinct generated ids, and repeated reads on the same instance return the same cached value) and MessageId_WhenSetFromJson_IsPreservedExactly (an explicit messageId in the payload is not overwritten by the lazy default).
Fixes #396.
Problem
Real-world v0.3 agents return
status.messagepayloads that omitkindandmessageId, a discrepancy between the.protoand JSON Schema definitions ofv0.3.
A2A.V0_3.AgentMessagemarked both[JsonRequired], so the whole taskresponse failed to deserialize with
JSON deserialization for type 'A2A.V0_3.AgentMessage' was missing required properties including: 'kind', 'messageId'., even though the outer"kind": "task"discriminator was handledcorrectly.
Fix
Kind(declared on the sharedA2AEventbase): removed[JsonRequired].Presence of the discriminator is already enforced independently by
BaseKindDiscriminatorConverter.Readfor polymorphic deserialization(through
A2AEventorA2AResponse). The attribute only mattered for theconcretely-typed path, where the converter never runs, exactly the case here
(
AgentTaskStatus.Messageis declared as concreteAgentMessage?, not thepolymorphic base). Verified the existing
A2AEvent_Deserialize_MissingKind_Throwsand
A2AEvent_Deserialize_UnknownKind_Throws_A2AExceptiontests, which exercisethe polymorphic path, are unaffected: they still throw via the converter's own
presence check, independent of the attribute.
MessageId: removed[JsonRequired]. Falls back to a lazily generated id(per review, a backing field with a lazy getter, so the default is only
generated if the property is read without having been set, not on every
construction) instead of an empty string, so a message deserialized without
one still has a usable, unique identifier.
Tests
Adds
tests/A2A.V0_3.UnitTests/GitHubIssues/Issue396.cs, mirroring the existingGitHubIssues/Issue160.csconvention:Issue_396_Passes: the exact payload shape from the issue report deserializes successfully.MessageId_WhenNotSet_IsLazyAndUniquePerInstance: two separately constructed messages get distinct generated ids; repeated reads on the same instance return the same cached value.MessageId_WhenSetFromJson_IsPreservedExactly: an explicitmessageIdin the payload is never overwritten by the lazy default.Testing performed
No local .NET SDK in my primary environment, so built and tested on a separate
machine against a fresh clone of
main(commit8fe65cfaa6):missing required properties including: 'kind', 'messageId'). Applied the fix, confirmed it passes.dotnet build/dotnet test --framework net10.0, rootA2A.slnx, matching thebuildSamplesCI job): all 4 test projects passed, 827/827 tests.dotnet build ./src/A2ALibs.slnx --framework net8.0(buildLibsnet8.0 leg): succeeded, 0 warnings, 0 errors.