@@ -1049,6 +1049,13 @@ def process_tool_result(
10491049
10501050 tool_result_files = []
10511051
1052+ # Detect base64 image data URIs from tool results (e.g. binary image
1053+ # responses from execute_tool_server). Move the data URI to
1054+ # tool_result_files and replace tool_result with a text summary.
1055+ if isinstance (tool_result , str ) and tool_result .startswith ('data:image/' ):
1056+ tool_result_files .append ({'type' : 'image' , 'url' : tool_result })
1057+ tool_result = f'{ tool_function_name } : Image file read successfully.'
1058+
10521059 if isinstance (tool_result , list ):
10531060 if tool_type == 'mcp' : # MCP
10541061 tool_response = []
@@ -4181,19 +4188,27 @@ async def flush_pending_delta_data(threshold: int = 0):
41814188 break
41824189
41834190 for result in results :
4191+ output_parts = [{'type' : 'input_text' , 'text' : result .get ('content' , '' )}]
4192+
4193+ # Separate image data URIs (for LLM via input_image) from
4194+ # other files (for frontend display via files attribute).
4195+ display_files = []
4196+ for file_item in result .get ('files' , []):
4197+ if file_item .get ('type' ) == 'image' and file_item .get ('url' , '' ).startswith ('data:' ):
4198+ # LLM-only: add as input_image part (invisible to serialize_output)
4199+ output_parts .append ({'type' : 'input_image' , 'image_url' : file_item ['url' ]})
4200+ else :
4201+ # Frontend display (MCP images, audio, etc.)
4202+ display_files .append (file_item )
4203+
41844204 output .append (
41854205 {
41864206 'type' : 'function_call_output' ,
41874207 'id' : output_id ('fco' ),
41884208 'call_id' : result .get ('tool_call_id' , '' ),
4189- 'output' : [
4190- {
4191- 'type' : 'input_text' ,
4192- 'text' : result .get ('content' , '' ),
4193- }
4194- ],
4209+ 'output' : output_parts ,
41954210 'status' : 'completed' ,
4196- ** ({'files' : result . get ( 'files' ) } if result . get ( 'files' ) else {}),
4211+ ** ({'files' : display_files } if display_files else {}),
41974212 ** ({'embeds' : result .get ('embeds' )} if result .get ('embeds' ) else {}),
41984213 }
41994214 )
@@ -4262,12 +4277,23 @@ async def flush_pending_delta_data(threshold: int = 0):
42624277 )
42634278 tool_call_sources .clear ()
42644279
4280+ # Strip input_image parts (large base64 data URIs) from the
4281+ # output sent to the frontend — they're only for LLM consumption
4282+ # via convert_output_to_messages.
4283+ frontend_output = []
4284+ for item in output :
4285+ if item .get ('type' ) == 'function_call_output' :
4286+ parts = item .get ('output' , [])
4287+ if any (p .get ('type' ) == 'input_image' for p in parts ):
4288+ item = {** item , 'output' : [p for p in parts if p .get ('type' ) != 'input_image' ]}
4289+ frontend_output .append (item )
4290+
42654291 await event_emitter (
42664292 {
42674293 'type' : 'chat:completion' ,
42684294 'data' : {
42694295 'content' : serialize_output (output ),
4270- 'output' : output ,
4296+ 'output' : frontend_output ,
42714297 },
42724298 }
42734299 )
@@ -4287,11 +4313,35 @@ async def flush_pending_delta_data(threshold: int = 0):
42874313 )
42884314 new_form_data ['previous_response_id' ] = last_response_id
42894315 else :
4316+ tool_messages = convert_output_to_messages (output , raw = True )
4317+
4318+ # Chat Completions providers don't support multimodal
4319+ # tool messages. Extract images into a user message.
4320+ image_urls = []
4321+ for message in tool_messages :
4322+ if message .get ('role' ) == 'tool' and isinstance (message .get ('content' ), list ):
4323+ text_parts = []
4324+ for part in message ['content' ]:
4325+ if part .get ('type' ) == 'input_text' :
4326+ text_parts .append (part .get ('text' , '' ))
4327+ elif part .get ('type' ) == 'input_image' :
4328+ image_urls .append (part .get ('image_url' , '' ))
4329+ message ['content' ] = '' .join (text_parts )
4330+
42904331 new_form_data ['messages' ] = [
42914332 * form_data ['messages' ],
4292- * convert_output_to_messages ( output , raw = True ) ,
4333+ * tool_messages ,
42934334 ]
42944335
4336+ if image_urls :
4337+ new_form_data ['messages' ].append ({
4338+ 'role' : 'user' ,
4339+ 'content' : [
4340+ {'type' : 'text' , 'text' : 'Here are the images from the tool results above. Please analyze them.' },
4341+ * [{'type' : 'image_url' , 'image_url' : {'url' : url }} for url in image_urls ],
4342+ ],
4343+ })
4344+
42954345 res = await generate_chat_completion (
42964346 request ,
42974347 new_form_data ,
@@ -4416,7 +4466,7 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
44164466 if isinstance (stdout , str ):
44174467 stdoutLines = stdout .split ('\n ' )
44184468 for idx , line in enumerate (stdoutLines ):
4419- if 'data:image/png ;base64' in line :
4469+ if re . match ( r 'data:image/\w+ ;base64', line ) :
44204470 image_url = get_image_url_from_base64 (
44214471 request ,
44224472 line ,
@@ -4433,7 +4483,7 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
44334483 if isinstance (result , str ):
44344484 resultLines = result .split ('\n ' )
44354485 for idx , line in enumerate (resultLines ):
4436- if 'data:image/png ;base64' in line :
4486+ if re . match ( r 'data:image/\w+ ;base64', line ) :
44374487 image_url = get_image_url_from_base64 (
44384488 request ,
44394489 line ,
0 commit comments