Add interactive ValidationDialog for handling 403 VALIDATION_REQUIRED errors. (#16231)

This commit is contained in:
Gaurav
2026-01-20 16:23:01 -08:00
committed by GitHub
parent aceb06a587
commit 3b626e7c61
18 changed files with 1060 additions and 12 deletions
@@ -9,6 +9,8 @@ import {
type Config,
type FallbackModelHandler,
type FallbackIntent,
type ValidationHandler,
type ValidationIntent,
TerminalQuotaError,
ModelNotFoundError,
type UserTierId,
@@ -19,7 +21,10 @@ import {
import { useCallback, useEffect, useRef, useState } from 'react';
import { type UseHistoryManagerReturn } from './useHistoryManager.js';
import { MessageType } from '../types.js';
import { type ProQuotaDialogRequest } from '../contexts/UIStateContext.js';
import {
type ProQuotaDialogRequest,
type ValidationDialogRequest,
} from '../contexts/UIStateContext.js';
interface UseQuotaAndFallbackArgs {
config: Config;
@@ -36,7 +41,10 @@ export function useQuotaAndFallback({
}: UseQuotaAndFallbackArgs) {
const [proQuotaRequest, setProQuotaRequest] =
useState<ProQuotaDialogRequest | null>(null);
const [validationRequest, setValidationRequest] =
useState<ValidationDialogRequest | null>(null);
const isDialogPending = useRef(false);
const isValidationPending = useRef(false);
// Set up Flash fallback handler
useEffect(() => {
@@ -120,6 +128,36 @@ export function useQuotaAndFallback({
config.setFallbackModelHandler(fallbackHandler);
}, [config, historyManager, userTier, setModelSwitchedFromQuotaError]);
// Set up validation handler for 403 VALIDATION_REQUIRED errors
useEffect(() => {
const validationHandler: ValidationHandler = async (
validationLink,
validationDescription,
learnMoreUrl,
): Promise<ValidationIntent> => {
if (isValidationPending.current) {
return 'cancel'; // A validation dialog is already active
}
isValidationPending.current = true;
const intent: ValidationIntent = await new Promise<ValidationIntent>(
(resolve) => {
// Call setValidationRequest directly - same pattern as proQuotaRequest
setValidationRequest({
validationLink,
validationDescription,
learnMoreUrl,
resolve,
});
},
);
return intent;
};
config.setValidationHandler(validationHandler);
}, [config]);
const handleProQuotaChoice = useCallback(
(choice: FallbackIntent) => {
if (!proQuotaRequest) return;
@@ -148,9 +186,35 @@ export function useQuotaAndFallback({
[proQuotaRequest, historyManager, config, setModelSwitchedFromQuotaError],
);
const handleValidationChoice = useCallback(
(choice: ValidationIntent) => {
// Guard against double-execution (e.g. rapid clicks) and stale requests
if (!isValidationPending.current || !validationRequest) return;
// Immediately clear the flag to prevent any subsequent calls from passing the guard
isValidationPending.current = false;
validationRequest.resolve(choice);
setValidationRequest(null);
if (choice === 'change_auth') {
historyManager.addItem(
{
type: MessageType.INFO,
text: 'Use /auth to change authentication method.',
},
Date.now(),
);
}
},
[validationRequest, historyManager],
);
return {
proQuotaRequest,
handleProQuotaChoice,
validationRequest,
handleValidationChoice,
};
}