@@ -425,3 +425,125 @@ describe("Gemini — content + toolCalls", () => {
425425 expect ( body . candidates [ 0 ] . finishReason ) . toBe ( "FUNCTION_CALL" ) ;
426426 } ) ;
427427} ) ;
428+
429+ import {
430+ collapseOpenAISSE ,
431+ collapseAnthropicSSE ,
432+ collapseGeminiSSE ,
433+ collapseOllamaNDJSON ,
434+ } from "../stream-collapse.js" ;
435+
436+ describe ( "stream-collapse — content + toolCalls coexistence" , ( ) => {
437+ it ( "OpenAI: preserves both content and toolCalls" , ( ) => {
438+ const body = [
439+ `data: ${ JSON . stringify ( { id : "c1" , choices : [ { delta : { role : "assistant" } } ] } ) } ` ,
440+ "" ,
441+ `data: ${ JSON . stringify ( { id : "c1" , choices : [ { delta : { content : "Hello" } } ] } ) } ` ,
442+ "" ,
443+ `data: ${ JSON . stringify ( {
444+ id : "c1" ,
445+ choices : [
446+ {
447+ delta : {
448+ tool_calls : [
449+ {
450+ index : 0 ,
451+ id : "call_abc" ,
452+ type : "function" ,
453+ function : { name : "get_weather" , arguments : '{"city":"NYC"}' } ,
454+ } ,
455+ ] ,
456+ } ,
457+ } ,
458+ ] ,
459+ } ) } `,
460+ "" ,
461+ "data: [DONE]" ,
462+ "" ,
463+ ] . join ( "\n" ) ;
464+
465+ const result = collapseOpenAISSE ( body ) ;
466+ expect ( result . content ) . toBe ( "Hello" ) ;
467+ expect ( result . toolCalls ) . toHaveLength ( 1 ) ;
468+ expect ( result . toolCalls ! [ 0 ] . name ) . toBe ( "get_weather" ) ;
469+ } ) ;
470+
471+ it ( "Anthropic: preserves both content and toolCalls" , ( ) => {
472+ const body = [
473+ `event: message_start\ndata: ${ JSON . stringify ( { type : "message_start" , message : { } } ) } ` ,
474+ "" ,
475+ `event: content_block_start\ndata: ${ JSON . stringify ( { type : "content_block_start" , index : 0 , content_block : { type : "text" , text : "" } } ) } ` ,
476+ "" ,
477+ `event: content_block_delta\ndata: ${ JSON . stringify ( { type : "content_block_delta" , index : 0 , delta : { type : "text_delta" , text : "Hello" } } ) } ` ,
478+ "" ,
479+ `event: content_block_stop\ndata: ${ JSON . stringify ( { type : "content_block_stop" , index : 0 } ) } ` ,
480+ "" ,
481+ `event: content_block_start\ndata: ${ JSON . stringify ( { type : "content_block_start" , index : 1 , content_block : { type : "tool_use" , id : "toolu_abc" , name : "get_weather" , input : { } } } ) } ` ,
482+ "" ,
483+ `event: content_block_delta\ndata: ${ JSON . stringify ( { type : "content_block_delta" , index : 1 , delta : { type : "input_json_delta" , partial_json : '{"city":"NYC"}' } } ) } ` ,
484+ "" ,
485+ `event: content_block_stop\ndata: ${ JSON . stringify ( { type : "content_block_stop" , index : 1 } ) } ` ,
486+ "" ,
487+ `event: message_delta\ndata: ${ JSON . stringify ( { type : "message_delta" , delta : { stop_reason : "tool_use" } } ) } ` ,
488+ "" ,
489+ `event: message_stop\ndata: ${ JSON . stringify ( { type : "message_stop" } ) } ` ,
490+ "" ,
491+ ] . join ( "\n" ) ;
492+
493+ const result = collapseAnthropicSSE ( body ) ;
494+ expect ( result . content ) . toBe ( "Hello" ) ;
495+ expect ( result . toolCalls ) . toHaveLength ( 1 ) ;
496+ expect ( result . toolCalls ! [ 0 ] . name ) . toBe ( "get_weather" ) ;
497+ } ) ;
498+
499+ it ( "Gemini: preserves both content and toolCalls" , ( ) => {
500+ const body = [
501+ `data: ${ JSON . stringify ( {
502+ candidates : [
503+ { content : { role : "model" , parts : [ { text : "Hello" } ] } , index : 0 } ,
504+ ] ,
505+ } ) } `,
506+ "" ,
507+ `data: ${ JSON . stringify ( {
508+ candidates : [
509+ {
510+ content : {
511+ role : "model" ,
512+ parts : [
513+ { functionCall : { name : "get_weather" , args : { city : "NYC" } } } ,
514+ ] ,
515+ } ,
516+ finishReason : "FUNCTION_CALL" ,
517+ index : 0 ,
518+ } ,
519+ ] ,
520+ } ) } `,
521+ "" ,
522+ ] . join ( "\n" ) ;
523+
524+ const result = collapseGeminiSSE ( body ) ;
525+ expect ( result . content ) . toBe ( "Hello" ) ;
526+ expect ( result . toolCalls ) . toHaveLength ( 1 ) ;
527+ expect ( result . toolCalls ! [ 0 ] . name ) . toBe ( "get_weather" ) ;
528+ } ) ;
529+
530+ it ( "Ollama: preserves both content and toolCalls" , ( ) => {
531+ const body = [
532+ JSON . stringify ( {
533+ model : "llama3" ,
534+ message : {
535+ role : "assistant" ,
536+ content : "Hello" ,
537+ tool_calls : [ { function : { name : "get_weather" , arguments : { city : "NYC" } } } ] ,
538+ } ,
539+ done : false ,
540+ } ) ,
541+ JSON . stringify ( { model : "llama3" , message : { role : "assistant" , content : "" } , done : true } ) ,
542+ ] . join ( "\n" ) ;
543+
544+ const result = collapseOllamaNDJSON ( body ) ;
545+ expect ( result . content ) . toBe ( "Hello" ) ;
546+ expect ( result . toolCalls ) . toHaveLength ( 1 ) ;
547+ expect ( result . toolCalls ! [ 0 ] . name ) . toBe ( "get_weather" ) ;
548+ } ) ;
549+ } ) ;
0 commit comments