@@ -279,3 +279,74 @@ async def test_pre_processor_does_not_inject_instruction_for_builtin_executor():
279279
280280 system_instruction = str (llm_request .config .system_instruction or '' )
281281 assert _NON_BUILTIN_EXECUTOR_INSTRUCTION not in system_instruction
282+
283+
284+ # ---------------------------------------------------------------------------
285+ # Post-processor: API rejection recovery path
286+ # ---------------------------------------------------------------------------
287+
288+
289+ @pytest .mark .asyncio
290+ @patch ('google.adk.flows.llm_flows._code_execution.logger' )
291+ async def test_post_processor_recovers_from_unexpected_tool_call (mock_logger ):
292+ mock_executor = MagicMock (spec = BaseCodeExecutor )
293+ mock_executor .code_block_delimiters = [('```tool_code\n ' , '\n ```' )]
294+ mock_executor .error_retry_attempts = 2
295+ mock_executor .stateful = False
296+ mock_executor .execute_code .return_value = CodeExecutionResult (stdout = '42' )
297+
298+ agent = Agent (name = 'test_agent' , code_executor = mock_executor )
299+ invocation_context = await testing_utils .create_invocation_context (
300+ agent = agent , user_content = 'run some code'
301+ )
302+ invocation_context .artifact_service = MagicMock ()
303+ invocation_context .artifact_service .save_artifact = AsyncMock (
304+ return_value = 'v1'
305+ )
306+
307+ llm_response = LlmResponse (
308+ content = None ,
309+ error_code = 'UNEXPECTED_TOOL_CALL' ,
310+ error_message = 'Unexpected tool call: print(6*7)' ,
311+ )
312+
313+ events = [
314+ event
315+ async for event in response_processor .run_async (
316+ invocation_context , llm_response
317+ )
318+ ]
319+
320+ mock_executor .execute_code .assert_called_once ()
321+ call_input = mock_executor .execute_code .call_args [0 ][1 ]
322+ assert call_input .code == 'print(6*7)'
323+ assert len (events ) == 2
324+ mock_logger .info .assert_called_once ()
325+
326+
327+ @pytest .mark .asyncio
328+ async def test_post_processor_skips_recovery_for_builtin_executor ():
329+ code_executor = BuiltInCodeExecutor ()
330+ agent = Agent (name = 'test_agent' , code_executor = code_executor )
331+ invocation_context = await testing_utils .create_invocation_context (
332+ agent = agent , user_content = 'run some code'
333+ )
334+ invocation_context .artifact_service = MagicMock ()
335+ invocation_context .artifact_service .save_artifact = AsyncMock ()
336+
337+ llm_response = LlmResponse (
338+ content = None ,
339+ error_code = 'UNEXPECTED_TOOL_CALL' ,
340+ error_message = 'Unexpected tool call: print(1)' ,
341+ )
342+
343+ events = [
344+ event
345+ async for event in response_processor .run_async (
346+ invocation_context , llm_response
347+ )
348+ ]
349+
350+ # BuiltInCodeExecutor path bails out early — no events, no artifact saves.
351+ assert events == []
352+ invocation_context .artifact_service .save_artifact .assert_not_called ()
0 commit comments