@@ -157,6 +157,40 @@ def output_id(prefix: str) -> str:
157157 return f'{ prefix } _{ uuid4 ().hex [:24 ]} '
158158
159159
160+ def merge_reasoning_details (details : list , chunk ) -> list :
161+ """Merge streamed reasoning_details fragments in place, keyed by index.
162+
163+ Concatenates text/summary for matching indexes and overwrites other fields
164+ (e.g. signature) so signed/encrypted blocks stay intact instead of
165+ fragmenting across chunks.
166+ """
167+ items = (
168+ chunk if isinstance (chunk , list ) else ([chunk ] if isinstance (chunk , dict ) else [])
169+ )
170+ for item in items :
171+ if not isinstance (item , dict ):
172+ continue
173+ index = item .get ('index' )
174+ existing = (
175+ next ((e for e in details if e .get ('index' ) == index ), None )
176+ if isinstance (index , int ) and index >= 0
177+ else None
178+ )
179+ if existing is None :
180+ entry = dict (item )
181+ entry .setdefault ('type' , 'reasoning.text' )
182+ details .append (entry )
183+ continue
184+ for key , value in item .items ():
185+ if key in ('text' , 'summary' ):
186+ if isinstance (value , str ):
187+ base = existing .get (key )
188+ existing [key ] = (base + value ) if isinstance (base , str ) else value
189+ else :
190+ existing [key ] = value
191+ return details
192+
193+
160194def _split_tool_calls (
161195 tool_calls : list [dict ],
162196) -> list [dict ]:
@@ -4251,7 +4285,7 @@ async def queue_pending_delta_data(delta_data: dict, delta_type: str):
42514285 reasoning_content = ''
42524286 reasoning_details = None
42534287
4254- if reasoning_content or reasoning_details :
4288+ if reasoning_content :
42554289 if not output or output [- 1 ].get ('type' ) != 'reasoning' :
42564290 reasoning_item = {
42574291 'type' : 'reasoning' ,
@@ -4268,30 +4302,53 @@ async def queue_pending_delta_data(delta_data: dict, delta_type: str):
42684302 else :
42694303 reasoning_item = output [- 1 ]
42704304
4271- if reasoning_content :
4272- # Append to reasoning content
4273- parts = reasoning_item .get ('content' , [])
4274- if parts and parts [- 1 ].get ('type' ) == 'output_text' :
4275- parts [- 1 ]['text' ] += reasoning_content
4276- else :
4277- reasoning_item ['content' ] = [
4278- {
4279- 'type' : 'output_text' ,
4280- 'text' : reasoning_content ,
4281- }
4282- ]
4305+ # Append to reasoning content
4306+ parts = reasoning_item .get ('content' , [])
4307+ if parts and parts [- 1 ].get ('type' ) == 'output_text' :
4308+ parts [- 1 ]['text' ] += reasoning_content
4309+ else :
4310+ reasoning_item ['content' ] = [
4311+ {
4312+ 'type' : 'output_text' ,
4313+ 'text' : reasoning_content ,
4314+ }
4315+ ]
42834316
4284- data = {
4285- 'output' : full_output (),
4317+ data = {
4318+ 'output' : full_output (),
4319+ }
4320+ delta_type = 'content'
4321+
4322+ if reasoning_details :
4323+ # Keep signatures attached to their reasoning text
4324+ # (they may stream later); create an item if encrypted.
4325+ reasoning_target = next (
4326+ (
4327+ item
4328+ for item in reversed (output )
4329+ if item .get ('type' ) == 'reasoning'
4330+ ),
4331+ None ,
4332+ )
4333+ if reasoning_target is None :
4334+ reasoning_target = {
4335+ 'type' : 'reasoning' ,
4336+ 'id' : output_id ('r' ),
4337+ 'status' : 'in_progress' ,
4338+ 'start_tag' : '<think>' ,
4339+ 'end_tag' : '</think>' ,
4340+ 'attributes' : {'type' : 'reasoning_content' },
4341+ 'content' : [],
4342+ 'summary' : None ,
4343+ 'started_at' : time .time (),
42864344 }
4287- delta_type = 'content'
4288-
4289- if reasoning_details :
4290- reasoning_item .setdefault ('reasoning_details' , []).extend (
4291- reasoning_details
4292- if isinstance (reasoning_details , list )
4293- else [reasoning_details ]
4294- )
4345+ output .append (reasoning_target )
4346+ merge_reasoning_details (
4347+ reasoning_target .setdefault (
4348+ 'reasoning_details' , []
4349+ ),
4350+ reasoning_details ,
4351+ )
42954352
42964353 if value :
42974354 if (
0 commit comments