Skip to content

feat(client): Revisit client side interceptors#796

Open
guglielmo-san wants to merge 17 commits intoa2aproject:1.0-devfrom
guglielmo-san:guglielmoc/rework_client_interceptors
Open

feat(client): Revisit client side interceptors#796
guglielmo-san wants to merge 17 commits intoa2aproject:1.0-devfrom
guglielmo-san:guglielmoc/rework_client_interceptors

Conversation

@guglielmo-san
Copy link
Member

@guglielmo-san guglielmo-san commented Mar 10, 2026

Description

This PR refactors the client interceptors architecture, centralizing their execution within the BaseClient rather than delegating them to the underlying transport implementations.
These interceptors allow to modify request before being sent to the server, and server responses before are sent back to the caller, with an early return mechanism.
The Authentication interceptor is updated as well to store authentication values in the ServiceParameters class of the ClientCallContext.

Fix: #757

@guglielmo-san guglielmo-san changed the title Guglielmoc/rework client interceptors feat(Client): Introduce client side interceptors Mar 10, 2026
@guglielmo-san
Copy link
Member Author

/gemini review

@guglielmo-san guglielmo-san marked this pull request as ready for review March 10, 2026 10:21
@guglielmo-san guglielmo-san requested a review from a team as a code owner March 10, 2026 10:21
@guglielmo-san guglielmo-san requested a review from ishymko March 10, 2026 10:21
@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 refactors the client-side interceptor architecture. The primary goal is to centralize the application of interceptors within the BaseClient, moving away from transport-specific implementations. This change ensures a unified and predictable interception flow across all client operations. Additionally, the new design incorporates robust generic typing for interceptor hooks, enhancing code clarity and maintainability. The refactoring also involved reorganizing related classes and renaming concepts for better consistency.

Highlights

  • Centralized Interceptor Execution: Interceptor logic has been moved from individual transport implementations to the BaseClient, ensuring all client methods consistently apply interceptors.
  • Strongly Typed Interceptor Hooks: Introduced strict generic type definitions for before and after interceptor hooks, improving type safety and developer experience for all client methods.
  • Refactored Interceptor Structure: The middleware.py module has been removed, and its core components, ClientCallContext and ClientCallInterceptor, have been relocated and redefined in client.py and a new interceptors.py module, respectively. The term 'middleware' has been consistently replaced with 'interceptors'.
  • Interceptor Lifecycle Management: New internal methods (_execute_with_interceptors, _intercept_before, _intercept_after) were added to BaseClient to manage the interceptor lifecycle, including support for early returns.
