何夕2077
|
ebf03d9e37
|
Revert "feat: model-router插件 + 安全增强 + embeddings支持"
|
2026-04-05 22:09:24 +08:00 |
|
何夕2077
|
9c1a84c156
|
Merge pull request #447 from liwen30678/feat/model-router-and-improvements
feat: model-router插件 + 安全增强 + embeddings支持
|
2026-04-05 22:05:04 +08:00 |
|
何夕2077
|
429fc1b16f
|
Merge branch 'main' into feat/model-router-and-improvements
|
2026-04-05 22:04:46 +08:00 |
|
hex2077
|
1d4710f92d
|
chore: 更新版本号至2.12.6
|
2026-04-05 21:46:32 +08:00 |
|
hex2077
|
1ee4ca37d1
|
feat(ui): 增强提供商刷新状态显示和版本选择功能
- 在提供商管理界面添加刷新状态徽章,显示“刷新中”状态
- 为更新功能添加版本选择下拉框,支持选择特定版本进行更新
- 在提供商状态中新增 needsRefresh 字段用于跟踪刷新状态
- 修复冷启动时刷新状态重置逻辑,避免持久化状态影响新会话
- 为刷新操作添加超时保护机制,防止适配器调用无限挂起
- 完善国际化翻译,添加相关状态和版本标签
|
2026-04-05 21:46:05 +08:00 |
|
product-manager-claude
|
4385421e11
|
feat: model-router插件 + 安全增强 + embeddings支持
- 新增 model-router 中间件插件(别名路由、热配置、管理面板)
- api-manager 支持 /v1/embeddings 端点透传
- auth.js 安全增强:原子写入、空数据防护、token清理防护
- GrokConverter 兼容性改进
- ecosystem.config.cjs PM2配置
|
2026-04-05 21:32:35 +08:00 |
|
hex2077
|
85d7b50cb1
|
fix: 修复Qwen API配额错误处理和Gemini初始化顺序问题
- 修复Qwen API的配额错误识别和速率限制,避免因配额耗尽导致服务中断
- 修正Gemini API服务初始化顺序,确保OAuth2客户端在HTTP代理配置后创建
- 优化提供商数据脱敏逻辑,防止保存时覆盖真实的敏感信息
- 增强前端错误处理,支持国际化错误消息的翻译和显示
- 移除Antigravity中冗余的思考签名修复代码,简化历史记录处理
- 修复服务管理器初始化逻辑,确保提供商池状态正确更新
- 统一日志下载文件名格式,改进文件下载错误处理
- 更新翻译文件,添加缺失的通用错误消息国际化支持
|
2026-04-05 17:50:11 +08:00 |
|
hex2077
|
8531343c2b
|
Reapply "feat: 更新版本至2.12.3并修复多个问题"
This reverts commit ceff3771ea.
|
2026-04-05 15:23:09 +08:00 |
|
hex2077
|
02fdc39571
|
Reapply "feat: 支持动态提供商配置组和前缀匹配机制"
This reverts commit b8a983a3cd.
|
2026-04-05 15:20:48 +08:00 |
|
hex2077
|
47d92a41cb
|
docs: 添加 Poixe AI 赞助商信息
在 README 及其翻译版本中新增 Poixe AI 作为赞助商,并添加其徽标图片。此举旨在感谢赞助商支持并为用户提供更多 API 服务选择。
|
2026-04-05 15:20:24 +08:00 |
|
hex2077
|
7c0f26fca4
|
docs: 在README中添加LingtrueAPI赞助商信息
- 在README.md、README-ZH.md、README-JA.md的赞助商部分添加LingtrueAPI条目
- 添加赞助商Logo图片 static/lingtrueapi.png
- 描述LingtrueAPI服务内容及为用户提供的专属优惠
|
2026-04-05 08:12:02 +08:00 |
|
hex2077
|
8f4de503c6
|
先回滚
|
2026-04-04 22:50:11 +08:00 |
|
hex2077
|
b8a983a3cd
|
Revert "feat: 支持动态提供商配置组和前缀匹配机制"
This reverts commit 0c9d52f537.
|
2026-04-04 22:49:52 +08:00 |
|
hex2077
|
ceff3771ea
|
Revert "feat: 更新版本至2.12.3并修复多个问题"
This reverts commit 1570fbb096.
|
2026-04-04 22:49:44 +08:00 |
|
hex2077
|
1570fbb096
|
feat: 更新版本至2.12.3并修复多个问题
更新项目版本至2.12.3,新增赞助商LingtrueAPI信息至README文档。修复Qwen提供商缺少系统提示词时自动添加默认提示词的问题。优化Gemini和Antigravity提供商的OAuth2Client代理配置逻辑,根据baseURL自动选择HTTP/HTTPS agent。修复Antigravity提供商中thinking budget逻辑及历史记录中思考签名缺失的问题。将Windows安装脚本翻译为英文。
|
2026-04-03 23:21:32 +08:00 |
|
hex2077
|
0c9d52f537
|
feat: 支持动态提供商配置组和前缀匹配机制
- 添加动态提供商配置组功能,支持通过后缀创建自定义配置组
- 实现前缀匹配机制,使自定义配置组能继承基础类型的配置和模型
- 更新代理、TLS Sidecar、健康检查等系统以支持前缀匹配
- 添加提供商组管理界面,支持创建和显示自定义配置组
- 改进提供商状态API,支持显示所有配置组及其状态
- 升级axios依赖至v1.14.0以获取安全更新
|
2026-04-03 22:42:26 +08:00 |
|
何夕2077
|
734c63bf7a
|
Merge pull request #430 from Wenaixi/main
feat: 定时健康检查系统 + 全面安全强化与稳定性优化
|
2026-04-03 17:30:23 +08:00 |
|
Wenaixi
|
2a6e297238
|
fix: 修复 currentConfig 未定义错误
- 将 currentConfig?.CRON_NEAR_MINUTES 更正为 this.globalConfig?.CRON_NEAR_MINUTES
- 修复代码审查发现的变量引用错误
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
2026-04-03 03:04:43 +08:00 |
|
Wenaixi
|
af3915311d
|
fix: 修复代码审查发现的10个安全与正确性问题
- fix: provider-pool-manager: 移除 if(true) 占位符,改为读取凭据文件真实过期时间
- fix: provider-pool-manager: Math.min 展开大数组改为 reduce,防止栈溢出
- fix: provider-pool-manager: forceRefreshToken 调用前检查方法是否实现,不存在则 fallback
- fix: provider-api: handleAddProvider 默认路径统一为 configs/provider_pools.json
- fix: config-api: handleGetConfig 改为白名单字段过滤,REQUIRED_API_KEY 脱敏返回
- fix: api-server: 启动日志中 API Key 遮码处理
- fix: utils: generateUUID 改用 crypto.randomUUID() 替代 Math.random()
- fix: config-manager: renderProviderTags innerHTML 加 escHtml 防 XSS 注入
- fix: config-manager: PROVIDER_POOLS_FILE_PATH 未定义时加 || '' 兜底
- fix: section-config.css: white 改为 var(--bg-primary, white) 支持暗黑模式
- chore: .gitignore 添加 AGENTS.md
- chore: docker-compose.yml 添加代理环境变量
diff --git a/.gitignore b/.gitignore
index f752bb4..c375cbc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,5 @@ api-potluck-keys.json
api-potluck-data.json
# Codex credentials
configs/codex/
+AGENTS.md
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 6977d13..8c08ef3 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -14,6 +14,12 @@ services:
- ./configs:/app/configs
environment:
- ARGS=
+ - HTTP_PROXY=http://host.docker.internal:10801
+ - http_proxy=http://host.docker.internal:10801
+ - HTTPS_PROXY=http://host.docker.internal:10801
+ - https_proxy=http://host.docker.internal:10801
+ - NO_PROXY=localhost,127.0.0.1,host.docker.internal
+ - no_proxy=localhost,127.0.0.1,host.docker.internal
healthcheck:
test: ["CMD", "node", "healthcheck.js"]
interval: 30s
diff --git a/src/providers/provider-pool-manager.js b/src/providers/provider-pool-manager.js
index f039e48..5875f1f 100644
--- a/src/providers/provider-pool-manager.js
+++ b/src/providers/provider-pool-manager.js
@@ -112,7 +112,12 @@ export class ProviderPoolManager {
if (configPath && fs.existsSync(configPath)) {
try {
- if (true) {
+ const fileContent = fs.readFileSync(configPath, 'utf-8');
+ const credData = JSON.parse(fileContent);
+ const expiryTime = credData.expiry_date || credData.expiry || credData.expires_at;
+ const nearExpiryMs = (currentConfig?.CRON_NEAR_MINUTES || 10) * 60 * 1000;
+ const isNearExpiry = expiryTime && (expiryTime - Date.now()) < nearExpiryMs;
+ if (isNearExpiry) {
this._log('warn', `Node ${providerStatus.uuid} (${providerType}) is near expiration. Enqueuing refresh...`);
this._enqueueRefresh(providerType, providerStatus);
}
@@ -389,7 +394,16 @@ export class ProviderPoolManager {
// 调用适配器的 refreshToken 方法(内部封装了具体的刷新逻辑)
if (typeof serviceAdapter.refreshToken === 'function') {
const startTime = Date.now();
- force ? await serviceAdapter.forceRefreshToken() : await serviceAdapter.refreshToken()
+ if (force) {
+ if (typeof serviceAdapter.forceRefreshToken === 'function') {
+ await serviceAdapter.forceRefreshToken();
+ } else {
+ this._log('warn', `forceRefreshToken not implemented for ${providerType}, falling back to refreshToken`);
+ await serviceAdapter.refreshToken();
+ }
+ } else {
+ await serviceAdapter.refreshToken();
+ }
const duration = Date.now() - startTime;
this._log('info', `Token refresh successful for node ${providerStatus.uuid} (Duration: ${duration}ms)`);
@@ -452,7 +466,7 @@ export class ProviderPoolManager {
const lastSelectionSeq = config._lastSelectionSeq || 0;
if (minSeqInPool === -1) {
const pool = this.providerStatus[providerStatus.type] || [];
- minSeqInPool = Math.min(...pool.map(p => p.config._lastSelectionSeq || 0));
+ minSeqInPool = pool.reduce((min, p) => Math.min(min, p.config._lastSelectionSeq || 0), Infinity);
}
const relativeSeq = Math.max(0, lastSelectionSeq - minSeqInPool);
const cappedRelativeSeq = Math.min(relativeSeq, 100);
@@ -1819,14 +1833,14 @@ export class ProviderPoolManager {
continue;
}
- const checkStartTime = Date.now();
+ const providerCheckStart = Date.now();
const checkModelName = provider.config.checkModelName || ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType] || 'unknown';
const displayName = customName || uuid.substring(0, 8);
-
+
try {
// Perform health check (health check is based on providerTypes configuration, not per-provider checkHealth flag)
const result = await this._checkProviderHealth(providerType, provider.config);
- const checkDuration = Date.now() - checkStartTime;
+ const checkDuration = Date.now() - providerCheckStart;
if (!result.success) {
// Provider is unhealthy
@@ -1840,7 +1854,7 @@ export class ProviderPoolManager {
this.markProviderHealthy(providerType, provider.config, false, result.modelName);
}
} catch (error) {
- const checkDuration = Date.now() - checkStartTime;
+ const checkDuration = Date.now() - providerCheckStart;
failCount++;
this._log('error', `[ScheduledHealthCheck] ${displayName} (${providerType}) EXCEPTION: ${error.message} (${checkDuration}ms)`);
this.markProviderUnhealthyImmediately(providerType, provider.config, error.message);
diff --git a/src/services/api-server.js b/src/services/api-server.js
index ad9bdc9..c1a850f 100644
--- a/src/services/api-server.js
+++ b/src/services/api-server.js
@@ -313,7 +313,7 @@ async function startServer() {
logger.info(` System Prompt Mode: ${CONFIG.SYSTEM_PROMPT_MODE}`);
logger.info(` Host: ${CONFIG.HOST}`);
logger.info(` Port: ${CONFIG.SERVER_PORT}`);
- logger.info(` Required API Key: ${CONFIG.REQUIRED_API_KEY}`);
+ logger.info(` Required API Key: ${CONFIG.REQUIRED_API_KEY ? CONFIG.REQUIRED_API_KEY.slice(0, 4) + '****' : '(none)'}`);
logger.info(` Prompt Logging: ${CONFIG.PROMPT_LOG_MODE}${CONFIG.PROMPT_LOG_FILENAME ? ` (to ${CONFIG.PROMPT_LOG_FILENAME})` : ''}`);
logger.info(`------------------------------------------`);
logger.info(`\nUnified API Server running on http://${CONFIG.HOST}:${CONFIG.SERVER_PORT}`);
@@ -355,14 +355,14 @@ async function startServer() {
setInterval(heartbeatAndRefreshToken, CONFIG.CRON_NEAR_MINUTES * 60 * 1000);
}
// 服务器完全启动后,执行初始健康检查
- const poolManager = getProviderPoolManager();
- if (poolManager) {
- logger.info('[Initialization] Performing initial health checks for provider pools...');
- poolManager.performHealthChecks();
- }
+ const poolManager = getProviderPoolManager();
+ if (poolManager) {
+ logger.info('[Initialization] Performing initial health checks for provider pools...');
+ poolManager.performHealthChecks();
+ }
// 定时健康检查
- const scheduledConfig = CONFIG.SCHEDULED_HEALTH_CHECK;
+ const scheduledConfig = CONFIG.SCHEDULED_HEALTH_CHECK;
if (scheduledConfig?.enabled) {
// 设计决策:只验证最小值 60000ms,不设最大值。
// 前端有 max=3600000 (1小时) 的 UI 限制,但后端允许更大值以支持特殊需求。
diff --git a/src/ui-modules/config-api.js b/src/ui-modules/config-api.js
index 92c4dac..f6f2b0e 100644
--- a/src/ui-modules/config-api.js
+++ b/src/ui-modules/config-api.js
@@ -57,11 +57,48 @@ export async function handleGetConfig(req, res, currentConfig) {
}
}
+ // 白名单过滤:只返回前端需要的字段,避免泄露凭据路径、内部状态等敏感信息
+ const safeConfig = {
+ HOST: currentConfig.HOST,
+ SERVER_PORT: currentConfig.SERVER_PORT,
+ MODEL_PROVIDER: currentConfig.MODEL_PROVIDER,
+ SYSTEM_PROMPT_FILE_PATH: currentConfig.SYSTEM_PROMPT_FILE_PATH,
+ SYSTEM_PROMPT_MODE: currentConfig.SYSTEM_PROMPT_MODE,
+ PROMPT_LOG_BASE_NAME: currentConfig.PROMPT_LOG_BASE_NAME,
+ PROMPT_LOG_MODE: currentConfig.PROMPT_LOG_MODE,
+ REQUEST_MAX_RETRIES: currentConfig.REQUEST_MAX_RETRIES,
+ REQUEST_BASE_DELAY: currentConfig.REQUEST_BASE_DELAY,
+ CREDENTIAL_SWITCH_MAX_RETRIES: currentConfig.CREDENTIAL_SWITCH_MAX_RETRIES,
+ CRON_NEAR_MINUTES: currentConfig.CRON_NEAR_MINUTES,
+ CRON_REFRESH_TOKEN: currentConfig.CRON_REFRESH_TOKEN,
+ LOGIN_EXPIRY: currentConfig.LOGIN_EXPIRY,
+ PROVIDER_POOLS_FILE_PATH: currentConfig.PROVIDER_POOLS_FILE_PATH,
+ MAX_ERROR_COUNT: currentConfig.MAX_ERROR_COUNT,
+ WARMUP_TARGET: currentConfig.WARMUP_TARGET,
+ REFRESH_CONCURRENCY_PER_PROVIDER: currentConfig.REFRESH_CONCURRENCY_PER_PROVIDER,
+ providerFallbackChain: currentConfig.providerFallbackChain,
+ modelFallbackMapping: currentConfig.modelFallbackMapping,
+ PROXY_URL: currentConfig.PROXY_URL,
+ PROXY_ENABLED_PROVIDERS: currentConfig.PROXY_ENABLED_PROVIDERS,
+ TLS_SIDECAR_ENABLED: currentConfig.TLS_SIDECAR_ENABLED,
+ TLS_SIDECAR_ENABLED_PROVIDERS: currentConfig.TLS_SIDECAR_ENABLED_PROVIDERS,
+ TLS_SIDECAR_PORT: currentConfig.TLS_SIDECAR_PORT,
+ TLS_SIDECAR_PROXY_URL: currentConfig.TLS_SIDECAR_PROXY_URL,
+ LOG_ENABLED: currentConfig.LOG_ENABLED,
+ LOG_OUTPUT_MODE: currentConfig.LOG_OUTPUT_MODE,
+ LOG_LEVEL: currentConfig.LOG_LEVEL,
+ LOG_DIR: currentConfig.LOG_DIR,
+ LOG_INCLUDE_REQUEST_ID: currentConfig.LOG_INCLUDE_REQUEST_ID,
+ LOG_INCLUDE_TIMESTAMP: currentConfig.LOG_INCLUDE_TIMESTAMP,
+ LOG_MAX_FILE_SIZE: currentConfig.LOG_MAX_FILE_SIZE,
+ LOG_MAX_FILES: currentConfig.LOG_MAX_FILES,
+ SCHEDULED_HEALTH_CHECK: currentConfig.SCHEDULED_HEALTH_CHECK,
+ // 脱敏:只返回是否设置了 API Key,不返回原文
+ REQUIRED_API_KEY: currentConfig.REQUIRED_API_KEY ? '******' : '',
+ systemPrompt,
+ };
res.writeHead(200, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({
- ...currentConfig,
- systemPrompt
- }));
+ res.end(JSON.stringify(safeConfig));
return true;
}
@@ -129,8 +166,10 @@ export async function handleUpdateConfig(req, res, currentConfig) {
providerTypes: Array.isArray(incoming?.providerTypes) ? incoming.providerTypes : []
};
- // 如果定时器已存在且 enabled,重新加载 timer(interval 变化时)
- if (globalThis.reloadHealthCheckTimer && currentConfig.SCHEDULED_HEALTH_CHECK.enabled) {
+ // 如果定时器已存在且 enabled,仅在 interval 实际变化时重新加载 timer
+ const previousInterval = currentConfig.SCHEDULED_HEALTH_CHECK._activeInterval;
+ if (globalThis.reloadHealthCheckTimer && currentConfig.SCHEDULED_HEALTH_CHECK.enabled && newInterval !== previousInterval) {
+ currentConfig.SCHEDULED_HEALTH_CHECK._activeInterval = newInterval;
globalThis.reloadHealthCheckTimer(newInterval);
}
}
diff --git a/src/ui-modules/provider-api.js b/src/ui-modules/provider-api.js
index 2789de3..a2a02fc 100644
--- a/src/ui-modules/provider-api.js
+++ b/src/ui-modules/provider-api.js
@@ -115,7 +115,7 @@ export async function handleAddProvider(req, res, currentConfig, providerPoolMan
providerConfig.errorCount = providerConfig.errorCount || 0;
providerConfig.lastErrorTime = providerConfig.lastErrorTime || null;
- const filePath = currentConfig.PROVIDER_POOLS_FILE_PATH || 'provider_pools.json';
+ const filePath = currentConfig.PROVIDER_POOLS_FILE_PATH || 'configs/provider_pools.json';
let providerPools = {};
// Load existing pools
diff --git a/src/utils/provider-utils.js b/src/utils/provider-utils.js
index 905b8e8..d7e9134 100644
--- a/src/utils/provider-utils.js
+++ b/src/utils/provider-utils.js
@@ -97,11 +97,7 @@ export const PROVIDER_MAPPINGS = [
* @returns {string} UUID 字符串
*/
export function generateUUID() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- const r = Math.random() * 16 | 0;
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
- return v.toString(16);
- });
+ return crypto.randomUUID();
}
/**
diff --git a/static/app/config-manager.js b/static/app/config-manager.js
index 130bfdc..36ac1cb 100644
--- a/static/app/config-manager.js
+++ b/static/app/config-manager.js
@@ -53,10 +53,11 @@ function renderProviderTags(container, configs, isRequired) {
// 过滤掉不可见的提供商
const visibleConfigs = configs.filter(c => c.visible !== false);
+ const escHtml = s => String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
container.innerHTML = visibleConfigs.map(c => `
- <button type="button" class="provider-tag" data-value="${c.id}">
- <i class="fas ${c.icon || 'fa-server'}"></i>
- <span>${c.name}</span>
+ <button type="button" class="provider-tag" data-value="${escHtml(c.id)}">
+ <i class="fas ${escHtml(c.icon || 'fa-server')}"></i>
+ <span>${escHtml(c.name)}</span>
</button>
`).join('');
@@ -157,7 +158,7 @@ async function loadConfiguration() {
if (cronNearMinutesEl) cronNearMinutesEl.value = data.CRON_NEAR_MINUTES || 1;
if (cronRefreshTokenEl) cronRefreshTokenEl.checked = data.CRON_REFRESH_TOKEN || false;
if (loginExpiryEl) loginExpiryEl.value = data.LOGIN_EXPIRY || 3600;
- if (providerPoolsFilePathEl) providerPoolsFilePathEl.value = data.PROVIDER_POOLS_FILE_PATH;
+ if (providerPoolsFilePathEl) providerPoolsFilePathEl.value = data.PROVIDER_POOLS_FILE_PATH || '';
if (maxErrorCountEl) maxErrorCountEl.value = data.MAX_ERROR_COUNT || 10;
if (warmupTargetEl) warmupTargetEl.value = data.WARMUP_TARGET || 0;
if (refreshConcurrencyPerProviderEl) refreshConcurrencyPerProviderEl.value = data.REFRESH_CONCURRENCY_PER_PROVIDER || 1;
@@ -248,7 +249,7 @@ async function loadConfiguration() {
const scheduledHealthCheckIntervalEl = document.getElementById('scheduledHealthCheckInterval');
if (data.SCHEDULED_HEALTH_CHECK) {
- if (scheduledHealthCheckEnabledEl) scheduledHealthCheckEnabledEl.checked = data.SCHEDULED_HEALTH_CHECK.enabled !== false;
+ if (scheduledHealthCheckEnabledEl) scheduledHealthCheckEnabledEl.checked = data.SCHEDULED_HEALTH_CHECK.enabled === true;
if (scheduledHealthCheckStartupRunEl) scheduledHealthCheckStartupRunEl.checked = data.SCHEDULED_HEALTH_CHECK.startupRun !== false;
if (scheduledHealthCheckIntervalEl) scheduledHealthCheckIntervalEl.value = data.SCHEDULED_HEALTH_CHECK.interval || 600000;
} else {
diff --git a/static/components/section-config.css b/static/components/section-config.css
index e16338b..aebf6c4 100644
--- a/static/components/section-config.css
+++ b/static/components/section-config.css
@@ -173,7 +173,7 @@ textarea.form-control {
width: 18px;
left: 3px;
bottom: 2px;
- background-color: white;
+ background-color: var(--bg-primary, white);
transition: var(--transition);
border-radius: 50%;
box-shadow: 0 1px 3px var(--neutral-shadow-30);
|
2026-04-03 02:56:34 +08:00 |
|
Wenaixi
|
bc018cf16f
|
fix: 修复代码审查中发现的关键问题
1. 修复方法名错误
- 将 performScheduledHealthChecks() 改为 performHealthChecks()
- 修复启动时健康检查调用错误
2. 改进路径遍历防护
- 仅在 Windows 平台执行小写转换
- 避免 Linux 平台路径大小写误判
3. 增强 XSS 防护
- 扩展危险协议检测(data/javascript/vbscript)
- 使用更安全的 HTML 标签移除方式
- 添加 HTML 实体编码攻击防护
4. 为文件锁添加超时机制
- 防止操作永久挂起导致锁链阻塞
- 默认超时时间 30 秒
|
2026-04-03 02:56:34 +08:00 |
|
Wenaixi
|
1018750388
|
fix: 深度review后续修复——安全强化、i18n补全、代码清理
安全修复:
- PBKDF2迭代次数从100k提升至310k(OWASP 2023 SHA-512标准)
- 密码最小长度从8位提升至12位
- sanitizeProviderData正则加强:data:协议拒绝而非部分移除,
on\w+事件处理器更严格,javascript:加单词边界防止误匹配
- withFileLock错误处理改为重新抛出,不再静默吞错误
- 后端interval上限校验(MAX_INTERVAL_MS)确保配置一致性
功能修复:
- 重命名performHealthChecks/performScheduledHealthChecks方法,
明确区分初始化检查和定时检查的职责
- generateUUID回退方案兼容Node.js <14.17.0
- 凭据无expiry字段时强制刷新(安全措施)
代码清理:
- 移除未使用的RETRY.DEFAULT_RETRIES常量
- 添加定时健康检查完整英文i18n翻译
|
2026-04-03 02:56:34 +08:00 |
|
Wenaixi
|
740f930f34
|
fix: 深度代码审查修复——空队列泄漏、XSS防护、docker代理清理
- fix(provider-pool): 修复 ownsGlobalSlot=false 时空队列未清理的内存泄漏
- fix(provider-api): 新增 sanitizeProviderData/ProviderPools,对 customName 等用户输入字段做 HTML 转义,防止 XSS
- fix(docker): 删除 docker-compose.yml 中的代理硬编码配置
- fix(api-server): 重构定时健康检查 timer 管理,支持热更新 enabled 状态(stopHealthCheckTimer + 状态变化追踪)
- fix(constants): 提取 HEALTH_CHECK/PASSWORD/NETWORK/RETRY 常量到 constants.js
- style(api-server): 移除日志中密码长度记录,防止敏感元信息泄露
|
2026-04-03 02:56:34 +08:00 |
|
hex2077
|
617109f887
|
feat(grok): 添加WebSocket图片生成支持与多图并发处理
- 新增 WebSocket 图片生成服务类,支持流式生成图片
- 在常规 API 失败时自动回退到 WebSocket 方式生成图片
- 支持单次生成超过2张图片时自动拆分为并发请求
- 改进图片生成参数处理,支持返回 base64 格式图片
- 更新版本号至 2.12.2.2
|
2026-04-03 02:56:34 +08:00 |
|
hex2077
|
791303325b
|
fix(grok): 修复流式响应中图片渲染和思考块处理问题
- 修复流式响应中图片 URL 被截断的问题,通过缓冲区累积完整 URL
- 改进卡片附件处理,支持从 cardAttachmentsJson 解析并渲染图片
- 优化思考块逻辑,避免在正式内容开始后显示无意义的内部注释
- 修复思考块未正确关闭的问题,确保格式完整性
- 更新文档中的模型列表,将 Qwen Code 替换为 Codex
|
2026-04-03 02:56:33 +08:00 |
|
hex2077
|
31708f97c4
|
docs: 添加 AICodeMirror 赞助商信息与趋势徽章
- 在 README 中新增 AICodeMirror 赞助商区块,包含介绍、图片与注册链接
- 为所有语言版本的 README 添加 Trendshift 仓库趋势徽章
- 新增赞助商 logo 图片文件 static/aicodemirror.jpg
|
2026-04-03 02:56:33 +08:00 |
|
Wenaixi
|
9d4864dfed
|
fix: 修复代码审查发现的5个安全与正确性问题
- pbkdf2Sync 改为异步避免阻塞事件循环 (auth.js, config-api.js)
- 路径遍历检查改用 path.resolve 验证绝对路径在 cwd 内 (config-api.js)
- _activeInterval 移出配置对象避免序列化到 JSON (config-api.js, api-server.js)
- 删除 performScheduledHealthChecks 中冗余的 isDisabled 二次检查 (provider-pool-manager.js)
|
2026-04-03 02:56:33 +08:00 |
|
Wenaixi
|
fca9413f26
|
fix: 修复架构性问题——并发计数、文件锁、输入校验、密码哈希
- fix: provider-pool-manager: activeProviderRefreshes 计数器修复,情况1(已有队列)不持有全局槽位,通过 ownsGlobalSlot 闭包变量精确控制递减时机,防止出现负值
- fix: provider-api: 为 handleAddProvider/handleUpdateProvider/handleDeleteProvider/handleDisableEnableProvider 添加模块级 Promise 链文件锁(withFileLock),防止并发读写同一 JSON 文件导致数据丢失
- fix: config-api: handleUpdateConfig 添加输入类型校验——SERVER_PORT 校验整数范围、REQUEST_MAX_RETRIES 校验数值范围、SYSTEM_PROMPT_FILE_PATH 禁止路径遍历(含..)、REQUIRED_API_KEY 限定字符串类型
- fix: config-api/auth: 密码改为 PBKDF2(SHA-512, 100000轮) 哈希存储,格式 pbkdf2🧂hash,验证使用 timingSafeEqual 防时序攻击,兼容旧明文格式平滑迁移,并增加最小长度 8 位校验
- 所有修改均使用 Node.js 内置 crypto 模块,无新依赖
|
2026-04-03 02:56:33 +08:00 |
|
Wenaixi
|
a97b05dd2d
|
fix: 修复代码审查发现的10个安全与正确性问题
- fix: provider-pool-manager: 移除 if(true) 占位符,改为读取凭据文件真实过期时间
- fix: provider-pool-manager: Math.min 展开大数组改为 reduce,防止栈溢出
- fix: provider-pool-manager: forceRefreshToken 调用前检查方法是否实现,不存在则 fallback
- fix: provider-api: handleAddProvider 默认路径统一为 configs/provider_pools.json
- fix: config-api: handleGetConfig 改为白名单字段过滤,REQUIRED_API_KEY 脱敏返回
- fix: api-server: 启动日志中 API Key 遮码处理
- fix: utils: generateUUID 改用 crypto.randomUUID() 替代 Math.random()
- fix: config-manager: renderProviderTags innerHTML 加 escHtml 防 XSS 注入
- fix: config-manager: PROVIDER_POOLS_FILE_PATH 未定义时加 || '' 兜底
- fix: section-config.css: white 改为 var(--bg-primary, white) 支持暗黑模式
- chore: .gitignore 添加 AGENTS.md
- chore: docker-compose.yml 添加代理环境变量
|
2026-04-03 02:56:33 +08:00 |
|
hex2077
|
de3f46149f
|
feat(grok): 添加WebSocket图片生成支持与多图并发处理
- 新增 WebSocket 图片生成服务类,支持流式生成图片
- 在常规 API 失败时自动回退到 WebSocket 方式生成图片
- 支持单次生成超过2张图片时自动拆分为并发请求
- 改进图片生成参数处理,支持返回 base64 格式图片
- 更新版本号至 2.12.2.2
|
2026-04-01 23:07:02 +08:00 |
|
hex2077
|
dc153730f7
|
fix(grok): 修复流式响应中图片渲染和思考块处理问题
- 修复流式响应中图片 URL 被截断的问题,通过缓冲区累积完整 URL
- 改进卡片附件处理,支持从 cardAttachmentsJson 解析并渲染图片
- 优化思考块逻辑,避免在正式内容开始后显示无意义的内部注释
- 修复思考块未正确关闭的问题,确保格式完整性
- 更新文档中的模型列表,将 Qwen Code 替换为 Codex
|
2026-04-01 22:25:43 +08:00 |
|
hex2077
|
2b0f3adb8a
|
docs: 添加 AICodeMirror 赞助商信息与趋势徽章
- 在 README 中新增 AICodeMirror 赞助商区块,包含介绍、图片与注册链接
- 为所有语言版本的 README 添加 Trendshift 仓库趋势徽章
- 新增赞助商 logo 图片文件 static/aicodemirror.jpg
|
2026-04-01 20:40:25 +08:00 |
|
Wenaixi
|
801917b478
|
fix: 修复审查发现的3个问题
1. api-server.js: 更新过时的 performHealthChecks(true) 调用
- 方法签名改了,不再接受 isInit 参数
- 改为 performHealthChecks()
2. i18n.js: 更新描述以匹配实际行为
- '留空则检查所有供应商' -> '留空则不进行任何检查'
3. provider-pool-manager.js & provider-api.js: 移除死代码
- _checkProviderHealth 从不返回 null
- 移除对 result === null 的检查
|
2026-03-31 22:46:30 +08:00 |
|
Wenaixi
|
714aee54bd
|
docs: 添加设计决策注释说明有意为之的行为变更
- performHealthChecks: 如果没有选择任何 provider types 则不进行检查
- _checkProviderHealth: 不检查 per-provider checkHealth 标志,统一由 providerTypes 控制
- interval 验证: 后端不设最大值,允许通过 API 设置超长间隔
|
2026-03-31 22:37:27 +08:00 |
|
Wenaixi
|
bacf2baf40
|
chore: 移除 performHealthChecks 中未使用的 isInit 参数
|
2026-03-31 12:23:12 +08:00 |
|
Wenaixi
|
9f45e77448
|
fix: 修复健康检查的两个关键bug
1. 修复双重计数usage的问题:
- 移除 _checkProviderHealth 中的 markProviderHealthy 调用
- 使用量计数现在只由调用方处理
2. 修复热重载timer时的竞态条件:
- reload时重置 isHealthCheckRunning 标志
- 避免reload期间运行时导致新timer被跳过
|
2026-03-31 11:54:27 +08:00 |
|
Wenaixi
|
642c498544
|
fix: 如果没有选择任何provider types则不进行健康检查,而不是默认全选
|
2026-03-31 01:43:19 +08:00 |
|
Wenaixi
|
b2bb744916
|
chore: 更新健康检查注释,反映简化后的逻辑(只依赖providerTypes,不使用per-provider checkHealth flag)
|
2026-03-31 01:42:00 +08:00 |
|
Wenaixi
|
157bb489b5
|
Merge remote-tracking branch 'origin/main'
|
2026-03-31 01:24:06 +08:00 |
|
Wenaixi
|
e764075cd9
|
fix: 修复审查发现的两个问题
1. 移除 api-server.js 中重复的启动健康检查代码
2. 恢复 ClaudeConverter 和 OpenAIConverter 中对 googleSearch、url_context、googleMaps 扩展工具的支持
|
2026-03-31 01:23:22 +08:00 |
|
Wenaixi
|
9172401a50
|
fix: 简化健康检查逻辑,支持热更新interval,修复provider-tag点击问题
- 移除checkHealth per-instance flag,简化逻辑为只依赖providerTypes勾选
- performHealthChecks启动检查也遵循providerTypes过滤
- 优化健康检查日志:显示耗时、通过/失败计数
- 支持修改interval后热更新,无需重启(globalThis.reloadHealthCheckTimer)
- 移除openai-iflow选项(未注册的provider)
- 修复config-manager.js中scheduledHealthCheckProviders点击不生效问题
- providerTypes配置修改后下次检查自动生效
|
2026-03-31 01:14:44 +08:00 |
|
Wenaixi
|
df9a36291c
|
fix: 添加配置验证防止格式错误的数据
1. config-api.js: 添加 SCHEDULED_HEALTH_CHECK 结构验证
2. config-manager.js: 添加 interval 值范围验证 (60000-3600000ms)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 23:41:49 +08:00 |
|
Wenaixi
|
1bc193f5eb
|
feat: 添加定时健康检查供应商类型选择功能
类似启用代理的提供商,现在可以选择对哪些供应商类型进行定时健康检查
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 23:28:19 +08:00 |
|
Wenaixi
|
21c92ff214
|
fix: 修复定时健康检查两个bug
1. startupRun配置现在会在启动时立即运行健康检查
2. 修复快捷按钮重复绑定事件监听器的问题
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 23:20:07 +08:00 |
|
Wenaixi
|
129b4f688f
|
fix: 修复定时健康检查enabled检查bug
将scheduledConfig?.disabled改为!scheduledConfig?.enabled
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 23:13:47 +08:00 |
|
Wenaixi
|
94561f2750
|
feat: 优化定时健康检查配置UI,支持手动输入间隔
改为数字输入框+快捷按钮,支持自定义任意间隔值
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 23:12:59 +08:00 |
|
Wenaixi
|
ceb078c455
|
fix: 添加定时健康检查 i18n 中文翻译
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 23:08:48 +08:00 |
|
Wenaixi
|
79d2441c23
|
feat: 添加定时健康检查前端配置 UI
在配置管理页面添加定时健康检查开关、间隔配置
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 23:06:05 +08:00 |
|
Wenaixi
|
1e7e391b0d
|
feat: 添加 SCHEDULED_HEALTH_CHECK 配置管理支持
允许通过 UI API 查看和修改定时健康检查配置
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 22:58:04 +08:00 |
|
hex2077
|
8e8a8fc551
|
feat(converter): 支持转换 url_context 和 google_maps 工具
扩展 OpenAI 和 Claude 转换器以支持将 url_context 和 google_maps 工具转换为 Gemini API 格式。现在工具数组可以包含多个独立的工具对象,而不是合并到单个对象中。
|
2026-03-30 12:00:11 +08:00 |
|
Wenaixi
|
b6206a5d5c
|
feat: 添加定时健康检查功能
实现对供应商节点进行周期性健康测试,失败1次立即标记unhealthy
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
2026-03-30 01:14:49 +08:00 |
|