From 5d203a138c383c2abadd7dfc3794d55bcf3d7774 Mon Sep 17 00:00:00 2001 From: chelsealong Date: Wed, 2 Sep 2026 14:53:57 +0000 Subject: [PATCH] fix(tools): send the body for oneOf/anyOf/allOf OpenAPI request schemas OperationParser names a polymorphic or untyped request body parameter 'body' (instead of '' used for plain scalar bodies) to avoid emitting an empty-named property in the function declaration. RestApiTool's _prepare_request_params only recognized the '' sentinel, so it never matched the 'body'-named parameter and silently sent no body at all. Fixes #6991 --- .../openapi_spec_parser/rest_api_tool.py | 10 ++++- .../openapi_spec_parser/test_rest_api_tool.py | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py index 7b26402044..438ee6e529 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py @@ -460,8 +460,14 @@ def _prepare_request_params( break else: # like string for param in parameters: - # original_name = '' indicating this param applies to the full body. - if param.param_location == "body" and not param.original_name: + # original_name = '' indicates this param applies to the full + # body. OperationParser also uses original_name = 'body' for the + # same purpose when the schema is oneOf/anyOf/allOf or untyped, + # to avoid an empty-named property in the function declaration. + if param.param_location == "body" and param.original_name in ( + "", + "body", + ): body_data = ( kwargs.get(param.py_name) if param.py_name in kwargs else None ) diff --git a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py index 10079bfafd..5b74f8167f 100644 --- a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py +++ b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py @@ -647,6 +647,49 @@ def test_prepare_request_params_string( assert request_params["data"] == "test_value" assert request_params["headers"]["Content-Type"] == "text/plain" + def test_prepare_request_params_oneof_body( + self, sample_endpoint, sample_auth_credential, sample_auth_scheme + ): + """A oneOf/anyOf/allOf body is named 'body' by the parser and must + still be sent as the JSON payload, not silently dropped.""" + oneof_schema = OpenAPISchema( + oneOf=[ + OpenAPISchema( + type="object", properties={"card": OpenAPISchema(type="string")} + ), + OpenAPISchema( + type="object", properties={"iban": OpenAPISchema(type="string")} + ), + ] + ) + mock_operation = Operation( + operationId="test_op", + requestBody=RequestBody( + content={"application/json": MediaType(schema=oneof_schema)} + ), + ) + tool = RestApiTool( + name="test_tool", + description="Test Tool", + endpoint=sample_endpoint, + operation=mock_operation, + auth_credential=sample_auth_credential, + auth_scheme=sample_auth_scheme, + ) + params = [ + ApiParameter( + original_name="body", + py_name="body", + param_location="body", + param_schema=oneof_schema, + ) + ] + kwargs = {"body": {"card": "4111-1111"}} + + request_params = tool._prepare_request_params(params, kwargs) + + assert request_params["json"] == {"card": "4111-1111"} + def test_prepare_request_params_form_data( self, sample_endpoint, sample_auth_scheme, sample_auth_credential ):