Context
PR #116 added required-extension validation and response header echoing for REST and JSON-RPC server transports.
That change exposed repeated HttpServerResponse setup across transports:
- HTTP status assignment.
- JSON content type header.
- SSE content type and cache-control headers.
A2A-Version response header.
- Optional
A2A-Extensions response header for activated extensions.
- Conversion from transport-level responses into
HttpServerResponse.
The current duplication is small but protocol-sensitive. It is easy to miss a required header when adding a new response path, especially for streaming/SSE responses.
Problem
src/server/rest_server_transport.cpp and src/server/json_rpc_server_transport.cpp both manually construct
HttpServerResponse objects and set common headers. For example:
HttpServerResponse response;
response.status_code = core::http::kStatusOk;
response.headers[std::string(core::http::kContentTypeHeaderName)] =
std::string(core::http::kContentTypeApplicationJson);
response.headers[std::string(core::Version::kHeaderName)] = core::Version::HeaderValue();
AddActivatedExtensionsHeader(activated_extensions, &response);
Similar setup exists for SSE:
HttpServerResponse response;
response.status_code = core::http::kStatusOk;
response.headers["Content-Type"] = "text/event-stream";
response.headers["Cache-Control"] = "no-cache";
response.headers[std::string(core::Version::kHeaderName)] = core::Version::HeaderValue();
This creates three issues:
- Header behavior is duplicated and can drift between REST and JSON-RPC transports.
- Activated extension handling is duplicated through local helper functions.
- Some protocol literals are still written directly instead of using shared constants.
Proposal
Introduce a lightweight HttpServerResponseBuilder for assembling HttpServerResponse values with consistent common
headers.
This should be a builder, not an abstract factory:
- We are building one concrete value type:
HttpServerResponse.
- We do not currently need multiple runtime response creation strategies.
- A virtual factory would add indirection without a demonstrated need.
- The builder keeps construction explicit while removing repeated boilerplate.
Suggested API Shape
The exact API can be adjusted during implementation, but it should stay simple and value-oriented:
auto response = HttpServerResponseBuilder()
.WithStatus(core::http::kStatusOk)
.WithJsonContentType()
.WithA2aVersion()
.WithActivatedExtensions(activated_extensions)
.WithBody(body)
.Build();
For SSE responses:
auto response = HttpServerResponseBuilder()
.WithStatus(core::http::kStatusOk)
.WithSseContentType()
.WithCacheControlNoCache()
.WithA2aVersion()
.WithActivatedExtensions(activated_extensions)
.Build();
For adapting REST transport responses:
auto response = HttpServerResponseBuilder::FromRestResponse(rest_response)
.WithA2aVersion()
.WithActivatedExtensions(activated_extensions)
.Build();
Scope
Add a new server-side builder component, for example:
include/a2a/server/http_server_response_builder.h
src/server/http_server_response_builder.cpp
Use it in:
src/server/rest_server_transport.cpp
src/server/json_rpc_server_transport.cpp
- optionally
src/server/transport_mux.cpp, if it improves consistency without widening the change too much.
The builder should support:
- Setting status code.
- Setting body.
- Setting stream writer.
- Setting or preserving existing headers.
- Adding JSON content type.
- Adding SSE content type.
- Adding
Cache-Control: no-cache.
- Adding
A2A-Version.
- Adding
A2A-Extensions only when activated extensions are non-empty.
- Converting or wrapping an existing
RestResponse.
Constants
Use existing constants where available:
core::http::kContentTypeHeaderName
core::http::kContentTypeApplicationJson
core::http::kContentTypeTextEventStream
core::Version::kHeaderName
core::Extensions::kHeaderName
Add shared constants if they do not exist yet:
Avoid introducing new magic strings in production or test code.
Non-Goals
- Do not introduce a virtual abstract factory.
- Do not add dependency injection for response creation unless a real use case appears.
- Do not move JSON-RPC envelope serialization into the builder.
- Do not move REST error body serialization into the builder.
- Do not change response bodies, HTTP status codes, streaming behavior, or TCK semantics.
- Do not refactor unrelated transport logic.
Acceptance Criteria
- No local
AddActivatedExtensionsHeader helper remains in individual transports.
- Common JSON/SSE response header setup is centralized in
HttpServerResponseBuilder.
- REST and JSON-RPC successful responses still echo activated extensions when required-extension validation succeeds.
- SSE responses still include:
Content-Type: text/event-stream
Cache-Control: no-cache
A2A-Version
A2A-Extensions when activated extensions are present.
- Existing REST and JSON-RPC behavior remains unchanged.
- New or updated unit tests cover the builder.
- Existing functional tests for required-extension behavior continue to pass.
./scripts/verify_changes.sh passes.
- Mandatory TCK conformance remains 100%.
Suggested Tests
Add focused unit tests for HttpServerResponseBuilder:
- JSON response includes status, body, JSON content type, and
A2A-Version.
- SSE response includes status, SSE content type,
Cache-Control: no-cache, and A2A-Version.
- Activated extensions are omitted when the activated extension list is empty.
- Activated extensions are formatted and included when present.
- Existing headers are preserved when wrapping or adapting a transport response.
- Builder can preserve a stream writer when adapting a streaming response.
Update existing transport tests only where needed to verify integration with the builder.
Context
PR #116 added required-extension validation and response header echoing for REST and JSON-RPC server transports.
That change exposed repeated
HttpServerResponsesetup across transports:A2A-Versionresponse header.A2A-Extensionsresponse header for activated extensions.HttpServerResponse.The current duplication is small but protocol-sensitive. It is easy to miss a required header when adding a new response path, especially for streaming/SSE responses.
Problem
src/server/rest_server_transport.cppandsrc/server/json_rpc_server_transport.cppboth manually constructHttpServerResponseobjects and set common headers. For example:Similar setup exists for SSE:
This creates three issues:
Proposal
Introduce a lightweight
HttpServerResponseBuilderfor assemblingHttpServerResponsevalues with consistent commonheaders.
This should be a builder, not an abstract factory:
HttpServerResponse.Suggested API Shape
The exact API can be adjusted during implementation, but it should stay simple and value-oriented:
For SSE responses:
For adapting REST transport responses:
auto response = HttpServerResponseBuilder::FromRestResponse(rest_response) .WithA2aVersion() .WithActivatedExtensions(activated_extensions) .Build();Scope
Add a new server-side builder component, for example:
include/a2a/server/http_server_response_builder.hsrc/server/http_server_response_builder.cppUse it in:
src/server/rest_server_transport.cppsrc/server/json_rpc_server_transport.cppsrc/server/transport_mux.cpp, if it improves consistency without widening the change too much.The builder should support:
Cache-Control: no-cache.A2A-Version.A2A-Extensionsonly when activated extensions are non-empty.RestResponse.Constants
Use existing constants where available:
core::http::kContentTypeHeaderNamecore::http::kContentTypeApplicationJsoncore::http::kContentTypeTextEventStreamcore::Version::kHeaderNamecore::Extensions::kHeaderNameAdd shared constants if they do not exist yet:
Cache-Controlno-cacheAvoid introducing new magic strings in production or test code.
Non-Goals
Acceptance Criteria
AddActivatedExtensionsHeaderhelper remains in individual transports.HttpServerResponseBuilder.Content-Type: text/event-streamCache-Control: no-cacheA2A-VersionA2A-Extensionswhen activated extensions are present../scripts/verify_changes.shpasses.Suggested Tests
Add focused unit tests for
HttpServerResponseBuilder:A2A-Version.Cache-Control: no-cache, andA2A-Version.Update existing transport tests only where needed to verify integration with the builder.