@@ -219,6 +219,38 @@ def split_json_objects(raw: str) -> list[str]:
219219 return expanded
220220
221221
222+ def _merge_reasoning_details (details : list , chunk : object ) -> None :
223+ """Merge reasoning_details delta items into a list in-place, keyed by index.
224+
225+ Items sharing the same index are merged: text and summary fields are
226+ concatenated; all other fields are overwritten with the latest value.
227+ chunk may be a list of items or a single dict.
228+ """
229+ items = chunk if isinstance (chunk , list ) else ([chunk ] if isinstance (chunk , dict ) else [])
230+ for item in items :
231+ if not isinstance (item , dict ):
232+ continue
233+ idx = item .get ('index' , 0 )
234+ if not isinstance (idx , int ) or idx < 0 :
235+ continue
236+ while len (details ) <= idx :
237+ details .append ({})
238+ existing = details [idx ]
239+ existing ['type' ] = item .get ('type' , existing .get ('type' , 'reasoning.text' ))
240+ if 'format' in item :
241+ existing ['format' ] = item ['format' ]
242+ if 'id' in item :
243+ existing ['id' ] = item ['id' ]
244+ if item .get ('text' ):
245+ existing ['text' ] = existing .get ('text' , '' ) + item ['text' ]
246+ if item .get ('summary' ):
247+ existing ['summary' ] = existing .get ('summary' , '' ) + item ['summary' ]
248+ if 'data' in item :
249+ existing ['data' ] = item ['data' ]
250+ if item .get ('signature' ):
251+ existing ['signature' ] = item ['signature' ]
252+
253+
222254def get_citation_source_from_tool_result (
223255 tool_name : str , tool_params : dict , tool_result : str , tool_id : str = ''
224256) -> list [dict ]:
@@ -3361,15 +3393,43 @@ async def non_streaming_chat_response_handler(response, ctx):
33613393 # otherwise generate from response content
33623394 response_output = response_data .get ('output' )
33633395 if not response_output :
3364- response_output = [
3396+ message_obj = choices [0 ].get ('message' , {})
3397+ reasoning_text = (
3398+ message_obj .get ('reasoning_content' )
3399+ or message_obj .get ('reasoning' )
3400+ )
3401+ reasoning_details = message_obj .get ('reasoning_details' )
3402+
3403+ response_output = []
3404+
3405+ if reasoning_text or reasoning_details :
3406+ r_item = {
3407+ 'type' : 'reasoning' ,
3408+ 'id' : output_id ('r' ),
3409+ 'status' : 'completed' ,
3410+ 'start_tag' : '<think>' ,
3411+ 'end_tag' : '</think>' ,
3412+ 'attributes' : {'type' : 'reasoning_content' },
3413+ 'content' : [{'type' : 'output_text' , 'text' : reasoning_text }] if reasoning_text else [],
3414+ 'summary' : None ,
3415+ }
3416+ if reasoning_details :
3417+ r_item ['reasoning_details' ] = (
3418+ reasoning_details
3419+ if isinstance (reasoning_details , list )
3420+ else [reasoning_details ]
3421+ )
3422+ response_output .append (r_item )
3423+
3424+ response_output .append (
33653425 {
33663426 'type' : 'message' ,
33673427 'id' : output_id ('msg' ),
33683428 'status' : 'completed' ,
33693429 'role' : 'assistant' ,
33703430 'content' : [{'type' : 'output_text' , 'text' : content }],
33713431 }
3372- ]
3432+ )
33733433
33743434 await event_emitter (
33753435 {
@@ -4075,9 +4135,10 @@ async def flush_pending_delta_data(threshold: int = 0):
40754135 or delta .get ('reasoning' )
40764136 or delta .get ('thinking' )
40774137 )
4138+ reasoning_details_chunk = delta .get ('reasoning_details' )
40784139 if reasoning_content :
40794140 if not output or output [- 1 ].get ('type' ) != 'reasoning' :
4080- reasoning_item = {
4141+ output . append ( {
40814142 'type' : 'reasoning' ,
40824143 'id' : output_id ('r' ),
40834144 'status' : 'in_progress' ,
@@ -4087,23 +4148,24 @@ async def flush_pending_delta_data(threshold: int = 0):
40874148 'content' : [],
40884149 'summary' : None ,
40894150 'started_at' : time .time (),
4090- }
4091- output .append (reasoning_item )
4092- else :
4093- reasoning_item = output [- 1 ]
4151+ })
40944152
4095- # Append to reasoning content
4153+ reasoning_item = output [ - 1 ]
40964154 parts = reasoning_item .get ('content' , [])
40974155 if parts and parts [- 1 ].get ('type' ) == 'output_text' :
40984156 parts [- 1 ]['text' ] += reasoning_content
40994157 else :
4100- reasoning_item ['content' ] = [
4101- {
4102- 'type' : 'output_text' ,
4103- 'text' : reasoning_content ,
4104- }
4105- ]
4158+ reasoning_item ['content' ] = [{'type' : 'output_text' , 'text' : reasoning_content }]
4159+
4160+ # Accumulate raw structured reasoning_details for provider round-trip.
4161+ # Only attach to an existing reasoning item to avoid blank reasoning blocks in the UI.
4162+ if reasoning_details_chunk and output and output [- 1 ].get ('type' ) == 'reasoning' :
4163+ _merge_reasoning_details (
4164+ output [- 1 ].setdefault ('reasoning_details' , []),
4165+ reasoning_details_chunk ,
4166+ )
41064167
4168+ if reasoning_content or reasoning_details_chunk :
41074169 data = {'content' : serialize_output (full_output ())}
41084170
41094171 if value :
0 commit comments