1- import { streamResponse } from "@continuedev/fetch" ;
1+ import { streamSse } from "@continuedev/fetch" ;
22import { v4 as uuidv4 } from "uuid" ;
33import {
44 AssistantChatMessage ,
@@ -312,84 +312,57 @@ class Gemini extends BaseLLM {
312312 }
313313
314314 public async * processGeminiResponse (
315- stream : AsyncIterable < string > ,
315+ response : Response ,
316316 ) : AsyncGenerator < ChatMessage > {
317- let buffer = "" ;
318- for await ( const chunk of stream ) {
319- buffer += chunk ;
320- if ( buffer . startsWith ( "[" ) ) {
321- buffer = buffer . slice ( 1 ) ;
317+ for await ( const chunk of streamSse ( response ) ) {
318+ let data : GeminiChatResponse ;
319+ try {
320+ data = JSON . parse ( chunk ) as GeminiChatResponse ;
321+ } catch ( e ) {
322+ continue ;
322323 }
323- if ( buffer . endsWith ( "]" ) ) {
324- buffer = buffer . slice ( 0 , - 1 ) ;
325- }
326- if ( buffer . startsWith ( "," ) ) {
327- buffer = buffer . slice ( 1 ) ;
324+
325+ if ( "error" in data ) {
326+ throw new Error ( data . error . message ) ;
328327 }
329328
330- const parts = buffer . split ( "\n," ) ;
331-
332- let foundIncomplete = false ;
333- for ( let i = 0 ; i < parts . length ; i ++ ) {
334- const part = parts [ i ] ;
335- let data : GeminiChatResponse ;
336- try {
337- data = JSON . parse ( part ) as GeminiChatResponse ;
338- } catch ( e ) {
339- foundIncomplete = true ;
340- continue ; // yo!
329+ const contentParts = data ?. candidates ?. [ 0 ] ?. content ?. parts ;
330+ if ( contentParts ) {
331+ const textParts : MessagePart [ ] = [ ] ;
332+ const toolCalls : ToolCallDelta [ ] = [ ] ;
333+
334+ for ( const part of contentParts ) {
335+ if ( "text" in part ) {
336+ textParts . push ( { type : "text" , text : part . text } ) ;
337+ } else if ( "functionCall" in part ) {
338+ toolCalls . push ( {
339+ type : "function" ,
340+ id : part . functionCall . id ?? uuidv4 ( ) ,
341+ function : {
342+ name : part . functionCall . name ,
343+ arguments :
344+ typeof part . functionCall . args === "string"
345+ ? part . functionCall . args
346+ : JSON . stringify ( part . functionCall . args ) ,
347+ } ,
348+ } ) ;
349+ } else {
350+ console . warn ( "Unsupported gemini part type received" , part ) ;
351+ }
341352 }
342353
343- if ( "error" in data ) {
344- throw new Error ( data . error . message ) ;
354+ const assistantMessage : AssistantChatMessage = {
355+ role : "assistant" ,
356+ content : textParts . length ? textParts : "" ,
357+ } ;
358+ if ( toolCalls . length > 0 ) {
359+ assistantMessage . toolCalls = toolCalls ;
345360 }
346-
347- // In case of max tokens reached, gemini will sometimes return content with no parts, even though that doesn't match the API spec
348- const contentParts = data ?. candidates ?. [ 0 ] ?. content ?. parts ;
349- if ( contentParts ) {
350- const textParts : MessagePart [ ] = [ ] ;
351- const toolCalls : ToolCallDelta [ ] = [ ] ;
352-
353- for ( const part of contentParts ) {
354- if ( "text" in part ) {
355- textParts . push ( { type : "text" , text : part . text } ) ;
356- } else if ( "functionCall" in part ) {
357- toolCalls . push ( {
358- type : "function" ,
359- id : part . functionCall . id ?? uuidv4 ( ) ,
360- function : {
361- name : part . functionCall . name ,
362- arguments :
363- typeof part . functionCall . args === "string"
364- ? part . functionCall . args
365- : JSON . stringify ( part . functionCall . args ) ,
366- } ,
367- } ) ;
368- } else {
369- // Note: function responses shouldn't be streamed, images not supported
370- console . warn ( "Unsupported gemini part type received" , part ) ;
371- }
372- }
373-
374- const assistantMessage : AssistantChatMessage = {
375- role : "assistant" ,
376- content : textParts . length ? textParts : "" ,
377- } ;
378- if ( toolCalls . length > 0 ) {
379- assistantMessage . toolCalls = toolCalls ;
380- }
381- if ( textParts . length || toolCalls . length ) {
382- yield assistantMessage ;
383- }
384- } else {
385- // Handle the case where the expected data structure is not found
386- console . warn ( "Unexpected response format:" , data ) ;
361+ if ( textParts . length || toolCalls . length ) {
362+ yield assistantMessage ;
387363 }
388- }
389- if ( foundIncomplete ) {
390- buffer = parts [ parts . length - 1 ] ;
391364 } else {
392- buffer = "" ;
365+ console . warn ( "Unexpected response format:" , data ) ;
393366 }
394367 }
395368 }
@@ -414,10 +387,9 @@ class Gemini extends BaseLLM {
414387 body : JSON . stringify ( body ) ,
415388 signal,
416389 } ) ;
417- for await ( const message of this . processGeminiResponse (
418- streamResponse ( response ) ,
419- ) ) {
420- yield message ;
390+
391+ for await ( const chunk of this . processGeminiResponse ( response ) ) {
392+ yield chunk ;
421393 }
422394 }
423395 private async * streamChatBison (
0 commit comments