@@ -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 ]:
@@ -525,6 +557,8 @@ def serialize_output(output: list) -> str:
525557 pass
526558
527559 reasoning_content = '' .join (reasoning_parts ).strip ()
560+ if not reasoning_content :
561+ continue # no displayable text; item stays in output for round-trip
528562
529563 duration = item .get ('duration' )
530564 status = item .get ('status' , 'in_progress' )
@@ -3397,15 +3431,43 @@ async def non_streaming_chat_response_handler(response, ctx):
33973431 # otherwise generate from response content
33983432 response_output = response_data .get ('output' )
33993433 if not response_output :
3400- response_output = [
3434+ message_obj = choices [0 ].get ('message' , {})
3435+ reasoning_text = (
3436+ message_obj .get ('reasoning_content' )
3437+ or message_obj .get ('reasoning' )
3438+ )
3439+ reasoning_details = message_obj .get ('reasoning_details' )
3440+
3441+ response_output = []
3442+
3443+ if reasoning_text or reasoning_details :
3444+ r_item = {
3445+ 'type' : 'reasoning' ,
3446+ 'id' : output_id ('r' ),
3447+ 'status' : 'completed' ,
3448+ 'start_tag' : '<think>' ,
3449+ 'end_tag' : '</think>' ,
3450+ 'attributes' : {'type' : 'reasoning_content' },
3451+ 'content' : [{'type' : 'output_text' , 'text' : reasoning_text }] if reasoning_text else [],
3452+ 'summary' : None ,
3453+ }
3454+ if reasoning_details :
3455+ r_item ['reasoning_details' ] = (
3456+ reasoning_details
3457+ if isinstance (reasoning_details , list )
3458+ else [reasoning_details ]
3459+ )
3460+ response_output .append (r_item )
3461+
3462+ response_output .append (
34013463 {
34023464 'type' : 'message' ,
34033465 'id' : output_id ('msg' ),
34043466 'status' : 'completed' ,
34053467 'role' : 'assistant' ,
34063468 'content' : [{'type' : 'output_text' , 'text' : content }],
34073469 }
3408- ]
3470+ )
34093471
34103472 await event_emitter (
34113473 {
@@ -4111,9 +4173,12 @@ async def flush_pending_delta_data(threshold: int = 0):
41114173 or delta .get ('reasoning' )
41124174 or delta .get ('thinking' )
41134175 )
4114- if reasoning_content :
4176+ reasoning_details_chunk = delta .get ('reasoning_details' )
4177+
4178+ # Ensure a reasoning item exists for both text and structured-block deltas.
4179+ if reasoning_content or reasoning_details_chunk :
41154180 if not output or output [- 1 ].get ('type' ) != 'reasoning' :
4116- reasoning_item = {
4181+ output . append ( {
41174182 'type' : 'reasoning' ,
41184183 'id' : output_id ('r' ),
41194184 'status' : 'in_progress' ,
@@ -4123,23 +4188,24 @@ async def flush_pending_delta_data(threshold: int = 0):
41234188 'content' : [],
41244189 'summary' : None ,
41254190 'started_at' : time .time (),
4126- }
4127- output .append (reasoning_item )
4128- else :
4129- reasoning_item = output [- 1 ]
4191+ })
41304192
4131- # Append to reasoning content
4193+ if reasoning_content :
4194+ reasoning_item = output [- 1 ]
41324195 parts = reasoning_item .get ('content' , [])
41334196 if parts and parts [- 1 ].get ('type' ) == 'output_text' :
41344197 parts [- 1 ]['text' ] += reasoning_content
41354198 else :
4136- reasoning_item ['content' ] = [
4137- {
4138- 'type' : 'output_text' ,
4139- 'text' : reasoning_content ,
4140- }
4141- ]
4199+ reasoning_item ['content' ] = [{'type' : 'output_text' , 'text' : reasoning_content }]
4200+
4201+ # Accumulate raw structured reasoning_details for provider round-trip.
4202+ if reasoning_details_chunk :
4203+ _merge_reasoning_details (
4204+ output [- 1 ].setdefault ('reasoning_details' , []),
4205+ reasoning_details_chunk ,
4206+ )
41424207
4208+ if reasoning_content or reasoning_details_chunk :
41434209 data = {'content' : serialize_output (full_output ())}
41444210
41454211 if value :
0 commit comments