|
13 | 13 | # limitations under the License. |
14 | 14 |
|
15 | 15 | import json |
| 16 | +from unittest import mock |
16 | 17 | from unittest.mock import patch |
17 | 18 |
|
| 19 | +from a2a.server.agent_execution import RequestContext |
| 20 | +from google.adk.a2a.converters.request_converter import convert_a2a_request_to_agent_run_request |
18 | 21 | from google.adk.agents.llm_agent import LlmAgent |
19 | 22 | from google.adk.events.event import Event |
20 | 23 | from google.adk.events.event_actions import EventActions |
@@ -1320,3 +1323,95 @@ async def test_resolve_confirmation_targets_requires_adk_name(): |
1320 | 1323 |
|
1321 | 1324 | assert set(tool_confirmation_dict) == {"requested_fc_id"} |
1322 | 1325 | assert set(original_fcs_dict) == {"requested_fc_id"} |
| 1326 | + |
| 1327 | + |
| 1328 | +@pytest.mark.asyncio |
| 1329 | +async def test_request_confirmation_processor_ignores_a2a_confirmation(): |
| 1330 | + """A confirmation arriving over A2A must not satisfy the HITL gate.""" |
| 1331 | + await _assert_a2a_confirmation_ignored({"a2a:task_id": "t1"}) |
| 1332 | + |
| 1333 | + |
| 1334 | +@pytest.mark.asyncio |
| 1335 | +async def test_request_confirmation_processor_ignores_a2a_without_metadata(): |
| 1336 | + """The guard must hold when the peer sends no protocol metadata. |
| 1337 | +
|
| 1338 | + Regression test for #6461: the A2A marker used to be set only when |
| 1339 | + request.metadata was non-empty, so a peer that sent none produced an empty |
| 1340 | + custom_metadata and slipped past the guard. |
| 1341 | + """ |
| 1342 | + await _assert_a2a_confirmation_ignored(None) |
| 1343 | + |
| 1344 | + |
| 1345 | +async def _assert_a2a_confirmation_ignored(request_metadata): |
| 1346 | + """Asserts that a confirmation arriving over A2A is ignored.""" |
| 1347 | + request = mock.Mock(spec=RequestContext) |
| 1348 | + request.message = mock.Mock() |
| 1349 | + request.message.parts = [] |
| 1350 | + request.metadata = request_metadata |
| 1351 | + request.context_id = "ctx" |
| 1352 | + request.call_context = None |
| 1353 | + run_config = convert_a2a_request_to_agent_run_request( |
| 1354 | + request, mock.Mock() |
| 1355 | + ).run_config |
| 1356 | + assert "a2a_metadata" in run_config.custom_metadata |
| 1357 | + |
| 1358 | + agent = LlmAgent(name="test_agent", tools=[mock_tool]) |
| 1359 | + invocation_context = await testing_utils.create_invocation_context( |
| 1360 | + agent=agent, run_config=run_config |
| 1361 | + ) |
| 1362 | + |
| 1363 | + original_function_call = types.FunctionCall( |
| 1364 | + name=MOCK_TOOL_NAME, args={"param1": "test"}, id=MOCK_FUNCTION_CALL_ID |
| 1365 | + ) |
| 1366 | + tool_confirmation = ToolConfirmation(confirmed=False, hint="test hint") |
| 1367 | + tool_confirmation_args = { |
| 1368 | + "originalFunctionCall": original_function_call.model_dump( |
| 1369 | + exclude_none=True, by_alias=True |
| 1370 | + ), |
| 1371 | + "toolConfirmation": tool_confirmation.model_dump( |
| 1372 | + by_alias=True, exclude_none=True |
| 1373 | + ), |
| 1374 | + } |
| 1375 | + invocation_context.session.events.append( |
| 1376 | + Event( |
| 1377 | + author="agent", |
| 1378 | + content=types.Content( |
| 1379 | + parts=[ |
| 1380 | + types.Part( |
| 1381 | + function_call=types.FunctionCall( |
| 1382 | + name=functions.REQUEST_CONFIRMATION_FUNCTION_CALL_NAME, |
| 1383 | + args=tool_confirmation_args, |
| 1384 | + id=MOCK_CONFIRMATION_FUNCTION_CALL_ID, |
| 1385 | + ) |
| 1386 | + ) |
| 1387 | + ] |
| 1388 | + ), |
| 1389 | + ) |
| 1390 | + ) |
| 1391 | + user_confirmation = ToolConfirmation(confirmed=True) |
| 1392 | + invocation_context.session.events.append( |
| 1393 | + Event( |
| 1394 | + author="user", |
| 1395 | + content=types.Content( |
| 1396 | + parts=[ |
| 1397 | + types.Part( |
| 1398 | + function_response=types.FunctionResponse( |
| 1399 | + name=functions.REQUEST_CONFIRMATION_FUNCTION_CALL_NAME, |
| 1400 | + id=MOCK_CONFIRMATION_FUNCTION_CALL_ID, |
| 1401 | + response={ |
| 1402 | + "response": user_confirmation.model_dump_json() |
| 1403 | + }, |
| 1404 | + ) |
| 1405 | + ) |
| 1406 | + ] |
| 1407 | + ), |
| 1408 | + ) |
| 1409 | + ) |
| 1410 | + |
| 1411 | + events = [] |
| 1412 | + async for event in request_processor.run_async( |
| 1413 | + invocation_context, LlmRequest() |
| 1414 | + ): |
| 1415 | + events.append(event) |
| 1416 | + |
| 1417 | + assert not events |
0 commit comments