A React Native app for the ultimate thinking partner.

fix(streaming): handle message boundary detection for tool_call chunks

The issue was that tool_call and assistant chunks could arrive without
a preceding reasoning_message, causing them to be rejected. Also, we
weren't detecting message boundaries on tool_call ID changes.

Changes:
- accumulateToolCall: Create new message if no current or ID mismatch
- accumulateAssistant: Create new message if no current or ID mismatch
- useMessageStream: Detect ID changes on both reasoning AND tool_call

This fixes the issue where multiple tool calls were being split into
separate message groups instead of accumulating into one.

+28 -6
+3 -2
src/hooks/useMessageStream.ts
··· 32 32 33 33 console.log(`๐Ÿ“ฆ [${chunkType}] ID: ${chunkId?.substring(0, 8)}...`); 34 34 35 - // DETECT NEW MESSAGE: If we see a new reasoning with different ID, finalize current 36 - if (chunkType === 'reasoning_message' && chunkId) { 35 + // DETECT NEW MESSAGE: If we see a different ID on reasoning OR tool_call, finalize current 36 + // This handles both: reasoning โ†’ tool_call transitions AND tool_call โ†’ reasoning transitions 37 + if ((chunkType === 'reasoning_message' || chunkType === 'tool_call_message') && chunkId) { 37 38 if (lastMessageIdRef.current && chunkId !== lastMessageIdRef.current) { 38 39 console.log('๐Ÿ”„ NEW MESSAGE DETECTED - finalizing previous'); 39 40 chatStore.finalizeCurrentMessage();
+25 -4
src/stores/chatStore.ts
··· 169 169 // Accumulate tool call (delta) 170 170 accumulateToolCall: (messageId, toolName, args) => { 171 171 set((state) => { 172 + // If no current message OR different ID, create new 172 173 if (!state.currentStreamingMessage || state.currentStreamingMessage.id !== messageId) { 173 - console.error('โŒ Tool call for unknown message:', messageId); 174 - return {}; 174 + console.log('๐Ÿ†• New tool call message started:', messageId.substring(0, 20)); 175 + return { 176 + currentStreamingMessage: { 177 + id: messageId, 178 + reasoning: '', 179 + content: args, 180 + type: 'tool_call', 181 + toolCallName: toolName, 182 + timestamp: new Date().toISOString(), 183 + }, 184 + }; 175 185 } 176 186 187 + // Same message, accumulate tool call args 177 188 return { 178 189 currentStreamingMessage: { 179 190 ...state.currentStreamingMessage, ··· 188 199 // Accumulate assistant (delta) 189 200 accumulateAssistant: (messageId, content) => { 190 201 set((state) => { 202 + // If no current message OR different ID, create new 191 203 if (!state.currentStreamingMessage || state.currentStreamingMessage.id !== messageId) { 192 - console.error('โŒ Assistant content for unknown message:', messageId); 193 - return {}; 204 + console.log('๐Ÿ†• New assistant message started:', messageId.substring(0, 20)); 205 + return { 206 + currentStreamingMessage: { 207 + id: messageId, 208 + reasoning: '', 209 + content: content, 210 + type: 'assistant', 211 + timestamp: new Date().toISOString(), 212 + }, 213 + }; 194 214 } 195 215 216 + // Same message, accumulate assistant content 196 217 return { 197 218 currentStreamingMessage: { 198 219 ...state.currentStreamingMessage,