feat: add stopFailure and postCompact (#2825)

This commit is contained in:
DennisYu07 2026-04-13 12:54:44 +08:00 committed by GitHub
parent 732cee2604
commit dddb56d885
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1484 additions and 9 deletions

View file

@ -17,6 +17,7 @@ import type {
ThoughtSummary,
ToolCallRequestInfo,
GeminiErrorEventValue,
StopFailureErrorType,
} from '@qwen-code/qwen-code-core';
import {
GeminiEventType as ServerGeminiEventType,
@ -78,6 +79,43 @@ import { t } from '../../i18n/index.js';
const debugLogger = createDebugLogger('GEMINI_STREAM');
/**
* Classify API error to StopFailureErrorType
* @internal Exported for testing purposes
*/
export function classifyApiError(error: {
message: string;
status?: number;
}): StopFailureErrorType {
const status = error.status;
const message = error.message?.toLowerCase() ?? '';
if (status === 429 || message.includes('rate limit')) {
return 'rate_limit';
}
if (status === 401 || message.includes('unauthorized')) {
return 'authentication_failed';
}
if (
status === 402 ||
status === 403 ||
message.includes('billing') ||
message.includes('quota')
) {
return 'billing_error';
}
if (status === 400 || message.includes('invalid')) {
return 'invalid_request';
}
if (status !== undefined && status >= 500) {
return 'server_error';
}
if (message.includes('max_tokens') || message.includes('token limit')) {
return 'max_output_tokens';
}
return 'unknown';
}
/**
* Checks if image parts have supported formats and returns unsupported ones
*/
@ -795,6 +833,12 @@ export const useGeminiStream = (
// (auto-retry countdown is shown when retryCountdownTimerRef is active)
const isShowingAutoRetry = retryCountdownTimerRef.current !== null;
clearRetryCountdown();
const formattedErrorText = parseAndFormatApiError(
eventValue.error,
config.getContentGeneratorConfig()?.authType,
);
if (!isShowingAutoRetry) {
const retryHint = t('Press Ctrl+Y to retry');
// Store error with hint as a pending item (not in history).
@ -802,14 +846,24 @@ export const useGeminiStream = (
// since pending items are in the dynamic rendering area (not <Static>).
setPendingRetryErrorItem({
type: 'error' as const,
text: parseAndFormatApiError(
eventValue.error,
config.getContentGeneratorConfig()?.authType,
),
text: formattedErrorText,
hint: retryHint,
});
}
setThought(null); // Reset thought when there's an error
// Fire StopFailure hook (fire-and-forget, replaces Stop event for API errors)
const errorType = classifyApiError(eventValue.error);
config
.getHookSystem()
?.fireStopFailureEvent(
errorType,
eventValue.error.message,
formattedErrorText,
)
.catch((err) => {
debugLogger.warn(`StopFailure hook failed: ${err}`);
});
},
[
addItem,