Download packages/cli/src/ui/hooks/creditsFlowHandler.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 7.95 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/hooks/creditsFlowHandler.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/ui/hooks/creditsFlowHandler.ts
-
curl -L -o creditsFlowHandler.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/hooks/creditsFlowHandler.ts
7.95 kB
| /** | |
| * @license | |
| * Copyright 2026 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { | |
| type Config, | |
| type FallbackIntent, | |
| type GeminiUserTier, | |
| type OverageOption, | |
| getG1CreditBalance, | |
| shouldAutoUseCredits, | |
| shouldShowOverageMenu, | |
| shouldShowEmptyWalletMenu, | |
| openBrowserSecurely, | |
| shouldLaunchBrowser, | |
| logBillingEvent, | |
| OverageMenuShownEvent, | |
| OverageOptionSelectedEvent, | |
| EmptyWalletMenuShownEvent, | |
| CreditPurchaseClickEvent, | |
| buildG1Url, | |
| G1_UTM_CAMPAIGNS, | |
| UserAccountManager, | |
| recordOverageOptionSelected, | |
| recordCreditPurchaseClick, | |
| } from '@google/gemini-cli-core'; | |
| import { MessageType } from '../types.js'; | |
| import type { UseHistoryManagerReturn } from './useHistoryManager.js'; | |
| import type { | |
| OverageMenuIntent, | |
| EmptyWalletIntent, | |
| EmptyWalletDialogRequest, | |
| } from '../contexts/UIStateContext.js'; | |
| interface CreditsFlowArgs { | |
| config: Config; | |
| paidTier: GeminiUserTier; | |
| overageStrategy: 'ask' | 'always' | 'never'; | |
| failedModel: string; | |
| fallbackModel: string; | |
| usageLimitReachedModel: string; | |
| resetTime: string | undefined; | |
| historyManager: UseHistoryManagerReturn; | |
| setModelSwitchedFromQuotaError: (value: boolean) => void; | |
| isDialogPending: React.MutableRefObject<boolean>; | |
| setOverageMenuRequest: ( | |
| req: { | |
| failedModel: string; | |
| fallbackModel: string; | |
| resetTime: string | undefined; | |
| creditBalance: number; | |
| resolve: (intent: OverageMenuIntent) => void; | |
| } | null, | |
| ) => void; | |
| setEmptyWalletRequest: (req: EmptyWalletDialogRequest | null) => void; | |
| } | |
| /** | |
| * Handles the G1 AI Credits flow when a quota error occurs. | |
| * Returns a FallbackIntent if the credits flow handled the error, | |
| * or null to fall through to the default ProQuotaDialog. | |
| */ | |
| export async function handleCreditsFlow( | |
| args: CreditsFlowArgs, | |
| ): Promise<FallbackIntent | null> { | |
| const creditBalance = getG1CreditBalance(args.paidTier); | |
| // creditBalance is null when user is not eligible for G1 credits. | |
| if (creditBalance == null) { | |
| return null; | |
| } | |
| const { overageStrategy } = args; | |
| // If credits are already auto-enabled (strategy='always'), the request | |
| // that just failed already included enabledCreditTypes — credits didn't | |
| // help. Fall through to ProQuotaDialog which offers the Flash downgrade. | |
| if (shouldAutoUseCredits(overageStrategy, creditBalance)) { | |
| return null; | |
| } | |
| // Show overage menu when strategy is 'ask' and credits > 0 | |
| if (shouldShowOverageMenu(overageStrategy, creditBalance)) { | |
| return handleOverageMenu(args, creditBalance); | |
| } | |
| // Show empty wallet when credits === 0 and strategy isn't 'never' | |
| if (shouldShowEmptyWalletMenu(overageStrategy, creditBalance)) { | |
| return handleEmptyWalletMenu(args); | |
| } | |
| return null; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Overage menu flow | |
| // --------------------------------------------------------------------------- | |
| async function handleOverageMenu( | |
| args: CreditsFlowArgs, | |
| creditBalance: number, | |
| ): Promise<FallbackIntent> { | |
| const { | |
| config, | |
| fallbackModel, | |
| usageLimitReachedModel, | |
| overageStrategy, | |
| resetTime, | |
| isDialogPending, | |
| setOverageMenuRequest, | |
| setModelSwitchedFromQuotaError, | |
| } = args; | |
| logBillingEvent( | |
| config, | |
| new OverageMenuShownEvent( | |
| usageLimitReachedModel, | |
| creditBalance, | |
| overageStrategy, | |
| ), | |
| ); | |
| if (isDialogPending.current) { | |
| return 'stop'; | |
| } | |
| isDialogPending.current = true; | |
| setModelSwitchedFromQuotaError(true); | |
| config.setQuotaErrorOccurred(true); | |
| const overageIntent = await new Promise<OverageMenuIntent>((resolve) => { | |
| setOverageMenuRequest({ | |
| failedModel: usageLimitReachedModel, | |
| fallbackModel, | |
| resetTime, | |
| creditBalance, | |
| resolve, | |
| }); | |
| }); | |
| setOverageMenuRequest(null); | |
| isDialogPending.current = false; | |
| logOverageOptionSelected( | |
| config, | |
| usageLimitReachedModel, | |
| overageIntent, | |
| creditBalance, | |
| ); | |
| switch (overageIntent) { | |
| case 'use_credits': | |
| setModelSwitchedFromQuotaError(false); | |
| config.setQuotaErrorOccurred(false); | |
| config.setOverageStrategy('always'); | |
| return 'retry_with_credits'; | |
| case 'use_fallback': | |
| return 'retry_always'; | |
| case 'manage': { | |
| logCreditPurchaseClick(config, 'manage', usageLimitReachedModel); | |
| const manageUrl = await openG1Url( | |
| 'activity', | |
| G1_UTM_CAMPAIGNS.MANAGE_ACTIVITY, | |
| ); | |
| if (manageUrl) { | |
| args.historyManager.addItem( | |
| { | |
| type: MessageType.INFO, | |
| text: `Please open this URL in a browser: ${manageUrl}`, | |
| }, | |
| Date.now(), | |
| ); | |
| } | |
| return 'stop'; | |
| } | |
| case 'stop': | |
| default: | |
| return 'stop'; | |
| } | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Empty wallet flow | |
| // --------------------------------------------------------------------------- | |
| async function handleEmptyWalletMenu( | |
| args: CreditsFlowArgs, | |
| ): Promise<FallbackIntent> { | |
| const { | |
| config, | |
| fallbackModel, | |
| usageLimitReachedModel, | |
| resetTime, | |
| isDialogPending, | |
| setEmptyWalletRequest, | |
| setModelSwitchedFromQuotaError, | |
| } = args; | |
| logBillingEvent( | |
| config, | |
| new EmptyWalletMenuShownEvent(usageLimitReachedModel), | |
| ); | |
| if (isDialogPending.current) { | |
| return 'stop'; | |
| } | |
| isDialogPending.current = true; | |
| setModelSwitchedFromQuotaError(true); | |
| config.setQuotaErrorOccurred(true); | |
| const emptyWalletIntent = await new Promise<EmptyWalletIntent>((resolve) => { | |
| setEmptyWalletRequest({ | |
| failedModel: usageLimitReachedModel, | |
| fallbackModel, | |
| resetTime, | |
| onGetCredits: async () => { | |
| logCreditPurchaseClick( | |
| config, | |
| 'empty_wallet_menu', | |
| usageLimitReachedModel, | |
| ); | |
| const creditsUrl = await openG1Url( | |
| 'credits', | |
| G1_UTM_CAMPAIGNS.EMPTY_WALLET_ADD_CREDITS, | |
| ); | |
| if (creditsUrl) { | |
| args.historyManager.addItem( | |
| { | |
| type: MessageType.INFO, | |
| text: `Please open this URL in a browser: ${creditsUrl}`, | |
| }, | |
| Date.now(), | |
| ); | |
| } | |
| }, | |
| resolve, | |
| }); | |
| }); | |
| setEmptyWalletRequest(null); | |
| isDialogPending.current = false; | |
| switch (emptyWalletIntent) { | |
| case 'get_credits': | |
| args.historyManager.addItem( | |
| { | |
| type: MessageType.INFO, | |
| text: 'Newly purchased AI credits may take a few minutes to update. Run /stats to check your balance.', | |
| }, | |
| Date.now(), | |
| ); | |
| return 'stop'; | |
| case 'use_fallback': | |
| return 'retry_always'; | |
| case 'stop': | |
| default: | |
| return 'stop'; | |
| } | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Telemetry helpers | |
| // --------------------------------------------------------------------------- | |
| function logOverageOptionSelected( | |
| config: Config, | |
| model: string, | |
| option: OverageOption, | |
| creditBalance: number, | |
| ): void { | |
| logBillingEvent( | |
| config, | |
| new OverageOptionSelectedEvent(model, option, creditBalance), | |
| ); | |
| recordOverageOptionSelected(config, { | |
| selected_option: option, | |
| model, | |
| }); | |
| } | |
| function logCreditPurchaseClick( | |
| config: Config, | |
| source: 'overage_menu' | 'empty_wallet_menu' | 'manage', | |
| model: string, | |
| ): void { | |
| logBillingEvent(config, new CreditPurchaseClickEvent(source, model)); | |
| recordCreditPurchaseClick(config, { source, model }); | |
| } | |
| async function openG1Url( | |
| path: 'activity' | 'credits', | |
| campaign: string, | |
| ): Promise<string | undefined> { | |
| try { | |
| const userEmail = new UserAccountManager().getCachedGoogleAccount() ?? ''; | |
| const url = buildG1Url(path, userEmail, campaign); | |
| if (!shouldLaunchBrowser()) { | |
| return url; | |
| } | |
| await openBrowserSecurely(url); | |
| } catch { | |
| // Ignore browser open errors | |
| } | |
| return undefined; | |
| } | |