@@ -356,6 +356,98 @@ def test_items_to_messages_with_function_output_item():
356356 assert tool_msg ["content" ] == func_output_item ["output" ]
357357
358358
359+ def test_items_to_messages_with_non_text_only_function_output_uses_placeholder_by_default ():
360+ """Default conversion should keep running without sending an empty tool message."""
361+ func_output_item : FunctionCallOutput = {
362+ "type" : "function_call_output" ,
363+ "call_id" : "somecall" ,
364+ "output" : [
365+ {
366+ "type" : "input_image" ,
367+ "image_url" : "https://example.com/image.png" ,
368+ }
369+ ],
370+ }
371+
372+ messages = Converter .items_to_messages ([func_output_item ])
373+
374+ assert len (messages ) == 1
375+ tool_msg = messages [0 ]
376+ assert tool_msg ["role" ] == "tool"
377+ assert tool_msg ["tool_call_id" ] == func_output_item ["call_id" ]
378+ assert tool_msg ["content" ] == "[non-text tool output omitted]"
379+
380+
381+ def test_items_to_messages_with_non_text_only_function_output_raises_in_strict_mode ():
382+ """Strict validation should fail explicitly instead of silently losing the output."""
383+ func_output_item : FunctionCallOutput = {
384+ "type" : "function_call_output" ,
385+ "call_id" : "somecall" ,
386+ "output" : [
387+ {
388+ "type" : "input_image" ,
389+ "image_url" : "https://example.com/image.png" ,
390+ }
391+ ],
392+ }
393+
394+ with pytest .raises (UserError , match = "cannot contain only non-text content" ):
395+ Converter .items_to_messages ([func_output_item ], strict_feature_validation = True )
396+
397+
398+ def test_items_to_messages_with_mixed_function_output_keeps_text_by_default ():
399+ """Default conversion should preserve text parts and omit unsupported non-text parts."""
400+ func_output_item : FunctionCallOutput = {
401+ "type" : "function_call_output" ,
402+ "call_id" : "somecall" ,
403+ "output" : [
404+ {"type" : "input_text" , "text" : "visible text" },
405+ {
406+ "type" : "input_image" ,
407+ "image_url" : "https://example.com/image.png" ,
408+ },
409+ ],
410+ }
411+
412+ messages = Converter .items_to_messages ([func_output_item ])
413+
414+ assert len (messages ) == 1
415+ tool_msg = messages [0 ]
416+ assert tool_msg ["role" ] == "tool"
417+ assert tool_msg ["tool_call_id" ] == func_output_item ["call_id" ]
418+ assert tool_msg ["content" ] == [{"type" : "text" , "text" : "visible text" }]
419+
420+
421+ def test_items_to_messages_can_preserve_non_text_function_output () -> None :
422+ """Compatible providers can opt in to preserving non-text tool output."""
423+ func_output_item : FunctionCallOutput = {
424+ "type" : "function_call_output" ,
425+ "call_id" : "somecall" ,
426+ "output" : [
427+ {
428+ "type" : "input_image" ,
429+ "image_url" : "https://example.com/image.png" ,
430+ }
431+ ],
432+ }
433+
434+ messages = Converter .items_to_messages (
435+ [func_output_item ],
436+ preserve_tool_output_all_content = True ,
437+ )
438+
439+ assert len (messages ) == 1
440+ tool_msg = messages [0 ]
441+ assert tool_msg ["role" ] == "tool"
442+ assert tool_msg ["tool_call_id" ] == func_output_item ["call_id" ]
443+ assert tool_msg ["content" ] == [
444+ {
445+ "type" : "image_url" ,
446+ "image_url" : {"url" : "https://example.com/image.png" , "detail" : "auto" },
447+ }
448+ ]
449+
450+
359451def test_extract_all_and_text_content_for_strings_and_lists ():
360452 """
361453 The converter provides helpers for extracting user-supplied message content
0 commit comments