2222from google .genai import types
2323from typing_extensions import override
2424
25+ from ....agents ._caller_principal import caller_may_confirm
2526from ....agents .invocation_context import InvocationContext
2627from ....agents .readonly_context import ReadonlyContext
2728from ....events .event import Event
28- from ....features import FeatureName
29- from ....features import is_feature_enabled
3029from ....models .llm_request import LlmRequest
3130from ....tools .base_tool import BaseTool
3231from ....tools .tool_confirmation import ToolConfirmation
@@ -264,32 +263,12 @@ def _apply_caller_principal_gate(
264263) -> dict [str , ToolConfirmation ]:
265264 """Decides whether the caller of this invocation may approve a tool call.
266265
267- Three states, and only the middle one is a refusal:
268-
269- - No principal: nothing vouched for the caller because nothing had to. The
270- invocation was started in process, so the caller is the operator.
271- - Principal present, not authenticated: a serving layer handled this request
272- and could not say who sent it, so the approval is not known to be the
273- operator's.
274- - Principal present and authenticated: the serving layer verified the caller.
275-
276- The question asked here is deliberately about authentication and not about
277- transport. A transport is a proxy for identity, and a proxy for identity
278- fails in both directions: it refuses authenticated peers that happen to
279- arrive over the wire, and it admits anyone who reaches an ungated path.
280-
281- A refusal rewrites the confirmation to ``confirmed=False`` instead of
282- dropping it. Dropping it leaves the ``adk_request_confirmation`` call pending
283- with nothing left to resolve it, which is what made an earlier attempt at
284- this guard stall every human-in-the-loop tool. ``confirmed=False`` is a state
285- the framework already has a contract for -- it is what a human decline
286- produces -- so the tool returns its rejection response and the turn ends with
287- a reason the caller can see.
288-
289- Strict mode is the STRICT_CALLER_PRINCIPAL feature, off by default for now
290- so that upgrading cannot break a working deployment that runs its A2A server
291- without an authenticator; those get a warning instead. The intent is to flip
292- the registry default at the next major version.
266+ The decision itself is ``caller_may_confirm``, shared with the workflow tool
267+ node so that both consumers of a human-in-the-loop answer refuse the same
268+ callers. A refusal rewrites each confirmation to ``confirmed=False`` instead
269+ of dropping it: dropping it leaves the ``adk_request_confirmation`` call
270+ pending with nothing left to resolve it, which is what made an earlier
271+ attempt at this guard stall every human-in-the-loop tool.
293272
294273 Args:
295274 invocation_context: Current invocation context.
@@ -299,27 +278,8 @@ def _apply_caller_principal_gate(
299278 The confirmations to act on, with refused ones forced to
300279 ``confirmed=False``.
301280 """
302- principal = invocation_context .caller_principal
303- if principal is None or principal .authenticated :
281+ if caller_may_confirm (invocation_context .caller_principal ):
304282 return confirmations_by_fc_id
305-
306- if not is_feature_enabled (FeatureName .STRICT_CALLER_PRINCIPAL ):
307- logger .warning (
308- "Honoring a tool confirmation from an unauthenticated caller"
309- " (principal source %r). The serving layer could not say who sent this"
310- " approval, so it is not known to be the operator's. Enable the"
311- " STRICT_CALLER_PRINCIPAL feature to refuse these instead; that is"
312- " intended to become the default in a future major version." ,
313- principal .source ,
314- )
315- return confirmations_by_fc_id
316-
317- logger .error (
318- "Refusing a tool confirmation from an unauthenticated caller (principal"
319- " source %r). Enable authentication on the serving layer, or disable the"
320- " STRICT_CALLER_PRINCIPAL feature to downgrade this to a warning." ,
321- principal .source ,
322- )
323283 return {
324284 confirmation_fc_id : confirmation .model_copy (update = {"confirmed" : False })
325285 for confirmation_fc_id , confirmation in confirmations_by_fc_id .items ()
0 commit comments