Skip to content

feat(a2acompat): add HTTP+JSON REST support to a2av0 compat layer#280

Merged
yarolegovich merged 8 commits intoa2aproject:mainfrom
brucearctor:feat/rest-compat
Mar 24, 2026
Merged

feat(a2acompat): add HTTP+JSON REST support to a2av0 compat layer#280
yarolegovich merged 8 commits intoa2aproject:mainfrom
brucearctor:feat/rest-compat

Conversation

@brucearctor
Copy link
Copy Markdown
Contributor

@brucearctor brucearctor commented Mar 15, 2026

Summary

Adds NewRESTHandler and NewRESTTransport to a2acompat/a2av0, completing the compat layer's support for the v0.3 HTTP+JSON protocol binding.

Closes #269

Background

The a2acompat/a2av0 package already provides NewJSONRPCHandler and NewJSONRPCTransport for v0.3 JSON-RPC compatibility. However, Google's Agent Engine and Python ADK use the HTTP+JSON (REST) transport binding of the v0.3 spec, which was not covered. This PR adds the missing REST half.

Changes

a2acompat/a2av0/rest_server.go (new)

  • NewRESTHandler(handler a2asrv.RequestHandler, opts ...) — an http.Handler that exposes the same URL routes as a2asrv.NewRESTHandler but accepts/returns v0.3 JSON payloads.
  • Routes: POST /message:send, POST /message:stream, GET /tasks/{id}, GET /tasks, POST /tasks/{id}:cancel, POST /tasks/{id}:subscribe, and push notification config CRUD.
  • All type translation reuses the existing conversions.go helpers (ToV1* / FromV1*).
  • SSE streaming events are serialised as bare v0.3 event JSON (no StreamResponse wrapper).

a2acompat/a2av0/rest_client.go (new)

  • NewRESTTransport(cfg RESTTransportConfig) — an a2aclient.Transport that calls a v0.3 HTTP+JSON server and converts responses to v1.0 types.
  • NewRESTTransportFactory(cfg) — factory for use with a2aclient auto-discovery.
  • Mirrors the structure of the existing jsonrpc_client.go.

a2acompat/a2av0/rest_compat_test.go (new)

  • Server tests: send message, get task, streaming, extension header propagation.
  • Client tests: round-trip send message and get task against a fake v0.3 HTTP server.

Testing

go test -race ./a2acompat/...
ok  github.com/a2aproject/a2a-go/v2/a2acompat/a2av0  (14 tests, 0 failures)

Full suite (go test ./...) passes with no regressions.

Adds NewRESTHandler (http.Handler) and NewRESTTransport (a2aclient.Transport)
to a2acompat/a2av0, allowing v0.3 HTTP+JSON clients (e.g. Python ADK Agent
Engine deployments) to communicate with v1.0 Go servers, and vice versa.

The new REST handler serves the same URL routes as a2asrv.NewRESTHandler
but accepts and returns v0.3-compatible JSON payloads, reusing all existing
type converters from conversions.go.

The client transport translates v1.0 client calls to v0.3 HTTP+JSON
requests and converts the v0.3 responses back to v1.0 types.

Fixes: a2aproject#269
@gemini-code-assist
Copy link
Copy Markdown
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 a2acompat layer by integrating full HTTP+JSON REST support for the v0.3 protocol. This addition bridges the gap between v1.0 Go clients and servers and existing v0.3 deployments, such as Google's Agent Engine and Python ADK, ensuring seamless communication and broader interoperability without requiring changes to legacy systems.

Highlights

  • HTTP+JSON REST Support for v0.3: Added comprehensive support for the v0.3 HTTP+JSON protocol binding within the a2acompat layer, completing the compatibility for legacy systems.
  • New REST Server Implementation: Introduced a2acompat/a2av0/rest_server.go which provides an http.Handler to expose v1.0 A2A services via v0.3 HTTP+JSON routes, including message sending, streaming, task management, and push notification configuration.
  • New REST Client Implementation: Implemented a2acompat/a2av0/rest_client.go to enable v1.0 Go clients to communicate with v0.3 HTTP+JSON servers, translating v1.0 client calls to v0.3 HTTP requests and responses.
  • Extensive Compatibility Testing: Included a2acompat/a2av0/rest_compat_test.go with server-side tests for message handling, task operations, streaming, and extension headers, as well as client-side round-trip tests against a fake v0.3 HTTP server.
Changelog
  • a2acompat/a2av0/rest_client.go
    • Added NewRESTTransport to create an a2aclient.Transport for v0.3 HTTP+JSON servers.
    • Implemented NewRESTTransportFactory for use with a2aclient auto-discovery.
    • Designed to translate v1.0 client calls to v0.3 HTTP requests and convert v0.3 responses back to v1.0 types.
  • a2acompat/a2av0/rest_compat_test.go
    • Added server tests for SendMessage, GetTask, extension header propagation, and StreamMessage.
    • Included client round-trip tests for SendMessage and GetTask against a fake v0.3 HTTP server.
  • a2acompat/a2av0/rest_server.go
    • Added NewRESTHandler to serve A2A protocol over HTTP+JSON v0.3.
    • Implemented handlers for POST /message:send, POST /message:stream, GET /tasks/{id}, GET /tasks, POST /tasks/{id}:cancel, POST /tasks/{id}:subscribe, and push notification config CRUD operations.
    • Ensured all type translations reuse existing conversions.go helpers.
    • Configured SSE streaming events to be serialized as bare v0.3 event JSON.
