feat(错误处理): 添加网络错误重试机制并统一处理逻辑
在common.js中定义可重试网络错误列表和检查函数 修改各API服务(qwen/iflow/claude/gemini/openai/antigravity)调用逻辑 添加网络错误检测和指数退避重试机制 统一错误日志格式包含状态码和错误标识
This commit is contained in:
parent
8200712081
commit
4edd0ce2a4
9 changed files with 328 additions and 140 deletions
|
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
|||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { configureAxiosProxy } from '../proxy-utils.js';
|
||||
import { isRetryableNetworkError } from '../common.js';
|
||||
|
||||
/**
|
||||
* Claude API Core Service Class.
|
||||
|
|
@ -75,36 +76,52 @@ export class ClaudeApiService {
|
|||
* @returns {Promise<object>} API response data.
|
||||
*/
|
||||
async callApi(endpoint, body, isRetry = false, retryCount = 0) {
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY; // 1 second base delay
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
|
||||
|
||||
try {
|
||||
const response = await this.client.post(endpoint, body);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
// 对于 Claude API,401 通常意味着 API Key 无效,不进行重试
|
||||
if (error.response?.status === 401 || error.response?.status === 403) {
|
||||
console.error(`[API] Received ${error.response.status}. API Key might be invalid or expired.`);
|
||||
if (status === 401 || status === 403) {
|
||||
console.error(`[Claude API] Received ${status}. API Key might be invalid or expired.`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 处理 429 (Too Many Requests) 与指数退避
|
||||
if (error.response?.status === 429 && retryCount < maxRetries) {
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Claude API] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(endpoint, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
// 处理其他可重试错误 (5xx 服务器错误)
|
||||
if (error.response?.status >= 500 && error.response?.status < 600 && retryCount < maxRetries) {
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received ${error.response.status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Claude API] Received ${status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(endpoint, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error("[ClaudeApiService] Error calling API:", error.response ? error.response.data : error.message);
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Claude API] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(endpoint, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error(`[Claude API] Error calling API (Status: ${status}, Code: ${errorCode}):`, error.response ? error.response.data : error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -118,8 +135,8 @@ export class ClaudeApiService {
|
|||
* @returns {AsyncIterable<object>} API response stream.
|
||||
*/
|
||||
async *streamApi(endpoint, body, isRetry = false, retryCount = 0) {
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY; // 1 second base delay
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
|
||||
|
||||
try {
|
||||
const response = await this.client.post(endpoint, { ...body, stream: true }, { responseType: 'stream' });
|
||||
|
|
@ -155,31 +172,48 @@ export class ClaudeApiService {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
// 对于 Claude API,401 通常意味着 API Key 无效,不进行重试
|
||||
if (error.response?.status === 401 || error.response?.status === 403) {
|
||||
console.error(`[API] Received ${error.response.status} during stream. API Key might be invalid or expired.`);
|
||||
if (status === 401 || status === 403) {
|
||||
console.error(`[Claude API] Received ${status} during stream. API Key might be invalid or expired.`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 处理 429 (Too Many Requests) 与指数退避
|
||||
if (error.response?.status === 429 && retryCount < maxRetries) {
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received 429 (Too Many Requests) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Claude API] Received 429 (Too Many Requests) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理其他可重试错误 (5xx 服务器错误)
|
||||
if (error.response?.status >= 500 && error.response?.status < 600 && retryCount < maxRetries) {
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received ${error.response.status} server error during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Claude API] Received ${status} server error during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("[ClaudeApiService] Error generating content stream:", error.response ? error.response.data : error.message);
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Claude API] Network error (${errorIdentifier}) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`[Claude API] Error generating content stream (Status: ${status}, Code: ${errorCode}):`, error.response ? error.response.data : error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import * as https from 'https';
|
|||
import { getProviderModels } from '../provider-models.js';
|
||||
import { countTokens } from '@anthropic-ai/tokenizer';
|
||||
import { configureAxiosProxy } from '../proxy-utils.js';
|
||||
import { isRetryableNetworkError } from '../common.js';
|
||||
|
||||
const KIRO_CONSTANTS = {
|
||||
REFRESH_URL: 'https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken',
|
||||
|
|
@ -1051,7 +1052,14 @@ async initializeAuth(forceRefresh = false) {
|
|||
const response = await this.axiosInstance.post(requestUrl, requestData, { headers });
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 403 && !isRetry) {
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
if (status === 403 && !isRetry) {
|
||||
console.log('[Kiro] Received 403. Attempting token refresh and retrying...');
|
||||
try {
|
||||
await this.initializeAuth(true); // Force refresh token
|
||||
|
|
@ -1063,22 +1071,31 @@ async initializeAuth(forceRefresh = false) {
|
|||
}
|
||||
|
||||
// Handle 429 (Too Many Requests) with exponential backoff
|
||||
if (error.response?.status === 429 && retryCount < maxRetries) {
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[Kiro] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, model, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
// Handle other retryable errors (5xx server errors)
|
||||
if (error.response?.status >= 500 && error.response?.status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[Kiro] Received ${error.response.status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, model, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error('[Kiro] API call failed:', error.message);
|
||||
// Handle other retryable errors (5xx server errors)
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[Kiro] Received ${status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, model, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Kiro] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, model, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error(`[Kiro] API call failed (Status: ${status}, Code: ${errorCode}):`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -1347,22 +1364,48 @@ async initializeAuth(forceRefresh = false) {
|
|||
stream.destroy();
|
||||
}
|
||||
|
||||
if (error.response?.status === 403 && !isRetry) {
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
if (status === 403 && !isRetry) {
|
||||
console.log('[Kiro] Received 403 in stream. Attempting token refresh and retrying...');
|
||||
await this.initializeAuth(true);
|
||||
yield* this.streamApiReal(method, model, body, true, retryCount);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error.response?.status === 429 && retryCount < maxRetries) {
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[Kiro] Received 429 in stream. Retrying in ${delay}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApiReal(method, model, body, isRetry, retryCount + 1);
|
||||
console.log(`[Kiro] Received 429 in stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApiReal(method, model, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('[Kiro] Stream API call failed:', error.message);
|
||||
// Handle 5xx server errors with exponential backoff
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[Kiro] Received ${status} server error in stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApiReal(method, model, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Kiro] Network error (${errorIdentifier}) in stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApiReal(method, model, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`[Kiro] Stream API call failed (Status: ${status}, Code: ${errorCode}):`, error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
// 确保流被关闭,释放资源
|
||||
|
|
|
|||
|
|
@ -5,6 +5,43 @@ import * as crypto from 'crypto'; // Import crypto for MD5 hashing
|
|||
import { convertData, getOpenAIStreamChunkStop } from './convert.js';
|
||||
import { ProviderStrategyFactory } from './provider-strategies.js';
|
||||
|
||||
// ==================== 网络错误处理 ====================
|
||||
|
||||
/**
|
||||
* 可重试的网络错误标识列表
|
||||
* 这些错误可能出现在 error.code 或 error.message 中
|
||||
*/
|
||||
export const RETRYABLE_NETWORK_ERRORS = [
|
||||
'ECONNRESET', // 连接被重置
|
||||
'ETIMEDOUT', // 连接超时
|
||||
'ECONNREFUSED', // 连接被拒绝
|
||||
'ENOTFOUND', // DNS 解析失败
|
||||
'ENETUNREACH', // 网络不可达
|
||||
'EHOSTUNREACH', // 主机不可达
|
||||
'EPIPE', // 管道破裂
|
||||
'EAI_AGAIN', // DNS 临时失败
|
||||
'ECONNABORTED', // 连接中止
|
||||
'ESOCKETTIMEDOUT', // Socket 超时
|
||||
];
|
||||
|
||||
/**
|
||||
* 检查是否为可重试的网络错误
|
||||
* @param {Error} error - 错误对象
|
||||
* @returns {boolean} - 是否为可重试的网络错误
|
||||
*/
|
||||
export function isRetryableNetworkError(error) {
|
||||
if (!error) return false;
|
||||
|
||||
const errorCode = error.code || '';
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
return RETRYABLE_NETWORK_ERRORS.some(errId =>
|
||||
errorCode === errId || errorMessage.includes(errId)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== API 常量 ====================
|
||||
|
||||
export const API_ACTIONS = {
|
||||
GENERATE_CONTENT: 'generateContent',
|
||||
STREAM_GENERATE_CONTENT: 'streamGenerateContent',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import * as os from 'os';
|
|||
import * as readline from 'readline';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import open from 'open';
|
||||
import { formatExpiryTime } from '../common.js';
|
||||
import { formatExpiryTime, isRetryableNetworkError } from '../common.js';
|
||||
import { getProviderModels } from '../provider-models.js';
|
||||
import { handleGeminiAntigravityOAuth } from '../oauth-handlers.js';
|
||||
import { getProxyConfigForProvider, getGoogleAuthProxyConfig } from '../proxy-utils.js';
|
||||
|
|
@ -991,15 +991,22 @@ export class AntigravityApiService {
|
|||
const res = await this.authClient.request(requestOptions);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error(`[Antigravity API] Error calling ${method} on ${baseURL}:`, error.response?.status, error.message);
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
console.error(`[Antigravity API] Error calling ${method} on ${baseURL}:`, status, error.message);
|
||||
|
||||
if ((error.response?.status === 400 || error.response?.status === 401) && !isRetry) {
|
||||
if ((status === 400 || status === 401) && !isRetry) {
|
||||
console.log('[Antigravity API] Received 401/400. Refreshing auth and retrying...');
|
||||
await this.initializeAuth(true);
|
||||
return this.callApi(method, body, true, retryCount, baseURLIndex);
|
||||
}
|
||||
|
||||
if (error.response?.status === 429) {
|
||||
if (status === 429) {
|
||||
if (baseURLIndex + 1 < this.baseURLs.length) {
|
||||
console.log(`[Antigravity API] Rate limited on ${baseURL}. Trying next base URL...`);
|
||||
return this.callApi(method, body, isRetry, retryCount, baseURLIndex + 1);
|
||||
|
|
@ -1011,14 +1018,24 @@ export class AntigravityApiService {
|
|||
}
|
||||
}
|
||||
|
||||
if (!error.response && baseURLIndex + 1 < this.baseURLs.length) {
|
||||
console.log(`[Antigravity API] Network error on ${baseURL}. Trying next base URL...`);
|
||||
return this.callApi(method, body, isRetry, retryCount, baseURLIndex + 1);
|
||||
// Handle network errors - try next base URL first, then retry with backoff
|
||||
if (isNetworkError) {
|
||||
if (baseURLIndex + 1 < this.baseURLs.length) {
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Antigravity API] Network error (${errorIdentifier}) on ${baseURL}. Trying next base URL...`);
|
||||
return this.callApi(method, body, isRetry, retryCount, baseURLIndex + 1);
|
||||
} else if (retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Antigravity API] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, body, isRetry, retryCount + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (error.response?.status >= 500 && error.response?.status < 600 && retryCount < maxRetries) {
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[Antigravity API] Server error ${error.response.status}. Retrying in ${delay}ms...`);
|
||||
console.log(`[Antigravity API] Server error ${status}. Retrying in ${delay}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, body, isRetry, retryCount + 1, baseURLIndex);
|
||||
}
|
||||
|
|
@ -1063,16 +1080,23 @@ export class AntigravityApiService {
|
|||
|
||||
yield* this.parseSSEStream(res.data);
|
||||
} catch (error) {
|
||||
console.error(`[Antigravity API] Error during stream ${method} on ${baseURL}:`, error.response?.status, error.message);
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
console.error(`[Antigravity API] Error during stream ${method} on ${baseURL}:`, status, error.message);
|
||||
|
||||
if ((error.response?.status === 400 || error.response?.status === 401) && !isRetry) {
|
||||
if ((status === 400 || status === 401) && !isRetry) {
|
||||
console.log('[Antigravity API] Received 401/400 during stream. Refreshing auth and retrying...');
|
||||
await this.initializeAuth(true);
|
||||
yield* this.streamApi(method, body, true, retryCount, baseURLIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error.response?.status === 429) {
|
||||
if (status === 429) {
|
||||
if (baseURLIndex + 1 < this.baseURLs.length) {
|
||||
console.log(`[Antigravity API] Rate limited on ${baseURL}. Trying next base URL...`);
|
||||
yield* this.streamApi(method, body, isRetry, retryCount, baseURLIndex + 1);
|
||||
|
|
@ -1086,15 +1110,26 @@ export class AntigravityApiService {
|
|||
}
|
||||
}
|
||||
|
||||
if (!error.response && baseURLIndex + 1 < this.baseURLs.length) {
|
||||
console.log(`[Antigravity API] Network error on ${baseURL}. Trying next base URL...`);
|
||||
yield* this.streamApi(method, body, isRetry, retryCount, baseURLIndex + 1);
|
||||
return;
|
||||
// Handle network errors - try next base URL first, then retry with backoff
|
||||
if (isNetworkError) {
|
||||
if (baseURLIndex + 1 < this.baseURLs.length) {
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Antigravity API] Network error (${errorIdentifier}) on ${baseURL} during stream. Trying next base URL...`);
|
||||
yield* this.streamApi(method, body, isRetry, retryCount, baseURLIndex + 1);
|
||||
return;
|
||||
} else if (retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Antigravity API] Network error (${errorIdentifier}) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(method, body, isRetry, retryCount + 1, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (error.response?.status >= 500 && error.response?.status < 600 && retryCount < maxRetries) {
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[Antigravity API] Server error ${error.response.status} during stream. Retrying in ${delay}ms...`);
|
||||
console.log(`[Antigravity API] Server error ${status} during stream. Retrying in ${delay}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(method, body, isRetry, retryCount + 1, baseURLIndex);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import * as path from 'path';
|
|||
import * as os from 'os';
|
||||
import * as readline from 'readline';
|
||||
import open from 'open';
|
||||
import { API_ACTIONS, formatExpiryTime } from '../common.js';
|
||||
import { API_ACTIONS, formatExpiryTime, isRetryableNetworkError } from '../common.js';
|
||||
import { getProviderModels } from '../provider-models.js';
|
||||
import { handleGeminiCliOAuth } from '../oauth-handlers.js';
|
||||
import { getProxyConfigForProvider, getGoogleAuthProxyConfig } from '../proxy-utils.js';
|
||||
|
|
@ -436,27 +436,43 @@ export class GeminiApiService {
|
|||
const res = await this.authClient.request(requestOptions);
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
console.error(`[API] Error calling ${method}:`, error.response?.status, error.message);
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
console.error(`[Gemini API] Error calling ${method}:`, status, error.message);
|
||||
|
||||
// Handle 401 (Unauthorized) - refresh auth and retry once
|
||||
if ((error.response?.status === 400 || error.response?.status === 401) && !isRetry) {
|
||||
console.log('[API] Received 401/400. Refreshing auth and retrying...');
|
||||
if ((status === 400 || status === 401) && !isRetry) {
|
||||
console.log('[Gemini API] Received 401/400. Refreshing auth and retrying...');
|
||||
await this.initializeAuth(true);
|
||||
return this.callApi(method, body, true, retryCount);
|
||||
}
|
||||
|
||||
// Handle 429 (Too Many Requests) with exponential backoff
|
||||
if (error.response?.status === 429 && retryCount < maxRetries) {
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Gemini API] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
// Handle other retryable errors (5xx server errors)
|
||||
if (error.response?.status >= 500 && error.response?.status < 600 && retryCount < maxRetries) {
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received ${error.response.status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Gemini API] Received ${status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Gemini API] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(method, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
|
@ -486,29 +502,46 @@ export class GeminiApiService {
|
|||
}
|
||||
yield* this.parseSSEStream(res.data);
|
||||
} catch (error) {
|
||||
console.error(`[API] Error during stream ${method}:`, error.response?.status, error.message);
|
||||
const status = error.response?.status;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
console.error(`[Gemini API] Error during stream ${method}:`, status, error.message);
|
||||
|
||||
// Handle 401 (Unauthorized) - refresh auth and retry once
|
||||
if ((error.response?.status === 400 || error.response?.status === 401) && !isRetry) {
|
||||
console.log('[API] Received 401/400 during stream. Refreshing auth and retrying...');
|
||||
if ((status === 400 || status === 401) && !isRetry) {
|
||||
console.log('[Gemini API] Received 401/400 during stream. Refreshing auth and retrying...');
|
||||
await this.initializeAuth(true);
|
||||
yield* this.streamApi(method, body, true, retryCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle 429 (Too Many Requests) with exponential backoff
|
||||
if (error.response?.status === 429 && retryCount < maxRetries) {
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received 429 (Too Many Requests) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Gemini API] Received 429 (Too Many Requests) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(method, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle other retryable errors (5xx server errors)
|
||||
if (error.response?.status >= 500 && error.response?.status < 600 && retryCount < maxRetries) {
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received ${error.response.status} server error during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[Gemini API] Received ${status} server error during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(method, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[Gemini API] Network error (${errorIdentifier}) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(method, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1290,15 +1290,12 @@ function createIFlowCallbackServer(port, redirectUri, expectedState, options = {
|
|||
|
||||
// 3. 组合完整的凭据数据
|
||||
const credentialsData = {
|
||||
accessToken: tokenData.accessToken,
|
||||
refreshToken: tokenData.refreshToken,
|
||||
tokenType: tokenData.tokenType,
|
||||
access_token: tokenData.accessToken,
|
||||
refresh_token: tokenData.refreshToken,
|
||||
expiry_date: new Date(tokenData.expiresAt).getTime(),
|
||||
token_type: tokenData.tokenType,
|
||||
scope: tokenData.scope,
|
||||
expiresAt: tokenData.expiresAt,
|
||||
apiKey: userInfo.apiKey,
|
||||
email: userInfo.email,
|
||||
lastRefresh: new Date().toISOString(),
|
||||
type: 'iflow'
|
||||
apiKey: userInfo.apiKey
|
||||
};
|
||||
|
||||
// 4. 保存凭据
|
||||
|
|
@ -1474,14 +1471,11 @@ export async function refreshIFlowTokens(refreshToken) {
|
|||
const userInfo = await fetchIFlowUserInfo(tokenData.access_token);
|
||||
|
||||
return {
|
||||
accessToken: tokenData.access_token,
|
||||
refreshToken: tokenData.refresh_token,
|
||||
tokenType: tokenData.token_type,
|
||||
access_token: tokenData.access_token,
|
||||
refresh_token: tokenData.refresh_token,
|
||||
expiry_date: Date.now() + tokenData.expires_in * 1000,
|
||||
token_type: tokenData.token_type,
|
||||
scope: tokenData.scope,
|
||||
expiresAt: new Date(Date.now() + tokenData.expires_in * 1000).toISOString(),
|
||||
apiKey: userInfo.apiKey,
|
||||
email: userInfo.email,
|
||||
lastRefresh: new Date().toISOString(),
|
||||
type: 'iflow'
|
||||
apiKey: userInfo.apiKey
|
||||
};
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import { promises as fs } from 'fs';
|
|||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { configureAxiosProxy } from '../proxy-utils.js';
|
||||
import { isRetryableNetworkError } from '../common.js';
|
||||
|
||||
// iFlow API 端点
|
||||
const IFLOW_API_BASE_URL = 'https://apis.iflow.cn/v1';
|
||||
|
|
@ -71,10 +72,8 @@ class IFlowTokenStorage {
|
|||
this.refreshToken = data.refreshToken || data.refresh_token || '';
|
||||
this.expiryDate = data.expiryDate || data.expiry_date || '';
|
||||
this.apiKey = data.apiKey || data.api_key || '';
|
||||
this.email = data.email || '';
|
||||
this.tokenType = data.tokenType || data.token_type || '';
|
||||
this.scope = data.scope || '';
|
||||
this.type = data.type || 'iflow';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -87,9 +86,7 @@ class IFlowTokenStorage {
|
|||
expiry_date: this.expiryDate,
|
||||
token_type: this.tokenType,
|
||||
scope: this.scope,
|
||||
apiKey: this.apiKey,
|
||||
email: this.email,
|
||||
type: this.type
|
||||
apiKey: this.apiKey
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -743,24 +740,8 @@ export class IFlowApiService {
|
|||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 定义可重试的网络错误标识(可能出现在 code 或 message 中)
|
||||
const retryableNetworkErrors = [
|
||||
'ECONNRESET', // 连接被重置
|
||||
'ETIMEDOUT', // 连接超时
|
||||
'ECONNREFUSED', // 连接被拒绝
|
||||
'ENOTFOUND', // DNS 解析失败
|
||||
'ENETUNREACH', // 网络不可达
|
||||
'EHOSTUNREACH', // 主机不可达
|
||||
'EPIPE', // 管道破裂
|
||||
'EAI_AGAIN', // DNS 临时失败
|
||||
'ECONNABORTED', // 连接中止
|
||||
'ESOCKETTIMEDOUT', // Socket 超时
|
||||
];
|
||||
|
||||
// 检查是否为可重试的网络错误(检查 code 和 message)
|
||||
const isRetryableNetworkError = retryableNetworkErrors.some(errId =>
|
||||
errorCode === errId || errorMessage.includes(errId)
|
||||
);
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
if (status === 401 || status === 403) {
|
||||
console.error(`[iFlow] Received ${status}. API Key might be invalid or expired.`);
|
||||
|
|
@ -784,7 +765,7 @@ export class IFlowApiService {
|
|||
}
|
||||
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isRetryableNetworkError && retryCount < maxRetries) {
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[iFlow] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
|
|
@ -897,24 +878,8 @@ export class IFlowApiService {
|
|||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 定义可重试的网络错误标识(可能出现在 code 或 message 中)
|
||||
const retryableNetworkErrors = [
|
||||
'ECONNRESET', // 连接被重置
|
||||
'ETIMEDOUT', // 连接超时
|
||||
'ECONNREFUSED', // 连接被拒绝
|
||||
'ENOTFOUND', // DNS 解析失败
|
||||
'ENETUNREACH', // 网络不可达
|
||||
'EHOSTUNREACH', // 主机不可达
|
||||
'EPIPE', // 管道破裂
|
||||
'EAI_AGAIN', // DNS 临时失败
|
||||
'ECONNABORTED', // 连接中止
|
||||
'ESOCKETTIMEDOUT', // Socket 超时
|
||||
];
|
||||
|
||||
// 检查是否为可重试的网络错误(检查 code 和 message)
|
||||
const isRetryableNetworkError = retryableNetworkErrors.some(errId =>
|
||||
errorCode === errId || errorMessage.includes(errId)
|
||||
);
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
if (status === 401 || status === 403) {
|
||||
console.error(`[iFlow] Received ${status} during stream. API Key might be invalid or expired.`);
|
||||
|
|
@ -940,7 +905,7 @@ export class IFlowApiService {
|
|||
}
|
||||
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isRetryableNetworkError && retryCount < maxRetries) {
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[iFlow] Network error (${errorIdentifier}) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
|||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { configureAxiosProxy } from '../proxy-utils.js';
|
||||
import { isRetryableNetworkError } from '../common.js';
|
||||
|
||||
// Assumed OpenAI API specification service for interacting with third-party models
|
||||
export class OpenAIApiService {
|
||||
|
|
@ -51,8 +52,8 @@ export class OpenAIApiService {
|
|||
}
|
||||
|
||||
async callApi(endpoint, body, isRetry = false, retryCount = 0) {
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY; // 1 second base delay
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
|
||||
|
||||
try {
|
||||
const response = await this.axiosInstance.post(endpoint, body);
|
||||
|
|
@ -60,15 +61,21 @@ export class OpenAIApiService {
|
|||
} catch (error) {
|
||||
const status = error.response?.status;
|
||||
const data = error.response?.data;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
if (status === 401 || status === 403) {
|
||||
console.error(`[API] Received ${status}. API Key might be invalid or expired.`);
|
||||
console.error(`[OpenAI API] Received ${status}. API Key might be invalid or expired.`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Handle 429 (Too Many Requests) with exponential backoff
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[OpenAI API] Received 429 (Too Many Requests). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(endpoint, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
|
@ -76,19 +83,28 @@ export class OpenAIApiService {
|
|||
// Handle other retryable errors (5xx server errors)
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received ${status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[OpenAI API] Received ${status} server error. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(endpoint, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error(`Error calling OpenAI API (Status: ${status}):`, data || error.message);
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[OpenAI API] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApi(endpoint, body, isRetry, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error(`[OpenAI API] Error calling API (Status: ${status}, Code: ${errorCode}):`, data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async *streamApi(endpoint, body, isRetry = false, retryCount = 0) {
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY; // 1 second base delay
|
||||
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
|
||||
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
|
||||
|
||||
// OpenAI 的流式请求需要将 stream 设置为 true
|
||||
const streamRequestBody = { ...body, stream: true };
|
||||
|
|
@ -127,15 +143,21 @@ export class OpenAIApiService {
|
|||
} catch (error) {
|
||||
const status = error.response?.status;
|
||||
const data = error.response?.data;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
if (status === 401 || status === 403) {
|
||||
console.error(`[API] Received ${status} during stream. API Key might be invalid or expired.`);
|
||||
console.error(`[OpenAI API] Received ${status} during stream. API Key might be invalid or expired.`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Handle 429 (Too Many Requests) with exponential backoff
|
||||
if (status === 429 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received 429 (Too Many Requests) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[OpenAI API] Received 429 (Too Many Requests) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
|
|
@ -144,13 +166,23 @@ export class OpenAIApiService {
|
|||
// Handle other retryable errors (5xx server errors)
|
||||
if (status >= 500 && status < 600 && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
console.log(`[API] Received ${status} server error during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
console.log(`[OpenAI API] Received ${status} server error during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`Error calling OpenAI streaming API (Status: ${status}):`, data || error.message);
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[OpenAI API] Network error (${errorIdentifier}) during stream. Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
yield* this.streamApi(endpoint, body, isRetry, retryCount + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`[OpenAI API] Error calling streaming API (Status: ${status}, Code: ${errorCode}):`, data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { randomUUID } from 'node:crypto';
|
|||
import { getProviderModels } from '../provider-models.js';
|
||||
import { handleQwenOAuth } from '../oauth-handlers.js';
|
||||
import { configureAxiosProxy } from '../proxy-utils.js';
|
||||
import { isRetryableNetworkError } from '../common.js';
|
||||
|
||||
// --- Constants ---
|
||||
const QWEN_DIR = '.qwen';
|
||||
|
|
@ -550,6 +551,11 @@ export class QwenApiService {
|
|||
} catch (error) {
|
||||
const status = error.response?.status;
|
||||
const data = error.response?.data || error.message;
|
||||
const errorCode = error.code;
|
||||
const errorMessage = error.message || '';
|
||||
|
||||
// 检查是否为可重试的网络错误
|
||||
const isNetworkError = isRetryableNetworkError(error);
|
||||
|
||||
if (this.isAuthError(error) && retryCount === 0) {
|
||||
console.warn(`[QwenApiService] Auth error (${status}). Refreshing token...`);
|
||||
|
|
@ -573,7 +579,16 @@ export class QwenApiService {
|
|||
return this.callApiWithAuthAndRetry(endpoint, body, isStream, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error(`Error calling Qwen API (Status: ${status}):`, data);
|
||||
// Handle network errors (ECONNRESET, ETIMEDOUT, etc.) with exponential backoff
|
||||
if (isNetworkError && retryCount < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, retryCount);
|
||||
const errorIdentifier = errorCode || errorMessage.substring(0, 50);
|
||||
console.log(`[QwenApiService] Network error (${errorIdentifier}). Retrying in ${delay}ms... (attempt ${retryCount + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return this.callApiWithAuthAndRetry(endpoint, body, isStream, retryCount + 1);
|
||||
}
|
||||
|
||||
console.error(`[QwenApiService] Error calling API (Status: ${status}, Code: ${errorCode}):`, data);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue