1212use Cortex \LLM \Data \ChatGenerationChunk ;
1313use Cortex \LLM \Data \Messages \AssistantMessage ;
1414use Cortex \SDK \Anthropic \Contracts \StreamEvent ;
15+ use Cortex \LLM \Data \Messages \Content \TextContent ;
1516use Cortex \SDK \Anthropic \Data \Messages \MessageStream ;
17+ use Cortex \LLM \Data \Messages \Content \ReasoningContent ;
1618use Cortex \SDK \Anthropic \Data \Messages \Streaming \TextDelta ;
1719use Cortex \SDK \Anthropic \Data \Messages \Streaming \ThinkingDelta ;
1820use Cortex \SDK \Anthropic \Data \Messages \Streaming \InputJsonDelta ;
21+ use Cortex \SDK \Anthropic \Data \Messages \Streaming \SignatureDelta ;
1922use Cortex \SDK \Anthropic \Data \Messages \Streaming \Events \MessageStop ;
2023use Cortex \SDK \Anthropic \Data \Messages \Streaming \Events \MessageDelta ;
2124use Cortex \SDK \Anthropic \Data \Messages \Streaming \Events \MessageStart ;
@@ -40,7 +43,9 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult
4043 return new ChatStreamResult (function () use ($ response ): Generator {
4144 yield from $ this ->streamBuffer ?->drain() ?? [];
4245
43- $ contentSoFar = '' ;
46+ $ contentSoFar = [];
47+ $ currentTextContent = null ;
48+ $ currentReasoningContent = null ;
4449
4550 /** @var \Cortex\SDK\Anthropic\Contracts\StreamEvent $event */
4651 foreach ($ response as $ event ) {
@@ -58,13 +63,59 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult
5863 ? $ this ->mapFinishReason ($ event ->stopReason )
5964 : null ;
6065
61- $ contentSoFar .= $ event instanceof ContentBlockDelta
62- ? ($ event ->delta instanceof TextDelta
63- ? $ event ->delta ->text
64- : ($ event ->delta instanceof InputJsonDelta
65- ? $ event ->delta ->partialJson
66- : $ event ->delta ->thinking ))
67- : '' ;
66+ // Handle ContentBlockStart - initialize new content objects
67+ if ($ event instanceof ContentBlockStart) {
68+ if ($ event ->contentBlock instanceof TextContentBlock) {
69+ $ currentTextContent = new TextContent ($ event ->contentBlock ->text );
70+ } elseif ($ event ->contentBlock instanceof ThinkingContentBlock) {
71+ $ currentReasoningContent = new ReasoningContent (
72+ $ event ->contentBlock ->signature ,
73+ $ event ->contentBlock ->thinking ,
74+ );
75+ } elseif ($ event ->contentBlock instanceof RedactedThinkingContentBlock) {
76+ // For redacted thinking, we might use the data field as id
77+ $ currentReasoningContent = new ReasoningContent (
78+ $ event ->contentBlock ->data ,
79+ $ event ->contentBlock ->text ,
80+ );
81+ }
82+ }
83+
84+ // Handle ContentBlockDelta - append incremental content
85+ if ($ event instanceof ContentBlockDelta) {
86+ if ($ event ->delta instanceof TextDelta && $ currentTextContent !== null ) {
87+ $ currentTextContent = $ currentTextContent ->append ($ event ->delta ->text );
88+ } elseif ($ event ->delta instanceof ThinkingDelta && $ currentReasoningContent !== null ) {
89+ $ currentReasoningContent = $ currentReasoningContent ->append ($ event ->delta ->thinking );
90+ } elseif ($ event ->delta instanceof SignatureDelta && $ currentReasoningContent !== null ) {
91+ // Update the ID if signature delta comes after content block start
92+ $ currentReasoningContent = new ReasoningContent (
93+ $ event ->delta ->signature ,
94+ $ currentReasoningContent ->reasoning ,
95+ );
96+ }
97+ }
98+
99+ // Handle ContentBlockStop - finalize and add to contentSoFar
100+ if ($ event instanceof ContentBlockStop) {
101+ if ($ currentTextContent !== null ) {
102+ $ contentSoFar [] = $ currentTextContent ;
103+ $ currentTextContent = null ;
104+ } elseif ($ currentReasoningContent !== null ) {
105+ $ contentSoFar [] = $ currentReasoningContent ;
106+ $ currentReasoningContent = null ;
107+ }
108+ }
109+
110+ // Build current contentSoFar array including partial content being built
111+ // Clone content objects to avoid mutating references in previous chunks
112+ $ currentContentSoFar = [...$ contentSoFar ];
113+
114+ if ($ currentTextContent !== null ) {
115+ $ currentContentSoFar [] = new TextContent ($ currentTextContent ->text );
116+ } elseif ($ currentReasoningContent !== null ) {
117+ $ currentContentSoFar [] = new ReasoningContent ($ currentReasoningContent ->id , $ currentReasoningContent ->reasoning );
118+ }
68119
69120 $ id = $ event instanceof MessageStart
70121 ? $ event ->message ->id
@@ -95,7 +146,7 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult
95146 createdAt: $ meta ->createdAt ?? new DateTimeImmutable (),
96147 finishReason: $ finishReason ,
97148 usage: $ usage ,
98- contentSoFar: $ contentSoFar ,
149+ contentSoFar: $ currentContentSoFar ,
99150 isFinal: $ usage !== null ,
100151 rawChunk: $ this ->includeRaw ? $ event ->raw () : null ,
101152 );
0 commit comments