From 4dbff2983da1268217dc732dc2674e7cdc79031e Mon Sep 17 00:00:00 2001 From: doug <110487462+doughayden@users.noreply.github.com> Date: Sun, 7 Jun 2026 17:49:29 -0400 Subject: [PATCH 1/2] fix: Make EUC request args JSON-serializable build_auth_request_event dumped AuthToolArguments with the default mode="python", leaving auth_scheme.type as a live SecuritySchemeType enum in the adk_request_credential FunctionCall args. Any consumer that json.dumps the in-memory event before it round-trips the session DB raises TypeError; memory ingestion is a concrete trigger. Use mode="json" to coerce the enum to its string value. Parse-back is unaffected: pydantic validation accepts the string form, exactly what DB-read events already carry. --- src/google/adk/flows/llm_flows/functions.py | 2 +- .../llm_flows/test_functions_request_euc.py | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/google/adk/flows/llm_flows/functions.py b/src/google/adk/flows/llm_flows/functions.py index 259d40b6b6..7bf597d29a 100644 --- a/src/google/adk/flows/llm_flows/functions.py +++ b/src/google/adk/flows/llm_flows/functions.py @@ -302,7 +302,7 @@ def build_auth_request_event( args=AuthToolArguments( function_call_id=function_call_id, auth_config=auth_config, - ).model_dump(exclude_none=True, by_alias=True), + ).model_dump(mode='json', exclude_none=True, by_alias=True), ) long_running_tool_ids.add(request_euc_function_call.id) parts.append(types.Part(function_call=request_euc_function_call)) diff --git a/tests/unittests/flows/llm_flows/test_functions_request_euc.py b/tests/unittests/flows/llm_flows/test_functions_request_euc.py index f1e1d1f610..2ab97af07c 100644 --- a/tests/unittests/flows/llm_flows/test_functions_request_euc.py +++ b/tests/unittests/flows/llm_flows/test_functions_request_euc.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json from typing import Any from typing import Optional @@ -153,6 +154,68 @@ def call_external_api2(tool_context: ToolContext) -> Optional[int]: ) +def test_function_request_euc_args_are_json_serializable(): + responses = [ + [ + types.Part.from_function_call(name='call_external_api', args={}), + ], + [ + types.Part.from_text(text='response1'), + ], + ] + + auth_config = AuthConfig( + auth_scheme=OAuth2( + flows=OAuthFlows( + authorizationCode=OAuthFlowAuthorizationCode( + authorizationUrl='https://accounts.google.com/o/oauth2/auth', + tokenUrl='https://oauth2.googleapis.com/token', + scopes={ + 'https://www.googleapis.com/auth/calendar': ( + 'See, edit, share, and permanently delete all the' + ' calendars you can access using Google Calendar' + ) + }, + ) + ) + ), + raw_auth_credential=AuthCredential( + auth_type=AuthCredentialTypes.OAUTH2, + oauth2=OAuth2Auth( + client_id='oauth_client_id', + client_secret='oauth_client_secret', + ), + ), + ) + + mock_model = testing_utils.MockModel.create(responses=responses) + + def call_external_api(tool_context: ToolContext) -> Optional[int]: + tool_context.request_credential(auth_config) + + agent = Agent( + name='root_agent', + model=mock_model, + tools=[call_external_api], + ) + runner = testing_utils.InMemoryRunner(agent) + events = runner.run('test') + + request_euc_function_call = events[1].content.parts[0].function_call + assert ( + request_euc_function_call.name == functions.REQUEST_EUC_FUNCTION_CALL_NAME + ) + + # The in-memory event args must be JSON-serializable. A python-mode dump + # leaves auth_scheme.type as a live SecuritySchemeType enum, which breaks any + # consumer that json.dumps the event before it round-trips the session DB. + json.dumps(request_euc_function_call.args) + assert ( + request_euc_function_call.args['authConfig']['authScheme']['type'] + == 'oauth2' + ) + + def test_function_get_auth_response(): id_1 = 'id_1' id_2 = 'id_2' From c607d36450374d3d05ce846972a9b2c6711090d7 Mon Sep 17 00:00:00 2001 From: doug <110487462+doughayden@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:20:06 -0400 Subject: [PATCH 2/2] fix: JSON-mode EUC dump on workflow HITL path create_auth_request_event in _workflow_hitl_utils.py dumped AuthToolArguments with the default mode="python", the same hazard fixed in functions.py: auth_scheme.type stays a live enum, so any consumer that json.dumps the in-memory event before it round-trips the session DB raises TypeError. Use mode="json" there too. - Add regression test asserting create_auth_request_event args are JSON-serializable (fails before, passes after) - Trim prior commit's multi-line test comment to a one-liner Closes #6006 --- .../workflow/utils/_workflow_hitl_utils.py | 2 +- .../llm_flows/test_functions_request_euc.py | 4 +- .../utils/test_workflow_hitl_utils.py | 43 +++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/google/adk/workflow/utils/_workflow_hitl_utils.py b/src/google/adk/workflow/utils/_workflow_hitl_utils.py index be718b5e89..b659741bab 100644 --- a/src/google/adk/workflow/utils/_workflow_hitl_utils.py +++ b/src/google/adk/workflow/utils/_workflow_hitl_utils.py @@ -172,7 +172,7 @@ def create_auth_request_event( args = AuthToolArguments( function_call_id=interrupt_id, auth_config=auth_request, - ).model_dump(exclude_none=True, by_alias=True) + ).model_dump(mode='json', exclude_none=True, by_alias=True) # Add message so the UI / CLI knows what to display. args['message'] = _build_auth_message(auth_config) diff --git a/tests/unittests/flows/llm_flows/test_functions_request_euc.py b/tests/unittests/flows/llm_flows/test_functions_request_euc.py index 2ab97af07c..04b6819cec 100644 --- a/tests/unittests/flows/llm_flows/test_functions_request_euc.py +++ b/tests/unittests/flows/llm_flows/test_functions_request_euc.py @@ -206,9 +206,7 @@ def call_external_api(tool_context: ToolContext) -> Optional[int]: request_euc_function_call.name == functions.REQUEST_EUC_FUNCTION_CALL_NAME ) - # The in-memory event args must be JSON-serializable. A python-mode dump - # leaves auth_scheme.type as a live SecuritySchemeType enum, which breaks any - # consumer that json.dumps the event before it round-trips the session DB. + # python-mode dump leaves auth_scheme.type a live enum, breaking json.dumps json.dumps(request_euc_function_call.args) assert ( request_euc_function_call.args['authConfig']['authScheme']['type'] diff --git a/tests/unittests/workflow/utils/test_workflow_hitl_utils.py b/tests/unittests/workflow/utils/test_workflow_hitl_utils.py index fcccb4e75a..5d84ba11cc 100644 --- a/tests/unittests/workflow/utils/test_workflow_hitl_utils.py +++ b/tests/unittests/workflow/utils/test_workflow_hitl_utils.py @@ -14,6 +14,8 @@ from __future__ import annotations +import json + from google.adk.events.event import Event from google.adk.events.event import NodeInfo from google.adk.events.request_input import RequestInput @@ -171,5 +173,46 @@ def test_creates_credential_request(self): assert fc.id == "auth-id-1" assert "authConfig" in fc.args + def test_args_are_json_serializable(self): + from fastapi.openapi.models import OAuth2 + from fastapi.openapi.models import OAuthFlowAuthorizationCode + from fastapi.openapi.models import OAuthFlows + from google.adk.auth.auth_credential import AuthCredential + from google.adk.auth.auth_credential import AuthCredentialTypes + from google.adk.auth.auth_credential import OAuth2Auth + from google.adk.auth.auth_tool import AuthConfig + + auth_config = AuthConfig( + auth_scheme=OAuth2( + flows=OAuthFlows( + authorizationCode=OAuthFlowAuthorizationCode( + authorizationUrl=( + "https://accounts.google.com/o/oauth2/auth" + ), + tokenUrl="https://oauth2.googleapis.com/token", + scopes={ + "https://www.googleapis.com/auth/calendar": ( + "See calendars" + ) + }, + ) + ) + ), + raw_auth_credential=AuthCredential( + auth_type=AuthCredentialTypes.OAUTH2, + oauth2=OAuth2Auth( + client_id="oauth_client_id", + client_secret="oauth_client_secret", + ), + ), + ) + event = create_auth_request_event(auth_config, "auth-id-1") + + fc = event.content.parts[0].function_call + + # python-mode dump leaves auth_scheme.type a live enum, breaking json.dumps + json.dumps(fc.args) + assert fc.args["authConfig"]["authScheme"]["type"] == "oauth2" + #