@@ -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 ]:
@@ -517,6 +549,8 @@ def serialize_output(output: list) -> str:
517549 pass
518550
519551 reasoning_content = '' .join (reasoning_parts ).strip ()
552+ if not reasoning_content :
553+ continue # no displayable text; item stays in output for round-trip
520554
521555 duration = item .get ('duration' )
522556 status = item .get ('status' , 'in_progress' )
@@ -3361,15 +3395,43 @@ async def non_streaming_chat_response_handler(response, ctx):
33613395 # otherwise generate from response content
33623396 response_output = response_data .get ('output' )
33633397 if not response_output :
3364- response_output = [
3398+ message_obj = choices [0 ].get ('message' , {})
3399+ reasoning_text = (
3400+ message_obj .get ('reasoning_content' )
3401+ or message_obj .get ('reasoning' )
3402+ )
3403+ reasoning_details = message_obj .get ('reasoning_details' )
3404+
3405+ response_output = []
3406+
3407+ if reasoning_text or reasoning_details :
3408+ r_item = {
3409+ 'type' : 'reasoning' ,
3410+ 'id' : output_id ('r' ),
3411+ 'status' : 'completed' ,
3412+ 'start_tag' : '<think>' ,
3413+ 'end_tag' : '</think>' ,
3414+ 'attributes' : {'type' : 'reasoning_content' },
3415+ 'content' : [{'type' : 'output_text' , 'text' : reasoning_text }] if reasoning_text else [],
3416+ 'summary' : None ,
3417+ }
3418+ if reasoning_details :
3419+ r_item ['reasoning_details' ] = (
3420+ reasoning_details
3421+ if isinstance (reasoning_details , list )
3422+ else [reasoning_details ]
3423+ )
3424+ response_output .append (r_item )
3425+
3426+ response_output .append (
33653427 {
33663428 'type' : 'message' ,
33673429 'id' : output_id ('msg' ),
33683430 'status' : 'completed' ,
33693431 'role' : 'assistant' ,
33703432 'content' : [{'type' : 'output_text' , 'text' : content }],
33713433 }
3372- ]
3434+ )
33733435
33743436 await event_emitter (
33753437 {
@@ -4075,9 +4137,12 @@ async def flush_pending_delta_data(threshold: int = 0):
40754137 or delta .get ('reasoning' )
40764138 or delta .get ('thinking' )
40774139 )
4078- if reasoning_content :
4140+ reasoning_details_chunk = delta .get ('reasoning_details' )
4141+
4142+ # Ensure a reasoning item exists for both text and structured-block deltas.
4143+ if reasoning_content or reasoning_details_chunk :
40794144 if not output or output [- 1 ].get ('type' ) != 'reasoning' :
4080- reasoning_item = {
4145+ output . append ( {
40814146 'type' : 'reasoning' ,
40824147 'id' : output_id ('r' ),
40834148 'status' : 'in_progress' ,
@@ -4087,23 +4152,24 @@ async def flush_pending_delta_data(threshold: int = 0):
40874152 'content' : [],
40884153 'summary' : None ,
40894154 'started_at' : time .time (),
4090- }
4091- output .append (reasoning_item )
4092- else :
4093- reasoning_item = output [- 1 ]
4155+ })
40944156
4095- # Append to reasoning content
4157+ if reasoning_content :
4158+ reasoning_item = output [- 1 ]
40964159 parts = reasoning_item .get ('content' , [])
40974160 if parts and parts [- 1 ].get ('type' ) == 'output_text' :
40984161 parts [- 1 ]['text' ] += reasoning_content
40994162 else :
4100- reasoning_item ['content' ] = [
4101- {
4102- 'type' : 'output_text' ,
4103- 'text' : reasoning_content ,
4104- }
4105- ]
4163+ reasoning_item ['content' ] = [{'type' : 'output_text' , 'text' : reasoning_content }]
4164+
4165+ # Accumulate raw structured reasoning_details for provider round-trip.
4166+ if reasoning_details_chunk :
4167+ _merge_reasoning_details (
4168+ output [- 1 ].setdefault ('reasoning_details' , []),
4169+ reasoning_details_chunk ,
4170+ )
41064171
4172+ if reasoning_content or reasoning_details_chunk :
41074173 data = {'content' : serialize_output (full_output ())}
41084174
41094175 if value :
0 commit comments