Skip to content

feat: Add HTTP+JSON (REST) protocol binding client and fix server routes#335

Merged
rokonec merged 6 commits intomainfrom
support-rest
Mar 24, 2026
Merged

feat: Add HTTP+JSON (REST) protocol binding client and fix server routes#335
rokonec merged 6 commits intomainfrom
support-rest

Conversation

@darrelmiller
Copy link
Collaborator

@darrelmiller darrelmiller commented Mar 22, 2026

Summary

Adds a complete HTTP+JSON (REST) protocol binding client alongside the existing JSON-RPC client, and fixes server-side REST routes to match the A2A v1.0 specification.

Verified end-to-end against the AgentBin compatibility dashboard all HTTP+JSON tests now pass.

Closes #278

Usage

Using the factory (recommended)

The factory inspects the agent card's SupportedInterfaces and creates the right client automatically:

// Fetch the agent card (e.g. from /.well-known/agent.json)
AgentCard card = await GetAgentCardAsync();

// Create a client  the factory picks the best available protocol binding
IA2AClient client = A2AClientFactory.Create(card);

// Use the client normally  the protocol binding is transparent
var response = await client.SendMessageAsync(new SendMessageRequest
{
    Message = new Message
    {
        MessageId = Guid.NewGuid().ToString(),
        Role = Role.User,
        Parts = [Part.FromText("Hello, agent!")]
    }
});

How the factory selects the client

  1. The factory walks the PreferredBindings list in order (default: ["HTTP+JSON", "JSONRPC"])
  2. For each preferred binding, it checks whether the agent card has a matching AgentInterface (case-insensitive)
  3. It creates the corresponding client pointed at the URL from that interface:
    • "HTTP+JSON" A2AHttpJsonClient (REST)
    • "JSONRPC" A2AClient (JSON-RPC)
  4. If no match is found, it throws A2AException

Overriding the preference

// Prefer JSON-RPC even if the agent supports both
var options = new A2AClientOptions
{
    PreferredBindings = [ProtocolBindingNames.JsonRpc, ProtocolBindingNames.HttpJson]
};

IA2AClient client = A2AClientFactory.Create(card, options: options);

Using a specific client directly

If you know which binding you want, skip the factory:

// REST client
var restClient = new A2AHttpJsonClient(new Uri("https://agent.example.com/a2a/v1"));

// JSON-RPC client (unchanged from before)
var rpcClient = new A2AClient(new Uri("https://agent.example.com/a2a/v1"));

New files

  • A2AHttpJsonClient Full IA2AClient implementation using REST endpoints per spec Section 5.3
  • A2AClientFactory Static factory that selects the protocol binding from an AgentCard's supported interfaces, with configurable preference ordering
  • A2AClientOptions Options class with PreferredBindings list (default: HTTP+JSON first, JSON-RPC fallback)
  • ProtocolBindingNames Constants for "HTTP+JSON" and "JSONRPC"
  • Unit tests for the REST client (18 tests), factory type selection (10 tests), and factory behavioral/URL wiring (5 tests)

Server-side fixes

  • Removed /v1/ prefix from all REST routes (version lives in AgentInterface.Url, not route paths)
  • Changed SubscribeToTask from GET to POST per spec Section 5.3
  • Server hosts wanting a version prefix can use the path parameter: MapHttpA2A(handler, card, path: "/v1")

Design decisions

  • Separate client classes A2AClient (JSON-RPC) and A2AHttpJsonClient (REST) both implement IA2AClient. Callers can use the factory for automatic selection or instantiate directly.
  • Factory pattern A2AClientFactory.Create(agentCard, options?) matches AgentCard.SupportedInterfaces against the preferred bindings list, case-insensitively.
  • Backward compatible A2AClient is completely unchanged. Existing code continues to work as-is.
  • Error mapping per spec Section 5.4: 404TaskNotFound, 400InvalidRequest, 409TaskNotCancelable, 415ContentTypeNotSupported, 502InvalidAgentResponse

REST endpoint mapping (spec Section 5.3)

