From c4fc9ca69a64133acb7bf7a1c960f12a1acf44a5 Mon Sep 17 00:00:00 2001 From: Naveen Chatlapalli Date: Sat, 19 Sep 2026 00:44:31 -0500 Subject: [PATCH] fix(openapi): let operation parameters override path-level ones _collect_operations() appended every path-level parameter to each operation's parameters. An operation-level parameter with the same name and location overrides the path-level one (OpenAPI Path Item Object), so keeping both asked the model for the same path value twice, as account_id and account_id_0. Both map to the same placeholder, and the path-level value won, dropping the operation's more specific parameter. Skip path-level parameters whose (name, in) the operation already declares. Fixes #7205 Claude-Session: https://claude.ai/code/session_013vXxD1ga1hnCq2uFRwNks7 --- .../openapi_spec_parser.py | 19 +++++-- .../test_openapi_spec_parser.py | 50 +++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py b/src/google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py index 2e0e543fa37..1745698e410 100644 --- a/src/google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +++ b/src/google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py @@ -205,10 +205,21 @@ def _collect_operations( if operation_dict is None: continue - # Append path-level parameters - operation_dict["parameters"] = operation_dict.get( - "parameters", [] - ) + path_item.get("parameters", []) + # Append path-level parameters. An operation-level parameter with the + # same name and location overrides the path-level one, so appending + # both would ask the model for the same value twice. + operation_parameters = operation_dict.get("parameters", []) + overridden = { + (parameter["name"], parameter.get("in")) + for parameter in operation_parameters + if isinstance(parameter, dict) and "name" in parameter + } + operation_dict["parameters"] = operation_parameters + [ + parameter + for parameter in path_item.get("parameters", []) + if not isinstance(parameter, dict) + or (parameter.get("name"), parameter.get("in")) not in overridden + ] # If operation ID is missing, assign an operation id based on path # and method diff --git a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py index e5bff337cec..c6ba7ecc57c 100644 --- a/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py +++ b/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py @@ -685,6 +685,56 @@ def test_parse_spec_with_path_level_parameters(openapi_spec_generator): assert local_param.type_value is int +def test_parse_spec_operation_parameter_overrides_path_level_parameter( + openapi_spec_generator, +): + """A same-named operation parameter replaces the path-level one.""" + openapi_spec = { + "openapi": "3.1.0", + "info": {"title": "Accounts API", "version": "1.0.0"}, + "paths": { + "/accounts/{accountId}": { + "parameters": [ + { + "name": "accountId", + "in": "path", + "required": True, + "schema": {"type": "string"}, + "description": "Shared account id", + }, + # Same name, different location: not overridden. + { + "name": "accountId", + "in": "header", + "schema": {"type": "string"}, + }, + ], + "get": { + "operationId": "getAccount", + "parameters": [{ + "name": "accountId", + "in": "path", + "required": True, + "schema": {"type": "string"}, + "description": "Account id, e.g. ACC-123", + }], + "responses": {"200": {"description": "ok"}}, + }, + } + }, + } + + operation = openapi_spec_generator.parse(openapi_spec)[0] + + assert [ + (p.original_name, p.param_location) for p in operation.parameters + ] == [ + ("accountId", "path"), + ("accountId", "header"), + ] + assert operation.parameters[0].description == "Account id, e.g. ACC-123" + + def test_parse_spec_with_invalid_type_any(openapi_spec_generator): """Test that schemas with type='Any' are sanitized for Pydantic 2.11+.