2025-09-08 16:19:52 -04:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
import {
AuthType ,
type Config ,
type FallbackModelHandler ,
type FallbackIntent ,
2025-10-24 11:09:06 -07:00
TerminalQuotaError ,
2025-09-08 16:19:52 -04:00
UserTierId ,
2025-10-24 11:09:06 -07:00
RetryableQuotaError ,
2025-09-08 16:19:52 -04:00
} from '@google/gemini-cli-core' ;
import { useCallback , useEffect , useRef , useState } from 'react' ;
import { type UseHistoryManagerReturn } from './useHistoryManager.js' ;
import { AuthState , MessageType } from '../types.js' ;
import { type ProQuotaDialogRequest } from '../contexts/UIStateContext.js' ;
interface UseQuotaAndFallbackArgs {
config : Config ;
historyManager : UseHistoryManagerReturn ;
userTier : UserTierId | undefined ;
setAuthState : ( state : AuthState ) = > void ;
setModelSwitchedFromQuotaError : ( value : boolean ) = > void ;
}
export function useQuotaAndFallback ( {
config ,
historyManager ,
userTier ,
setAuthState ,
setModelSwitchedFromQuotaError ,
} : UseQuotaAndFallbackArgs ) {
const [ proQuotaRequest , setProQuotaRequest ] =
useState < ProQuotaDialogRequest | null > ( null ) ;
const isDialogPending = useRef ( false ) ;
// Set up Flash fallback handler
useEffect ( ( ) = > {
const fallbackHandler : FallbackModelHandler = async (
failedModel ,
fallbackModel ,
error ,
) : Promise < FallbackIntent | null > = > {
if ( config . isInFallbackMode ( ) ) {
return null ;
}
// Fallbacks are currently only handled for OAuth users.
const contentGeneratorConfig = config . getContentGeneratorConfig ( ) ;
if (
! contentGeneratorConfig ||
contentGeneratorConfig . authType !== AuthType . LOGIN_WITH_GOOGLE
) {
return null ;
}
// Use actual user tier if available; otherwise, default to FREE tier behavior (safe default)
const isPaidTier =
userTier === UserTierId . LEGACY || userTier === UserTierId . STANDARD ;
let message : string ;
2025-10-24 11:09:06 -07:00
if ( error instanceof TerminalQuotaError ) {
2025-09-08 16:19:52 -04:00
// Pro Quota specific messages (Interactive)
if ( isPaidTier ) {
message = ` ⚡ You have reached your daily ${ failedModel } quota limit.
⚡ You can choose to authenticate with a paid API key or continue with the fallback model .
⚡ To continue accessing the $ { failedModel } model today , consider using / auth to switch to using a paid API key from AI Studio at https : //aistudio.google.com/apikey`;
} else {
message = ` ⚡ You have reached your daily ${ failedModel } quota limit.
⚡ You can choose to authenticate with a paid API key or continue with the fallback model .
⚡ To increase your limits , upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https : //goo.gle/set-up-gemini-code-assist
⚡ Or you can utilize a Gemini API Key . See : https : //goo.gle/gemini-cli-docs-auth#gemini-api-key
2025-10-14 16:30:59 -07:00
⚡ You can switch authentication methods by typing / auth ` ;
}
2025-10-24 11:09:06 -07:00
} else if ( error instanceof RetryableQuotaError ) {
// Short term quota retries exhausted (Automatic fallback)
const actionMessage = ` ⚡ Your requests are being throttled right now due to server being at capacity for ${ failedModel } . \ n⚡ Automatically switching from ${ failedModel } to ${ fallbackModel } for the remainder of this session. ` ;
2025-10-14 16:30:59 -07:00
if ( isPaidTier ) {
message = ` ${ actionMessage }
2025-10-24 11:09:06 -07:00
⚡ To continue accessing the $ { failedModel } model , retry your request after some time or consider using / auth to switch to using a paid API key from AI Studio at https : //aistudio.google.com/apikey`;
2025-10-14 16:30:59 -07:00
} else {
message = ` ${ actionMessage }
2025-10-24 11:09:06 -07:00
⚡ Retry your requests after some time . Otherwise consider upgrading to a Gemini Code Assist Standard or Enterprise plan with higher limits at https : //goo.gle/set-up-gemini-code-assist
2025-09-08 16:19:52 -04:00
⚡ You can switch authentication methods by typing / auth ` ;
}
} else {
2025-10-24 11:09:06 -07:00
// Other errors (Automatic fallback)
2025-09-08 16:19:52 -04:00
const actionMessage = ` ⚡ Automatically switching from ${ failedModel } to ${ fallbackModel } for faster responses for the remainder of this session. ` ;
if ( isPaidTier ) {
message = ` ${ actionMessage }
2025-10-24 11:09:06 -07:00
⚡ Your requests are being throttled temporarily due to server being at capacity for $ { failedModel } or there is a service outage .
⚡ To continue accessing the $ { failedModel } model , you can retry your request after some time or consider using / auth to switch to using a paid API key from AI Studio at https : //aistudio.google.com/apikey`;
2025-09-08 16:19:52 -04:00
} else {
message = ` ${ actionMessage }
2025-10-24 11:09:06 -07:00
⚡ Your requests are being throttled temporarily due to server being at capacity for $ { failedModel } or there is a service outage .
⚡ To avoid being throttled , you can retry your request after some time or upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https : //goo.gle/set-up-gemini-code-assist
2025-09-08 16:19:52 -04:00
⚡ Or you can utilize a Gemini API Key . See : https : //goo.gle/gemini-cli-docs-auth#gemini-api-key
⚡ You can switch authentication methods by typing / auth ` ;
}
}
// Add message to UI history
historyManager . addItem (
{
type : MessageType . INFO ,
text : message ,
} ,
Date . now ( ) ,
) ;
setModelSwitchedFromQuotaError ( true ) ;
config . setQuotaErrorOccurred ( true ) ;
// Interactive Fallback for Pro quota
2025-10-24 11:09:06 -07:00
if ( error instanceof TerminalQuotaError ) {
2025-09-08 16:19:52 -04:00
if ( isDialogPending . current ) {
return 'stop' ; // A dialog is already active, so just stop this request.
}
isDialogPending . current = true ;
const intent : FallbackIntent = await new Promise < FallbackIntent > (
( resolve ) = > {
setProQuotaRequest ( {
failedModel ,
fallbackModel ,
resolve ,
} ) ;
} ,
) ;
return intent ;
}
return 'stop' ;
} ;
config . setFallbackModelHandler ( fallbackHandler ) ;
} , [ config , historyManager , userTier , setModelSwitchedFromQuotaError ] ) ;
const handleProQuotaChoice = useCallback (
( choice : 'auth' | 'continue' ) = > {
if ( ! proQuotaRequest ) return ;
const intent : FallbackIntent = choice === 'auth' ? 'auth' : 'retry' ;
proQuotaRequest . resolve ( intent ) ;
setProQuotaRequest ( null ) ;
isDialogPending . current = false ; // Reset the flag here
if ( choice === 'auth' ) {
setAuthState ( AuthState . Updating ) ;
} else {
historyManager . addItem (
{
type : MessageType . INFO ,
text : 'Switched to fallback model. Tip: Press Ctrl+P (or Up Arrow) to recall your previous prompt and submit it again if you wish.' ,
} ,
Date . now ( ) ,
) ;
}
} ,
[ proQuotaRequest , setAuthState , historyManager ] ,
) ;
return {
proQuotaRequest ,
handleProQuotaChoice ,
} ;
}