Skip to content

Commit df9ef3d

Browse files
author
Smoke Tester
committed
feat:增加重试
1 parent 609f1e5 commit df9ef3d

5 files changed

Lines changed: 66 additions & 7 deletions

File tree

requirements/done/错误重试.md

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
整体要求是如果出错了,不应该轻易打断工作循环,而是应该积极重试.
2+
<!-- 1. 新的打破循环的报错
3+
Error: Empty or insufficient response detected: "" -->
4+
2.
5+
Error: OpenAI Responses API error: 403 Forbidden
6+
也要重试

source/api/responses.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -637,9 +637,53 @@ export async function* createStreamingResponse(
637637

638638
if (!response.ok) {
639639
const errorText = await response.text();
640-
throw new Error(
641-
`OpenAI Responses API error: ${response.status} ${response.statusText} - ${errorText}`,
642-
);
640+
const baseErrorMsg = `OpenAI Responses API error: ${response.status} ${response.statusText} - ${errorText}`;
641+
642+
const error = new Error(baseErrorMsg) as Error & {
643+
code?: string;
644+
isRetryable?: boolean;
645+
};
646+
647+
if (response.status === 403) {
648+
const lower = errorText.toLowerCase();
649+
const likelyPersistent =
650+
lower.includes('insufficient_quota') ||
651+
lower.includes('quota') ||
652+
lower.includes('billing') ||
653+
lower.includes('invalid api key') ||
654+
lower.includes('api key') ||
655+
lower.includes('permission') ||
656+
lower.includes('organization') ||
657+
lower.includes('not allowed');
658+
659+
error.isRetryable = true;
660+
error.code = likelyPersistent
661+
? 'HTTP_403_FORBIDDEN_PERSISTENT'
662+
: 'HTTP_403_FORBIDDEN_TRANSIENT';
663+
664+
const hint = likelyPersistent
665+
? 'Hint: This 403 likely indicates auth/quota/permission. Retrying may not help, but Snow CLI will retry up to the configured limit.'
666+
: 'Hint: This 403 may be transient when using a third-party relay/WAF. Snow CLI will retry with backoff.';
667+
error.message = `${baseErrorMsg}\n${hint}`;
668+
669+
try {
670+
const {logger} = await import('../utils/core/logger.js');
671+
logger.warn(
672+
`[API_ERROR] OpenAI Responses API HTTP 403 (${error.code}), will retry with backoff`,
673+
{
674+
url,
675+
model: requestPayload.model,
676+
code: error.code,
677+
status: response.status,
678+
statusText: response.statusText,
679+
},
680+
);
681+
} catch {
682+
// ignore logging errors
683+
}
684+
}
685+
686+
throw error;
643687
}
644688

645689
if (!response.body) {

source/hooks/conversation/useConversation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ export async function handleConversationWithTools(
177177
message.includes('502') ||
178178
message.includes('503') ||
179179
message.includes('504') ||
180+
message.includes('403') ||
181+
message.includes('forbidden') ||
180182
message.includes('fetch failed') ||
181183
message.includes('fetcherror') ||
182184
getIsEmptyResponseError(error)

source/utils/core/retryUtils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ function isRetriableError(error: Error): boolean {
5353
return true;
5454
}
5555

56+
// 优先支持统一错误分类字段,避免上层只能依赖 message 关键字
57+
if ((error as any)?.isRetryable === true) {
58+
return true;
59+
}
60+
5661
const errorMessage = error.message.toLowerCase();
5762

5863
// 网络错误
@@ -75,8 +80,13 @@ function isRetriableError(error: Error): boolean {
7580
return true;
7681
}
7782

83+
// Forbidden errors (403). 通过第三方中转时可能是瞬时风控/权限抖动,允许在上限内重试
84+
if (errorMessage.includes('403') && errorMessage.includes('forbidden')) {
85+
return true;
86+
}
87+
7888
// Server errors (5xx - temporary server issues, retryable)
79-
// Note: 400, 403, 405 are client errors - typically not retryable
89+
// Note: 400, 405 are client errors - typically not retryable
8090
// as they indicate request format issues that won't change on retry
8191
if (
8292
errorMessage.includes('500') ||

0 commit comments

Comments
 (0)