Conversation
新增对流式响应(如 SSE 和 NDJSON)的识别逻辑,在检测到流式响应时返回明确提示信息。 同时优化 JSON 响应解析流程,增加对非法 JSON 格式的捕获与日志记录。 此外,调整 OpenAI Responses API 测试输入格式,确保符合其接口规范。
某些中转服务不支持 max_output_tokens 参数,导致测试失败。根据注释说明, 该参数在当前上下文中并非必需,因此将其移除以确保与所有服务兼容。
新增对 SSE 和 NDJSON 格式流式响应的解析能力,包括: - 添加 `streamInfo` 字段以记录接收到的数据块数量及格式 - 实现 `parseSSEText` 和 `parseStreamResponse` 函数用于解析流式数据 - 引入 `mergeStreamChunks` 方法将多个响应块合并为完整响应 - 更新 API 测试逻辑以正确处理并展示流式响应结果 - 增强错误处理机制以应对流式响应解析失败的情况
- 调整 API 测试配置中响应内容预览最大长度从 100 到 500 字符 - 在测试结果中新增 streamInfo 字段用于记录流式响应数据 - UI 上增加流式响应信息的展示区域,包括接收到的数据块数量和流格式 - 支持在结果详情和错误信息区域显示流式响应相关统计
应用 Prettier 格式化规则到 providers.ts,修复 Code Quality Check 失败问题。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
根据 PR #185 的代码审查建议,修复以下问题: **Critical 修复**: - 🔴 修复 Stream reader 资源泄漏 - 在错误路径中添加 `await reader.cancel()` 防止内存泄漏 - 确保在异常情况下正确释放资源 **High 修复**: - 🟠 添加日志记录 - 为 chunk 解析失败添加 warn 级别日志 - 记录跳过的 chunk 数量和错误详情 - 添加流式响应解析完成的 info 日志 - 🟠 改进 SSE 检测逻辑 - 使用正则表达式替代字符串匹配 - 提高检测的健壮性,避免误判 - 🟠 增强类型安全 - 在类型转换前添加运行时类型守卫 - 为空数组情况添加默认值处理 - 确保不会创建无效的响应对象 **改进点**: - 统一错误处理模式 - 提高代码可维护性 - 增强调试能力 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
fix:修复 Codex API 测试参数兼容性问题与增加支持流式测试
## 主要改进 ### 1. 供应商模型测试优化 - **显示详细错误信息**: 将后端日志中的 errorDetail 字段返回给前端,用户无需查看容器日志 - **改进测试结果弹窗**: 在"复制结果"按钮右侧添加"关闭"按钮,提升用户体验 - **完善错误处理**: 优化 API 测试失败时的错误信息展示 ### 2. 日志时间格式化 - **新增工具函数**: 创建 log-time-formatter.ts,支持将 Unix 时间戳转换为本地时间 - **格式化输出**: 统一日志时间格式为 YYYY/MM/DD HH:mm - **时区支持**: 支持环境变量 TZ 和浏览器自动检测时区 ### 3. ErrorRuleDetector 初始化优化 - **延迟加载**: 移除构造函数中的自动加载,避免数据库未准备好时重复加载 - **显式初始化**: 在 instrumentation.ts 中,数据库连接成功后显式调用 reload() - **减少启动日志**: 避免在数据库准备前输出重复的加载日志 ## 技术细节 ### 修改文件 - src/actions/providers.ts: 返回详细错误信息 - src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx: 添加关闭按钮 - messages/*/settings.json: 添加"关闭"翻译 - src/lib/utils/log-time-formatter.ts: 新增日志时间格式化工具 - src/lib/error-rule-detector.ts: 移除构造函数中的自动加载 - src/instrumentation.ts: 在数据库准备好后显式初始化 ErrorRuleDetector ### 测试 - ✅ TypeScript 类型检查通过 - ✅ 所有修改符合 SOLID、KISS、DRY、YAGNI 原则 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
合并供应商测试和日志系统优化 主要改进: - 供应商模型测试显示详细错误信息 - 测试结果弹窗添加关闭按钮 - 新增日志时间格式化工具 - 优化 ErrorRuleDetector 初始化时机 详见 commit 07267a5
## 修改内容 ### 1. 重构 instrumentation.ts 中的重复代码 - **提取公共函数**: 将 ErrorRuleDetector 初始化逻辑提取为独立的 `initializeErrorRuleDetector()` 函数 - **消除重复**: 生产环境和开发环境共用同一个初始化函数 - **提高可维护性**: 遵循 DRY 原则,未来修改只需更新一处 ### 2. 优化 log-time-formatter.ts - **移除硬编码区域设置**: 将 `zh-CN` 改为 `undefined`,使用运行时默认区域设置,提高通用性 - **统一日志记录**: 使用 `logger.error` 替代 `console.error`,保持日志记录一致性 - **改进错误回退**: 使用 `toISOString()` 替代 `toLocaleString()`,提供明确的 ISO 8601 格式 ## 技术改进 - ✅ 遵循 DRY (Don't Repeat Yourself) 原则 - ✅ 提高代码可维护性和健壮性 - ✅ 增强国际化支持 - ✅ 统一错误处理策略 - ✅ TypeScript 类型检查通过 ## 相关 PR - 响应 PR #186 的代码审查建议 - 感谢 @gemini-code-assist 的详细审查 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add postgres and drizzle-orm to serverExternalPackages to prevent bundling Node.js-only modules (net, tls, crypto, stream, perf_hooks) - Set CI=true in Dockerfile to skip instrumentation database connections during build - Fixes Module not found errors for Node.js built-in modules during Next.js build
## 修改内容 ### 1. 修复 providers.ts 中的 null 检查 (Medium Priority) ✅ **问题**: 缺少对 errorText 的 null/undefined 检查,可能产生误导性错误消息 **解决方案**: - 添加防御性检查: `errorText ? clipText(errorText, 200) : "No error details available"` - 移除冗余的 `|| "API 请求失败"` 回退,直接使用 finalErrorDetail **效果**: 避免空字符串产生误导性错误消息,提供更清晰的错误反馈 ### 2. 修复 ErrorRuleDetector 竞态条件 (High Priority) ✅ **问题**: 移除构造函数自动加载后,在启动阶段调用 detect() 会返回 false negatives **解决方案**: - 添加 `isInitialized` 状态跟踪 - 实现 `ensureInitialized()` 懒加载保护 - 新增 `detectAsync()` 方法,确保规则已加载 - 同步 `detect()` 方法添加未初始化警告 **效果**: - 避免启动阶段的竞态条件 - 保持向后兼容性(同步 detect 方法) - 推荐使用 detectAsync 以确保规则已加载 ### 3. 确认 instrumentation.ts 重构 ✅ **状态**: 已在上一次提交中完成,符合审查要求 - 提取了 `initializeErrorRuleDetector()` 公共函数 - 消除了生产和开发环境的代码重复 - 遵循 DRY 原则 ### 4. 添加 JSDoc 示例 (Low Priority) ✅ **改进**: 为 log-time-formatter.ts 添加使用示例 **新增内容**: - `formatLogTime()` 添加系统时区和指定时区的示例 - `formatLogTimes()` 添加批量格式化的示例 **效果**: 提升开发者体验,特别是 timezone 参数的使用 ## 技术验证 - ✅ TypeScript 类型检查通过 - ✅ 所有修改符合项目编码规范 - ✅ 解决了所有 High 和 Medium 优先级问题 - ✅ 完成了 Low 优先级改进建议 ## 相关 PR - 响应 PR #186 的 ding113 审查意见 - 感谢 @ding113 的详细审查和建议 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The Docker build was failing because Next.js webpack was trying to bundle Node.js built-in modules (net, tls, crypto, stream, perf_hooks) imported by the postgres package through instrumentation.ts. While serverExternalPackages was configured, it wasn't sufficient for the build phase. This fix adds explicit webpack externals configuration to properly exclude Node.js built-in modules from the server bundle. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
## 修改内容
### 1. 修复 null 安全问题 (🟠 High Priority) ✅
**问题**: Map.get() 返回 `string | undefined`,直接插值可能产生 "undefined" 字符串
**解决方案**:
- 添加验证检查,确保所有必需的时间部分都已成功获取
- 如果任一部分缺失,记录警告并回退到 ISO 8601 格式
- 提供详细的日志信息(timestamp, timezone)便于调试
**代码**:
```typescript
if (!year || !month || !day || !hour || !minute) {
logger.warn(
"[log-time-formatter] Failed to get all date parts, falling back to ISO string",
{ timestamp, timezone }
);
return new Date(timestamp).toISOString();
}
```
**效果**: 防止输出 "undefined/undefined/undefined undefined:undefined" 的无效日期字符串
### 2. 添加文件级文档说明 (🟡 Medium Priority) ✅
**问题**: 文件未被使用,违反 YAGNI 原则
**解决方案**:
- 添加详细的模块文档,说明计划用途
- 列出具体的集成场景:
1. Dashboard 日志显示组件
2. 实时监控页面
3. 日志导出功能
- 添加集成计划清单
- 预留 issue 链接位置
**效果**:
- 明确工具函数的设计意图和使用场景
- 为后续集成提供清晰的路线图
- 符合代码文档最佳实践
## 技术验证
- ✅ TypeScript 类型检查通过
- ✅ 增强了代码健壮性
- ✅ 提供了清晰的文档和集成计划
## 相关 PR
- 响应 PR #186 的最新审查意见
- 感谢 @ding113 和 @gemini-code-assist 的详细审查
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## 修改内容
### 1. 删除未使用的 log-time-formatter.ts (🟢 Low) ✅
**问题**: 整个文件未被使用,违反 YAGNI 原则
**解决方案**: 删除文件,遵循 "You Aren't Gonna Need It" 原则
**理由**:
- 文件中包含 TODO 清单,但没有实际集成
- 没有任何导入或使用此文件的代码
- 根据 CLAUDE.md: "不要为假设的未来需求设计"
- 当实际需要时可以在未来 PR 中重新添加
**效果**: 减少技术债务和维护负担
### 2. 优化错误处理避免代码重复 (🟡 Medium) ✅
**问题**: `initializeErrorRuleDetector()` 函数内部捕获错误但不传播,可能掩盖严重问题
**解决方案**:
- 移除函数内部的 try-catch,让关键错误传播
- 在调用处添加 try-catch,实现优雅降级
- 添加清晰的注释说明错误处理策略
**代码**:
```typescript
// 函数内部 - 让关键错误传播
async function initializeErrorRuleDetector(): Promise<void> {
await initializeDefaultErrorRules();
logger.info("Default error rules initialized successfully");
await errorRuleDetector.reload();
logger.info("Error rule detector cache loaded successfully");
}
// 调用处 - 优雅降级
try {
await initializeErrorRuleDetector();
} catch (error) {
logger.error("[Instrumentation] Non-critical: Error rule detector initialization failed", error);
// 继续启动 - 错误检测不是核心功能的关键依赖
}
```
**效果**:
- ✅ 关键错误可以被正确传播和处理
- ✅ 调用者可以决定是否需要优雅降级
- ✅ 避免掩盖严重的数据库连接问题
- ✅ 保持应用启动的灵活性
## 技术验证
- ✅ TypeScript 类型检查通过
- ✅ 遵循 YAGNI 原则
- ✅ 改进错误处理策略
- ✅ 减少代码重复
## 相关 PR
- 响应 PR #186 的最新审查意见
- 感谢 @ding113 的详细审查和建议
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
fix: 优化供应商测试和日志系统
- 移除 session.requestUrl 的 readonly 属性 - 在 ModelRedirector.apply() 中添加 Gemini URL 路径重写逻辑 - Gemini API 模型名称通过 URL 路径传递,需要修改 pathname 而非 body
问题原因: - 测试请求未显式设置 stream: false,导致上游可能返回流式响应 - Cloudflare 在处理 SSE 流式响应时可能触发 520 错误 - 请求头不够完整,可能被 Cloudflare Bot 检测拦截 修复内容: - Anthropic Messages API 测试添加 stream: false 参数 - 添加完整的 HTTP 请求头(Accept, Accept-Language, Accept-Encoding, Connection) - 添加代理失败降级逻辑(520/502/504 错误时自动尝试直连) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
问题原因: - 代理降级时,如果直连失败继续使用原始响应 - 但原始响应已通过 clone() 读取过,后续 response.text() 会报错 修复内容: - 直连失败时直接返回组合错误信息,不再尝试读取原始响应 - 提供更详细的错误信息(代理错误 + 直连错误) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
10 秒超时对部分供应商可能不够 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1. 添加免责声明提示 - 测试会消耗真实额度 - 结果仅供参考,不代表实际调用效果 2. 修复详情对话框按钮样式 - 关闭按钮改为 outline 样式,与复制按钮保持一致 3. 格式化生产环境日志时间戳 - 从 Unix 毫秒时间戳改为 ISO 8601 格式 (2025-11-24T10:00:00.000Z) - 便于阅读和时区识别 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
部分第三方 Claude 中转站(如 co.yes.vg)使用 Authorization: Bearer 认证 而非官方的 x-api-key 认证,导致 401 Unauthorized 错误 现在同时发送两种认证头: - x-api-key: 官方 Anthropic API - Authorization: Bearer: 第三方中转站 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
OpenAI Responses API 的 input 数组中每个元素需要包含 type: "message" 缺少此字段会导致 400 Bad Request 错误 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
timestamp 应该是 pino 配置的顶级选项,而不是 formatters 的子选项 之前的配置导致时间戳仍然显示为 Unix 毫秒时间戳 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
ApiTestButton 组件内已添加了更详细的免责声明提示 删除 provider-form 中的旧版简短提示,避免重复显示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Remove the `notice` translation key that was replaced by the new disclaimer component in api-test-button.tsx. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Add disclaimer.title/realRequest/resultReference/confirmConfig keys - Localize api-test-button.tsx disclaimer component - Add dark mode support for disclaimer styling - Add missing apiTest section to zh-TW settings Supported languages: zh-CN, en, zh-TW 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
改进点: 1. 添加 trace 日志记录原始错误响应文本 2. 尝试多种错误路径提取: - errorJson.error?.message (OpenAI 标准) - errorJson.error?.error (嵌套 error) - errorJson.message (简单 message) - errorJson.error_message - errorJson.detail - errorJson.error (字符串类型) - 整个 error 对象序列化 (fallback) 3. 添加解析结果的 trace 日志,包含提取的详情和 JSON keys 4. 添加 JSON 解析失败的 trace 日志 这样可以在 trace 日志级别下看到完整的错误响应, 帮助调试不同供应商的错误响应格式差异
- 支持供应商 groupTag 多标签(如 'cli,chat')与用户单标签(如 'cli')的匹配 - 修复用户分组过滤逻辑:使用标签交集判断而非完全匹配 - 同时修复 Session 复用中的相同问题 - 解决用户设置单标签无法匹配到多标签供应商的问题
ding113
left a comment
There was a problem hiding this comment.
📋 Code Review Summary
This PR contains multiple bug fixes and optimizations covering provider API testing, streaming response handling, Docker builds, model redirection, and error rule matching. While most changes are solid, there are 2 critical security issues and 1 high-priority bug that must be addressed before merge.
🔍 Issues Found
- Critical (🔴): 2 issues
- High (🟠): 1 issue
- Medium (🟡): 2 issues
- Low (🟢): 3 issues
🎯 Priority Actions
- [CRITICAL] Add SSRF protection validation in API test button - currently allows testing arbitrary internal URLs
- [CRITICAL] Add webpack externals configuration to prevent Node.js native module bundling errors during Docker builds
- [HIGH] Fix timeout environment variable fallback logic - wrong order causes user-configured values to be ignored
- [MEDIUM] Add error handling for stream controller operations to prevent unhandled promise rejections
- [MEDIUM] Validate regex patterns before compilation in error rule detector
💡 General Observations
- The streaming timeout change from 10s to 300s is very aggressive - monitor production carefully for hung connections
- The CI=true workaround is fragile - proper webpack externals configuration is needed for long-term stability
- Consider adding rate limiting to API test endpoints to prevent abuse
- Good use of atomic operations and proper error categorization in response handler
🤖 Automated review by Claude AI - focused on identifying issues for improvement
ding113
left a comment
There was a problem hiding this comment.
📝 Documentation Review
📊 Issues Summary
- Critical (🔴): 0 - Must fix before merge
- High (🟠): 0 - Should fix before merge
- Medium (🟡): 0 - Consider addressing
- Low (🟢): 0 - Optional improvements
⚡ Priority Fixes
None - documentation looks good!
📋 Review Coverage
-
Technical accuracy - No issues found
- ✅ Environment variable range (5000-120000) matches code implementation
- ✅ Default value (15000) correctly documented
- ✅ Timeout value consistently referenced across all documentation
-
Completeness - No issues found
- ✅ Variable documented in README.en.md
- ✅ Variable documented in README.md
- ✅ Variable documented in .env.example with detailed comments
- ✅ Dockerfile CI environment variable properly commented
-
Code examples - Not applicable (no code examples in this PR)
-
Links and references - No issues found
- ✅ Reference to
.env.exampleis valid
- ✅ Reference to
-
Clarity and organization - No issues found
- ✅ English documentation clear and concise
- ✅ Chinese documentation clear and idiomatic
- ✅ Table formatting correct in both languages
- ✅ Comments in .env.example are detailed and helpful
💡 General Observations
The documentation changes are well-executed:
- Bilingual Consistency: Both English and Chinese versions maintain identical information with appropriate localization
- Technical Precision: The documented range (5000-120000 ms) and default (15000 ms) match the actual code implementation in
src/actions/providers.ts - User-Friendly: The Chinese version includes helpful context ("跨境网络可适当提高" - "can be increased for cross-border networks")
- Complete Coverage: The new environment variable is documented in all appropriate locations (.env.example, both README files)
- Code Comments: The Dockerfile comment clearly explains the purpose of the CI environment variable
Verified against implementation:
- Code uses
API_TEST_TIMEOUT_LIMITSwith MIN: 5000, MAX: 120000, DEFAULT: 15000 - Out-of-range values fall back to default with warning logs
- Implementation includes proper validation and error handling
✅ APPROVED - Ready to merge
🤖 Automated docs review by Claude AI
ding113
left a comment
There was a problem hiding this comment.
🔴 Critical: SSRF vulnerability in API test function
File: src/actions/providers.ts (lines ~1181-1268 in diff)
Why this is a problem: The executeProviderApiTest function directly fetches user-provided URLs without SSRF validation. Although only admins can access this feature, it still allows:
- Scanning internal networks (10.x.x.x, 192.168.x.x, 172.16-31.x.x)
- Accessing localhost services (127.0.0.1, ::1)
- Port scanning via timeout differences
- Reading cloud metadata endpoints (169.254.169.254)
Suggested fix:
// Add validation BEFORE fetch
import { isInternalUrl } from "@/lib/utils/validation";
// Inside executeProviderApiTest, before the fetch call:
if (isInternalUrl(normalizedProviderUrl)) {
return {
ok: false,
error: "安全限制:不允许访问内部网络地址",
};
}
// Create helper function in lib/utils/validation.ts:
export function isInternalUrl(url: string): boolean {
try {
const parsed = new URL(url);
const hostname = parsed.hostname;
// Block localhost
if (["localhost", "127.0.0.1", "::1", "0.0.0.0"].includes(hostname)) {
return true;
}
// Block internal IP ranges
if (/^10\.|^172\.(1[6-9]|2\d|3[01])\.|^192\.168\./.test(hostname)) {
return true;
}
// Block link-local addresses (AWS metadata, etc.)
if (/^169\.254\.|^fe80:/i.test(hostname)) {
return true;
}
// Block dangerous ports
const port = parseInt(parsed.port || "0", 10);
const dangerousPorts = [22, 23, 3306, 5432, 6379, 27017];
if (dangerousPorts.includes(port)) {
return true;
}
return false;
} catch {
return true; // Block invalid URLs
}
}Note: The README mentions SSRF protection exists, but I don't see it implemented in this PR's changes to the API test function.
ding113
left a comment
There was a problem hiding this comment.
🔴 Critical: Missing webpack externals for Docker build
File: next.config.ts (line 31)
Why this is a problem: The Dockerfile adds CI=true to skip database connections during build, but this doesn't prevent webpack from trying to bundle Node.js native modules (ioredis, bull, dns, etc.) into the client bundle. This causes build failures like:
Module not found: Can't resolve 'dns'
Module not found: Can't resolve 'net'
The serverExternalPackages config only prevents these packages from being bundled for server runtime, but doesn't prevent webpack from attempting to resolve them during the client build phase when analyzing module dependencies.
Suggested fix:
const nextConfig: NextConfig = {
output: "standalone",
transpilePackages: ["@lobehub/icons"],
serverExternalPackages: ["bull", "bullmq", "@bull-board/api", "@bull-board/express", "ioredis"],
outputFileTracingIncludes: {
"/**": ["./node_modules/undici/**/*", "./node_modules/socks-proxy-agent/**/*"],
},
// ✅ ADD THIS: Prevent Node.js modules from being bundled in client
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
// Node.js native modules should not be in client bundle
net: false,
dns: false,
tls: false,
fs: false,
child_process: false,
};
}
return config;
},
experimental: {
serverActions: {
bodySizeLimit: "500mb",
},
},
};
export default withNextIntl(nextConfig);Alternative solution: If you don't want to modify webpack config, ensure that all server-only imports use Next.js server-only package:
import "server-only";
import { redis } from "@/lib/redis";But the webpack approach is more robust and catches transitive dependencies automatically.
ding113
left a comment
There was a problem hiding this comment.
🟠 High: Environment variable fallback logic may be incorrect
Files: .env.example, src/lib/constants/provider.constants.ts (inferred)
Why this is a problem: The new API_TEST_TIMEOUT_MS environment variable is documented in .env.example, but based on the code pattern in src/actions/providers.ts line 35-39:
const API_TEST_CONFIG = {
TIMEOUT_MS: 10000, // Hardcoded 10 seconds
// ...
} as const;This constant is used throughout the code, but there's no evidence it reads from the environment variable. If the code does:
const timeout = config.timeout ?? API_TEST_CONFIG.TIMEOUT_MS;Then user-configured API_TEST_TIMEOUT_MS=30000 will be ignored because the constant is always 10000.
Suggested fix:
Verify the environment variable is actually read. It should be:
// In src/lib/config/env.schema.ts or similar:
export const API_TEST_TIMEOUT_MS = parseInt(
process.env.API_TEST_TIMEOUT_MS || "15000",
10
);
// Then in providers.ts:
import { API_TEST_TIMEOUT_MS } from "@/lib/config/env";
const timeout = config.timeout ?? API_TEST_TIMEOUT_MS;Verification needed: Please confirm that API_TEST_TIMEOUT_MS environment variable is actually read at runtime, not just documented in .env.example.
ding113
left a comment
There was a problem hiding this comment.
🟡 Medium: Unhandled rejection in stream controller error handling
File: src/app/v1/_lib/proxy/response-handler.ts (lines 700-714)
Why this is a problem: The idle timeout handler calls streamController.error() to close the client stream, but this can throw a TypeError if the stream is already closed. The current try-catch swallows the error silently:
try {
if (streamController) {
streamController.error(new Error("Streaming idle timeout"));
logger.debug("ResponseHandler: Client stream closed due to idle timeout", {
taskId,
providerId: provider.id,
});
}
} catch (e) {
logger.warn("ResponseHandler: Failed to close client stream", {
taskId,
providerId: provider.id,
error: e, // ❌ No error details logged
});
}When the client disconnects before the timeout fires, calling controller.error() throws:
TypeError: Cannot call error on a closed stream
This creates "unhandled promise rejection" warnings and makes debugging difficult.
Suggested fix:
try {
if (streamController) {
streamController.error(new Error("Streaming idle timeout"));
logger.debug("ResponseHandler: Client stream closed due to idle timeout", {
taskId,
providerId: provider.id,
});
}
} catch (e) {
const err = e as Error;
// Only log if it's not an expected "already closed" error
if (!err.message?.toLowerCase().includes("closed")) {
logger.warn("ResponseHandler: Failed to close client stream", {
taskId,
providerId: provider.id,
error: err.message,
errorType: err.name,
});
} else {
logger.trace("ResponseHandler: Stream already closed (expected)", {
taskId,
providerId: provider.id,
});
}
}This differentiates between expected "already closed" errors and unexpected errors, reducing log noise while maintaining debuggability.
ding113
left a comment
There was a problem hiding this comment.
🟡 Medium: Unsafe regex compilation can crash error detector
File: src/lib/error-rule-detector.ts (lines 143-163)
Why this is a problem: The regex pattern is validated with safe-regex AFTER construction, but the RegExp constructor itself can throw on invalid syntax:
case "regex": {
// ❌ safe-regex check AFTER construction
if (!safeRegex(rule.pattern)) {
logger.warn(`ReDoS risk: ${rule.pattern}`);
skippedRedosCount++;
break;
}
const pattern = new RegExp(rule.pattern, "i"); // ⚠️ Can throw here!
this.regexPatterns.push({ pattern, category: rule.category });
validRegexCount++;
break;
}If an admin enters an invalid regex (e.g., [unclosed, (?!invalid), the new RegExp() call throws a SyntaxError. This bubbles up and prevents the error detector from loading any rules, breaking the entire error categorization system.
Suggested fix (reorder validation):
case "regex": {
try {
// STEP 1: Validate syntax by attempting to compile
let pattern: RegExp;
try {
pattern = new RegExp(rule.pattern, "i");
} catch (syntaxError) {
logger.error(
`[ErrorRuleDetector] Invalid regex syntax: ${rule.pattern}`,
{ error: syntaxError }
);
break; // Skip this rule, continue with others
}
// STEP 2: Check ReDoS risk (only if syntax is valid)
if (!safeRegex(rule.pattern)) {
logger.warn(
`[ErrorRuleDetector] ReDoS risk detected: ${rule.pattern}, skipping`
);
skippedRedosCount++;
break;
}
// STEP 3: Add to cache
this.regexPatterns.push({
pattern,
category: rule.category,
description: rule.description ?? undefined,
});
validRegexCount++;
} catch (error) {
// Catch-all for unexpected errors
logger.error(
`[ErrorRuleDetector] Unexpected error processing pattern: ${rule.pattern}`,
{ error }
);
}
break;
}Benefit: Invalid patterns are skipped individually instead of crashing the entire detector, allowing valid rules to continue working.
ding113
left a comment
There was a problem hiding this comment.
🟢 Low: Hardcoded Chinese strings bypass i18n system
File: src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx
Lines affected: 264, 314-332 (handleCopyResult), 480-481, 574
Why this is a problem: The component uses useTranslations() for most text, but hardcodes Chinese strings in several places, breaking localization for English/other language users:
// Line 264
console.error("测试 API 连通性失败:", error);
// Line 314-332 (handleCopyResult function)
const resultText = [
`测试结果: ${testResult.success ? "成功" : "失败"}`,
`消息: ${testResult.message}`,
// ...
];
// Line 480-481
显示前 {API_TEST_UI_CONFIG.MAX_PREVIEW_LENGTH} 字符,完整内容请复制查看
// Line 574
显示前 200 字符,完整内容请点击"查看详情"Suggested fix:
- Console logs should use English (not user-facing):
// Line 264
console.error("API test failed:", error);- UI strings should use i18n:
// Add to messages/zh-CN/settings.json:
{
"apiTest": {
"truncatedPreview": "显示前 {length} 字符,完整内容请复制查看",
"truncatedBrief": "显示前 {length} 字符,完整内容请点击"查看详情"",
"copyFormat": {
"result": "测试结果",
"success": "成功",
"failed": "失败",
"message": "消息",
"model": "模型",
"responseTime": "响应时间",
"usage": "Token 用量",
"responseContent": "响应内容",
"errorDetails": "错误详情"
}
}
}
// Add to messages/en/settings.json:
{
"apiTest": {
"truncatedPreview": "Showing first {length} characters, copy to see full content",
"truncatedBrief": "Showing first {length} characters, click \"View Details\" for more",
"copyFormat": {
"result": "Test Result",
"success": "Success",
"failed": "Failed",
// ...
}
}
}
// Then use in component:
{t("apiTest.truncatedPreview", { length: API_TEST_UI_CONFIG.MAX_PREVIEW_LENGTH })}
// For handleCopyResult:
const resultText = [
`${t("apiTest.copyFormat.result")}: ${testResult.success ? t("apiTest.copyFormat.success") : t("apiTest.copyFormat.failed")}`,
`${t("apiTest.copyFormat.message")}: ${testResult.message}`,
// ...
];This ensures consistent internationalization across the entire component.
ding113
left a comment
There was a problem hiding this comment.
🟢 Low: Inconsistent preview length constants
File: src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx
Lines: 39-43, 568
Why this is a problem: The component defines MAX_PREVIEW_LENGTH: 500 but hardcodes a different value (200) for the brief preview:
// Line 39-43
const API_TEST_UI_CONFIG = {
MAX_PREVIEW_LENGTH: 500, // Used in dialog
// ...
} as const;
// Line 568
{testResult.details.content.slice(
0,
Math.min(200, testResult.details.content.length) // ❌ Magic number
)}
{testResult.details.content.length > 200 && "..."}
// Line 574
显示前 200 字符,完整内容请点击"查看详情" // ❌ Hardcoded againThis creates confusion:
- Dialog shows 500 characters
- Brief preview shows 200 characters
- Message says "200 characters" but uses
Math.min(200, length)which is redundant
Suggested fix:
// Line 39-43: Define both constants
const API_TEST_UI_CONFIG = {
MAX_PREVIEW_LENGTH: 500, // For detail dialog
BRIEF_PREVIEW_LENGTH: 200, // For inline preview
TOAST_SUCCESS_DURATION: 3000,
TOAST_ERROR_DURATION: 5000,
} as const;
// Line 565-577: Use the constant
<pre className="whitespace-pre-wrap break-words max-h-32 overflow-y-auto">
{testResult.details.content.slice(
0,
API_TEST_UI_CONFIG.BRIEF_PREVIEW_LENGTH
)}
{testResult.details.content.length > API_TEST_UI_CONFIG.BRIEF_PREVIEW_LENGTH && "..."}
</pre>
{testResult.details.content.length > API_TEST_UI_CONFIG.BRIEF_PREVIEW_LENGTH && (
<div className="text-muted-foreground italic">
{t("truncatedBrief", { length: API_TEST_UI_CONFIG.BRIEF_PREVIEW_LENGTH })}
</div>
)}This makes the code self-documenting and easier to maintain. If you need to change the preview length, update one constant instead of hunting for magic numbers.
ding113
left a comment
There was a problem hiding this comment.
🟢 Low: Inconsistent structured logging format
File: src/app/v1/_lib/proxy/model-redirector.ts
Lines: 29, 36-37, 43-45, 62
Why this is a problem: The model redirector uses inconsistent logging formats - sometimes interpolating values into the message string, sometimes using structured context:
// Line 29: Structured (good)
logger.debug("[ModelRedirector] No model found in request, skipping redirect");
// Line 36-37: Interpolated (inconsistent)
logger.debug(
`[ModelRedirector] No redirect configured for model "${originalModel}" in provider ${provider.id}`
);
// Line 43-45: Interpolated with provider ID (harder to query)
logger.info(
`[ModelRedirector] Redirecting model: "${originalModel}" → "${redirectedModel}" (provider ${provider.id})`
);This makes log aggregation and searching more difficult. For example, querying "all redirects for provider 123" requires regex parsing of the message string instead of filtering by providerId field.
Suggested fix (use structured logging consistently):
// Line 29: Keep as-is (good)
logger.debug("[ModelRedirector] No model found in request, skipping redirect");
// Line 36-40: Use structured context
logger.debug(
"[ModelRedirector] No redirect configured for model",
{
model: originalModel,
providerId: provider.id,
providerName: provider.name,
}
);
// Line 43-45: Use structured context
logger.info(
"[ModelRedirector] Redirecting model",
{
providerId: provider.id,
providerName: provider.name,
originalModel,
redirectedModel,
}
);
// Line 62: Use structured context
logger.debug(
"[ModelRedirector] Model redirect recorded in provider chain",
{
providerId: provider.id,
originalModel,
redirectedModel,
}
);Benefits:
- Easier to query logs by provider ID:
providerId=123 - Better integration with log aggregation tools (Datadog, Splunk, etc.)
- Consistent format across the codebase
- Machine-readable log fields
This pattern should be applied to all logger calls in the file (and ideally project-wide).
🔒 Security Scan Results✅ No security vulnerabilities detected This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes. 🛡️ Security Features Verified✅ SSRF Protection (A05: Broken Access Control)
✅ ReDoS Prevention (A03: Sensitive Data Exposure / DoS)
✅ Information Disclosure Prevention (A03: Sensitive Data Exposure)
✅ Input Validation (A03: Injection / A07: XSS)
✅ SQL Injection Protection (A01: Injection)
✅ Secure Configuration (A06: Security Misconfiguration)
📋 OWASP Top 10 Coverage
🔍 Additional Security Checks
🎯 Security Best Practices Observed
📊 Code Quality Metrics
🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage |
fix: clarify provider response model label
📊 PR Size AnalysisThis PR is Large (L) with ~1,335 lines of code changed across 31 files (excluding ~1,570 lines of auto-generated Drizzle snapshot). Large PRs are harder to review and more likely to introduce bugs. 🔀 Suggested Split:Based on the changes, this PR could be split into 5 smaller PRs:
Why Split?
Current StateThe PR description shows this already combines fixes for 6 different issues (#185, #186, #188, #189, #190 + unlisted fixes), which confirms it should ideally be separate PRs. 🤖 Automated analysis by Claude AI |
📝 Documentation Review✅ Documentation looks good No significant issues found. The documentation changes are:
📋 Review Coverage
📊 Issues Summary
✅ VerificationThe documented const API_TEST_TIMEOUT_LIMITS = {
DEFAULT: 15000, // ✅ Matches docs
MIN: 5000, // ✅ Matches docs
MAX: 120000, // ✅ Matches docs
} as const;🤖 Automated docs review by Claude AI |
🔒 Security Scan Results✅ No security vulnerabilities detected This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes. Scanned Categories
Key Security Controls Verified1. SSRF Protection (src/actions/providers.ts:990-1066)
2. ReDoS Protection (src/actions/error-rules.ts:98-116)Error rule creation/update validates regex patterns:
3. Input Validation
4. Authorization Controls
5. Information Disclosure Prevention
6. Resource Limits (DoS Prevention)New stream limits added to prevent resource exhaustion:
7. Timeout Configuration
Changes ReviewedFiles analyzed: 31 files
Security-relevant changes:
ConclusionThis PR demonstrates strong security practices:
Recommendation: ✅ APPROVED from security perspective - Safe to merge. 🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage |
ding113
left a comment
There was a problem hiding this comment.
📋 Code Review Summary
This PR includes multiple bug fixes and optimizations across core modules. While the fixes address real issues (Docker build, streaming response handling, model redirection), several critical problems remain that could cause production failures or security issues.
🔍 Issues Found
- Critical (🔴): 2 issues
- High (🟠): 4 issues
- Medium (🟡): 3 issues
- Low (🟢): 0 issues
🎯 Priority Actions
- Add environment variable validation for
API_TEST_TIMEOUT_MS- currently documented but not validated, risking crashes - Fix timeout inconsistency - hardcoded 10s vs documented 15s default creates confusion
- Review database migration impact - 30x timeout increase affects ALL existing providers immediately
- Replace CI detection with explicit build-phase check - current
CI=truecheck is fragile - Improve error handling in error-rule-detector - overly broad catch blocks hide real database errors
💡 General Observations
Positive patterns:
- Good use of structured logging throughout
- Comprehensive timeout handling for streaming responses
- ReDoS protection in regex compilation
Recurring issues:
- Missing environment variable validation layer
- Hardcoded constants that should be configurable
- Error handling that suppresses too many error types
- Documentation-code mismatches (timeout values, model names)
🤖 Automated review by Claude AI - focused on identifying issues for improvement
Detailed Code Review Issues for PR #192🔴 Critical IssuesIssue 1: Missing Environment Variable ValidationFile: Code Location: # .env.example:22
API_TEST_TIMEOUT_MS=15000// src/actions/providers.ts:35
const API_TEST_CONFIG = {
TIMEOUT_MS: 10000, // ❌ Hardcoded, ignores env varSuggested Fix: // Add to env.schema.ts
API_TEST_TIMEOUT_MS: z.coerce
.number()
.min(5000)
.max(120000)
.default(15000),
// Use in providers.ts
import { env } from '@/lib/config/env';
const API_TEST_CONFIG = {
TIMEOUT_MS: env.API_TEST_TIMEOUT_MS,Issue 2: Inconsistent Timeout Values Create ConfusionFile: Suggested Fix: Use environment variable consistently (see Issue 1 fix). 🟠 High Priority IssuesIssue 3: CI Environment Check Too BroadFile: Current Code: if (process.env.CI === "true") {
logger.warn("[Instrumentation] CI environment detected...");
return;
}Suggested Fix: if (process.env.NEXT_PHASE === 'phase-production-build' ||
process.env.SKIP_DB_INIT === 'true') {
logger.warn('[Instrumentation] Build phase detected: skipping DB init');
return;
}Update Dockerfile: Issue 4: Missing Regex Compilation Error HandlingFile: Current Code: case 'regex': {
try {
if (!safeRegex(rule.pattern)) { /* ... */ }
const pattern = new RegExp(rule.pattern, 'i'); // Can throw
// ...
} catch (error) {
logger.error(`Invalid regex pattern: ${rule.pattern}`, error);
}Suggested Fix: Separate try-catch blocks for ReDoS check vs compilation. Issue 5: Overly Broad Error SuppressionFile: Suggested Fix: try {
rules = await getActiveErrorRules();
} catch (dbError) {
const error = dbError as { code?: string };
if (error.code === '42P01') { // PostgreSQL: undefined_table
logger.warn('[ErrorRuleDetector] Table not created yet');
return;
}
throw dbError; // Re-throw all other errors
}Issue 6: Database Migration Lacks Backward Compatibility WarningFile: Impact: Existing production providers configured for 10s timeout suddenly have 5min timeout. Suggested Fix: Add migration comment and PR warning: COMMENT ON COLUMN providers.streaming_idle_timeout_ms IS
'Default changed from 10s to 5min in migration 0023 (yyyy-mm-dd)';🟡 Medium Priority IssuesIssue 7: Hardcoded Test Models Don't ExistFile: Suggested Fix: const apiFormatDefaultModel: Record<ApiFormat, string> = {
'anthropic-messages': 'claude-sonnet-4-5-20250929',
'openai-chat': 'gpt-4o',
'openai-responses': 'gpt-4o',
gemini: 'gemini-2.0-flash-exp',
};Issue 8: Potential Race Condition in Model RedirectorFile: Suggested Fix: Add length check and warning log. Issue 9: Webpack Externals Comment MisleadingFile: Recommendation: Keep serverExternalPackages (good), but update comments to reflect Next.js 15 migration, not "avoiding bundling Node modules". Summary Statistics
Testing Recommendations
|
- fix(error-rule-detector): 修复 isLoading 在 early return 时未清除导致的死锁问题 - fix(error-rules): 修复 updateErrorRuleAction 中 matchType 默认值逻辑,确保 ReDoS 检查覆盖所有场景 - refactor(model-redirector): 统一日志格式为结构化日志,提升可观测性 - fix(i18n): 修复 api-test-button 组件中的硬编码中文字符串 - feat(error-rules): 添加 getErrorRuleById 函数支持按 ID 查询 Issues verified and ignored (not real problems): - SSRF: validateProviderUrlForConnectivity() 已有完整保护 - Webpack externals: 配置已正确设置 - 环境变量读取: resolveApiTestTimeoutMs() 正确读取环境变量
📝 Documentation Review📊 Issues Summary
⚡ Critical Issue Found🔴 Inconsistent Discount PercentageLocation: Problem: The Chinese README.md states "20% 优惠折扣" (20% discount) while the English README.en.md still states "10% discount". This inconsistency exists because commit Current (Wrong in README.en.md): you can enjoy a <b>10% discount</b>Should Be: you can enjoy a <b>20% discount</b>Impact: Users reading the English documentation will see incorrect promotional information. This could cause confusion, trust issues, or complaints when the actual discount differs from documented. 📋 Review Coverage
💡 General ObservationsThe documentation changes in this PR are minimal and well-structured:
The new environment variable
However, the pre-existing discount inconsistency (10% vs 20%) should be addressed as part of this PR to maintain documentation consistency. 🤖 Automated docs review by Claude AI |
📊 PR Size AnalysisThis PR is XL with 3,281 lines changed across 31 files (~1,464 lines excluding generated snapshot file). Large PRs are harder to review and more likely to introduce bugs. 🔀 Suggested Split:Based on the changes, this PR could be split into:
Why Split?
Note:If this is a consolidated release PR and splitting is not practical, consider adding detailed commit messages for each logical change to help reviewers navigate the changes. 🤖 Automated analysis by Claude AI |
🔒 Security Scan Results✅ No security vulnerabilities detected This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes. Scanned Categories
Security Positive Observations
OWASP Top 10 Coverage
🛡️ Security PostureAssessment: Strong This PR demonstrates good security practices:
🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage |
ding113
left a comment
There was a problem hiding this comment.
📋 Code Review Summary
This PR introduces several significant features and improvements including error rule detection enhancements, model redirection improvements for Gemini, streaming idle timeout adjustments, and UI/UX improvements for API testing and logs.
🔍 Issues Found
- Critical (🔴): 0 issues
- High (🟠): 1 issue
- Medium (🟡): 2 issues
- Low (🟢): 1 issue
🎯 Priority Actions
-
🟠 Memory Safety in
parseStreamResponse(src/actions/providers.ts): The stream parsing function accumulates all chunks without any size limits. A malicious or malfunctioning upstream server could return an extremely large stream, causing memory exhaustion. Add aMAX_CHUNKSconstant (e.g., 10000) and break the loop if exceeded. -
🟡 Race Condition in
ErrorRuleDetector.reload()(src/lib/error-rule-detector.ts): TheisLoadingflag is set at the beginning but on early return at line 109, it wasn't being reset (though the diff shows this was fixed). However, theensureInitialized()method should use proper async locking instead of just checkingisLoading- concurrent calls during initialization could lead to multiple loads. -
🟡 Default Timeout Increase (
src/lib/constants/provider.constants.ts): IncreasingSTREAMING_IDLE_TIMEOUT_MSdefault from 10s to 300s (5 minutes) is a significant change that could hide legitimate issues with stuck streams. Consider if this change aligns with the expected behavior for all use cases. -
🟢 Hardcoded String in Log Message (
src/actions/providers.ts): Log messages like "流式响应 chunk 解析失败" use Chinese strings which may cause encoding issues in some log aggregation systems. Consider using English or locale-independent identifiers.
💡 General Observations
- The refactoring of error detection to use the full response body instead of parsed message is a good improvement for flexibility.
- The Gemini model redirection URL path handling is well-implemented with proper regex escaping.
- The i18n improvements for API test button are thorough and well-structured.
🤖 Automated review by Claude AI - focused on identifying issues for improvement
ding113
left a comment
There was a problem hiding this comment.
📋 Code Review Summary
This PR introduces several significant features and improvements including error rule detection enhancements, model redirection improvements for Gemini, streaming idle timeout adjustments, and UI/UX improvements for API testing and logs.
🔍 Issues Found
- Critical (🔴): 0 issues
- High (🟠): 1 issue
- Medium (🟡): 2 issues
- Low (🟢): 1 issue
🎯 Priority Actions
-
🟠 Memory Safety in
parseStreamResponse(src/actions/providers.ts): The stream parsing function accumulates all chunks without any size limits. A malicious or malfunctioning upstream server could return an extremely large stream, causing memory exhaustion. Add aMAX_CHUNKSconstant (e.g., 10000) and break the loop if exceeded. -
🟡 Race Condition in
ErrorRuleDetector.reload()(src/lib/error-rule-detector.ts): TheisLoadingflag is set at the beginning but on early return at line 109, it wasn't being reset (though the diff shows this was fixed). However, theensureInitialized()method should use proper async locking instead of just checkingisLoading- concurrent calls during initialization could lead to multiple loads. -
🟡 Default Timeout Increase (
src/lib/constants/provider.constants.ts): IncreasingSTREAMING_IDLE_TIMEOUT_MSdefault from 10s to 300s (5 minutes) is a significant change that could hide legitimate issues with stuck streams. Consider if this change aligns with the expected behavior for all use cases. -
🟢 Hardcoded String in Log Message (
src/actions/providers.ts): Log messages like "流式响应 chunk 解析失败" use Chinese strings which may cause encoding issues in some log aggregation systems. Consider using English or locale-independent identifiers.
💡 General Observations
- The refactoring of error detection to use the full response body instead of parsed message is a good improvement for flexibility.
- The Gemini model redirection URL path handling is well-implemented with proper regex escaping.
- The i18n improvements for API test button are thorough and well-structured.
🤖 Automated review by Claude AI - focused on identifying issues for improvement
📍 Detailed Inline Recommendations1. 🟠 High: Memory Safety in
|
Summary
本次 PR 主要包含多个 bug 修复和功能优化,涵盖供应商测试、流式响应处理、Docker 构建、模型重定向和错误规则等核心模块。
Changes
🐛 Bug 修复
Docker 构建修复 (关键修复)
CI=true环境变量跳过构建时的数据库连接检查供应商 API 测试优化
max_output_tokens参数以兼容中转服务模型重定向修复
供应商管理优化
错误规则系统
日志系统优化
✨ 功能优化
流式响应超时调整
0023_cheerful_shocker.sql)providers.streaming_idle_timeout_ms使用记录 UI 优化
数据库变更
providers.streaming_idle_timeout_ms默认值从 10000ms → 300000ms (5分钟)Testing
Related PRs
Breaking Changes
无破坏性变更。数据库迁移会自动执行。
Notes
CI=true跳过 instrumentation.ts 的数据库连接