1- import { streamSse } from "@continuedev/fetch" ;
1+ import { streamResponse } from "@continuedev/fetch" ;
22import { v4 as uuidv4 } from "uuid" ;
33import {
44 AssistantChatMessage ,
@@ -312,57 +312,84 @@ class Gemini extends BaseLLM {
312312 }
313313
314314 public async * processGeminiResponse (
315- response : Response ,
315+ stream : AsyncIterable < string > ,
316316 ) : AsyncGenerator < ChatMessage > {
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 ;
317+ let buffer = "" ;
318+ for await ( const chunk of stream ) {
319+ buffer += chunk ;
320+ if ( buffer . startsWith ( "[" ) ) {
321+ buffer = buffer . slice ( 1 ) ;
323322 }
324-
325- if ( "error" in data ) {
326- throw new Error ( data . error . message ) ;
323+ if ( buffer . endsWith ( "]" ) ) {
324+ buffer = buffer . slice ( 0 , - 1 ) ;
325+ }
326+ if ( buffer . startsWith ( "," ) ) {
327+ buffer = buffer . slice ( 1 ) ;
327328 }
328329
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- }
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!
352341 }
353342
354- const assistantMessage : AssistantChatMessage = {
355- role : "assistant" ,
356- content : textParts . length ? textParts : "" ,
357- } ;
358- if ( toolCalls . length > 0 ) {
359- assistantMessage . toolCalls = toolCalls ;
343+ if ( "error" in data ) {
344+ throw new Error ( data . error . message ) ;
360345 }
361- if ( textParts . length || toolCalls . length ) {
362- yield assistantMessage ;
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 ) ;
363387 }
388+ }
389+ if ( foundIncomplete ) {
390+ buffer = parts [ parts . length - 1 ] ;
364391 } else {
365- console . warn ( "Unexpected response format:" , data ) ;
392+ buffer = "" ;
366393 }
367394 }
368395 }
@@ -387,9 +414,10 @@ class Gemini extends BaseLLM {
387414 body : JSON . stringify ( body ) ,
388415 signal,
389416 } ) ;
390-
391- for await ( const chunk of this . processGeminiResponse ( response ) ) {
392- yield chunk ;
417+ for await ( const message of this . processGeminiResponse (
418+ streamResponse ( response ) ,
419+ ) ) {
420+ yield message ;
393421 }
394422 }
395423 private async * streamChatBison (
0 commit comments