@@ -235,6 +235,135 @@ describe('fetchSSE', () => {
235235 } ) ;
236236 } ) ;
237237
238+ describe ( 'content_part and reasoning_part' , ( ) => {
239+ it ( 'should handle content_part event with text and accumulate output' , async ( ) => {
240+ const mockOnMessageHandle = vi . fn ( ) ;
241+ const mockOnFinish = vi . fn ( ) ;
242+
243+ ( fetchEventSource as any ) . mockImplementationOnce (
244+ async ( url : string , options : FetchEventSourceInit ) => {
245+ options . onopen ! ( { clone : ( ) => ( { ok : true , headers : new Headers ( ) } ) } as any ) ;
246+ options . onmessage ! ( {
247+ event : 'content_part' ,
248+ data : JSON . stringify ( { content : 'Hello' , partType : 'text' } ) ,
249+ } as any ) ;
250+ options . onmessage ! ( {
251+ event : 'content_part' ,
252+ data : JSON . stringify ( { content : ' World' , partType : 'text' } ) ,
253+ } as any ) ;
254+ } ,
255+ ) ;
256+
257+ await fetchSSE ( '/' , {
258+ onMessageHandle : mockOnMessageHandle ,
259+ onFinish : mockOnFinish ,
260+ responseAnimation : 'none' ,
261+ } ) ;
262+
263+ expect ( mockOnMessageHandle ) . toHaveBeenNthCalledWith ( 1 , {
264+ content : 'Hello' ,
265+ mimeType : undefined ,
266+ partType : 'text' ,
267+ thoughtSignature : undefined ,
268+ type : 'content_part' ,
269+ } ) ;
270+ expect ( mockOnMessageHandle ) . toHaveBeenNthCalledWith ( 2 , {
271+ content : ' World' ,
272+ mimeType : undefined ,
273+ partType : 'text' ,
274+ thoughtSignature : undefined ,
275+ type : 'content_part' ,
276+ } ) ;
277+
278+ // Verify output is accumulated correctly
279+ expect ( mockOnFinish ) . toHaveBeenCalledWith ( 'Hello World' , {
280+ observationId : null ,
281+ toolCalls : undefined ,
282+ traceId : null ,
283+ type : 'done' ,
284+ } ) ;
285+ } ) ;
286+
287+ it ( 'should handle reasoning_part event with text and accumulate thinking' , async ( ) => {
288+ const mockOnMessageHandle = vi . fn ( ) ;
289+ const mockOnFinish = vi . fn ( ) ;
290+
291+ ( fetchEventSource as any ) . mockImplementationOnce (
292+ async ( url : string , options : FetchEventSourceInit ) => {
293+ options . onopen ! ( { clone : ( ) => ( { ok : true , headers : new Headers ( ) } ) } as any ) ;
294+ options . onmessage ! ( {
295+ event : 'reasoning_part' ,
296+ data : JSON . stringify ( { content : 'Thinking:' , partType : 'text' } ) ,
297+ } as any ) ;
298+ options . onmessage ! ( {
299+ event : 'reasoning_part' ,
300+ data : JSON . stringify ( { content : ' step 1' , partType : 'text' } ) ,
301+ } as any ) ;
302+ options . onmessage ! ( {
303+ event : 'content_part' ,
304+ data : JSON . stringify ( { content : 'Final answer' , partType : 'text' } ) ,
305+ } as any ) ;
306+ } ,
307+ ) ;
308+
309+ await fetchSSE ( '/' , {
310+ onMessageHandle : mockOnMessageHandle ,
311+ onFinish : mockOnFinish ,
312+ responseAnimation : 'none' ,
313+ } ) ;
314+
315+ // Verify reasoning is accumulated correctly
316+ expect ( mockOnFinish ) . toHaveBeenCalledWith ( 'Final answer' , {
317+ observationId : null ,
318+ reasoning : { content : 'Thinking: step 1' } ,
319+ toolCalls : undefined ,
320+ traceId : null ,
321+ type : 'done' ,
322+ } ) ;
323+ } ) ;
324+
325+ it ( 'should not accumulate output for non-text content_part (e.g., image)' , async ( ) => {
326+ const mockOnMessageHandle = vi . fn ( ) ;
327+ const mockOnFinish = vi . fn ( ) ;
328+
329+ ( fetchEventSource as any ) . mockImplementationOnce (
330+ async ( url : string , options : FetchEventSourceInit ) => {
331+ options . onopen ! ( { clone : ( ) => ( { ok : true , headers : new Headers ( ) } ) } as any ) ;
332+ options . onmessage ! ( {
333+ event : 'content_part' ,
334+ data : JSON . stringify ( {
335+ content : 'base64imagedata' ,
336+ partType : 'image' ,
337+ mimeType : 'image/png' ,
338+ } ) ,
339+ } as any ) ;
340+ } ,
341+ ) ;
342+
343+ await fetchSSE ( '/' , {
344+ onMessageHandle : mockOnMessageHandle ,
345+ onFinish : mockOnFinish ,
346+ responseAnimation : 'none' ,
347+ } ) ;
348+
349+ expect ( mockOnMessageHandle ) . toHaveBeenCalledWith ( {
350+ content : 'base64imagedata' ,
351+ mimeType : 'image/png' ,
352+ partType : 'image' ,
353+ thoughtSignature : undefined ,
354+ type : 'content_part' ,
355+ } ) ;
356+
357+ // Output should be empty since image content is not accumulated
358+ expect ( mockOnFinish ) . toHaveBeenCalledWith ( '' , {
359+ observationId : null ,
360+ toolCalls : undefined ,
361+ traceId : null ,
362+ type : 'done' ,
363+ } ) ;
364+ } ) ;
365+ } ) ;
366+
238367 it ( 'should handle grounding event' , async ( ) => {
239368 const mockOnMessageHandle = vi . fn ( ) ;
240369 const mockOnFinish = vi . fn ( ) ;
0 commit comments