3737TOOLSET_AUTH_CREDENTIAL_ID_PREFIX = '_adk_toolset_auth_'
3838
3939
40+ def _find_function_call (
41+ events : list [Event ], function_call_id : str
42+ ) -> Any | None :
43+ for event in events :
44+ for function_call in event .get_function_calls ():
45+ if function_call .id == function_call_id :
46+ return function_call
47+ return None
48+
49+
50+ def _has_requested_auth_config (
51+ events : list [Event ], function_call_id : str
52+ ) -> bool :
53+ return any (
54+ function_call_id in event .actions .requested_auth_configs
55+ for event in events
56+ )
57+
58+
59+ def _is_valid_auth_resume_target (
60+ events : list [Event ], args : AuthToolArguments
61+ ) -> bool :
62+ if args .function_call_id .startswith (TOOLSET_AUTH_CREDENTIAL_ID_PREFIX ):
63+ return False
64+ if not _has_requested_auth_config (events , args .function_call_id ):
65+ return False
66+ if not args .function_call_digest :
67+ return False
68+
69+ function_call = _find_function_call (events , args .function_call_id )
70+ if not function_call :
71+ return False
72+
73+ return (
74+ functions .function_call_digest (function_call ) == args .function_call_digest
75+ )
76+
77+
4078async def _store_auth_and_collect_resume_targets (
4179 events : list [Event ],
4280 auth_fc_ids : set [str ],
@@ -64,62 +102,47 @@ async def _store_auth_and_collect_resume_targets(
64102 """
65103 # Step 1: Scan events for matching adk_request_credential function calls
66104 # to extract AuthToolArguments (contains credential_key).
67- requested_auth_config_by_id : dict [str , AuthConfig ] = {}
105+ requested_auth_args_by_id : dict [str , AuthToolArguments ] = {}
68106 for event in events :
69107 event_function_calls = event .get_function_calls ()
70108 if not event_function_calls :
71109 continue
72- try :
73- for function_call in event_function_calls :
74- if (
75- function_call .id in auth_fc_ids
76- and function_call .name == REQUEST_EUC_FUNCTION_CALL_NAME
77- ):
78- args = AuthToolArguments .model_validate (function_call .args )
79- requested_auth_config_by_id [function_call .id ] = args .auth_config
80- except TypeError :
81- continue
110+ for function_call in event_function_calls :
111+ if (
112+ function_call .id not in auth_fc_ids
113+ or function_call .name != REQUEST_EUC_FUNCTION_CALL_NAME
114+ ):
115+ continue
116+ try :
117+ requested_auth_args_by_id [function_call .id ] = (
118+ AuthToolArguments .model_validate (function_call .args )
119+ )
120+ except TypeError :
121+ continue
82122
83123 # Step 2: Store credentials. Merge credential_key from the original
84124 # request into the client's auth response before storing.
85125 for fc_id in auth_fc_ids :
86126 if fc_id not in auth_responses :
87127 continue
88128 auth_config = AuthConfig .model_validate (auth_responses [fc_id ])
89- requested_auth_config = requested_auth_config_by_id .get (fc_id )
90- if (
91- requested_auth_config
92- and requested_auth_config .credential_key is not None
129+ requested_auth_args = requested_auth_args_by_id .get (fc_id )
130+ if requested_auth_args and (
131+ requested_auth_args .auth_config .credential_key is not None
93132 ):
94- auth_config .credential_key = requested_auth_config .credential_key
133+ auth_config .credential_key = (
134+ requested_auth_args .auth_config .credential_key
135+ )
95136 await AuthHandler (auth_config = auth_config ).parse_and_store_auth_response (
96137 state = state
97138 )
98139
99140 # Step 3: Collect original function call IDs to resume, skipping
100141 # toolset auth entries which don't map to a resumable function call.
101142 tools_to_resume : set [str ] = set ()
102- for fc_id in auth_fc_ids :
103- requested_auth_config = requested_auth_config_by_id .get (fc_id )
104- if not requested_auth_config :
105- continue
106- # Re-parse to get function_call_id (AuthConfig doesn't carry it;
107- # AuthToolArguments does).
108- for event in events :
109- event_function_calls = event .get_function_calls ()
110- if not event_function_calls :
111- continue
112- for function_call in event_function_calls :
113- if (
114- function_call .id == fc_id
115- and function_call .name == REQUEST_EUC_FUNCTION_CALL_NAME
116- ):
117- args = AuthToolArguments .model_validate (function_call .args )
118- if args .function_call_id .startswith (
119- TOOLSET_AUTH_CREDENTIAL_ID_PREFIX
120- ):
121- continue
122- tools_to_resume .add (args .function_call_id )
143+ for args in requested_auth_args_by_id .values ():
144+ if _is_valid_auth_resume_target (events , args ):
145+ tools_to_resume .add (args .function_call_id )
123146
124147 return tools_to_resume
125148
@@ -141,10 +164,12 @@ async def run_async(
141164 # Find the last user-authored event with function responses to
142165 # identify adk_request_credential responses.
143166 last_event_with_content = None
167+ last_event_with_content_index = - 1
144168 for i in range (len (events ) - 1 , - 1 , - 1 ):
145169 event = events [i ]
146170 if event .content is not None :
147171 last_event_with_content = event
172+ last_event_with_content_index = i
148173 break
149174
150175 if not last_event_with_content or last_event_with_content .author != 'user' :
@@ -170,16 +195,20 @@ async def run_async(
170195 return
171196
172197 # Store credentials and collect tools to resume.
198+ prior_events = events [:last_event_with_content_index ]
173199 tools_to_resume = await _store_auth_and_collect_resume_targets (
174- events , auth_fc_ids , auth_responses , invocation_context .session .state
200+ prior_events ,
201+ auth_fc_ids ,
202+ auth_responses ,
203+ invocation_context .session .state ,
175204 )
176205
177206 if not tools_to_resume :
178207 return
179208
180209 # Find the original function call event and re-execute the tools
181210 # that needed auth.
182- for i in range (len ( events ) - 2 , - 1 , - 1 ):
211+ for i in range (last_event_with_content_index - 1 , - 1 , - 1 ):
183212 event = events [i ]
184213 function_calls = event .get_function_calls ()
185214 if not function_calls :
0 commit comments