feat(错误处理): 添加网络错误重试机制并统一处理逻辑

在common.js中定义可重试网络错误列表和检查函数
修改各API服务(qwen/iflow/claude/gemini/openai/antigravity)调用逻辑
添加网络错误检测和指数退避重试机制
统一错误日志格式包含状态码和错误标识
This commit is contained in:
hex2077 2026-01-07 22:22:59 +08:00
parent 8200712081
commit 4edd0ce2a4
9 changed files with 328 additions and 140 deletions

View file

@ -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 API401 通常意味着 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 API401 通常意味着 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;
}
}

View file

@ -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 {
// 确保流被关闭,释放资源

View file

@ -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',

View file

@ -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;

View file

@ -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;

View file

@ -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
};
}

View file

@ -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})`);

View file

@ -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;
}
}

View file

@ -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;
}
}