refactor(provider-pool): 重构提供者池管理器的日志和健康检查逻辑

- 添加日志级别控制和统一的日志输出方法
- 重构健康检查逻辑,使用默认模型配置和标准化的请求构建
- 优化批量保存机制,减少文件I/O操作
- 添加参数校验和错误处理
- 重命名方法以更准确表达其功能
This commit is contained in:
hex2077 2025-11-25 16:02:30 +08:00
parent 08881ae144
commit 1c22b8c5c9

View file

@ -6,15 +6,28 @@ import { MODEL_PROVIDER } from './common.js';
* Manages a pool of API service providers, handling their health and selection. * Manages a pool of API service providers, handling their health and selection.
*/ */
export class ProviderPoolManager { export class ProviderPoolManager {
// 默认健康检查模型配置
static DEFAULT_HEALTH_CHECK_MODELS = {
'gemini-cli': 'gemini-2.5-flash',
'openai-custom': 'gpt-3.5-turbo',
'claude-custom': 'claude-3-7-sonnet-20250219',
'kiro-api': 'claude-3-7-sonnet-20250219',
'qwen-api': 'qwen3-coder-flash',
'openai-custom-responses': 'gpt-5-low'
};
constructor(providerPools, options = {}) { constructor(providerPools, options = {}) {
this.providerPools = providerPools; this.providerPools = providerPools;
this.globalConfig = options.globalConfig || {}; // 存储全局配置 this.globalConfig = options.globalConfig || {}; // 存储全局配置
this.providerStatus = {}; // Tracks health and usage for each provider instance this.providerStatus = {}; // Tracks health and usage for each provider instance
this.roundRobinIndex = {}; // Tracks the current index for round-robin selection for each provider type this.roundRobinIndex = {}; // Tracks the current index for round-robin selection for each provider type
this.maxErrorCount = options.maxErrorCount || 3; // Default to 1 errors before marking unhealthy this.maxErrorCount = options.maxErrorCount || 3; // Default to 3 errors before marking unhealthy
this.healthCheckInterval = options.healthCheckInterval || 10 * 60 * 1000; // Default to 10 minutes this.healthCheckInterval = options.healthCheckInterval || 10 * 60 * 1000; // Default to 10 minutes
// 优化1: 添加防抖机制,避免频繁的文件 I/O 操作 // 日志级别控制
this.logLevel = options.logLevel || 'info'; // 'debug', 'info', 'warn', 'error'
// 添加防抖机制,避免频繁的文件 I/O 操作
this.saveDebounceTime = options.saveDebounceTime || 1000; // 默认1秒防抖 this.saveDebounceTime = options.saveDebounceTime || 1000; // 默认1秒防抖
this.saveTimer = null; this.saveTimer = null;
this.pendingSaves = new Set(); // 记录待保存的 providerType this.pendingSaves = new Set(); // 记录待保存的 providerType
@ -22,6 +35,31 @@ export class ProviderPoolManager {
this.initializeProviderStatus(); this.initializeProviderStatus();
} }
/**
* 日志输出方法支持日志级别控制
* @private
*/
_log(level, message) {
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
if (levels[level] >= levels[this.logLevel]) {
const logMethod = level === 'debug' ? 'log' : level;
console[logMethod](`[ProviderPoolManager] ${message}`);
}
}
/**
* 查找指定的 provider
* @private
*/
_findProvider(providerType, uuid) {
if (!providerType || !uuid) {
this._log('error', `Invalid parameters: providerType=${providerType}, uuid=${uuid}`);
return null;
}
const pool = this.providerStatus[providerType];
return pool?.find(p => p.uuid === uuid) || null;
}
/** /**
* Initializes the status for each provider in the pools. * Initializes the status for each provider in the pools.
* Initially, all providers are considered healthy and have zero usage. * Initially, all providers are considered healthy and have zero usage.
@ -49,7 +87,7 @@ export class ProviderPoolManager {
}); });
}); });
} }
console.log('[ProviderPoolManager] Initialized provider statuses: ok'); this._log('info', 'Initialized provider statuses: ok');
} }
/** /**
@ -59,17 +97,23 @@ export class ProviderPoolManager {
* @returns {object|null} The selected provider's configuration, or null if no healthy provider is found. * @returns {object|null} The selected provider's configuration, or null if no healthy provider is found.
*/ */
selectProvider(providerType) { selectProvider(providerType) {
// 参数校验
if (!providerType || typeof providerType !== 'string') {
this._log('error', `Invalid providerType: ${providerType}`);
return null;
}
const availableProviders = this.providerStatus[providerType] || []; const availableProviders = this.providerStatus[providerType] || [];
const availableAndHealthyProviders = availableProviders.filter(p => const availableAndHealthyProviders = availableProviders.filter(p =>
p.config.isHealthy && !p.config.isDisabled p.config.isHealthy && !p.config.isDisabled
); );
if (availableAndHealthyProviders.length === 0) { if (availableAndHealthyProviders.length === 0) {
console.warn(`[ProviderPoolManager] No available and healthy providers for type: ${providerType}`); this._log('warn', `No available and healthy providers for type: ${providerType}`);
return null; return null;
} }
// 优化3: 简化轮询逻辑,移除不必要的循环 // 简化轮询逻辑
const currentIndex = this.roundRobinIndex[providerType] || 0; const currentIndex = this.roundRobinIndex[providerType] || 0;
const providerIndex = currentIndex % availableAndHealthyProviders.length; const providerIndex = currentIndex % availableAndHealthyProviders.length;
const selected = availableAndHealthyProviders[providerIndex]; const selected = availableAndHealthyProviders[providerIndex];
@ -81,9 +125,9 @@ export class ProviderPoolManager {
selected.config.lastUsed = new Date().toISOString(); selected.config.lastUsed = new Date().toISOString();
selected.config.usageCount++; selected.config.usageCount++;
console.log(`[ProviderPoolManager] Selected provider for ${providerType} (round-robin): ${JSON.stringify(selected.config)}`); this._log('debug', `Selected provider for ${providerType} (round-robin): ${selected.config.uuid}`);
// 优化1: 使用防抖保存 // 使用防抖保存
this._debouncedSave(providerType); this._debouncedSave(providerType);
return selected.config; return selected.config;
@ -95,23 +139,24 @@ export class ProviderPoolManager {
* @param {object} providerConfig - The configuration of the provider to mark. * @param {object} providerConfig - The configuration of the provider to mark.
*/ */
markProviderUnhealthy(providerType, providerConfig) { markProviderUnhealthy(providerType, providerConfig) {
const pool = this.providerStatus[providerType]; if (!providerConfig?.uuid) {
if (pool) { this._log('error', 'Invalid providerConfig in markProviderUnhealthy');
const provider = pool.find(p => p.uuid === providerConfig.uuid); return;
if (provider) { }
provider.config.errorCount++;
provider.config.lastErrorTime = new Date().toISOString(); // Update last error time in config
if (provider.config.errorCount >= this.maxErrorCount) { const provider = this._findProvider(providerType, providerConfig.uuid);
provider.config.isHealthy = false; if (provider) {
console.warn(`[ProviderPoolManager] Marked provider as unhealthy: ${providerConfig.uuid} for type ${providerType}. Total errors: ${provider.config.errorCount}`); provider.config.errorCount++;
} else { provider.config.lastErrorTime = new Date().toISOString();
console.warn(`[ProviderPoolManager] Provider ${providerConfig.uuid} for type ${providerType} error count: ${provider.config.errorCount}/${this.maxErrorCount}. Still healthy.`);
} if (provider.config.errorCount >= this.maxErrorCount) {
provider.config.isHealthy = false;
// 优化1: 使用防抖保存 this._log('warn', `Marked provider as unhealthy: ${providerConfig.uuid} for type ${providerType}. Total errors: ${provider.config.errorCount}`);
this._debouncedSave(providerType); } else {
this._log('warn', `Provider ${providerConfig.uuid} for type ${providerType} error count: ${provider.config.errorCount}/${this.maxErrorCount}. Still healthy.`);
} }
this._debouncedSave(providerType);
} }
} }
@ -119,43 +164,46 @@ export class ProviderPoolManager {
* Marks a provider as healthy. * Marks a provider as healthy.
* @param {string} providerType - The type of the provider. * @param {string} providerType - The type of the provider.
* @param {object} providerConfig - The configuration of the provider to mark. * @param {object} providerConfig - The configuration of the provider to mark.
* @param {boolean} isInit - Whether to reset usage count (optional, default: false).
*/ */
markProviderHealthy(isInit, providerType, providerConfig) { markProviderHealthy(providerType, providerConfig, isInit = false) {
const pool = this.providerStatus[providerType]; if (!providerConfig?.uuid) {
if (pool) { this._log('error', 'Invalid providerConfig in markProviderHealthy');
const provider = pool.find(p => p.uuid === providerConfig.uuid); return;
if (provider) { }
provider.config.isHealthy = true;
provider.config.errorCount = 0; // Reset error count on health recovery const provider = this._findProvider(providerType, providerConfig.uuid);
provider.config.lastErrorTime = null; // Reset lastErrorTime when healthy if (provider) {
if (isInit) { provider.config.isHealthy = true;
provider.config.usageCount = 0; // Reset usage count on health recovery provider.config.errorCount = 0;
} provider.config.lastErrorTime = null;
console.log(`[ProviderPoolManager] Marked provider as healthy: ${provider.config.uuid} for type ${providerType}`); if (isInit) {
provider.config.usageCount = 0;
// 优化1: 使用防抖保存
this._debouncedSave(providerType);
} }
this._log('info', `Marked provider as healthy: ${provider.config.uuid} for type ${providerType}`);
this._debouncedSave(providerType);
} }
} }
/** /**
* Marks a provider as healthy. * 重置提供商的计数器错误计数和使用计数
* @param {string} providerType - The type of the provider. * @param {string} providerType - The type of the provider.
* @param {object} providerConfig - The configuration of the provider to mark. * @param {object} providerConfig - The configuration of the provider to mark.
*/ */
markProviderZero(providerType, providerConfig) { resetProviderCounters(providerType, providerConfig) {
const pool = this.providerStatus[providerType]; if (!providerConfig?.uuid) {
if (pool) { this._log('error', 'Invalid providerConfig in resetProviderCounters');
const provider = pool.find(p => p.uuid === providerConfig.uuid); return;
if (provider) { }
provider.config.errorCount = 0; // Reset error count on health recovery
provider.config.usageCount = 0; // Reset usage count on health recovery const provider = this._findProvider(providerType, providerConfig.uuid);
console.log(`[ProviderPoolManager] Marked provider as zero: ${provider.config.uuid} for type ${providerType}`); if (provider) {
provider.config.errorCount = 0;
// 优化1: 使用防抖保存 provider.config.usageCount = 0;
this._debouncedSave(providerType); this._log('info', `Reset provider counters: ${provider.config.uuid} for type ${providerType}`);
}
this._debouncedSave(providerType);
} }
} }
@ -165,16 +213,16 @@ export class ProviderPoolManager {
* @param {object} providerConfig - 提供商配置 * @param {object} providerConfig - 提供商配置
*/ */
disableProvider(providerType, providerConfig) { disableProvider(providerType, providerConfig) {
const pool = this.providerStatus[providerType]; if (!providerConfig?.uuid) {
if (pool) { this._log('error', 'Invalid providerConfig in disableProvider');
const provider = pool.find(p => p.uuid === providerConfig.uuid); return;
if (provider) { }
provider.config.isDisabled = true;
console.log(`[ProviderPoolManager] Disabled provider: ${providerConfig.uuid} for type ${providerType}`); const provider = this._findProvider(providerType, providerConfig.uuid);
if (provider) {
// 使用防抖保存 provider.config.isDisabled = true;
this._debouncedSave(providerType); this._log('info', `Disabled provider: ${providerConfig.uuid} for type ${providerType}`);
} this._debouncedSave(providerType);
} }
} }
@ -184,16 +232,16 @@ export class ProviderPoolManager {
* @param {object} providerConfig - 提供商配置 * @param {object} providerConfig - 提供商配置
*/ */
enableProvider(providerType, providerConfig) { enableProvider(providerType, providerConfig) {
const pool = this.providerStatus[providerType]; if (!providerConfig?.uuid) {
if (pool) { this._log('error', 'Invalid providerConfig in enableProvider');
const provider = pool.find(p => p.uuid === providerConfig.uuid); return;
if (provider) { }
provider.config.isDisabled = false;
console.log(`[ProviderPoolManager] Enabled provider: ${providerConfig.uuid} for type ${providerType}`); const provider = this._findProvider(providerType, providerConfig.uuid);
if (provider) {
// 使用防抖保存 provider.config.isDisabled = false;
this._debouncedSave(providerType); this._log('info', `Enabled provider: ${providerConfig.uuid} for type ${providerType}`);
} this._debouncedSave(providerType);
} }
} }
@ -202,8 +250,9 @@ export class ProviderPoolManager {
* This method would typically be called periodically (e.g., via cron job). * This method would typically be called periodically (e.g., via cron job).
*/ */
async performHealthChecks(isInit = false) { async performHealthChecks(isInit = false) {
console.log('[ProviderPoolManager] Performing health checks on all providers...'); this._log('info', 'Performing health checks on all providers...');
const now = new Date(); const now = new Date();
for (const providerType in this.providerStatus) { for (const providerType in this.providerStatus) {
for (const providerStatus of this.providerStatus[providerType]) { for (const providerStatus of this.providerStatus[providerType]) {
const providerConfig = providerStatus.config; const providerConfig = providerStatus.config;
@ -211,37 +260,38 @@ export class ProviderPoolManager {
// Only attempt to health check unhealthy providers after a certain interval // Only attempt to health check unhealthy providers after a certain interval
if (!providerStatus.config.isHealthy && providerStatus.config.lastErrorTime && if (!providerStatus.config.isHealthy && providerStatus.config.lastErrorTime &&
(now.getTime() - new Date(providerStatus.config.lastErrorTime).getTime() < this.healthCheckInterval)) { (now.getTime() - new Date(providerStatus.config.lastErrorTime).getTime() < this.healthCheckInterval)) {
console.log(`[ProviderPoolManager] Skipping health check for ${providerConfig.uuid} (${providerType}). Last error too recent.`); this._log('debug', `Skipping health check for ${providerConfig.uuid} (${providerType}). Last error too recent.`);
continue; continue;
} }
try { try {
// Perform actual health check based on provider type // Perform actual health check based on provider type
const isHealthy = await this._checkProviderHealth(providerType, providerConfig); const isHealthy = await this._checkProviderHealth(providerType, providerConfig);
if(isHealthy === null){
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) skipped: Check not implemented.`); if (isHealthy === null) {
this.markProviderZero(providerType, providerConfig); this._log('debug', `Health check for ${providerConfig.uuid} (${providerType}) skipped: Check not implemented.`);
this.resetProviderCounters(providerType, providerConfig);
continue; continue;
} }
if (isHealthy) { if (isHealthy) {
if (!providerStatus.config.isHealthy) { if (!providerStatus.config.isHealthy) {
// Provider was unhealthy but is now healthy // Provider was unhealthy but is now healthy
this.markProviderHealthy(isInit, providerType, providerConfig); this.markProviderHealthy(providerType, providerConfig, isInit);
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}): Marked Healthy (actual check)`); this._log('info', `Health check for ${providerConfig.uuid} (${providerType}): Marked Healthy (actual check)`);
} else { } else {
// Provider was already healthy and still is // Provider was already healthy and still is
this.markProviderHealthy(isInit, providerType, providerConfig); this.markProviderHealthy(providerType, providerConfig, isInit);
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}): Still Healthy`); this._log('debug', `Health check for ${providerConfig.uuid} (${providerType}): Still Healthy`);
} }
} else { } else {
// Provider is not healthy // Provider is not healthy
console.warn(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) failed: Provider is not responding correctly.`); this._log('warn', `Health check for ${providerConfig.uuid} (${providerType}) failed: Provider is not responding correctly.`);
this.markProviderUnhealthy(providerType, providerConfig); this.markProviderUnhealthy(providerType, providerConfig);
} }
} catch (error) { } catch (error) {
console.error(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) failed: ${error.message}`); this._log('error', `Health check for ${providerConfig.uuid} (${providerType}) failed: ${error.message}`);
// If a health check fails, mark it unhealthy, which will update error count and lastErrorTime // If a health check fails, mark it unhealthy, which will update error count and lastErrorTime
this.markProviderUnhealthy(providerType, providerConfig); this.markProviderUnhealthy(providerType, providerConfig);
} }
@ -249,88 +299,85 @@ export class ProviderPoolManager {
} }
} }
/**
* 构建健康检查请求
* @private
*/
_buildHealthCheckRequest(providerType, modelName) {
const baseMessage = { role: 'user', content: 'Hello, are you ok?' };
// Gemini 使用不同的请求格式
if (providerType === 'gemini-cli') {
return {
contents: [{
role: 'user',
parts: [{ text: baseMessage.content }]
}]
};
}
// OpenAI Custom Responses 使用特殊格式
if (providerType === 'openai-custom-responses') {
return {
input: [baseMessage],
model: modelName
};
}
// 其他提供商OpenAI、Claude、Kiro、Qwen使用标准格式
return {
messages: [baseMessage],
model: modelName
};
}
/** /**
* Performs an actual health check for a specific provider. * Performs an actual health check for a specific provider.
* @param {string} providerType - The type of the provider. * @param {string} providerType - The type of the provider.
* @param {object} providerConfig - The configuration of the provider to check. * @param {object} providerConfig - The configuration of the provider to check.
* @returns {Promise<boolean>} - True if the provider is healthy, false otherwise. * @returns {Promise<boolean|null>} - True if healthy, false if unhealthy, null if check not implemented.
*/ */
async _checkProviderHealth(providerType, providerConfig) { async _checkProviderHealth(providerType, providerConfig) {
try { try {
// Create a temporary service adapter for health check // 如果未启用健康检查,返回 null
// 合并全局配置和 provider 配置 if (!providerConfig.checkHealth) {
const tempConfig = {
// ...this.globalConfig,
...providerConfig,
MODEL_PROVIDER: providerType,
USE_SYSTEM_PROXY_GEMINI: this.globalConfig.USE_SYSTEM_PROXY_GEMINI,
USE_SYSTEM_PROXY_OPENAI: this.globalConfig.USE_SYSTEM_PROXY_OPENAI,
USE_SYSTEM_PROXY_CLAUDE: this.globalConfig.USE_SYSTEM_PROXY_CLAUDE,
USE_SYSTEM_PROXY_QWEN: this.globalConfig.USE_SYSTEM_PROXY_QWEN,
USE_SYSTEM_PROXY_KIRO: this.globalConfig.USE_SYSTEM_PROXY_KIRO,
};
const serviceAdapter = getServiceAdapter(tempConfig);
if(!providerConfig.checkHealth){
return null; return null;
} }
// Determine a suitable model name for health check // 合并全局配置和 provider 配置(简化代理配置)
// First, try to get it from the provider configuration const proxyKeys = ['GEMINI', 'OPENAI', 'CLAUDE', 'QWEN', 'KIRO'];
let modelName = providerConfig.checkModelName; const tempConfig = {
...providerConfig,
// If not specified in config, use default model names based on provider type MODEL_PROVIDER: providerType
if (!modelName) {
switch (providerType) {
case MODEL_PROVIDER.GEMINI_CLI:
modelName = 'gemini-2.5-flash'; // Example model name for Gemini
break;
case MODEL_PROVIDER.OPENAI_CUSTOM:
modelName = 'gpt-3.5-turbo'; // Example model name for OpenAI
break;
case MODEL_PROVIDER.CLAUDE_CUSTOM:
modelName = 'claude-3-7-sonnet-20250219'; // Example model name for Claude
break;
case MODEL_PROVIDER.KIRO_API:
modelName = 'claude-3-7-sonnet-20250219'; // Example model name for Kiro API
break;
case MODEL_PROVIDER.QWEN_API:
modelName = 'qwen3-coder-flash'; // Example model name for Qwen
break;
case MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES:
modelName = 'gpt-5-low'; // Example model name for OpenAI Custom Responses
break;
default:
console.warn(`[ProviderPoolManager] Unknown provider type for health check: ${providerType}`);
return false;
}
}
// Perform a lightweight API call to check health
const healthCheckRequest = {
contents: [{
role: 'user',
parts: [{ text: 'Hello, are you ok?' }]
}]
}; };
if (providerType === MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES) { // 动态添加代理配置
healthCheckRequest.input = [{ role: 'user', content: 'Hello, are you ok?' }]; proxyKeys.forEach(key => {
healthCheckRequest.model = modelName; const proxyKey = `USE_SYSTEM_PROXY_${key}`;
delete healthCheckRequest.contents; if (this.globalConfig[proxyKey] !== undefined) {
tempConfig[proxyKey] = this.globalConfig[proxyKey];
}
});
const serviceAdapter = getServiceAdapter(tempConfig);
// 确定健康检查使用的模型名称
const modelName = providerConfig.checkModelName ||
ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType];
if (!modelName) {
this._log('warn', `Unknown provider type for health check: ${providerType}`);
return false;
} }
// For OpenAI and Claude providers, we need a different request format // 构建健康检查请求
if (providerType === MODEL_PROVIDER.OPENAI_CUSTOM || providerType === MODEL_PROVIDER.CLAUDE_CUSTOM || providerType === MODEL_PROVIDER.KIRO_API || providerType === MODEL_PROVIDER.QWEN_API) { const healthCheckRequest = this._buildHealthCheckRequest(providerType, modelName);
healthCheckRequest.messages = [{ role: 'user', content: 'Hello, are you ok?' }];
healthCheckRequest.model = modelName;
delete healthCheckRequest.contents;
}
// console.log(`[ProviderPoolManager] Health check request for ${modelName}: ${JSON.stringify(healthCheckRequest)}`); this._log('debug', `Health check request for ${modelName}: ${JSON.stringify(healthCheckRequest)}`);
await serviceAdapter.generateContent(modelName, healthCheckRequest); await serviceAdapter.generateContent(modelName, healthCheckRequest);
return true; return true;
} catch (error) { } catch (error) {
console.error(`[ProviderPoolManager] Health check failed for ${providerType}: ${error.message}`); this._log('error', `Health check failed for ${providerType}: ${error.message}`);
return false; return false;
} }
} }
@ -356,57 +403,56 @@ export class ProviderPoolManager {
} }
/** /**
* 优化1: 批量保存所有待保存的 providerType * 批量保存所有待保存的 providerType优化为单次文件写入
* @private * @private
*/ */
async _flushPendingSaves() { async _flushPendingSaves() {
const typesToSave = Array.from(this.pendingSaves); const typesToSave = Array.from(this.pendingSaves);
if (typesToSave.length === 0) return;
this.pendingSaves.clear(); this.pendingSaves.clear();
this.saveTimer = null; this.saveTimer = null;
for (const providerType of typesToSave) {
await this._saveProviderPoolsToJson(providerType);
}
}
/**
* Saves the current provider pools configuration to the JSON file.
* @private
*/
async _saveProviderPoolsToJson(providerTypeToUpdate) {
try { try {
const filePath = this.globalConfig.PROVIDER_POOLS_FILE_PATH || 'provider_pools.json'; const filePath = this.globalConfig.PROVIDER_POOLS_FILE_PATH || 'provider_pools.json';
let currentPools = {}; let currentPools = {};
// 一次性读取文件
try { try {
const fileContent = await fs.promises.readFile(filePath, 'utf8'); const fileContent = await fs.promises.readFile(filePath, 'utf8');
currentPools = JSON.parse(fileContent); currentPools = JSON.parse(fileContent);
} catch (readError) { } catch (readError) {
if (readError.code === 'ENOENT') { if (readError.code === 'ENOENT') {
console.log('[ProviderPoolManager] provider_pools.json does not exist, creating new file.'); this._log('info', 'provider_pools.json does not exist, creating new file.');
} else { } else {
throw readError; throw readError;
} }
} }
if (this.providerStatus[providerTypeToUpdate]) { // 更新所有待保存的 providerType
currentPools[providerTypeToUpdate] = this.providerStatus[providerTypeToUpdate].map(p => { for (const providerType of typesToSave) {
// Convert Date objects to ISOString if they exist if (this.providerStatus[providerType]) {
if (p.config.lastUsed instanceof Date) { currentPools[providerType] = this.providerStatus[providerType].map(p => {
p.config.lastUsed = p.config.lastUsed.toISOString(); // Convert Date objects to ISOString if they exist
} const config = { ...p.config };
if (p.config.lastErrorTime instanceof Date) { if (config.lastUsed instanceof Date) {
p.config.lastErrorTime = p.config.lastErrorTime.toISOString(); config.lastUsed = config.lastUsed.toISOString();
} }
return p.config; if (config.lastErrorTime instanceof Date) {
}); config.lastErrorTime = config.lastErrorTime.toISOString();
} else { }
console.warn(`[ProviderPoolManager] Attempted to save unknown providerType: ${providerTypeToUpdate}`); return config;
});
} else {
this._log('warn', `Attempted to save unknown providerType: ${providerType}`);
}
} }
// 一次性写入文件
await fs.promises.writeFile(filePath, JSON.stringify(currentPools, null, 2), 'utf8'); await fs.promises.writeFile(filePath, JSON.stringify(currentPools, null, 2), 'utf8');
console.log(`[ProviderPoolManager] provider_pools.json for ${providerTypeToUpdate} updated successfully.`); this._log('info', `provider_pools.json updated successfully for types: ${typesToSave.join(', ')}`);
} catch (error) { } catch (error) {
console.error('[ProviderPoolManager] Failed to write provider_pools.json:', error); this._log('error', `Failed to write provider_pools.json: ${error.message}`);
} }
} }