@@ -244,7 +244,11 @@ class _BaseReasoningDetail(BaseModel, frozen=True):
244244 """Common fields shared across all reasoning detail types."""
245245
246246 id : str | None = None
247- format : Literal ['unknown' , 'openai-responses-v1' , 'anthropic-claude-v1' , 'xai-responses-v1' ] | None
247+ format : (
248+ Literal ['unknown' , 'openai-responses-v1' , 'anthropic-claude-v1' , 'xai-responses-v1' , 'google-gemini-v1' ]
249+ | str
250+ | None
251+ )
248252 index : int | None
249253 type : Literal ['reasoning.text' , 'reasoning.summary' , 'reasoning.encrypted' ]
250254
@@ -610,13 +614,21 @@ def _map_thinking_delta(self, choice: chat_completion_chunk.Choice) -> Iterable[
610614 assert isinstance (choice , _OpenRouterChunkChoice )
611615
612616 if reasoning_details := choice .delta .reasoning_details :
613- for detail in reasoning_details :
617+ for i , detail in enumerate ( reasoning_details ) :
614618 thinking_part = _from_reasoning_detail (detail )
619+ # Use unique vendor_part_id for each reasoning detail type to prevent
620+ # different detail types (e.g., reasoning.text, reasoning.encrypted)
621+ # from being incorrectly merged into a single ThinkingPart.
622+ # This is required for Gemini 3 Pro which returns multiple reasoning
623+ # detail types that must be preserved separately for thought_signature handling.
624+ vendor_id = f'reasoning_detail_{ detail .type } _{ i } '
615625 yield self ._parts_manager .handle_thinking_delta (
616- vendor_part_id = 'reasoning_detail' ,
626+ vendor_part_id = vendor_id ,
617627 id = thinking_part .id ,
618628 content = thinking_part .content ,
629+ signature = thinking_part .signature ,
619630 provider_name = self ._provider_name ,
631+ provider_details = thinking_part .provider_details ,
620632 )
621633 else :
622634 return super ()._map_thinking_delta (choice )
0 commit comments