@@ -6,17 +6,156 @@ import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
66import { ApiStream } from "../transform/stream"
77
88const GEMINI_DEFAULT_TEMPERATURE = 0
9+ const DEFAULT_REQUEST_COUNT = 10 // Default number of requests before switching API keys
10+
11+ // Define a callback type for API key rotation
12+ export type ApiKeyRotationCallback = ( newIndex : number , totalKeys : number , apiKey : string ) => void
13+ export type RequestCountUpdateCallback = ( newCount : number ) => void
914
1015export class GeminiHandler implements ApiHandler , SingleCompletionHandler {
1116 private options : ApiHandlerOptions
1217 private client : GoogleGenerativeAI
18+ private requestCount : number = 0
19+ private onApiKeyRotation ?: ApiKeyRotationCallback
20+ private onRequestCountUpdate ?: RequestCountUpdateCallback
1321
14- constructor ( options : ApiHandlerOptions ) {
22+ constructor (
23+ options : ApiHandlerOptions ,
24+ callbacks ?: {
25+ onApiKeyRotation ?: ApiKeyRotationCallback
26+ onRequestCountUpdate ?: RequestCountUpdateCallback
27+ initialRequestCount ?: number
28+ } ,
29+ ) {
1530 this . options = options
16- this . client = new GoogleGenerativeAI ( options . geminiApiKey ?? "not-provided" )
31+ this . onApiKeyRotation = callbacks ?. onApiKeyRotation
32+ this . onRequestCountUpdate = callbacks ?. onRequestCountUpdate
33+
34+ // Initialize request count from saved state if provided
35+ if ( callbacks ?. initialRequestCount !== undefined ) {
36+ this . requestCount = callbacks . initialRequestCount
37+ console . log ( `[GeminiHandler] Initialized with request count: ${ this . requestCount } ` )
38+ }
39+
40+ // Initialize with the current API key
41+ const apiKey = this . getCurrentApiKey ( )
42+ this . client = new GoogleGenerativeAI ( apiKey )
43+
44+ // Log initial API key setup if load balancing is enabled
45+ if (
46+ this . options . geminiLoadBalancingEnabled &&
47+ this . options . geminiApiKeys &&
48+ this . options . geminiApiKeys . length > 0
49+ ) {
50+ console . log (
51+ `[GeminiHandler] Load balancing enabled with ${ this . options . geminiApiKeys . length } keys. Current index: ${ this . options . geminiCurrentApiKeyIndex ?? 0 } ` ,
52+ )
53+ }
54+ }
55+
56+ /**
57+ * Get the current API key based on load balancing settings
58+ */
59+ private getCurrentApiKey ( ) : string {
60+ // If load balancing is not enabled or there are no multiple API keys, use the single API key
61+ if (
62+ ! this . options . geminiLoadBalancingEnabled ||
63+ ! this . options . geminiApiKeys ||
64+ this . options . geminiApiKeys . length === 0
65+ ) {
66+ return this . options . geminiApiKey ?? "not-provided"
67+ }
68+
69+ // Get the current API key index, defaulting to 0 if not set
70+ const currentIndex = this . options . geminiCurrentApiKeyIndex ?? 0
71+
72+ // Return the API key at the current index
73+ return this . options . geminiApiKeys [ currentIndex ] ?? "not-provided"
74+ }
75+
76+ /**
77+ * Update the client with the next API key if load balancing is enabled
78+ */
79+ private updateApiKeyIfNeeded ( ) : void {
80+ // If load balancing is not enabled or there are no multiple API keys, do nothing
81+ if (
82+ ! this . options . geminiLoadBalancingEnabled ||
83+ ! this . options . geminiApiKeys ||
84+ this . options . geminiApiKeys . length <= 1
85+ ) {
86+ return
87+ }
88+
89+ // Increment the request count
90+ this . requestCount ++
91+ console . log (
92+ `[GeminiHandler] Request count: ${ this . requestCount } /${ this . options . geminiLoadBalancingRequestCount ?? DEFAULT_REQUEST_COUNT } ` ,
93+ )
94+
95+ // Notify about request count update
96+ if ( this . onRequestCountUpdate ) {
97+ this . onRequestCountUpdate ( this . requestCount )
98+ }
99+
100+ // Get the request count threshold, defaulting to DEFAULT_REQUEST_COUNT if not set
101+ const requestCountThreshold = this . options . geminiLoadBalancingRequestCount ?? DEFAULT_REQUEST_COUNT
102+
103+ // If the request count has reached the threshold, switch to the next API key
104+ if ( this . requestCount >= requestCountThreshold ) {
105+ // Reset the request count
106+ this . requestCount = 0
107+
108+ // Notify about request count reset
109+ if ( this . onRequestCountUpdate ) {
110+ this . onRequestCountUpdate ( 0 )
111+ }
112+
113+ // Get the current API key index, defaulting to 0 if not set
114+ let currentIndex = this . options . geminiCurrentApiKeyIndex ?? 0
115+
116+ // Calculate the next index, wrapping around if necessary
117+ currentIndex = ( currentIndex + 1 ) % this . options . geminiApiKeys . length
118+
119+ // Notify callback first to update global state
120+ if ( this . onApiKeyRotation ) {
121+ // Get the API key for the new index
122+ const apiKey = this . options . geminiApiKeys [ currentIndex ] ?? "not-provided"
123+
124+ // Only send the first few characters of the API key for security
125+ const maskedKey = apiKey . substring ( 0 , 4 ) + "..." + apiKey . substring ( apiKey . length - 4 )
126+
127+ // Call the callback to update global state
128+ this . onApiKeyRotation ( currentIndex , this . options . geminiApiKeys . length , maskedKey )
129+
130+ // Update the current index in the options AFTER the callback
131+ // This ensures we're using the index that was just set in global state
132+ this . options . geminiCurrentApiKeyIndex = currentIndex
133+
134+ // Update the client with the new API key
135+ this . client = new GoogleGenerativeAI ( apiKey )
136+
137+ console . log (
138+ `[GeminiHandler] Rotated to API key index: ${ currentIndex } (${ this . options . geminiApiKeys . length } total keys)` ,
139+ )
140+ } else {
141+ // No callback provided, just update locally
142+ this . options . geminiCurrentApiKeyIndex = currentIndex
143+
144+ // Update the client with the new API key
145+ const apiKey = this . getCurrentApiKey ( )
146+ this . client = new GoogleGenerativeAI ( apiKey )
147+
148+ console . log (
149+ `[GeminiHandler] Rotated to API key index: ${ currentIndex } (${ this . options . geminiApiKeys . length } total keys)` ,
150+ )
151+ }
152+ }
17153 }
18154
19155 async * createMessage ( systemPrompt : string , messages : Anthropic . Messages . MessageParam [ ] ) : ApiStream {
156+ // Update the API key if needed before making the request
157+ this . updateApiKeyIfNeeded ( )
158+
20159 const model = this . client . getGenerativeModel ( {
21160 model : this . getModel ( ) . id ,
22161 systemInstruction : systemPrompt ,
@@ -55,6 +194,9 @@ export class GeminiHandler implements ApiHandler, SingleCompletionHandler {
55194
56195 async completePrompt ( prompt : string ) : Promise < string > {
57196 try {
197+ // Update the API key if needed before making the request
198+ this . updateApiKeyIfNeeded ( )
199+
58200 const model = this . client . getGenerativeModel ( {
59201 model : this . getModel ( ) . id ,
60202 } )
0 commit comments