Activity
  • The pull request was created to add HTTP+JSON REST support to the a2av0 compatibility layer.
  • Initial implementation of rest_client.go, rest_server.go, and rest_compat_test.go was completed.
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
Copy Markdown
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

The pull request introduces a new a2acompat/a2av0 package, providing a compatibility layer for A2A Go clients and servers to interact with v0.3 REST endpoints. This includes a RESTTransport for v1.0 clients to make v0.3 requests and a RESTHandler for v1.0 servers to process v0.3 requests, along with comprehensive unit tests. Review feedback primarily focuses on improving code style and API consistency: several instances in rest_compat_test.go should use the a2a.NewMessage constructor instead of direct struct literals to adhere to the repository's style guide, and in rest_server.go, the FromV1TaskPushConfigs function should ensure an empty slice is returned for JSON serialization consistency, preventing null responses for empty lists.

- Use a2a.NewMessage constructor in test mocks per style guide
- Return empty slice instead of nil in handleListTaskPushConfigs
  to avoid null JSON serialization
@yarolegovich
Copy link
Copy Markdown
Member

yarolegovich commented Mar 16, 2026

JSON parsing is incorrect, 0.3 REST was using lower snake case.
I'd rather avoid duplicating the types, can we maybe implement it as a post-processing key transform?

@brucearctor
Copy link
Copy Markdown
Contributor Author

brucearctor commented Mar 16, 2026

JSON parsing is incorrect, 0.3 REST was using lower snake case. I'd rather avoid duplicating the types, can we maybe implement it as a post-processing key transform?

Thanks for initial review. Agree - json parsing def was using wrong case. On it

brucearctor and others added 3 commits March 16, 2026 07:40
v0.3 REST uses snake_case JSON keys (message_id, task_id, etc.) while
the legacy Go types have camelCase JSON tags. Instead of duplicating
the types, we add a post-processing JSON key transform layer:

- marshalSnakeCase: marshal to JSON then convert all keys camelCase→snake_case
- unmarshalSnakeCase: convert keys snake_case→camelCase then unmarshal

Both the REST server and client now use these transforms for all
JSON encoding/decoding, including SSE streaming events.
Copy link
Copy Markdown
Member

@yarolegovich yarolegovich left a comment

Choose a reason for hiding this comment

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

sorry for delaying the review, a busy month

return nil, err
}
// Transform snake_case response to camelCase for legacy unmarshal.
camelData, err := transformJSONKeys(rawResult, snakeToCamel)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looks like we're doing the second pass here, it's already called in doRequest->unmarshalSnakeCase

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in latest commit.

func NewRESTTransportFactory(cfg RESTTransportConfig) a2aclient.TransportFactory {
return a2aclient.TransportFactoryFn(func(ctx context.Context, card *a2a.AgentCard, iface *a2a.AgentInterface) (a2aclient.Transport, error) {
if cfg.URL == "" {
cfg.URL = iface.URL
Copy link
Copy Markdown
Member

@yarolegovich yarolegovich Mar 23, 2026

Choose a reason for hiding this comment

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

I know this is copied from jsonrpc, but can see a bug here now. captured config gets mutated which might cause problems when transport factory is called multiple times

upd. fixed in main for jsonrpc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I fixed in this part of code. Please advise if hoping for diff.

func writeSnakeCaseJSON(ctx context.Context, rw http.ResponseWriter, v any) {
data, err := marshalSnakeCase(v)
if err != nil {
log.Error(ctx, "failed to encode response", err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we should probably writeRESTCompatError(ctx, rw, fmt.Errorf("failed to marshal: %w, err), a2a.TaskID(""))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed

- rest_client.go: fix double snake-to-camel transform in SendMessage by
  using sendRequest directly instead of doRequest (which already calls
  unmarshalSnakeCase)
- rest_client.go: fix config mutation bug in NewRESTTransportFactory by
  copying cfg before mutating URL (matches jsonrpc fix on main)
- rest_server.go: improve writeSnakeCaseJSON error handling to return
  a proper error response via writeRESTCompatError instead of only
  logging
The rest.Error struct uses unexported httpStatus field with an
HTTPStatus() getter method. Updated writeRESTCompatError to use
the correct accessor.
@brucearctor
Copy link
Copy Markdown
Contributor Author

sorry for delaying the review, a busy month

I def understand. No worries at all. Have been glad for some interesting things to dig into. Not just busywork :-)

@yarolegovich yarolegovich merged commit bac00e7 into a2aproject:main Mar 24, 2026
4 checks passed
yarolegovich pushed a commit that referenced this pull request Mar 24, 2026
🤖 I have created a release *beep* *boop*
---


## [2.0.1](v2.0.0...v2.0.1)
(2026-03-24)


### Features

* **a2acompat:** add HTTP+JSON REST support to a2av0 compat layer
([#280](#280))
([bac00e7](bac00e7))
* agent executor cleaner
([#276](#276))
([9c95980](9c95980))


### Bug Fixes

* ensure correct concurrent cancellation results
([#287](#287))
([d0f8981](d0f8981)),
closes [#245](#245)


### Documentation

* **a2aclient:** add Example_* test functions for pkg.go.dev
documentation ([#263](#263))
([f8dae97](f8dae97))
* update diagrams, docs and instructions
([#289](#289))
([ed33c7a](ed33c7a))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

[Feat]: rest for a2acompat

2 participants