feat(core, cli): add rate limit throttling retry with countdown UI

- Refactor retry utility to support GLM rate limit errors (code 1302) and TPM throttling
- Add getRateLimitRetryInfo() for unified rate-limit error detection
- Add exponential backoff for non-TPM rate limit errors
- Extend StreamEventType.RETRY with RetryInfo payload for UI feedback
- Add RetryCountdownMessage component for visual retry countdown
- Update useGeminiStream hook to handle retry events with countdown timer
- Add i18n support for rate limit messages (en/zh)
This commit is contained in:
yiliang114 2026-02-12 16:21:10 +08:00
parent 2394d732c3
commit 3fb641ca1a
12 changed files with 796 additions and 42 deletions

View file

@ -20,6 +20,7 @@ import { GeminiThoughtMessageContent } from './messages/GeminiThoughtMessageCont
import { CompressionMessage } from './messages/CompressionMessage.js';
import { SummaryMessage } from './messages/SummaryMessage.js';
import { WarningMessage } from './messages/WarningMessage.js';
import { RetryCountdownMessage } from './messages/RetryCountdownMessage.js';
import { Box } from 'ink';
import { AboutBox } from './AboutBox.js';
import { StatsDisplay } from './StatsDisplay.js';
@ -126,6 +127,9 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
{itemForDisplay.type === 'error' && (
<ErrorMessage text={itemForDisplay.text} />
)}
{itemForDisplay.type === 'retry_countdown' && (
<RetryCountdownMessage text={itemForDisplay.text} />
)}
{itemForDisplay.type === 'about' && (
<AboutBox {...itemForDisplay.systemInfo} width={boxWidth} />
)}

View file

@ -0,0 +1,41 @@
/**
* @license
* Copyright 2025 Qwen
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { Text, Box } from 'ink';
import { theme } from '../../semantic-colors.js';
interface RetryCountdownMessageProps {
text: string;
}
/**
* Displays a retry countdown message in a dimmed/secondary style
* to visually distinguish it from error messages.
*/
export const RetryCountdownMessage: React.FC<RetryCountdownMessageProps> = ({
text,
}) => {
if (!text || text.trim() === '') {
return null;
}
const prefix = '↻ ';
const prefixWidth = prefix.length;
return (
<Box flexDirection="row">
<Box width={prefixWidth}>
<Text color={theme.text.secondary}>{prefix}</Text>
</Box>
<Box flexGrow={1}>
<Text wrap="wrap" color={theme.text.secondary}>
{text}
</Text>
</Box>
</Box>
);
};