Skip to content

[Feat]: Add support for gRPC #275

@guglielmo-san

Description

@guglielmo-san

Background

The A2A TypeScript SDK requires support for the gRPC transport protocol. This entails a Node.js server and a client implementation that is compatible with both Node.js and browser environments. This document evaluates available architectural approaches and proposes a recommended implementation strategy.

gRPC Transport Strategy

To effectively utilize generated types within the gRPC protocol, a specific transport runtime library is required. The following libraries are the industry-standard options for our target environments:

  • Node.js Environment: The recommended choice is grpc-node (specifically the @grpc/grpc-js package). This provides a pure JavaScript implementation of gRPC for Node.js.
  • Browser Environment: The recommended choice is grpc-web. Because browsers cannot handle the HTTP/2 trailers and raw TCP sockets required by standard gRPC, grpc-web allows the browser client to communicate with services via a proxy (typically Envoy) that translates the traffic.

Code Generation

To implement Protobuf support, the first step requires generating .ts files from the .proto.

Static vs Dynamic Generation

An architectural decision for gRPC in TypeScript is the choice between static and dynamic code generation:

  • Dynamic Generation: loads .proto files at runtime, allowing for more flexibility.
  • Static Generation: generates client and server code ahead of time.

proto-loader-gen-types is the plugin for code generation in a dynamic fashion, however the output generated is quite messy, with multiple files generated for each Message, and a complex Service definition. For this reason this solution is discarded, and will be preferred by the Static Generation.

Plugins for Code Generation

Different plugins have been evaluated to generate the typescript code for Message and Service from the .proto files.

1. ts-proto

Pros:

  • Idiomatic TypeScript: It generates Plain Old JavaScript Objects (POJOs) and Interfaces.
  • Smallest Bundle Size: Relies on minimal runtime code. It avoids importing massive libraries to parse messages and often generates a single, concise .ts file.
  • Simple gRPC Support: It supports gRPC on Node environments natively.
  • Customization: Offers extensive options to customize output.

Cons:

  • gRPC Web Integration: Native browser support requires an additional third-party library (nice-grpc-web).
  • Conformance Test: Does not perform well on Protobuf Conformance test compared to other options

Dependencies:

  • ts-proto: for Messages and Services generation
  • @grpc/grpc-js: for Server side implementation
  • nice-grpc-web: for support of browser gRPC

2. protobuf-ts

Pros:

  • Excellent gRPC-Web Transports: It ships with its own robust GrpcWebTransport (@protobuf-ts/grpcweb-transport). It handles interceptors (middleware), abort signals (canceling requests), and metadata handling very well out of the box.
  • Idiomatic TypeScript: Like ts-proto, it generates clean POJOs and Interfaces.
  • Standards Compliant: It passes the official Protocol Buffer conformance tests.

Cons:

  • Larger Bundle than ts-proto: Because it includes a robust runtime (for handling the transports and types), it adds more kilobytes to your final application bundle than ts-proto.
  • Naming convention: uses C# naming convention, appending an `I` at the beginning of Interfaces names.
  • Import format: this library does not support ESM import format in the generated files. This would require using another library to modify the generated imports.

Dependencies:

  • protobuf-ts: for Messages and Services generation
  • @grpc/grpc-js: for Server side implementation
  • @protobuf-ts/grpcweb-transport: for support of browser gRPC

3. ts-protoc-gen

Pros:

  • Plain Integration: it supports integration with gRPC and gRPC-web.

Cons:

  • Difficult Developer Experience: It generates "Java-style" code for Messages, with getters/setters.
  • Messy Output: This plugin generates both the types (.d.ts) and the JavaScript (.js)
  • Bloated Dependencies: It requires the google-protobuf runtime library, which is very large and does not tree-shake well.

Dependencies:

  • protoc-gen-ts: for Messages and Services generation
  • @grpc/grpc-js: for Server side implementation
  • Google-protobuf

4. protoc-gen-grpc-web

