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 18dbb70b259..b423e07a021 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 @@ -59,6 +59,7 @@ from ..auth.auth_helpers import credential_to_param from ..auth.auth_helpers import dict_to_auth_scheme from ..auth.credential_exchangers.auto_auth_credential_exchanger import AutoAuthCredentialExchanger +from ..common.common import _schema_from_openapi from ..common.common import ApiParameter from .openapi_spec_parser import OperationEndpoint from .openapi_spec_parser import ParsedOperation @@ -570,7 +571,14 @@ def _prepare_request_params( request_body = self.operation.requestBody if request_body: for mime_type, media_type_object in request_body.content.items(): - schema = media_type_object.schema_ + # Normalized exactly as OperationParser did when it built the body + # parameters, so a media type declared without a schema (e.g. + # `application/octet-stream: {}`) gets the same single body parameter + # instead of crashing here. + schema = _schema_from_openapi( + media_type_object.schema_, + context=f"request body media type {mime_type!r}", + ) body_data = None if schema.type == "object": 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 74d6cefd4f4..e61ad3e66ba 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 @@ -2253,6 +2253,33 @@ def test_prepare_request_params_octet_stream( request_params["headers"]["Content-Type"] == "application/octet-stream" ) + def test_prepare_request_params_media_type_without_schema( + self, sample_endpoint, sample_auth_scheme, sample_auth_credential + ): + """A media type with no schema gets the parser's single body parameter.""" + mock_operation = Operation( + operationId="upload_contract", + requestBody=RequestBody( + content={"application/octet-stream": MediaType()} + ), + ) + tool = RestApiTool( + name="upload_contract", + description="test", + endpoint=sample_endpoint, + operation=mock_operation, + auth_credential=sample_auth_credential, + auth_scheme=sample_auth_scheme, + ) + params = OperationParser(mock_operation).get_parameters() + + request_params = tool._prepare_request_params(params, {"body": b"%PDF-1.7"}) + + assert request_params["data"] == b"%PDF-1.7" + assert ( + request_params["headers"]["Content-Type"] == "application/octet-stream" + ) + def test_prepare_request_params_path_param( self, sample_endpoint, sample_auth_credential, sample_auth_scheme ):