Method Route Operation
POST /message:send SendMessage
POST /message:stream SendStreamingMessage (SSE)
GET /tasks/{id} GetTask
GET /tasks ListTasks
POST /tasks/{id}:cancel CancelTask
POST /tasks/{id}:subscribe SubscribeToTask (SSE)
POST /tasks/{id}/pushNotificationConfigs CreatePushNotificationConfig
GET /tasks/{id}/pushNotificationConfigs/{configId} GetPushNotificationConfig
GET /tasks/{id}/pushNotificationConfigs ListPushNotificationConfigs
DELETE /tasks/{id}/pushNotificationConfigs/{configId} DeletePushNotificationConfig
GET /extendedAgentCard GetExtendedAgentCard

Testing

All 1,244 existing tests pass (300 A2A.UnitTests + 60 AspNetCore + 262 V0_3, 2 TFMs). 33 new tests added.

darrelmiller and others added 2 commits March 21, 2026 12:14
- A2AHttpJsonClient: full IA2AClient implementation using REST endpoints
  per spec Section 5.3 (no /v1/ prefix; version is in AgentInterface.Url)
- Routes match spec: /message:send, /tasks/{id}, /extendedAgentCard, etc.
- SubscribeToTask uses POST per spec method mapping table
- A2AClientFactory: selects binding from AgentCard.SupportedInterfaces
  based on caller preferences (default: HTTP+JSON first, JSONRPC fallback)
- A2AClientOptions: configurable PreferredBindings list
- ProtocolBindingNames: constants for HTTP+JSON and JSONRPC bindings
- 28 unit tests covering all REST client methods, factory selection,
  error handling, query parameter building, and SSE streaming

Addresses #278

Co-authored-by: Copilot <[email protected]>
Server routes now match the A2A spec Section 5.3 method mapping:
- Removed /v1/ prefix from all routes (version is in AgentInterface.Url)
- Changed SubscribeToTask from MapGet to MapPost per spec
- Updated XML docs to reflect spec-compliant routes

Co-authored-by: Copilot <[email protected]>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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 significantly enhances the A2A client and server capabilities by introducing a new HTTP+JSON (REST) protocol binding client and a factory for intelligent client selection. It also refines the server-side REST routes to ensure full compliance with the A2A v1.0 specification, improving interoperability and adherence to standards. These changes allow for more flexible communication with A2A agents while maintaining compatibility with previous implementations.

Highlights

  • New HTTP+JSON (REST) Client: A complete client implementation (A2AHttpJsonClient) has been added for communicating with A2A agents using the HTTP+JSON (REST) protocol binding, complementing the existing JSON-RPC client.
  • Dynamic Client Selection Factory: A new A2AClientFactory has been introduced, allowing for dynamic selection of the appropriate IA2AClient (either HTTP+JSON or JSON-RPC) based on an AgentCard's supported interfaces and configurable preferences via A2AClientOptions.
  • Server-Side REST Route Alignment: Server-side REST routes in A2AEndpointRouteBuilderExtensions.cs have been updated to strictly adhere to the A2A v1.0 specification. This includes removing the /v1/ prefix from all routes and changing the SubscribeToTask endpoint from a GET to a POST request.
  • Enhanced Error Mapping: The new HTTP+JSON client includes robust error mapping for common HTTP status codes (e.g., 404, 400, 409, 415, 502) to specific A2AErrorCode values, improving error handling and clarity.
  • Backward Compatibility: The existing A2AClient (JSON-RPC) remains unchanged, ensuring that existing codebases continue to function without modification.
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.

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 introduces a comprehensive HTTP+JSON (REST) client and aligns the server-side routes with the A2A v1.0 specification. The changes are well-structured, with a new factory for client creation, separate client implementations for REST and JSON-RPC, and extensive unit tests. My review focuses on improving maintainability, consistency, and test robustness in the new client and factory code. I've suggested making a switch statement more explicit, using a helper function for consistency, adding documentation to a Dispose method, and enhancing test assertions.

Factory tests now make actual requests through a mock handler to verify:
- HTTP+JSON client uses URL from matching AgentInterface
- JSON-RPC client uses URL from matching AgentInterface
- Default preference selects HTTP+JSON URL, not JSON-RPC URL
- Explicit JSON-RPC preference selects RPC URL, not REST URL
- Case-insensitive binding match uses correct URL

Co-authored-by: Copilot <[email protected]>
@darrelmiller darrelmiller changed the title Add HTTP+JSON (REST) protocol binding client and fix server routes feat: Add HTTP+JSON (REST) protocol binding client and fix server routes Mar 23, 2026
Copy link
Collaborator