Pros:

  • Official library: this is the official plugin library developed for Typescript by grpc-Web

Cons:

  • Experimental: it is still defined “experimental” support in the documentation
  • Only gRpr-web: this plugin can generate code that will work only with gRpc-web on Browser

Alternative Approach: Connect Protocol

Instead of relying on the legacy @grpc/grpc-js library and grpc-web stack, a modern alternative is to adopt the Connect ecosystem (by Buf), using Protobuf-ES for code generation and Connect-ES as the transport library. Connect-ES does not use @grpc/grpc-js. Instead, it runs on top of the standard, built-in Node.js http2 modules.

Pros:

  • No Envoy Proxy Required: The "Connect Protocol" allows the browser client to talk directly to the Node.js server. The server (implemented with @connectgrpc/connect-node) automatically handles the translation of incoming gRPC-Web or Connect requests. This removes the need for a complex Envoy proxy to translate protocols.
  • Standards Compliant: Protobuf-ES generated code passes the official Protocol Buffer conformance tests.
  • Universal Handlers: The ConnectRPC server can work as a simple route handler in standard Node frameworks.
  • Browser Native: The client uses the standard fetch API. It is extremely lightweight and creates standard network requests that are visible and debuggable in the Chrome/Firefox DevTools "Network" tab.
  • Multi-Protocol Support: A single server definition automatically supports three protocols simultaneously:
    1. gRPC (for backend-to-backend).
    2. gRPC-Web (for legacy browser clients).
    3. Connect (for modern browser clients and easy debugging).

Cons:

  • Newer Ecosystem: As it represents a newer ecosystem, it has fewer use cases.
  • "Pure" gRPC Distinction: Browser communication is technically strictly HTTP/1.1 or HTTP/2 POST requests rather than raw TCP gRPC.

Summary

Generator plugin Node.js Runtime (Server/Client) Browser Runtime (gRPC-Web) Proxy Required?
ts-proto @grpc/grpc-js nice-grpc-web Yes (Envoy)
protobuf-ts @grpc/grpc-js @protobuf-ts/grpcweb-transport Yes (Envoy)
ts-protoc-gen @grpc/grpc-js @improbable-eng/grpc-web Yes (Envoy)
protoc-gen-grpc-web n/a grpc-web Yes (Envoy)
protoc-gen-es @connectrpc/connect-node @connectrpc/connect-web No (if both client and server use Connect)

Error Handling

According to the A2A specification, the sdk is expected to implement richer error formatting. The requested error information will be added to the trailing metadata, using the key grpc-status-details-bin.

Conclusion

1. Node.js Environment

The Node.js implementation will utilize ts-proto in conjunction with the @grpc/grpc-js runtime library. This choice is due to its high customization (generates import with .js suffix) and its ability to generate idiomatic, type-safe TypeScript interfaces.

2. Browser Environment: gRPC-Web Integration

To extend SDK functionality to the browser, the project will incorporate the official protoc-gen-grpc-web generator. This ensures the SDK adheres to the formal gRPC-Web specification, maintaining strict alignment with Google’s infrastructure standards. While this necessitates a proxy layer (e.g., Envoy) for browser-to-server communication, it preserves the protocol’s integrity.

3. Justification

Connect protocol was ultimately dismissed to maintain a "pure gRPC" ecosystem. While it offers simplified browser transport, it deviates from the standard gRPC-Web framing.
While protobuf-ts is a capable alternative, it required post-processing of generated files to ensure compliance with ESM (ECMAScript Modules) import standards. Avoiding this additional complexity reduces the maintenance work of the SDK.
ts-protoc-gen was excluded due to its reliance on legacy code patterns. The tool produces a verbose output utilizing manual getters and setters, which results in a suboptimal TypeScript experience.

IMPORTANT NOTE: The generated message types and service definition won’t be exported and will remain internal to A2A. This will give us the freedom to change the tool to generate the types without affecting the developers that started the adoption of the sdk.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions