@@ -539,7 +539,7 @@ event: content_block_start
539539data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "name": "web_searcher"}}
540540
541541event: content_block_delta
542- data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "text ": "Searching for information..."}}
542+ data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking ": "Searching for information..."}}
543543
544544event: content_block_stop
545545data: {"type": "content_block_stop", "index": 0}
@@ -564,6 +564,7 @@ data: {"type": "message_stop"}
564564 bodyStr := string (bm )
565565
566566 var contentDeltas []string
567+ var reasoningTexts []string
567568 var foundToolCallWithArgs bool
568569 var finalFinishReason openai.ChatCompletionChoicesFinishReason
569570
@@ -586,6 +587,11 @@ data: {"type": "message_stop"}
586587 if choice .Delta .Content != nil {
587588 contentDeltas = append (contentDeltas , * choice .Delta .Content )
588589 }
590+ if choice .Delta .ReasoningContent != nil {
591+ if choice .Delta .ReasoningContent .Text != "" {
592+ reasoningTexts = append (reasoningTexts , choice .Delta .ReasoningContent .Text )
593+ }
594+ }
589595 if len (choice .Delta .ToolCalls ) > 0 {
590596 toolCall := choice .Delta .ToolCalls [0 ]
591597 // Check if this is the tool chunk that contains the arguments.
@@ -607,11 +613,155 @@ data: {"type": "message_stop"}
607613 }
608614 }
609615
610- fullContent := strings .Join (contentDeltas , "" )
611- assert .Contains (t , fullContent , "Searching for information..." )
616+ fullReasoning := strings .Join (reasoningTexts , "" )
617+
618+ assert .Contains (t , fullReasoning , "Searching for information..." )
612619 require .True (t , foundToolCallWithArgs , "Did not find a tool call chunk with arguments to assert against" )
613620 assert .Equal (t , openai .ChatCompletionChoicesFinishReasonToolCalls , finalFinishReason , "Final finish reason should be 'tool_calls'" )
614621 })
622+
623+ t .Run ("handles thinking delta stream with text only" , func (t * testing.T ) {
624+ sseStream := `
625+ event: message_start
626+ data: {"type": "message_start", "message": {"id": "msg_thinking_1", "type": "message", "role": "assistant", "usage": {"input_tokens": 20, "output_tokens": 1}}}
627+
628+ event: content_block_start
629+ data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking"}}
630+
631+ event: content_block_delta
632+ data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "Let me think about this problem step by step."}}
633+
634+ event: content_block_delta
635+ data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": " First, I need to understand the requirements."}}
636+
637+ event: content_block_stop
638+ data: {"type": "content_block_stop", "index": 0}
639+
640+ event: message_delta
641+ data: {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 15}}
642+
643+ event: message_stop
644+ data: {"type": "message_stop"}
645+ `
646+ openAIReq := & openai.ChatCompletionRequest {Stream : true , Model : "test-model" , MaxTokens : new (int64 )}
647+ translator := NewChatCompletionOpenAIToGCPAnthropicTranslator ("" , "" ).(* openAIToGCPAnthropicTranslatorV1ChatCompletion )
648+ _ , _ , err := translator .RequestBody (nil , openAIReq , false )
649+ require .NoError (t , err )
650+
651+ _ , bm , _ , _ , err := translator .ResponseBody (map [string ]string {}, strings .NewReader (sseStream ), true , nil )
652+ require .NoError (t , err )
653+ require .NotNil (t , bm )
654+ bodyStr := string (bm )
655+
656+ var reasoningTexts []string
657+ var foundFinishReason bool
658+
659+ lines := strings .SplitSeq (strings .TrimSpace (bodyStr ), "\n \n " )
660+ for line := range lines {
661+ if ! strings .HasPrefix (line , "data: " ) || strings .Contains (line , "[DONE]" ) {
662+ continue
663+ }
664+ jsonBody := strings .TrimPrefix (line , "data: " )
665+
666+ var chunk openai.ChatCompletionResponseChunk
667+ err = json .Unmarshal ([]byte (jsonBody ), & chunk )
668+ require .NoError (t , err , "Failed to unmarshal chunk: %s" , jsonBody )
669+
670+ if len (chunk .Choices ) == 0 {
671+ continue
672+ }
673+ choice := chunk .Choices [0 ]
674+ if choice .Delta != nil && choice .Delta .ReasoningContent != nil {
675+ if choice .Delta .ReasoningContent .Text != "" {
676+ reasoningTexts = append (reasoningTexts , choice .Delta .ReasoningContent .Text )
677+ }
678+ }
679+ if choice .FinishReason == openai .ChatCompletionChoicesFinishReasonStop {
680+ foundFinishReason = true
681+ }
682+ }
683+
684+ fullReasoning := strings .Join (reasoningTexts , "" )
685+ assert .Contains (t , fullReasoning , "Let me think about this problem step by step." )
686+ assert .Contains (t , fullReasoning , " First, I need to understand the requirements." )
687+ require .True (t , foundFinishReason , "Should find stop finish reason" )
688+ })
689+
690+ t .Run ("handles thinking delta stream with text and signature" , func (t * testing.T ) {
691+ sseStream := `
692+ event: message_start
693+ data: {"type": "message_start", "message": {"id": "msg_thinking_2", "type": "message", "role": "assistant", "usage": {"input_tokens": 25, "output_tokens": 1}}}
694+
695+ event: content_block_start
696+ data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking"}}
697+
698+ event: content_block_delta
699+ data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "Processing request...", "signature": "sig_abc123"}}
700+
701+ event: content_block_delta
702+ data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": " Analyzing data...", "signature": "sig_def456"}}
703+
704+ event: content_block_stop
705+ data: {"type": "content_block_stop", "index": 0}
706+
707+ event: message_delta
708+ data: {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 20}}
709+
710+ event: message_stop
711+ data: {"type": "message_stop"}
712+ `
713+ openAIReq := & openai.ChatCompletionRequest {Stream : true , Model : "test-model" , MaxTokens : new (int64 )}
714+ translator := NewChatCompletionOpenAIToGCPAnthropicTranslator ("" , "" ).(* openAIToGCPAnthropicTranslatorV1ChatCompletion )
715+ _ , _ , err := translator .RequestBody (nil , openAIReq , false )
716+ require .NoError (t , err )
717+
718+ _ , bm , _ , _ , err := translator .ResponseBody (map [string ]string {}, strings .NewReader (sseStream ), true , nil )
719+ require .NoError (t , err )
720+ require .NotNil (t , bm )
721+ bodyStr := string (bm )
722+
723+ var reasoningTexts []string
724+ var signatures []string
725+ var foundFinishReason bool
726+
727+ lines := strings .SplitSeq (strings .TrimSpace (bodyStr ), "\n \n " )
728+ for line := range lines {
729+ if ! strings .HasPrefix (line , "data: " ) || strings .Contains (line , "[DONE]" ) {
730+ continue
731+ }
732+ jsonBody := strings .TrimPrefix (line , "data: " )
733+
734+ var chunk openai.ChatCompletionResponseChunk
735+ err = json .Unmarshal ([]byte (jsonBody ), & chunk )
736+ require .NoError (t , err , "Failed to unmarshal chunk: %s" , jsonBody )
737+
738+ if len (chunk .Choices ) == 0 {
739+ continue
740+ }
741+ choice := chunk .Choices [0 ]
742+ if choice .Delta != nil && choice .Delta .ReasoningContent != nil {
743+ if choice .Delta .ReasoningContent .Text != "" {
744+ reasoningTexts = append (reasoningTexts , choice .Delta .ReasoningContent .Text )
745+ }
746+ if choice .Delta .ReasoningContent .Signature != "" {
747+ signatures = append (signatures , choice .Delta .ReasoningContent .Signature )
748+ }
749+ }
750+ if choice .FinishReason == openai .ChatCompletionChoicesFinishReasonStop {
751+ foundFinishReason = true
752+ }
753+ }
754+
755+ fullReasoning := strings .Join (reasoningTexts , "" )
756+ assert .Contains (t , fullReasoning , "Processing request..." )
757+ assert .Contains (t , fullReasoning , " Analyzing data..." )
758+
759+ allSignatures := strings .Join (signatures , "," )
760+ assert .Contains (t , allSignatures , "sig_abc123" )
761+ assert .Contains (t , allSignatures , "sig_def456" )
762+
763+ require .True (t , foundFinishReason , "Should find stop finish reason" )
764+ })
615765}
616766
617767func TestAnthropicStreamParser_EventTypes (t * testing.T ) {
0 commit comments