@rokonec rokonec left a comment

Choose a reason for hiding this comment

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

Review: PR #335 — HTTP+JSON REST Protocol Binding

The goal is correct and well-aligned with the spec — implementing the HTTP+JSON/REST binding (Section 11) is essential for v1.0. URL patterns, HTTP methods, SSE streaming, factory pattern, and test coverage are strong. Build is clean, all 33 new tests pass on both TFMs.

However, several spec-compliance issues need attention before merge.

Summary: 3 CRITICAL, 7 MAJOR, 5 MINOR

Pre-existing issues surfaced by this PR (not blocking, but worth separate follow-up):

  • Server MapA2AExceptionToHttpResult maps TaskNotCancelable → 400 (spec: 409), ContentTypeNotSupported → 422 (spec: 415), InvalidAgentResponse falls to 500 (spec: 502). See Section 5.4.
  • Server Results.Problem() calls don't include RFC 9457 type URIs required by Section 11.6 (e.g. https://a2a-protocol.org/errors/task-not-found).

…ings

The A2A spec defines protocolBinding as an open-form string with three
built-in values (JSONRPC, GRPC, HTTP+JSON) and support for custom
bindings. Replace the hardcoded switch with a registration dictionary:

- Add A2AClientFactory.Register(binding, factory) for custom bindings
- Built-in HTTP+JSON and JSONRPC bindings are pre-registered
- Add ProtocolBindingNames.Grpc constant for the third spec binding
- Matching is case-insensitive, consistent with existing behavior
- Clear error message guides devs to Register when binding has no factory
- Use BuildQueryString helper in GetTaskAsync for consistency
- Add remarks to Dispose explaining no-op behavior

Co-authored-by: Copilot <[email protected]>
Copy link
Collaborator

@rokonec rokonec left a comment

Choose a reason for hiding this comment

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

The goal is correct and well-aligned with the spec — implementing the HTTP+JSON/REST binding (Section 11) is essential for v1.0. URL patterns, HTTP methods, SSE streaming, factory pattern, and test coverage are strong. Build is clean, all 33 new tests pass on both TFMs.

However, several spec-compliance issues need attention before merge.

Pre-existing issues surfaced by this PR (not blocking, but worth separate follow-up):
Server MapA2AExceptionToHttpResult maps TaskNotCancelable → 400 (spec: 409), ContentTypeNotSupported → 422 (spec: 415), InvalidAgentResponse falls to 500 (spec: 502). See Section 5.4.
Server Results.Problem() calls don't include RFC 9457 type URIs required by Section 11.6 (e.g. https://a2a-protocol.org/errors/task-not-found).

darrelmiller and others added 2 commits March 23, 2026 12:25
Review fixes:
- C1: Serialize TaskState using JsonStringEnumMemberName values
  (TASK_STATE_WORKING) instead of C# names (Working) for interop
- N2: Add statusTimestampAfter and includeArtifacts to ListTasks query
- M2: Send CancelTaskRequest.Metadata as JSON body when non-null
- N4: Extract shared SSE streaming logic into PostStreamingCoreAsync
- M6: Add error mapping tests for 409, 415, 502 status codes

A2A-Version header:
- Both A2AClient (JSON-RPC) and A2AHttpJsonClient (REST) now send
  A2A-Version: 1.0 on every outgoing HTTP request per spec Section 11.2
- Refactored all HTTP calls to use SendAsync with explicit request
  messages to enable consistent header injection

Co-authored-by: Copilot <[email protected]>
- parse google.rpc.Status error responses per spec Section 11.6 (AIP-193)
- respect agent's supportedInterfaces preference order per spec Section 8.3
- document /card endpoint as SDK-specific convenience (not in spec Section 11.3)
- add A2AErrorResponse model registered in JsonContext for AOT support

:test_tube: - Generated by Copilot
Copy link
Collaborator

@rokonec rokonec left a comment

Choose a reason for hiding this comment

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

It is good to go.
(I have addressed few missing concerns as well)

@rokonec rokonec added this pull request to the merge queue Mar 24, 2026
Merged via the queue into main with commit 2376b83 Mar 24, 2026
6 checks passed
@rokonec rokonec deleted the support-rest branch March 24, 2026 16:45
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.

A2AClient: Support for HTTP+JSON/REST

2 participants