feat: support v0.3 agent card parsing in A2ACardResolver - #417
feat: support v0.3 agent card parsing in A2ACardResolver#417darrelmiller wants to merge 3 commits into
Conversation
When fetching an agent card, if deserialization fails due to missing v1.0 required properties (supportedInterfaces, skills, etc.), attempt to parse it as a v0.3 card and upcast to a valid v1.0 AgentCard in memory. v0.3 cards have a top-level 'url' and optional 'preferredTransport' instead of 'supportedInterfaces'. The upcast maps these to a single AgentInterface entry with the appropriate protocol binding (defaulting to JSONRPC). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a fallback mechanism in A2ACardResolver to support parsing older v0.3 agent cards and upcasting them to v1.0 when v1.0 deserialization fails. It reads the response as a byte array, attempts standard deserialization, and falls back to manual parsing via UpcastV03AgentCard upon encountering specific JsonException errors. Feedback highlights two key areas for improvement: first, relying on ex.Message.Contains(...) for exception filtering is fragile due to localization and runtime changes, and catching any JsonException to attempt the upcast is recommended instead; second, calling GetString() on non-string JSON properties during manual parsing can throw an exception, so verifying ValueKind beforehand is advised.
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.
| Name = root.TryGetProperty("name", out var name) ? name.GetString() ?? "" : "", | ||
| Description = root.TryGetProperty("description", out var desc) ? desc.GetString() ?? "" : "", | ||
| Version = root.TryGetProperty("version", out var ver) ? ver.GetString() ?? "0.3" : "0.3", |
There was a problem hiding this comment.
If any of these properties exist in the JSON but are not strings (e.g., they are numbers, booleans, or objects), calling GetString() will throw an InvalidOperationException. It is safer to verify that the property value kind is JsonValueKind.String before calling GetString().
Name = root.TryGetProperty("name", out var name) && name.ValueKind == JsonValueKind.String ? name.GetString() ?? "" : "",
Description = root.TryGetProperty("description", out var desc) && desc.ValueKind == JsonValueKind.String ? desc.GetString() ?? "" : "",
Version = root.TryGetProperty("version", out var ver) && ver.ValueKind == JsonValueKind.String ? ver.GetString() ?? "0.3" : "0.3",|
Read this against the v0.3 and v1.0 AgentCard models since we work with both surfaces. The two-pass buffer-then-upcast structure is right, and buffering the raw bytes turns out to matter beyond retry (point 3). Three observations: 1. The upcast drops the card's security configuration. The v0.3 model carries 2. On the message-substring filter the bot flagged: the upcast already carries its own guard. 3. Dropping Happy to test this against a live v0.3 card if that is useful. |
- Remove fragile exception message filtering; catch all JsonException - Add root object guard to prevent InvalidOperationException on non-object JSON - Require top-level protocolVersion as v0.3 discriminator - Add ValueKind checks on all GetString() calls - Update log message to reflect broader catch semantics Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Verified the hardening commit independently against current Two things worth recording, since I checked them against the models directly: The discriminator is sound. One item from the earlier note this commit does not touch: the upcast still omits Happy to run this against a live v0.3 card carrying security config if that is useful. |
| }; | ||
|
|
||
| // Also include additionalInterfaces if present (a v0.3 extension) | ||
| if (root.TryGetProperty("additionalInterfaces", out var addlInterfaces) && addlInterfaces.ValueKind == JsonValueKind.Array) |
There was a problem hiding this comment.
Nice direction on the v0.3 fallback. One spec-shape issue here: v0.3 additionalInterfaces entries are { "transport": "...", "url": "..." }, while the v1 AgentInterface type expects protocolBinding and protocolVersion.
So a valid v0.3 card with additional HTTP+JSON/GRPC interfaces can fail this fallback, or lose the transport binding. Could we parse this as the v0.3 shape explicitly and map:
transport → ProtocolBinding
url → Url
top-level protocolVersion → ProtocolVersion
A focused test with additionalInterfaces: [{ "transport": "HTTP+JSON", "url": "..." }] would catch this.
| // Extract common fields | ||
| var card = new AgentCard | ||
| { | ||
| Name = root.TryGetProperty("name", out var name) && name.ValueKind == JsonValueKind.String ? name.GetString() ?? "" : "", |
There was a problem hiding this comment.
several v0.3 AgentCard fields have v1 destinations but are not preserved here, especially provider, securitySchemes, security, supportsAuthenticatedExtendedCard, signatures, and skill-level security.
If the goal is only “resolve a usable endpoint from a v0.3 card,” this is fine but should be called out as scope. If the goal is “support v0.3 agent card parsing,” I think we should preserve those fields, especially the security metadata.
Summary
Adds backward-compatible parsing of v0.3 agent cards in A2ACardResolver.
Problem
v0.3 agents expose agent cards without supportedInterfaces (a required field in v1.0), causing deserialization failures when a v1.0 client resolves a v0.3 agent's card.
Solution
A2ACardResolver now performs two-pass deserialization:
The upcast:
Testing