Changelog
  • src/a2a/client/init.py
    • Updated imports for ClientCallContext and ClientCallInterceptor to reflect their new locations
    • Added CallInterceptor to the module's __all__ export list
  • src/a2a/client/auth/credentials.py
    • Updated the import path for ClientCallContext
  • src/a2a/client/auth/interceptor.py
    • Updated the import path for ClientCallContext
    • Removed ClientCallInterceptor from the base classes of AuthInterceptor
  • src/a2a/client/base_client.py
    • Added new imports for interceptor-related types (AfterArgs, BeforeArgs, ClientCallInput, ClientCallResult, M, P, R, UnionAfterArgs, UnionBeforeArgs)
    • Renamed the middleware parameter to interceptors in the __init__ method
    • Introduced a new _interceptors attribute to store the list of interceptors
    • Modified all client methods (send_message, get_task, list_tasks, cancel_task, create_task_push_notification_config, get_task_push_notification_config, list_task_push_notification_configs, delete_task_push_notification_config, subscribe, get_extended_agent_card) to use the new _execute_with_interceptors method
    • Updated _process_stream to incorporate before_args and apply _intercept_after to stream responses
    • Added new private asynchronous methods: _execute_with_interceptors for orchestrating interceptor calls and transport execution, _intercept_before for processing pre-call interceptors, and _intercept_after for processing post-call interceptors
  • src/a2a/client/client.py
    • Added imports for BaseModel, Field from pydantic, and MutableMapping
    • Moved the ClientCallContext class definition from middleware.py to this file
    • Updated the import path for ClientCallInterceptor
    • Renamed the middleware parameter to interceptors in the Client.__init__ method
    • Renamed the _middleware attribute to _interceptors
    • Renamed the add_request_middleware method to add_interceptor
  • src/a2a/client/client_factory.py
    • Updated the import path for ClientCallInterceptor
    • Removed the interceptors parameter from the create calls for JsonRpcTransport, RestTransport, and GrpcTransport within _register_defaults and grpc_transport_producer
  • src/a2a/client/interceptors.py
    • Added a new file defining the core interceptor types and abstract base class
    • Defined ClientCallInput to represent method input payload
    • Defined ClientCallResult to represent method result payload
    • Defined BeforeArgs for arguments passed to interceptors before a method call, including support for early returns
    • Defined AfterArgs for arguments passed to interceptors after a method call completes
    • Introduced ClientCallInterceptor as an abstract base class with before and after abstract methods
    • Created UnionBeforeArgs and UnionAfterArgs TypeAliases for strongly typing interceptor arguments across various client methods
  • src/a2a/client/middleware.py
    • Removed the file, as its contents were refactored and moved to client.py and interceptors.py
  • src/a2a/client/transports/base.py
    • Updated the import path for ClientCallContext
  • src/a2a/client/transports/grpc.py
    • Updated the import path for ClientCallContext
    • Removed the import for ClientCallInterceptor
    • Removed the interceptors parameter from the create method
  • src/a2a/client/transports/http_helpers.py
    • Updated the import path for ClientCallContext
  • src/a2a/client/transports/jsonrpc.py
    • Updated the import path for ClientCallContext
    • Removed the import for ClientCallInterceptor
    • Removed the interceptors parameter from the __init__ method
  • src/a2a/client/transports/rest.py
    • Updated the import path for ClientCallContext
    • Removed the import for ClientCallInterceptor
    • Removed the interceptors parameter from the __init__ method
  • src/a2a/client/transports/tenant_decorator.py
    • Updated the import path for ClientCallContext
  • src/a2a/compat/v0_3/grpc_transport.py
    • Updated the import path for ClientCallContext
    • Removed the import for ClientCallInterceptor
    • Removed the interceptors parameter from the create method
  • tests/client/test_base_client.py
    • Renamed the middleware parameter to interceptors in the base_client fixture
  • tests/client/test_base_client_interceptors.py
    • Added a new test file to verify the functionality of the centralized interceptor logic in BaseClient
    • Included tests for normal interceptor flow and early return scenarios
  • tests/client/test_client_factory_grpc.py
    • Removed the interceptors parameter from mock_grpc.create calls in various test cases
  • tests/client/transports/test_grpc_client.py
    • Updated the import path for ClientCallContext in test functions
  • tests/client/transports/test_jsonrpc_client.py
    • Removed the test_init_with_interceptors test case
    • Updated the import path for ClientCallContext in test functions
  • tests/client/transports/test_rest_client.py
    • Updated the import path for ClientCallContext in test functions
  • tests/integration/test_client_server_integration.py
    • Renamed the middleware parameter to interceptors in BaseClient initialization within integration tests
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
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 significantly refactors the client-side interceptor architecture, centralizing logic in BaseClient and introducing strongly-typed generic interfaces for hooks, which improves maintainability and type safety. However, the refactor is incomplete as it breaks the existing AuthInterceptor by not updating it to the new interface, rendering authentication non-functional. Additionally, a type mismatch in handling early returns for streaming methods (send_message and subscribe) could cause runtime errors. There are also a few minor areas for code simplification and a small bug in the public API exports.

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 refactors the client-side interceptor (formerly middleware) system. It introduces a new interceptors.py module defining a more granular ClientCallInterceptor interface with before and after methods, replacing the single intercept method. The ClientCallContext class is moved to client.py. The BaseClient is updated to centralize interceptor execution logic, including handling early returns for both streaming and non-streaming calls, and all transport classes are modified to no longer directly manage interceptors. Review comments highlight a typo in the __all__ export list, suggest refactoring duplicated interceptor logic in streaming methods, and point out redundant checks in interceptor handling.

…plify default list assignments in the `Client` constructor.
…xecute_stream_with_interceptors` and add `_format_stream_event`.
…t `_intercept_before` return type, and optimize message event handling.
… and add streaming support to base client interceptors.
@guglielmo-san guglielmo-san changed the title feat(Client): Introduce client side interceptors feat(Client): Revisit client side interceptors Mar 10, 2026
@ishymko ishymko changed the title feat(Client): Revisit client side interceptors feat(client): Revisit client side interceptors Mar 10, 2026
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