@@ -218,6 +218,38 @@ def split_json_objects(raw: str) -> list[str]:
218218 return expanded
219219
220220
221+ def _merge_reasoning_details (details : list , chunk : object ) -> None :
222+ """Merge reasoning_details delta items into a list in-place, keyed by index.
223+
224+ Items sharing the same index are merged: text and summary fields are
225+ concatenated; all other fields are overwritten with the latest value.
226+ chunk may be a list of items or a single dict.
227+ """
228+ items = chunk if isinstance (chunk , list ) else ([chunk ] if isinstance (chunk , dict ) else [])
229+ for item in items :
230+ if not isinstance (item , dict ):
231+ continue
232+ idx = item .get ('index' , 0 )
233+ if not isinstance (idx , int ) or idx < 0 :
234+ continue
235+ while len (details ) <= idx :
236+ details .append ({})
237+ existing = details [idx ]
238+ existing ['type' ] = item .get ('type' , existing .get ('type' , 'reasoning.text' ))
239+ if 'format' in item :
240+ existing ['format' ] = item ['format' ]
241+ if 'id' in item :
242+ existing ['id' ] = item ['id' ]
243+ if item .get ('text' ):
244+ existing ['text' ] = existing .get ('text' , '' ) + item ['text' ]
245+ if item .get ('summary' ):
246+ existing ['summary' ] = existing .get ('summary' , '' ) + item ['summary' ]
247+ if 'data' in item :
248+ existing ['data' ] = item ['data' ]
249+ if item .get ('signature' ):
250+ existing ['signature' ] = item ['signature' ]
251+
252+
221253def get_citation_source_from_tool_result (
222254 tool_name : str , tool_params : dict , tool_result : str , tool_id : str = ''
223255) -> list [dict ]:
@@ -524,6 +556,8 @@ def serialize_output(output: list) -> str:
524556 pass
525557
526558 reasoning_content = '' .join (reasoning_parts ).strip ()
559+ if not reasoning_content :
560+ continue # no displayable text; item stays in output for round-trip
527561
528562 duration = item .get ('duration' )
529563 status = item .get ('status' , 'in_progress' )
@@ -3360,15 +3394,43 @@ async def non_streaming_chat_response_handler(response, ctx):
33603394 # otherwise generate from response content
33613395 response_output = response_data .get ('output' )
33623396 if not response_output :
3363- response_output = [
3397+ message_obj = choices [0 ].get ('message' , {})
3398+ reasoning_text = (
3399+ message_obj .get ('reasoning_content' )
3400+ or message_obj .get ('reasoning' )
3401+ )
3402+ reasoning_details = message_obj .get ('reasoning_details' )
3403+
3404+ response_output = []
3405+
3406+ if reasoning_text or reasoning_details :
3407+ r_item = {
3408+ 'type' : 'reasoning' ,
3409+ 'id' : output_id ('r' ),
3410+ 'status' : 'completed' ,
3411+ 'start_tag' : '<think>' ,
3412+ 'end_tag' : '</think>' ,
3413+ 'attributes' : {'type' : 'reasoning_content' },
3414+ 'content' : [{'type' : 'output_text' , 'text' : reasoning_text }] if reasoning_text else [],
3415+ 'summary' : None ,
3416+ }
3417+ if reasoning_details :
3418+ r_item ['reasoning_details' ] = (
3419+ reasoning_details
3420+ if isinstance (reasoning_details , list )
3421+ else [reasoning_details ]
3422+ )
3423+ response_output .append (r_item )
3424+
3425+ response_output .append (
33643426 {
33653427 'type' : 'message' ,
33663428 'id' : output_id ('msg' ),
33673429 'status' : 'completed' ,
33683430 'role' : 'assistant' ,
33693431 'content' : [{'type' : 'output_text' , 'text' : content }],
33703432 }
3371- ]
3433+ )
33723434
33733435 await event_emitter (
33743436 {
@@ -4074,9 +4136,12 @@ async def flush_pending_delta_data(threshold: int = 0):
40744136 or delta .get ('reasoning' )
40754137 or delta .get ('thinking' )
40764138 )
4077- if reasoning_content :
4139+ reasoning_details_chunk = delta .get ('reasoning_details' )
4140+
4141+ # Ensure a reasoning item exists for both text and structured-block deltas.
4142+ if reasoning_content or reasoning_details_chunk :
40784143 if not output or output [- 1 ].get ('type' ) != 'reasoning' :
4079- reasoning_item = {
4144+ output . append ( {
40804145 'type' : 'reasoning' ,
40814146 'id' : output_id ('r' ),
40824147 'status' : 'in_progress' ,
@@ -4086,23 +4151,24 @@ async def flush_pending_delta_data(threshold: int = 0):
40864151 'content' : [],
40874152 'summary' : None ,
40884153 'started_at' : time .time (),
4089- }
4090- output .append (reasoning_item )
4091- else :
4092- reasoning_item = output [- 1 ]
4154+ })
40934155
4094- # Append to reasoning content
4156+ if reasoning_content :
4157+ reasoning_item = output [- 1 ]
40954158 parts = reasoning_item .get ('content' , [])
40964159 if parts and parts [- 1 ].get ('type' ) == 'output_text' :
40974160 parts [- 1 ]['text' ] += reasoning_content
40984161 else :
4099- reasoning_item ['content' ] = [
4100- {
4101- 'type' : 'output_text' ,
4102- 'text' : reasoning_content ,
4103- }
4104- ]
4162+ reasoning_item ['content' ] = [{'type' : 'output_text' , 'text' : reasoning_content }]
4163+
4164+ # Accumulate raw structured reasoning_details for provider round-trip.
4165+ if reasoning_details_chunk :
4166+ _merge_reasoning_details (
4167+ output [- 1 ].setdefault ('reasoning_details' , []),
4168+ reasoning_details_chunk ,
4169+ )
41054170
4171+ if reasoning_content or reasoning_details_chunk :
41064172 data = {'content' : serialize_output (full_output ())}
41074173
41084174 if value :
0 commit comments