@@ -11,6 +11,7 @@ import {
1111 StreamToolCallChunkData ,
1212 createCallbacksTransformer ,
1313 createSSEProtocolTransformer ,
14+ createTokenSpeedCalculator ,
1415 generateToolCallId ,
1516} from './protocol' ;
1617
@@ -19,31 +20,62 @@ const transformGoogleGenerativeAIStream = (
1920 context : StreamContext ,
2021) : StreamProtocolChunk | StreamProtocolChunk [ ] => {
2122 // maybe need another structure to add support for multiple choices
23+ const candidate = chunk . candidates ?. [ 0 ] ;
24+ const usage = chunk . usageMetadata ;
25+ const usageChunks : StreamProtocolChunk [ ] = [ ] ;
26+ if ( candidate ?. finishReason && usage ) {
27+ const outputReasoningTokens = ( usage as any ) . thoughtsTokenCount || undefined ;
28+ const totalOutputTokens = ( usage . candidatesTokenCount ?? 0 ) + ( outputReasoningTokens ?? 0 ) ;
29+
30+ usageChunks . push (
31+ { data : candidate . finishReason , id : context ?. id , type : 'stop' } ,
32+ {
33+ data : {
34+ // TODO: Google SDK 0.24.0 don't have promptTokensDetails types
35+ inputImageTokens : ( usage as any ) . promptTokensDetails ?. find (
36+ ( i : any ) => i . modality === 'IMAGE' ,
37+ ) ?. tokenCount ,
38+ inputTextTokens : ( usage as any ) . promptTokensDetails ?. find (
39+ ( i : any ) => i . modality === 'TEXT' ,
40+ ) ?. tokenCount ,
41+ outputReasoningTokens,
42+ outputTextTokens : totalOutputTokens - ( outputReasoningTokens ?? 0 ) ,
43+ totalInputTokens : usage . promptTokenCount ,
44+ totalOutputTokens,
45+ totalTokens : usage . totalTokenCount ,
46+ } as ModelTokensUsage ,
47+ id : context ?. id ,
48+ type : 'usage' ,
49+ } ,
50+ ) ;
51+ }
52+
2253 const functionCalls = chunk . functionCalls ?.( ) ;
2354
2455 if ( functionCalls ) {
25- return {
26- data : functionCalls . map (
27- ( value , index ) : StreamToolCallChunkData => ( {
28- function : {
29- arguments : JSON . stringify ( value . args ) ,
30- name : value . name ,
31- } ,
32- id : generateToolCallId ( index , value . name ) ,
33- index : index ,
34- type : 'function' ,
35- } ) ,
36- ) ,
37- id : context . id ,
38- type : 'tool_calls' ,
39- } ;
56+ return [
57+ {
58+ data : functionCalls . map (
59+ ( value , index ) : StreamToolCallChunkData => ( {
60+ function : {
61+ arguments : JSON . stringify ( value . args ) ,
62+ name : value . name ,
63+ } ,
64+ id : generateToolCallId ( index , value . name ) ,
65+ index : index ,
66+ type : 'function' ,
67+ } ) ,
68+ ) ,
69+ id : context . id ,
70+ type : 'tool_calls' ,
71+ } ,
72+ ...usageChunks ,
73+ ] ;
4074 }
4175
4276 const text = chunk . text ?.( ) ;
4377
44- if ( chunk . candidates ) {
45- const candidate = chunk . candidates [ 0 ] ;
46-
78+ if ( candidate ) {
4779 // return the grounding
4880 if ( candidate . groundingMetadata ) {
4981 const { webSearchQueries, groundingChunks } = candidate . groundingMetadata ;
@@ -64,31 +96,15 @@ const transformGoogleGenerativeAIStream = (
6496 id : context . id ,
6597 type : 'grounding' ,
6698 } ,
99+ ...usageChunks ,
67100 ] ;
68101 }
69102
70103 if ( candidate . finishReason ) {
71104 if ( chunk . usageMetadata ) {
72- const usage = chunk . usageMetadata ;
73105 return [
74106 ! ! text ? { data : text , id : context ?. id , type : 'text' } : undefined ,
75- { data : candidate . finishReason , id : context ?. id , type : 'stop' } ,
76- {
77- data : {
78- // TODO: Google SDK 0.24.0 don't have promptTokensDetails types
79- inputImageTokens : ( usage as any ) . promptTokensDetails ?. find (
80- ( i : any ) => i . modality === 'IMAGE' ,
81- ) ?. tokenCount ,
82- inputTextTokens : ( usage as any ) . promptTokensDetails ?. find (
83- ( i : any ) => i . modality === 'TEXT' ,
84- ) ?. tokenCount ,
85- totalInputTokens : usage . promptTokenCount ,
86- totalOutputTokens : usage . candidatesTokenCount ,
87- totalTokens : usage . totalTokenCount ,
88- } as ModelTokensUsage ,
89- id : context ?. id ,
90- type : 'usage' ,
91- } ,
107+ ...usageChunks ,
92108 ] . filter ( Boolean ) as StreamProtocolChunk [ ] ;
93109 }
94110 return { data : candidate . finishReason , id : context ?. id , type : 'stop' } ;
@@ -117,13 +133,21 @@ const transformGoogleGenerativeAIStream = (
117133 } ;
118134} ;
119135
136+ export interface GoogleAIStreamOptions {
137+ callbacks ?: ChatStreamCallbacks ;
138+ inputStartAt ?: number ;
139+ }
140+
120141export const GoogleGenerativeAIStream = (
121142 rawStream : ReadableStream < EnhancedGenerateContentResponse > ,
122- callbacks ?: ChatStreamCallbacks ,
143+ { callbacks, inputStartAt } : GoogleAIStreamOptions = { } ,
123144) => {
124145 const streamStack : StreamContext = { id : 'chat_' + nanoid ( ) } ;
125146
126147 return rawStream
127- . pipeThrough ( createSSEProtocolTransformer ( transformGoogleGenerativeAIStream , streamStack ) )
148+ . pipeThrough (
149+ createTokenSpeedCalculator ( transformGoogleGenerativeAIStream , { inputStartAt, streamStack } ) ,
150+ )
151+ . pipeThrough ( createSSEProtocolTransformer ( ( c ) => c , streamStack ) )
128152 . pipeThrough ( createCallbacksTransformer ( callbacks ) ) ;
129153} ;
0 commit comments