Skip to content

Conversation

@danielblignaut
Copy link

@danielblignaut danielblignaut commented Jul 8, 2025

Description

This is a draft PR to solicit feedback on adding GRPC support to the JS sdk. It comes with a few side-effects, mainly due to the usage / restrictions on the timostamm-protobuf-ts generator.

  1. Changes to tsconfig. Because the generated TS server and message code uses discriminated unions for the oneof message types, we have to apply stricter checks in the tsconfig file, otherwise the discriminated unions result in type check failures by the ts compiler.
  2. Adding Biome (which hasn't yet fixed this issue), because nodenext target requires relative imports end with ".js". The protoc compiler doesn't have an option to adjust this. I'm hoping we can add biome, add a specific rule for this and have the linter write this change. I haven't run biome as it'll cause a load of additional noise across the project.
  3. No tests, not yet fully tested myself
  4. code still needs to entirely be cleaned up. Some of the type conversion functions from GRPC to internal were generated with AI assistance (not sure if this is against contribution guidelines).

Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Follow the CONTRIBUTING Guide.
  • Make your Pull Request title in the https://www.conventionalcommits.org/ specification.
    • Important Prefixes for release-please:
      • fix: which represents bug fixes, and correlates to a SemVer patch.
      • feat: represents a new feature, and correlates to a SemVer minor.
      • feat!:, or fix!:, refactor!:, etc., which represent a breaking change (indicated by the !) and will result in a SemVer major.
  • Ensure the tests and linter pass
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

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.

Summary of Changes

Hello @danielblignaut, 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 expands the A2A JS SDK's communication capabilities by introducing a gRPC transport layer. This change provides a more performant and strongly-typed alternative to the existing JSON-RPC, leveraging Protocol Buffers for service and message definitions. It also improves overall code quality and maintainability through new linting and formatting tools, and enhances type safety across the codebase.

Highlights

  • New gRPC Transport Implementation: Introduced a gRPC transport layer for the A2A JS SDK, enabling strongly typed, streaming communication between agents and clients. This provides an alternative to the existing JSON-RPC transport.
  • Protocol Buffer Integration: Added buf.gen.yaml to configure buf for generating gRPC client and server code from the a2a.proto specification. This includes generated TypeScript interfaces and classes for the A2A service and standard Google protobuf types (Empty, Struct, Timestamp).
  • Core Dependency Additions: Incorporated @grpc/grpc-js, @protobuf-ts/runtime, and @protobuf-ts/runtime-rpc as core dependencies to support the new gRPC functionality. BiomeJS has also been added as a development dependency for code formatting and linting.
  • Enhanced Type Safety and Code Quality: Updated tsconfig.json to enable stricter TypeScript checks (strict, strictNullChecks) and introduced a biome.json configuration for consistent code style and linting across the project.
  • Comprehensive Documentation and Example: Included a detailed README.md within the gRPC transport directory, explaining its overview, usage, service methods, authentication, type conversion, and error handling. An example.ts file is also provided to demonstrate how to set up and use the A2A gRPC service.
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

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 issue 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 is currently in preview and 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 to provide feedback.

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
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 gRPC support, a significant and well-structured feature. The code is clean, and the inclusion of documentation and examples is excellent. I've provided feedback focused on improving error handling, validation, and the implementation of stubbed methods to enhance the robustness of the new transport layer.

Comment on lines +277 to +307
async listTaskPushNotificationConfig(
call: ServerUnaryCall<
ListTaskPushNotificationConfigRequest,
ListTaskPushNotificationConfigResponse
>,
callback: sendUnaryData<ListTaskPushNotificationConfigResponse>,
): Promise<void> {
try {
// For now, we'll return a single config if it exists
const taskId = extractTaskIdFromParent(call.request.parent);
const params: TaskIdParams = { id: taskId };

try {
const config =
await requestHandler.getTaskPushNotificationConfig(params);
const response: ListTaskPushNotificationConfigResponse = {
configs: [convertPushNotificationConfigToGrpc(config)],
nextPageToken: "",
};
callback(null, response);
} catch (error) {
// If no config exists, return empty list
const response: ListTaskPushNotificationConfigResponse = {
configs: [],
nextPageToken: "",
};
callback(null, response);
}
} catch (error) {
callback(handleError(error));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The implementation for listTaskPushNotificationConfig swallows errors from requestHandler.getTaskPushNotificationConfig and returns an empty list. Modify the error handling to only catch 'config not found' scenarios and propagate other errors.

Comment on lines +679 to +697
function convertPushNotificationConfigToGrpc(
config: A2ATaskPushNotificationConfig,
): TaskPushNotificationConfig {
const pushConfig = config.pushNotificationConfig;
return {
name: `tasks/${config.taskId}/pushNotificationConfigs/${pushConfig.id}`,
pushNotificationConfig: {
id: pushConfig.id || "",
url: pushConfig.url,
token: pushConfig.token || "",
authentication: pushConfig.authentication
? {
schemes: pushConfig.authentication.schemes || [],
credentials: pushConfig.authentication.credentials || "",
}
: undefined,
},
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The resource name for the TaskPushNotificationConfig is constructed using pushConfig.id. Add validation to ensure pushConfig.id is a non-empty string before constructing the name to avoid invalid resource names.

Comment on lines +366 to +377
async deleteTaskPushNotificationConfig(
_call: ServerUnaryCall<DeleteTaskPushNotificationConfigRequest, Empty>,
callback: sendUnaryData<Empty>,
): Promise<void> {
try {
// The current interface doesn't support deletion, so we'll return success
// This might need to be implemented in the request handler
callback(null, {});
} catch (error) {
callback(handleError(error));
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The deleteTaskPushNotificationConfig method is implemented as a stub that always returns success. Return an UNIMPLEMENTED gRPC status code to signal that the feature is not available.

	async deleteTaskPushNotificationConfig(
		_call: ServerUnaryCall<DeleteTaskPushNotificationConfigRequest, Empty>,
		callback: sendUnaryData<Empty>,
	): Promise<void> {
			callback({
				code: status.UNIMPLEMENTED,
				message: "deleteTaskPushNotificationConfig is not implemented",
			});
	},

Comment on lines +390 to +396
function convertMessageSendParamsToInternal(request: SendMessageRequest): MessageSendParams {
return {
message: convertGrpcMessageToInternal(request.request!),
configuration: request.configuration ? convertGrpcMessageConfigurationToInternal(request.configuration) : undefined,
metadata: request.metadata ? structToObject(request.metadata) : undefined,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The non-null assertion operator (!) is used on request.request. Add an explicit check for null or undefined for more robust code.

function convertMessageSendParamsToInternal(request: SendMessageRequest): MessageSendParams {
    if (!request.request) {
        throw A2AError.invalidParams("request field is missing in SendMessageRequest");
    }
    return {
            message: convertGrpcMessageToInternal(request.request),
            configuration: request.configuration ? convertGrpcMessageConfigurationToInternal(request.configuration) : undefined,
            metadata: request.metadata ? structToObject(request.metadata) : undefined,
        };
}

} as DataPart;
}

throw new Error("Invalid part type");
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Throwing a generic Error for an "Invalid part type" will result in a gRPC INTERNAL error status. Throw an A2AError.invalidParams instead to provide a more informative INVALID_ARGUMENT status to the client.

    throw A2AError.invalidParams("Invalid part type");

}]
);

server.bindAsync('0.0.0.0:50051', credentials, callback);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The callback variable used in this TLS example is not defined. Replace it with a callback function, similar to the insecure example above it.

const cancelUpdate: TaskStatusUpdateEvent = {
kind: 'status-update',
taskId,
contextId: "",
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The contextId is hardcoded to an empty string. Use the actual contextId of the task being cancelled for consistency and correctness. Consider updating the AgentExecutor.cancelTask signature to receive the RequestContext or the contextId.

Suggested change
contextId: "",
contextId: "", // TODO: This should be the actual contextId of the task

Comment on lines +3 to +5
"strict": true,
"strictNullChecks": true,
"strictPropertyInitialization": false,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Consider enabling strictPropertyInitialization to ensure class properties are initialized either in the constructor or with a default value.

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.

1 participant