Skip to content

feat: port FluxFix response fixer#570

Merged
ding113 merged 2 commits intodevfrom
feat/response-fixer
Jan 9, 2026
Merged

feat: port FluxFix response fixer#570
ding113 merged 2 commits intodevfrom
feat/response-fixer

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 8, 2026

Summary

Port FluxFix 0.2's response fixing capabilities to CCH. Adds a three-stage fix pipeline (Encoding -> SSE -> JSON) that automatically repairs common upstream provider response issues. The feature is enabled by default and configurable via system settings.

Problem

Upstream API providers sometimes return malformed responses including:

  • UTF-8 BOM markers and null bytes in response bodies
  • Malformed SSE streams (missing data: prefix, inconsistent line endings)
  • Truncated or malformed JSON (unclosed brackets, trailing commas)

These issues can cause client-side parsing failures that are difficult to diagnose.

Solution

Implement a response fixer pipeline that runs before format transformation:

  1. EncodingFixer: Strips BOM/null bytes, normalizes invalid UTF-8
  2. SseFixer: Fixes line endings, adds missing data: prefixes, normalizes field formatting
  3. JsonFixer: Closes unclosed structures, removes trailing commas, fills missing values

Key Features

  • Supports both streaming (SSE) and non-streaming responses
  • Safety limits: max depth (200), max size (1MB)
  • Full audit trail via ResponseFixerSpecialSetting in session/message records
  • Configurable per-fixer toggles in system settings UI
  • Comprehensive test coverage (87 new test cases)

Changes

Core Changes

  • src/app/v1/_lib/proxy/response-fixer/ - New response fixer module with three specialized fixers
  • src/app/v1/_lib/proxy/response-handler.ts - Integration point calling ResponseFixer
  • src/app/v1/_lib/proxy/forwarder.ts - Changed to await special settings persistence to avoid race conditions

Database

  • drizzle/0050_flippant_jack_flag.sql - Migration adding enable_response_fixer and response_fixer_config columns

Configuration & UI

  • src/app/[locale]/settings/config/_components/system-settings-form.tsx - Settings UI with toggles
  • src/types/system-config.ts - Added ResponseFixerConfig type
  • src/types/special-settings.ts - Added ResponseFixerSpecialSetting type

i18n

  • Updated all 5 language files (en, ja, ru, zh-CN, zh-TW)

Testing

Automated Tests

  • Unit tests added (87 new test cases)
  • Integration tests updated
  • All existing tests pass

Verification Commands

bun run lint && bun run typecheck && bun run test && bun run build && bun run test:coverage

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally
  • i18n strings added for all 5 languages
  • Database migration is backward-compatible

Greptile Overview

Greptile Summary

This PR ports FluxFix 0.2's response fixing capabilities to CCH, introducing a robust three-stage fix pipeline (Encoding -> SSE -> JSON) that automatically repairs common upstream provider issues.

Key Changes:

  • New response fixer module with three specialized fixers: EncodingFixer (BOM/null bytes/UTF-8 normalization), SseFixer (line ending normalization, missing data: prefix, field formatting), and JsonFixer (unclosed structures, trailing commas, missing values)
  • Integrated into response pipeline: Runs before format transformation in ProxyResponseHandler.dispatch(), supporting both streaming and non-streaming responses
  • System configuration: Added enable_response_fixer toggle (default: enabled) and response_fixer_config JSONB with per-fixer feature flags and safety limits (maxJsonDepth: 200, maxFixSize: 1MB)
  • Audit trail: Writes ResponseFixerSpecialSetting to session and message request when fixers are applied, tracking which fixers ran, processing time, and bytes processed
  • UI controls: Settings page includes master toggle and three sub-toggles for granular control
  • Database migration: Safe migration (这段空白是否能修正 #50) adds two columns to system_settings with backward-compatible defaults
  • Comprehensive tests: 87 new test cases covering edge cases, safety limits, and streaming scenarios

Implementation Quality:

  • Proper buffer management for streaming with findProcessableEnd() to avoid splitting CRLF
  • Safety protections: max depth/size limits, fallback to original data on failure
  • Clean separation of concerns with FixResult<T> interface
  • Well-tested with edge cases (BOM, invalid UTF-8, malformed SSE, truncated JSON)
  • i18n support for all 5 languages (en, ja, ru, zh-CN, zh-TW)

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk - well-architected, thoroughly tested, and properly integrated
  • Score of 5 reflects exceptional implementation quality: comprehensive test coverage (87 tests), proper error handling with safety limits, clean integration without breaking changes, backward-compatible database migration with safe defaults, and extensive edge case handling (BOM, invalid UTF-8, malformed SSE, truncated JSON). The feature is enabled by default but configurable, includes audit trails, and has been validated with the full test suite (bun run lint && typecheck && test && build && test:coverage).
  • No files require special attention - all core files have excellent test coverage and follow established patterns

Important Files Changed

Filename Score Overview
src/app/v1/_lib/proxy/response-fixer/index.ts 5/5 Main ResponseFixer orchestrator with encoding/SSE/JSON fixing pipeline, proper session special settings tracking, and comprehensive error handling
src/app/v1/_lib/proxy/response-fixer/json-fixer.ts 5/5 JSON repair logic with unclosed structure handling, trailing comma removal, and depth/size protections
src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts 5/5 SSE format fixer normalizing line endings, adding missing data: prefixes, and fixing malformed field syntax
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts 5/5 Encoding fixer stripping BOM/null bytes and applying lossy UTF-8 normalization as fallback
src/app/v1/_lib/proxy/response-handler.ts 5/5 Integration point calling ResponseFixer before format transformation, maintaining existing timeout and error handling logic
src/drizzle/schema.ts 5/5 Added enable_response_fixer boolean and response_fixer_config JSONB columns to system_settings table with appropriate defaults
src/app/[locale]/settings/config/_components/system-settings-form.tsx 5/5 UI form with master toggle and three sub-toggles for encoding/SSE/JSON fixers, properly integrated with system settings
src/types/special-settings.ts 5/5 Added ResponseFixerSpecialSetting type for audit trail of fixer applications with per-fixer details

Sequence Diagram

sequenceDiagram
    participant Client
    participant ResponseHandler
    participant ResponseFixer
    participant EncodingFixer
    participant SseFixer
    participant JsonFixer
    participant SessionManager
    participant Database

    Client->>ResponseHandler: Upstream Response
    ResponseHandler->>ResponseFixer: process(session, response)
    
    ResponseFixer->>ResponseFixer: Check system settings
    alt Response Fixer Disabled
        ResponseFixer-->>ResponseHandler: Return original response
    else Response Fixer Enabled
        ResponseFixer->>ResponseFixer: Detect content type
        
        alt Non-Streaming Response
            ResponseFixer->>ResponseFixer: Read full body
            ResponseFixer->>EncodingFixer: fix(data)
            EncodingFixer->>EncodingFixer: Strip BOM/null bytes
            EncodingFixer->>EncodingFixer: Validate UTF-8
            EncodingFixer-->>ResponseFixer: Fixed data + applied flag
            
            ResponseFixer->>JsonFixer: fix(data)
            JsonFixer->>JsonFixer: Check if JSON-like
            JsonFixer->>JsonFixer: Close unclosed structures
            JsonFixer->>JsonFixer: Remove trailing commas
            JsonFixer->>JsonFixer: Fill missing values
            JsonFixer-->>ResponseFixer: Fixed data + applied flag
            
            ResponseFixer->>ResponseFixer: Build audit record
            ResponseFixer->>SessionManager: storeSessionSpecialSettings()
            SessionManager->>Database: Save special settings
            ResponseFixer->>Database: updateMessageRequestDetails()
            
            ResponseFixer-->>ResponseHandler: New Response with fixed body
        else Streaming SSE Response
            ResponseFixer->>ResponseFixer: Create TransformStream
            loop For each chunk
                ResponseFixer->>ResponseFixer: Buffer until line boundary
                ResponseFixer->>EncodingFixer: fix(chunk)
                EncodingFixer-->>ResponseFixer: Fixed chunk
                ResponseFixer->>SseFixer: fix(chunk)
                SseFixer->>SseFixer: Normalize line endings
                SseFixer->>SseFixer: Add missing data: prefix
                SseFixer->>SseFixer: Fix field formatting
                SseFixer-->>ResponseFixer: Fixed chunk
                ResponseFixer->>JsonFixer: fixSseJsonLines(chunk)
                JsonFixer->>JsonFixer: Parse SSE lines
                JsonFixer->>JsonFixer: Fix JSON in data: fields
                JsonFixer-->>ResponseFixer: Fixed chunk
                ResponseFixer->>Client: Enqueue fixed chunk
            end
            ResponseFixer->>ResponseFixer: On flush, finalize audit
            ResponseFixer->>SessionManager: storeSessionSpecialSettings()
            SessionManager->>Database: Save special settings
            ResponseFixer->>Database: updateMessageRequestDetails()
            ResponseFixer-->>ResponseHandler: New Response with transform stream
        end
    end
    
    ResponseHandler-->>Client: Final response
Loading

Description enhanced by Claude AI

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

引入响应修复器功能:新增数据库字段与默认配置、前端设置面板与多语言文案、三个修复器实现(编码、SSE、JSON)、响应处理管道集成及相关测试与类型/验证更新。

Changes

Cohort / File(s) 更改摘要
数据库迁移与快照
drizzle/0050_flippant_jack_flag.sql, drizzle/meta/0050_snapshot.json, drizzle/meta/_journal.json
system_settings 表添加 enable_response_fixer(boolean, default true)和 response_fixer_config(jsonb, 默认对象);包含快照与日志更新。
多语言 UI 文案
messages/en/settings.json, messages/ja/settings.json, messages/ru/settings.json, messages/zh-CN/settings.json, messages/zh-TW/settings.json
新增 8 个响应修复器相关翻译键(开关与三项修复及描述)。
类型、验证与默认设置
src/types/system-config.ts, src/types/special-settings.ts, src/lib/validation/schemas.ts, src/repository/_shared/transformers.ts, src/lib/config/system-settings-cache.ts
新增 ResponseFixerConfigResponseFixerSpecialSetting 类型;扩展 SystemSettingsUpdateSystemSettingsInput,添加验证规则并在转换/缓存层合并默认配置。
数据库 schema 映射
src/drizzle/schema.ts
在应用层 schema 中为 systemSettings 添加入库字段映射:enableResponseFixerresponseFixerConfig(含默认值)。
系统设置 UI 与保存逻辑
src/app/[locale]/settings/config/_components/system-settings-form.tsx, src/app/[locale]/settings/config/page.tsx, src/actions/system-config.ts
表单新增响应修复器开关与嵌套配置控件;saveSystemSettings 支持并传递新字段,返回结果同步新字段。
响应修复器实现(核心)
src/app/v1/_lib/proxy/response-fixer/index.ts, .../encoding-fixer.ts, .../json-fixer.ts, .../sse-fixer.ts, .../types.ts
实现 EncodingFixer、JsonFixer、SseFixer 与主 ResponseFixer:支持流与非流响应处理、大小/深度限制、应用审计信息与持久化点。新增 FixResult 类型。
集成到代理/处理链
src/app/v1/_lib/proxy/response-handler.ts, src/app/v1/_lib/proxy/forwarder.ts
在响应分发中调用 ResponseFixer.process;将部分原本不 await 的持久化调用改为 await,降低竞态。
测试
src/app/v1/_lib/proxy/response-fixer/*.test.ts, src/app/[locale]/dashboard/sessions/.../*.test.tsx, tests/.../*.test.ts
为三个修复器及 ResponseFixer 集成添加单元/集成测试;更新会话组件相关测试与系统设置缓存测试数据。
界面/可访问性与小改动
src/app/[locale]/dashboard/sessions/.../session-messages-client.tsx, src/app/[locale]/dashboard/sessions/.../collapsible-quota-card.tsx, src/actions/my-usage.ts
为操作按钮添加 aria-label 与屏读文本,移除局部 key 百分比计算;调整 my-usage 中日期边界为次日排他上界。
其它小项
biome.json, 若干测试/fixture 文件
小幅配置和测试夹具更新以包含默认 response fixer 设置;更新 biome schema 引用。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题清晰准确地总结了主要变更:移植FluxFix响应修复功能。标题简洁,直接反映核心目标。
Description check ✅ Passed PR描述详尽地阐述了变更目标、问题陈述、解决方案、关键特性、代码变更、测试覆盖和检查清单,与变更集高度相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/response-fixer

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求为 Claude Code Hub (CCH) 代理引入了一个强大的“响应整流器”功能,旨在通过自动纠正上游 API 响应中的常见格式错误来增强系统的健壮性。这包括处理不正确的字符编码、格式错误的 Server-Sent Events (SSE) 流以及不完整的 JSON 有效载荷。该功能已无缝集成到代理的响应处理流程中,并通过直观的系统设置界面提供全面的配置选项,允许管理员精细控制修复行为。所有自动应用的修复操作都会被详细记录,以便于后续的审计和问题诊断。

Highlights

  • 新响应整流功能: 引入了全新的“响应整流器”功能,能够自动检测并修复上游 API 响应中的常见问题,从而提高代理的可靠性和兼容性。
  • 全面的修复能力: 包含专门的修复器,用于处理编码问题(移除 BOM、空字节,规范化无效 UTF-8)、SSE 格式问题(补齐 data: 前缀,统一换行符,修正字段格式)以及截断的 JSON(闭合未闭合的结构,移除尾随逗号,填充缺失值)。
  • 系统设置可配置: 整个响应整流功能可以通过系统设置全局启用/禁用,并且可以单独切换编码、SSE 和 JSON 修复器。JSON 修复还支持配置最大深度和最大大小限制,以防止过度处理。
  • 审计日志记录: 当响应被修复时,修复的详细信息(哪个修复器被应用、具体细节、处理时间)会被记录到会话的 specialSettings 中,以便于审计和调试。
  • 日期处理改进: 优化了使用统计中 startTimeendTime 的日期解析逻辑,将 endTime 设置为“次日零点”作为排他性上限,以避免潜在的边界问题。
  • 可访问性增强: 为 UI 中的“复制”和“下载”按钮添加了 aria-labelsr-only 属性,以提升用户界面的可访问性。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added size/XL Extra Large PR (> 1000 lines) enhancement New feature or request area:core area:i18n labels Jan 8, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 PR 质量很高,成功地将 FluxFix 的响应修复功能移植到了 CCH 中。代码结构清晰,将编码、SSE 和 JSON 的修复逻辑分离到各自的 Fixer 类中,易于理解和维护。
主要亮点包括:

  • 模块化设计:每个 Fixer 都有明确的职责,并通过一个主 ResponseFixer 类进行协调。
  • 流式处理:对 SSE 响应使用了 TransformStream,这是处理流式数据的正确且高效的方式。
  • 健壮性:为 JSON 和 SSE 修复器编写了详细的测试用例,覆盖了多种常见的格式错误。
  • 配置化:新增的功能可以通过系统设置进行开关和详细配置,灵活性好。
  • 并发安全:在 forwarder.ts 中将 specialSettings 的写入从 void 改为 await,这是一个重要的并发问题修复,避免了竞态条件。

整体来看,这是一个出色的功能增强,显著提升了系统对上游服务响应异常的容错能力。

Comment on lines 360 to 367
// 处理末尾无换行的残留(理论上很少发生,但 flush 时可能出现)
if (lineStart < data.length) {
const line = data.subarray(lineStart);
const fixed = ResponseFixer.fixMaybeDataJsonLine(line, jsonFixer);
if (fixed.applied) applied = true;
out.push(...fixed.line);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这段代码似乎是无法访问的(unreachable code)。

在它之前运行的 SseFixer 会确保其输出(如果非空)总是以换行符 \n 结尾。因此,fixSseJsonLines 函数中的 for 循环(349-358行)会处理完所有行,循环结束后 lineStart 的值将等于 data.length

这意味着 if (lineStart < data.length) 这个条件永远不会为真,这块代码也就永远不会被执行。

建议移除这部分代码以提高代码的清晰度和可维护性。

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @src/actions/my-usage.ts:
- Around line 535-546: getMyStatsSummary currently parses dates as UTC with
Date.parse(`${filters.startDate}T00:00:00.000Z`) while getMyUsageLogs uses
local-time parsing (new Date(`${filters.startDate}T00:00:00`).getTime()),
causing timezone-inconsistent ranges; change the parsing in getMyStatsSummary to
use local-time parsing like new Date(`${filters.startDate}T00:00:00`).getTime()
(and similarly for the end date), update the variable name parsedEndStart to
parsedEnd or parsedEndDate, and keep the endTime calculation (adding
24*60*60*1000) intact so both functions produce millisecond timestamps in the
same (local) timezone for downstream queries.

In @src/app/v1/_lib/proxy/response-fixer/index.ts:
- Around line 176-178: The response fixer currently unconditionally sets the
header "x-cch-response-fixer" to "applied"; change this so the header reflects
whether a fix was actually applied by checking the audit result (audit.hit). In
the block after calling cleanResponseHeaders(response.headers) and computing the
audit (referencing headers, cleanResponseHeaders, and audit.hit), set
headers.set("x-cch-response-fixer", "applied") only when audit.hit is truthy,
otherwise set it to a distinct value such as "none" or "not-applied" to
accurately indicate no changes were made. Ensure this logic lives wherever the
response mutation decision occurs so the header always matches the actual
modification outcome.

In @src/app/v1/_lib/proxy/response-handler.ts:
- Around line 61-62: Wrap the call to ResponseFixer.process(session, response)
in a try-catch to prevent exceptions from bubbling up: call
ResponseFixer.process inside try, on success assign to fixedResponse, on error
log the error (including context like session and response identifiers) and fall
back to the original response as a safe degraded result; ensure you reference
ResponseFixer.processNonStream and getCachedSystemSettings as potential throwers
in your log or handling so debugging is easier. Also update the import at the
top from the relative path import { ResponseFixer } from "./response-fixer" to
the alias import { ResponseFixer } from "@/app/v1/_lib/proxy/response-fixer" so
the module resolution uses the project path alias.
🧹 Nitpick comments (5)
src/lib/config/system-settings-cache.ts (1)

100-101: 默认配置在多处重复定义。

responseFixerConfig 的默认值在 transformers.ts 和此处都有定义。虽然当前值一致,但后续维护时可能导致不同步。可以考虑将默认配置提取为共享常量。

♻️ 可选:提取共享默认配置

src/types/system-config.tssrc/lib/constants/ 中定义:

export const DEFAULT_RESPONSE_FIXER_CONFIG: ResponseFixerConfig = {
  fixTruncatedJson: true,
  fixSseFormat: true,
  fixEncoding: true,
  maxJsonDepth: 200,
  maxFixSize: 1024 * 1024,
};

然后在 transformers.tssystem-settings-cache.ts 中引用该常量。

src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts (1)

38-47: 建议:补充 UTF-16 BOM 测试用例。

EncodingFixer.canFix() 中检测了 UTF-16 BOM (hasUtf16Bom),但测试中未覆盖此场景。可以考虑添加相关测试以确保完整覆盖。

💡 可选:添加 UTF-16 BOM 测试
test("应处理 UTF-16 LE BOM", () => {
  // UTF-16 LE BOM: 0xFF 0xFE
  const input = new Uint8Array([0xff, 0xfe, 0x48, 0x00, 0x65, 0x00]); // "He" in UTF-16 LE

  const fixer = new EncodingFixer();
  const { applied } = fixer.fix(input);

  expect(applied).toBe(true);
});
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts (1)

22-33: 建议优化:移除冗余的数组重置操作。

第 28 行的 out.length = 0 是多余的,因为数组在第 27 行刚被初始化。

♻️ 建议的优化
  const out: number[] = [];
- out.length = 0;
  for (const b of data) {
    if (b !== 0) out.push(b);
  }

或者可以使用更简洁的函数式写法:

- const out: number[] = [];
- out.length = 0;
- for (const b of data) {
-   if (b !== 0) out.push(b);
- }
- return { data: Uint8Array.from(out), stripped: true };
+ return { data: data.filter(b => b !== 0), stripped: true };
src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts (1)

42-75: 建议提取魔法数字为常量以提高可读性。

fixDataLinefixFieldLine 中的字节数组前缀(如 [0x64, 0x61, 0x74, 0x61, 0x3a] 表示 data:)可以提取为模块级常量,便于维护和理解。

♻️ 可选重构
// 在文件顶部定义常量
const SSE_PREFIX_DATA = new Uint8Array([0x64, 0x61, 0x74, 0x61, 0x3a]); // data:
const SSE_PREFIX_EVENT = new Uint8Array([0x65, 0x76, 0x65, 0x6e, 0x74, 0x3a]); // event:
const SSE_PREFIX_ID = new Uint8Array([0x69, 0x64, 0x3a]); // id:
const SSE_PREFIX_RETRY = new Uint8Array([0x72, 0x65, 0x74, 0x72, 0x79, 0x3a]); // retry:
const SSE_PREFIX_COMMENT = new Uint8Array([0x3a]); // :
src/app/v1/_lib/proxy/response-fixer/index.ts (1)

340-369: 可考虑优化 fixSseJsonLines 的内存分配

当前实现使用 out.push(...fixed.line, 0x0a) 展开数组,对于大型 SSE 响应可能产生较多临时内存分配。如果性能分析显示此处为瓶颈,可考虑预分配或使用更高效的拼接方式。

当前实现功能正确,这是可选的后续优化点。

可选的优化方向
// 替代方案:直接操作字节数组避免展开
private static fixSseJsonLines(
  data: Uint8Array,
  jsonFixer: JsonFixer
): { data: Uint8Array; applied: boolean; details?: string } {
  const chunks: Uint8Array[] = [];
  let applied = false;
  let totalLength = 0;

  let lineStart = 0;
  for (let i = 0; i < data.length; i += 1) {
    if (data[i] !== 0x0a) continue;

    const line = data.subarray(lineStart, i);
    const fixed = ResponseFixer.fixMaybeDataJsonLine(line, jsonFixer);
    if (fixed.applied) applied = true;
    chunks.push(fixed.line);
    totalLength += fixed.line.length + 1;
    lineStart = i + 1;
  }

  // ... 合并 chunks
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0d32316 and 1f64c7a.

📒 Files selected for processing (37)
  • drizzle/0050_flippant_jack_flag.sql
  • drizzle/meta/0050_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/settings.json
  • messages/ja/settings.json
  • messages/ru/settings.json
  • messages/zh-CN/settings.json
  • messages/zh-TW/settings.json
  • src/actions/my-usage.ts
  • src/actions/system-config.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx
  • src/app/[locale]/my-usage/_components/collapsible-quota-card.tsx
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/types.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/drizzle/schema.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.ts
  • src/repository/system-config.ts
  • src/types/special-settings.ts
  • src/types/system-config.ts
  • tests/integration/billing-model-source.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • tests/unit/proxy/session.test.ts
💤 Files with no reviewable changes (1)
  • src/app/[locale]/my-usage/_components/collapsible-quota-card.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

No emoji characters in any code, comments, or string literals

Files:

  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/repository/_shared/transformers.ts
  • src/types/system-config.ts
  • src/app/v1/_lib/proxy/response-fixer/types.ts
  • src/actions/my-usage.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/drizzle/schema.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/types/special-settings.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts
  • src/repository/system-config.ts
  • tests/unit/proxy/session.test.ts
  • src/lib/validation/schemas.ts
  • tests/integration/billing-model-source.test.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/lib/config/system-settings-cache.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/actions/system-config.ts
**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,jsx,js}: All user-facing strings must use i18n (5 languages supported: zh-CN, en, ja, ko, de). Never hardcode display text
Use path alias @/ to map to ./src/
Use Biome for code formatting with configuration: double quotes, trailing commas, 2-space indent, 100 character line width
Prefer named exports over default exports
Use next-intl for internationalization
Use Next.js 16 App Router with Hono for API routes

Files:

  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/repository/_shared/transformers.ts
  • src/types/system-config.ts
  • src/app/v1/_lib/proxy/response-fixer/types.ts
  • src/actions/my-usage.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/drizzle/schema.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/types/special-settings.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts
  • src/repository/system-config.ts
  • tests/unit/proxy/session.test.ts
  • src/lib/validation/schemas.ts
  • tests/integration/billing-model-source.test.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/lib/config/system-settings-cache.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/actions/system-config.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use React 19, shadcn/ui, Tailwind CSS, and Recharts for the UI layer

Files:

  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place unit tests in tests/unit/, integration tests in tests/integration/, and source-adjacent tests in src/**/*.test.ts

Files:

  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
  • tests/unit/proxy/session.test.ts
  • tests/integration/billing-model-source.test.ts
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx
**/drizzle/**/*.sql

📄 CodeRabbit inference engine (CLAUDE.md)

Never create SQL migration files manually. Always generate migrations by editing src/drizzle/schema.ts, then run bun run db:generate, review the generated SQL, and run bun run db:migrate

Files:

  • drizzle/0050_flippant_jack_flag.sql
🧠 Learnings (4)
📚 Learning: 2026-01-05T03:02:06.594Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx:66-66
Timestamp: 2026-01-05T03:02:06.594Z
Learning: In the claude-code-hub project, the translations.actions.addKey field in UserKeyTableRowProps is defined as optional for backward compatibility, but all actual callers in the codebase provide the complete translations object. The field has been added to all 5 locale files (messages/{locale}/dashboard.json).

Applied to files:

  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx
  • messages/zh-TW/settings.json
  • messages/en/settings.json
  • messages/ja/settings.json
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx
📚 Learning: 2026-01-05T03:01:39.354Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/types/user.ts:158-170
Timestamp: 2026-01-05T03:01:39.354Z
Learning: In TypeScript interfaces, explicitly document and enforce distinct meanings for null and undefined. Example: for numeric limits like limitTotalUsd, use 'number | null | undefined' when null signifies explicitly unlimited (e.g., matches DB schema or special UI logic) and undefined signifies 'inherit default'. This pattern should be consistently reflected in type definitions across related fields to preserve semantic clarity between database constraints and UI behavior.

Applied to files:

  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/repository/_shared/transformers.ts
  • src/types/system-config.ts
  • src/app/v1/_lib/proxy/response-fixer/types.ts
  • src/actions/my-usage.ts
  • src/drizzle/schema.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/types/special-settings.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts
  • src/repository/system-config.ts
  • src/lib/validation/schemas.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/lib/config/system-settings-cache.ts
  • src/actions/system-config.ts
📚 Learning: 2026-01-07T17:05:36.362Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T17:05:36.362Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Place unit tests in tests/unit/, integration tests in tests/integration/, and source-adjacent tests in src/**/*.test.ts

Applied to files:

  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts
📚 Learning: 2026-01-07T17:05:36.362Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T17:05:36.362Z
Learning: Applies to **/*.{ts,tsx,jsx,js} : All user-facing strings must use i18n (5 languages supported: zh-CN, en, ja, ko, de). Never hardcode display text

Applied to files:

  • messages/zh-TW/settings.json
  • messages/ja/settings.json
  • src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx
🧬 Code graph analysis (15)
src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts (2)
src/app/v1/_lib/proxy/response-fixer/index.ts (1)
  • ResponseFixer (100-401)
src/lib/polyfills/file.ts (2)
  • stream (36-38)
  • text (40-42)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts (1)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts (1)
  • EncodingFixer (45-77)
src/app/v1/_lib/proxy/response-handler.ts (1)
src/app/v1/_lib/proxy/response-fixer/index.ts (1)
  • ResponseFixer (100-401)
src/repository/_shared/transformers.ts (1)
src/types/system-config.ts (1)
  • ResponseFixerConfig (6-12)
src/drizzle/schema.ts (1)
src/types/system-config.ts (1)
  • ResponseFixerConfig (6-12)
src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts (1)
src/app/v1/_lib/proxy/response-fixer/json-fixer.ts (1)
  • JsonFixer (39-183)
src/app/v1/_lib/proxy/forwarder.ts (2)
src/lib/session-manager.ts (1)
  • SessionManager (80-2008)
src/repository/message.ts (1)
  • updateMessageRequestDetails (116-200)
src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts (1)
src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts (2)
  • fix (170-230)
  • SseFixer (137-261)
src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts (1)
src/app/v1/_lib/proxy/response-fixer/types.ts (1)
  • FixResult (1-5)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts (1)
src/app/v1/_lib/proxy/response-fixer/types.ts (1)
  • FixResult (1-5)
src/repository/system-config.ts (1)
src/drizzle/schema.ts (1)
  • systemSettings (456-502)
src/app/v1/_lib/proxy/response-fixer/json-fixer.ts (1)
src/app/v1/_lib/proxy/response-fixer/types.ts (1)
  • FixResult (1-5)
src/lib/config/system-settings-cache.ts (1)
src/types/system-config.ts (1)
  • SystemSettings (14-49)
src/app/[locale]/settings/config/_components/system-settings-form.tsx (3)
scripts/sync-settings-keys.js (3)
  • result (31-31)
  • t (72-72)
  • p (102-102)
src/components/ui/label.tsx (1)
  • Label (21-21)
src/components/ui/switch.tsx (1)
  • Switch (28-28)
src/actions/system-config.ts (1)
src/types/system-config.ts (1)
  • ResponseFixerConfig (6-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: pr-review
  • GitHub Check: check-codex-status
  • GitHub Check: 🌐 API Tests
  • GitHub Check: pr-description
  • GitHub Check: Docker Build Test
  • GitHub Check: dev-build-deploy
🔇 Additional comments (62)
src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx (1)

78-93: 模拟实现正确且支持测试场景

模拟组件 SessionMessagesDetailsTabs 的更新实现良好:

  • 属性类型定义清晰,正确使用了可选标记
  • 条件渲染逻辑(第 85 行)合理:仅在 responseonCopyResponse 都存在时渲染按钮
  • 按钮文本根据 isResponseCopied 状态正确切换,与第 446-454 行的测试用例相匹配
  • 使用 type="button" 防止意外的表单提交

该模拟实现正确反映了真实组件的行为,并有效支持了复制响应功能的测试。

drizzle/meta/_journal.json (1)

355-361: 迁移日志格式正确

新增的迁移记录格式正确,遵循了现有的 journal 结构规范。作为自动生成的文件,符合预期。

src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx (1)

376-401: 优秀的无障碍性改进

为复制和下载按钮添加了 aria-label 属性和屏幕阅读器文本,符合 WCAG 无障碍标准。正确使用了 i18n 翻译,实现方式规范。

src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx (2)

191-197: 测试断言更新正确

测试期望值已正确更新为新的 "Storage Tip" 文本,与测试夹具中的翻译键保持一致。


24-24: 修正 i18n 翻译键验证的语言列表

测试夹具中新增的 storageTip 翻译键已存在于所有 5 个实际支持的语言文件中。请注意:应用支持的语言为 zh-CN、zh-TW、en、ru、ja(在 src/i18n/config.ts 中定义),而非 ko 和 de。已验证 dashboard.sessions.details.storageTip 键在所有 5 个语言的 dashboard.json 中均已完整添加。

Likely an incorrect or invalid review comment.

drizzle/0050_flippant_jack_flag.sql (1)

1-2: 数据库迁移结构合理,默认值设置恰当

新增的两列配置合理:

  • enable_response_fixer:默认启用(true),符合功能说明中的"默认开启"要求
  • response_fixer_config:JSONB 配置默认值设置合理
    • maxJsonDepth: 200:防止过深嵌套导致性能问题
    • maxFixSize: 1048576(1MB):合理的大小限制,避免处理超大响应

作为通过 bun run db:generate 生成的迁移文件,符合项目规范。

基于学习记录:根据编码规范,此文件应由 Drizzle 自动生成。请确认已先修改 src/drizzle/schema.ts,然后运行 bun run db:generatebun run db:migrate 生成此迁移。

messages/zh-CN/settings.json (1)

88-95: 响应整流功能翻译键已在所有支持的语言中完整配置

zh-CN 中的翻译内容准确完整,涵盖了功能开关和三个子配置项(编码修复、SSE 格式修复、JSON 截断修复)。已确认所有 5 个支持的语言文件(zh-CN、en、ja、zh-TW、ru)中都包含了这 8 个响应整流相关的翻译键。翻译实现一致且完整。

tests/integration/billing-model-source.test.ts (1)

103-110: LGTM!

测试夹具正确添加了新的 enableResponseFixerresponseFixerConfig 字段,与 PR 中其他位置的默认配置保持一致。

src/repository/_shared/transformers.ts (1)

151-177: LGTM!

默认配置与 DB 覆盖值的合并逻辑正确。使用展开运算符 {...defaultResponseFixerConfig, ...(dbSettings?.responseFixerConfig ?? {})} 确保 DB 中的部分配置可以覆盖默认值,同时保留未设置字段的默认值。

src/lib/config/system-settings-cache.ts (1)

27-41: LGTM!

默认设置的扩展正确,DEFAULT_SETTINGS 类型通过 Pick 精确限定了需要缓存的字段。默认值与 transformers.ts 中保持一致。

src/app/v1/_lib/proxy/forwarder.ts (2)

1009-1019: LGTM!

storeSessionSpecialSettings 改为 await 是正确的。注释清晰解释了原因:避免与响应侧(如 ResponseFixer)的并发写入产生竞态条件,防止旧快照覆盖新数据。


1023-1031: LGTM!

updateMessageRequestDetailsawait 处理与上方一致,确保 special_settings 的写入顺序正确。.catch() 处理器保留了错误日志记录,避免写入失败影响主请求流程。

src/app/v1/_lib/proxy/response-fixer/types.ts (1)

1-5: LGTM!

类型定义简洁清晰。泛型 FixResult<T> 设计合理,applied 标记修复是否生效,details 提供可选的调试/审计信息。

src/app/[locale]/settings/config/page.tsx (1)

44-45: LGTM!

正确将新的 enableResponseFixerresponseFixerConfig 字段传递给 SystemSettingsForm,与系统设置的扩展保持一致。

src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts (1)

1-48: LGTM!

测试覆盖了 EncodingFixer 的主要场景:有效 UTF-8 透传、BOM 移除、空字节移除、无效 UTF-8 有损修复。测试描述使用中文,与代码库风格一致。

tests/unit/proxy/session.test.ts (1)

35-42: LGTM - 测试数据更新正确

测试 fixture 中添加的 enableResponseFixerresponseFixerConfig 字段与扩展后的 SystemSettings 类型一致。默认值合理:启用所有修复器,最大深度 200,最大大小 1MB。

src/app/v1/_lib/proxy/response-fixer/json-fixer.test.ts (1)

1-87: LGTM - JSON 修复器测试覆盖全面

测试套件质量良好,涵盖了主要场景:

  • ✓ 有效 JSON 的透传(不修改)
  • ✓ 修复未闭合的对象、数组、字符串
  • ✓ 移除尾随逗号
  • ✓ 为缺失值补充 null
  • ✓ 修复嵌套结构
  • ✓ 保护性降级(超过最大深度/大小限制时保持原样)

测试实现简洁,使用 JSON.parse 验证修复后的 JSON 有效性是一个很好的实践。

src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts (1)

1-64: LGTM - SSE 修复器测试覆盖全面

测试套件质量优秀,覆盖了 SSE 格式的主要规范化场景:

  • ✓ 有效 SSE 的透传
  • ✓ 补齐 data: 后缺失的空格
  • ✓ 为裸 JSON 行包装 data: 前缀
  • ✓ 包装 [DONE] 终止标记
  • ✓ 保留注释行
  • ✓ 修复 event/id/retry 字段的空格
  • ✓ 统一换行符为 LF
  • ✓ 修正 data: 的大小写
  • ✓ 移除冒号前的空格(data :data:
  • ✓ 合并连续空行

辅助函数 fix() 的设计简化了测试代码,提高了可读性。

src/actions/system-config.ts (2)

9-9: 导入语句正确。

ResponseFixerConfig 类型的引入符合项目规范,使用了路径别名 @/。


41-42: 新增字段实现正确。

enableResponseFixer 和 responseFixerConfig 字段的添加遵循了现有的部分更新模式,类型定义合理(使用 Partial 支持部分配置),并且正确传递到了 repository 层。缓存失效逻辑保持不变,整体实现一致性良好。

Also applies to: 64-65

tests/unit/lib/config/system-settings-cache.test.ts (1)

41-48: 测试 fixture 更新正确。

新增的 enableResponseFixer 和 responseFixerConfig 字段的默认值与数据库 schema 定义保持一致,测试夹具能够正确表示扩展后的 SystemSettings 类型。

src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts (3)

3-32: Mock 设置合理。

使用 vi.hoisted 正确提升了 mock 函数,依赖注入的 mock 实现恰当。getCachedSystemSettings 的默认返回值与系统配置一致。


34-43: 测试辅助函数实现合理。

createSession 函数创建了简化的 mock session 对象,提供了 specialSettings 的追踪能力,满足测试需求。使用 as any 进行类型断言在测试场景中是可接受的做法。


45-112: 测试用例覆盖全面。

三个测试用例分别验证了:

  1. 禁用状态下的透传行为
  2. 非流式响应的编码修复与持久化
  3. 流式 SSE 的跨 chunk 缓冲与 JSON 修复

测试覆盖了主要代码路径,断言清晰具体,同时验证了转换结果和审计追踪。

src/drizzle/schema.ts (2)

17-17: 类型导入正确。

ResponseFixerConfig 类型的导入符合项目规范,使用了路径别名 @/。


488-498: 默认值设置合理,null 处理已正确实现。

新增列的默认值设置合理:

  • enableResponseFixer 默认为 true(默认启用,符合 PR 描述)
  • responseFixerConfig 的默认配置启用了所有修复器,限制参数(maxJsonDepth: 200, maxFixSize: 1MB)合理

关于 responseFixerConfig 的 null 处理,虽然列定义时未设置 .notNull()(允许部分 DB 更新),但应用层已正确处理:

  • 转换层(toSystemSettings)将 DB 的 null 值合并为 { ...defaultResponseFixerConfig, ...(dbSettings?.responseFixerConfig ?? {}) },确保始终返回有效配置
  • 使用层(response-fixer)有防御性的 null 合并:settings.responseFixerConfig ?? DEFAULT_CONFIG
  • 更新逻辑通过配置合并模式防止 null 值

无需额外处理。

drizzle/meta/0050_snapshot.json (1)

1923-1936: Migration snapshot 与 schema 定义一致。

数据库快照正确反映了 schema.ts 中的列定义:

  • enable_response_fixer: boolean 类型,非空,默认为 true
  • response_fixer_config: jsonb 类型,可空,默认值已正确序列化

字段名称按照 PostgreSQL 约定转换为 snake_case,类型和约束都与 TypeScript schema 保持一致。

src/types/system-config.ts (3)

6-12: LGTM:ResponseFixerConfig 接口定义清晰。

接口定义与验证层保持一致,所有字段都有明确的类型定义。数值字段 maxJsonDepthmaxFixSize 在验证 schema 中已定义了合理的范围约束(1-2000 和 1024-10MB)。


43-45: LGTM:系统设置字段定义合理。

enableResponseFixerresponseFixerConfig 定义为必需字段是正确的,因为系统设置应始终具有这些值(通过默认值)。这与其他系统设置字段的模式保持一致。


80-82: LGTM:更新输入类型设计合理。

使用可选字段和 Partial<ResponseFixerConfig> 支持部分更新,这是正确的模式,与接口中其他更新字段保持一致。

src/types/special-settings.ts (2)

8-8: LGTM:类型扩展清晰。

ResponseFixerSpecialSetting 添加到 SpecialSetting 联合类型中,遵循了现有的审计跟踪模式,保持了良好的关注点分离。


28-39: LGTM:审计类型设计完善。

ResponseFixerSpecialSetting 类型结构清晰,包含了完整的审计信息:

  • 修复器类型枚举(json、sse、encoding)与实现匹配
  • 可选的 details 字段提供了灵活性
  • 性能指标(处理字节数和耗时)有助于监控和分析

该设计遵循了现有的审计模式,适合记录响应修复的详细情况。

src/app/[locale]/settings/config/_components/system-settings-form.tsx (5)

33-34: LGTM:表单属性类型更新正确。

将新字段添加到 SystemSettingsFormProps 中,保持了类型安全和一致性。


59-64: LGTM:状态初始化遵循现有模式。

新增状态管理代码与其他设置字段保持一致,类型安全且实现清晰。


84-85: LGTM:表单提交逻辑完整。

新字段正确集成到 saveSystemSettings 调用中。


101-102: LGTM:状态更新正确。

成功保存后正确更新本地状态,保持 UI 与服务器数据同步。


230-306: LGTM:UI 实现遵循最佳实践。

新增的响应修复器设置 UI 模块设计合理:

  • 遵循了现有的 UI 布局和样式模式
  • 所有用户可见文本都使用 i18n(t() 函数),符合编码规范
  • 状态更新使用函数式 setter 并正确展开先前状态
  • 嵌套选项仅在主开关启用时显示,提供了清晰的层级关系
  • 提交期间正确禁用所有开关
src/lib/validation/schemas.ts (1)

744-760: LGTM:验证规则设计合理。

响应修复器配置的验证规则设置得当:

  • maxJsonDepth 上限 2000 可防止 JSON 解析栈溢出
  • maxFixSize 范围 1KB-10MB 在防止资源滥用和实用性之间取得平衡
  • 使用 z.coerce 适合处理表单数据类型转换
  • .partial().optional() 模式支持灵活的部分更新

所有约束都具有明确的业务意义,且错误消息清晰。

src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts (6)

3-5: LGTM:UTF-8 BOM 检测实现正确。

正确检测 UTF-8 BOM 字节序列(0xEF 0xBB 0xBF),并进行了适当的长度边界检查。


7-10: LGTM:UTF-16 BOM 检测实现完整。

正确检测 UTF-16 的两种字节序(大端和小端),并进行了适当的边界检查。


12-20: LGTM:BOM 移除逻辑清晰高效。

使用 subarray 方法高效移除 BOM(无需数据拷贝),并返回详细的操作信息,便于审计和调试。


35-43: LGTM:UTF-8 验证实现正确。

使用 TextDecoderfatal: true 选项来检测无效的 UTF-8 序列是标准且可靠的方法。异常处理得当,注释清晰。


46-50: LGTM:修复检测逻辑清晰。

通过检查 BOM、空字节和 UTF-8 有效性来判断是否需要修复,逻辑正确且高效。


52-76: LGTM:修复流程设计健壮。

修复方法实现了清晰的多阶段处理流程:

  1. 早期退出优化(无需修复时)
  2. 移除 BOM 和空字节
  3. 验证中间结果的 UTF-8 有效性
  4. 有损编码回退(使用 fatal: false 确保输出始终有效)

该流程确保了输出数据的可靠性,同时通过 applied 标志和 details 字段提供了完整的审计信息。有损修复的注释也清楚说明了回退策略。

messages/ru/settings.json (1)

105-112: LGTM!

俄语翻译键与其他语言文件一致,翻译内容清晰准确,符合 i18n 规范。

src/repository/system-config.ts (2)

151-158: LGTM!

createFallbackSettings 中的默认值与 drizzle/schema.ts 中的数据库默认值保持一致,配置结构完整。


315-325: LGTM!

responseFixerConfig 的合并逻辑正确,使用展开运算符先应用当前配置再覆盖新值,支持部分更新场景。

src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts (3)

3-18: LGTM!

辅助函数实现简洁高效,直接操作字节避免了字符串编解码开销,适合处理 SSE 流数据。


163-167: canFix 中的 TextDecoder 回退逻辑处理得当。

使用 try-catch 包装 TextDecoder 是正确的,因为无效的 UTF-8 序列可能导致解码失败。这里作为最后的检测手段,返回 false 是安全的回退行为。


185-206: 行结束符处理逻辑正确。

正确处理了 CRLF、CR、LF 三种情况,并在 end > start 时才执行 end -= 1 避免越界。最终统一输出 LF 结尾。

messages/en/settings.json (1)

107-114: LGTM!

英文翻译键完整,描述清晰,与其他语言文件结构一致,符合项目 i18n 规范。

messages/ja/settings.json (1)

105-112: LGTM!

Response Fixer 功能的日语翻译正确且专业。术语使用得当:「レスポンス整流」、「エンコーディング」、「SSE 形式」、「途切れた JSON」均符合技术文档的日语表达习惯。

src/app/v1/_lib/proxy/response-fixer/index.ts (5)

1-16: LGTM!

导入结构清晰,使用了 @/ 路径别名,类型定义简洁明确。


18-69: LGTM!

辅助函数实现正确:

  • nowMs() 提供了良好的跨环境兼容性
  • cleanResponseHeaders() 正确移除了需要重新计算的 headers
  • findProcessableEnd() 的 CRLF 处理逻辑完善,避免了在换行符中间切分

71-98: LGTM!

persistSpecialSettings 实现合理:

  • 使用 fire-and-forget 模式避免阻塞响应流
  • 错误处理得当,记录日志但不中断主流程
  • 同时持久化到 Session 和 Message 两个维度,便于审计追踪

100-119: LGTM!

入口方法实现清晰:

  • 默认启用符合 PR 描述
  • SSE 检测逻辑正确
  • 流式和非流式路径分离合理

186-313: 流式处理实现良好

TransformStream 的使用正确:

  • findProcessableEnd() 确保不会在行中间切分
  • flush() 处理流结束时的剩余数据
  • 三个 Fixer 按正确顺序应用(Encoding → SSE → JSON)

同样的 header 问题也存在于 Line 306,建议一并修复。

src/app/v1/_lib/proxy/response-fixer/json-fixer.ts (5)

1-32: LGTM!

辅助函数实现正确:

  • isWhitespace 覆盖了 JSON 规范定义的所有空白字符
  • looksLikeJson 提供了高效的前置检查
  • needsNullValue 正确处理了 "key": 这种截断情况

34-46: LGTM!

类型定义和构造函数简洁明确。


48-81: LGTM!

fix 方法的双路径设计合理:

  • 快速路径避免对有效 JSON 进行不必要的处理
  • 修复后再次验证确保输出正确性
  • 失败时返回原始数据并附带诊断信息

131-149: 不匹配的闭合符号处理需确认是否符合预期

当遇到 }] 但栈顶不匹配时,当前实现会静默丢弃该字符。例如:

  • 输入 {"a": 1] → 修复后 {"a": 1}] 被丢弃,用 } 闭合)

这种行为对于修复截断 JSON 是合理的,但对于完全错误的 JSON 可能产生语义变化。如果这是预期行为,建议在注释中说明设计意图。


155-181: LGTM!

后处理逻辑顺序正确:

  1. 移除悬空的转义字符
  2. 闭合未关闭的字符串
  3. 移除尾部逗号
  4. 为缺失值补充 null
  5. 闭合所有未关闭的结构

这个顺序确保了最终输出是有效的 JSON。

Comment on lines 176 to 178
const headers = cleanResponseHeaders(response.headers);
headers.set("x-cch-response-fixer", "applied");

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

x-cch-response-fixer header 在未实际修复时也被设置为 "applied"

当前实现无论是否实际应用了修复,都会设置 x-cch-response-fixer: applied。这可能会给调试带来困惑——header 暗示进行了修复,但实际上数据未被修改。

建议根据 audit.hit 状态区分 header 值:

建议的修复方案
     const headers = cleanResponseHeaders(response.headers);
-    headers.set("x-cch-response-fixer", "applied");
+    headers.set("x-cch-response-fixer", audit.hit ? "applied" : "checked");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const headers = cleanResponseHeaders(response.headers);
headers.set("x-cch-response-fixer", "applied");
const headers = cleanResponseHeaders(response.headers);
headers.set("x-cch-response-fixer", audit.hit ? "applied" : "checked");
🤖 Prompt for AI Agents
In @src/app/v1/_lib/proxy/response-fixer/index.ts around lines 176 - 178, The
response fixer currently unconditionally sets the header "x-cch-response-fixer"
to "applied"; change this so the header reflects whether a fix was actually
applied by checking the audit result (audit.hit). In the block after calling
cleanResponseHeaders(response.headers) and computing the audit (referencing
headers, cleanResponseHeaders, and audit.hit), set
headers.set("x-cch-response-fixer", "applied") only when audit.hit is truthy,
otherwise set it to a distinct value such as "none" or "not-applied" to
accurately indicate no changes were made. Ensure this logic lives wherever the
response mutation decision occurs so the header always matches the actual
modification outcome.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR implements a comprehensive response fixer pipeline with excellent architecture, thorough test coverage, and proper error handling. The three-stage fix pipeline (Encoding -> SSE -> JSON) is well-designed with safety limits and audit trails. No significant issues identified.

PR Size: XL

  • Lines changed: 3,945 (3,916 additions + 29 deletions)
  • Files changed: 37

Split suggestions for this XL PR: The PR is cohesive as it implements a single feature with all related components. The changes are logically grouped (core module, integration, types, UI, i18n, tests). Splitting would fragment the feature unnecessarily.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Notable Implementation Quality

Strengths observed during review:

  1. Safety Limits: maxJsonDepth: 200 and maxFixSize: 1MB prevent DoS vectors
  2. Proper Error Handling: All async operations use proper .catch() with logger.error() - no silent failures
  3. Clean Buffer Management: findProcessableEnd() correctly handles CRLF boundaries in streaming
  4. Type Safety: No unsafe any usage; proper Uint8Array handling throughout
  5. Audit Trail: ResponseFixerSpecialSetting properly tracks which fixers ran
  6. Test Coverage: 87 new test cases covering edge cases (BOM, invalid UTF-8, truncated JSON)
  7. Graceful Degradation: Fixers return original data on failure rather than throwing
  8. i18n Compliance: All user-facing strings use i18n (5 languages)
  9. Database Migration: Safe backward-compatible defaults

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate (87 new tests)
  • Code clarity - Good

Automated review by Claude AI


const fixed = this.fixLine(line);
if (fixed.applied) changed = true;
out.push(...fixed.line);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [LOGIC-BUG] out.push(...fixed.line) can throw on large SSE lines

Why this is a problem: Spreading a Uint8Array into Array.prototype.push passes one argument per byte. For moderately large lines this can throw RangeError: Maximum call stack size exceeded, which will abort the TransformStream and break the streamed response.

Suggested fix:

function pushBytes(out: number[], bytes: Uint8Array): void {
  for (let i = 0; i < bytes.length; i += 1) out.push(bytes[i]);
}

// ...
pushBytes(out, fixed.line);
out.push(0x0a);

Also update the same spread pattern in src/app/v1/_lib/proxy/response-fixer/index.ts:355 and src/app/v1/_lib/proxy/response-fixer/index.ts:365.

const line = data.subarray(lineStart, i);
const fixed = ResponseFixer.fixMaybeDataJsonLine(line, jsonFixer);
if (fixed.applied) applied = true;
out.push(...fixed.line, 0x0a);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [LOGIC-BUG] out.push(...fixed.line, 0x0a) can throw on large SSE chunks

Why this is a problem: Same spread-to-push issue as in SseFixer. For larger data: lines this can throw RangeError: Maximum call stack size exceeded, which will break the response stream and fail the request.

Suggested fix:

function pushBytes(out: number[], bytes: Uint8Array): void {
  for (let i = 0; i < bytes.length; i += 1) out.push(bytes[i]);
}

pushBytes(out, fixed.line);
out.push(0x0a);

Also replace the other occurrence at src/app/v1/_lib/proxy/response-fixer/index.ts:365.


function stripBom(data: Uint8Array): { data: Uint8Array; stripped: boolean; details?: string } {
if (hasUtf8Bom(data)) {
return { data: data.subarray(3), stripped: true, details: "removed utf-8 bom" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [STANDARD-VIOLATION] details stores hardcoded display text (not i18n-safe)

Why this is a problem: CLAUDE.md requires: "i18n Required - All user-facing strings must use i18n (5 languages supported). Never hardcode display text". These details values are persisted into specialSettings and are rendered as JSON in the dashboard (e.g. src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx:120), so admins/users will see English phrases.

Suggested fix:

// Prefer machine-readable codes in stored data (no display text).
export type ResponseFixerDetailCode =
  | "removed_utf8_bom"
  | "removed_utf16_bom"
  | "lossy_utf8_decode_encode";

return { data: data.subarray(3), stripped: true, details: "removed_utf8_bom" };

Same issue exists in src/app/v1/_lib/proxy/response-fixer/json-fixer.ts:54 and related details strings.


fix(data: Uint8Array): FixResult<Uint8Array> {
if (data.length > this.maxSize) {
return { data, applied: false, details: "exceeded max size" };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [STANDARD-VIOLATION] Hardcoded details string is user-visible without i18n

Why this is a problem: CLAUDE.md requires: "i18n Required - All user-facing strings must use i18n (5 languages supported). Never hardcode display text". FixResult.details is propagated into specialSettings (ResponseFixer audit) and shown as JSON in the dashboard (e.g. src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx:120).

Suggested fix:

// Use stable codes (or i18n keys) instead of display text.
return { data, applied: false, details: "exceeded_max_size" };
// ...
return { data, applied: false, details: "repair_failed" };

Consider typing details as a union (e.g. "exceeded_max_size" | "repair_failed" | ...) in src/types/special-settings.ts to prevent regressions.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR introduces a response-fixer pipeline (encoding/SSE/truncated JSON) wired into the proxy response handler, plus system settings, migrations, and UI to control it. The change set is XL and includes generated Drizzle metadata, which increases review/rollback risk. Two high-priority issues were found: a potential runtime failure in SSE rewriting and an i18n policy violation for user-visible audit details.

PR Size: XL

  • Lines changed: 3945
  • Files changed: 37
  • Split suggestion: (1) Drizzle migration + system settings plumbing, (2) response-fixer/ core + unit tests, (3) dashboard/settings UI + i18n message updates.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 1 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

None.

High Priority Issues (Should Fix)

  1. [HIGH] [LOGIC-BUG] out.push(...fixed.line) / out.push(...fixed.line, 0x0a) can throw RangeError for larger SSE lines, breaking the stream (src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts:224, src/app/v1/_lib/proxy/response-fixer/index.ts:355, src/app/v1/_lib/proxy/response-fixer/index.ts:365). Confidence: 85.
  2. [MEDIUM] [STANDARD-VIOLATION] Hardcoded English details strings are stored into specialSettings and surfaced in the dashboard without i18n (src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts:14, src/app/v1/_lib/proxy/response-fixer/json-fixer.ts:54). Confidence: 80.

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Identified open PR #570 (“feat: port FluxFix response fixer”), classified as XL (3945 lines changed, 37 files), and applied label size/XL.
  • Posted 4 inline review comments and submitted the required summary review on PR #570.

High-priority issues raised (diff lines only)

  • [HIGH] [LOGIC-BUG] out.push(...fixed.line) / out.push(...fixed.line, 0x0a) can throw RangeError on larger SSE lines and break streaming responses (src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts:224, src/app/v1/_lib/proxy/response-fixer/index.ts:355, src/app/v1/_lib/proxy/response-fixer/index.ts:365).
  • [MEDIUM] [STANDARD-VIOLATION] Hardcoded English details strings are persisted into specialSettings and surfaced in the dashboard without i18n (src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts:14, src/app/v1/_lib/proxy/response-fixer/json-fixer.ts:54).

@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts (2)

28-37: 可考虑添加注释说明多步修复过程。

该测试依赖多步修复:先移除 UTF-16 BOM (0xff 0xfe),再移除空字节 (0x00),最终得到有效的 UTF-8 字节 0x41 ("A")。虽然测试逻辑正确,但添加注释说明这一多步处理流程可提高可读性。

💡 可选的注释补充
  test("应移除 UTF-16 BOM", () => {
-   // UTF-16LE BOM + "A"(0x41 0x00)
+   // UTF-16LE BOM + "A"(0x41 0x00)
+   // 修复过程:移除 BOM [0xff 0xfe] → 移除空字节 [0x00] → 得到 UTF-8 "A" [0x41]
    const input = new Uint8Array([0xff, 0xfe, 0x41, 0x00]);

49-58: 可选增强:验证 details 字段以提高测试覆盖率。

当前测试正确验证了无效 UTF-8 被有损修复为可解码的有效 UTF-8。根据实现代码,fix() 方法还会返回 details 字段(例如 "lossy_utf8_decode_encode")。可考虑在断言中验证该字段,以确保修复器提供了正确的审计信息。

♻️ 可选的增强方案
  test("无效 UTF-8 应被有损修复为可解码 UTF-8", () => {
    // 0xC3 0x28 是无效 UTF-8 序列
    const input = new Uint8Array([0xc3, 0x28, 0x61]);

    const fixer = new EncodingFixer();
-   const { data, applied } = fixer.fix(input);
+   const { data, applied, details } = fixer.fix(input);

    expect(applied).toBe(true);
+   expect(details).toBe("lossy_utf8_decode_encode");
    expect(() => new TextDecoder("utf-8", { fatal: true }).decode(data)).not.toThrow();
  });
src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts (2)

17-25: 建议扩展 applied 标志的验证覆盖

该测试很好地验证了无需修复时的零拷贝优化(applied=falseres.data === input)。建议在其他修复场景(如 lines 27-29、38-39 等)中也增加对 applied=true 的断言,以确保修复器正确报告是否进行了修改。

♻️ 示例:在修复场景中验证 applied 标志

可在现有测试中添加对 applied 标志的检查,例如:

test("data: 后缺失空格应补齐", () => {
  const fixer = new SseFixer();
  const encoder = new TextEncoder();
  const input = encoder.encode('data:{"test": true}\n');
  const res = fixer.fix(input);
  
  expect(res.applied).toBe(true);
  expect(new TextDecoder().decode(res.data)).toBe('data: {"test": true}\n');
});

12-81: 测试覆盖全面,可选增强边缘场景

测试套件很好地覆盖了 SSE 修复的核心场景:空格修复、JSON 包装、换行符规范化、大小写处理和空行合并。建议考虑添加以下边缘场景测试以进一步提升健壮性:

  • 空输入(""
  • 仅包含换行符的输入("\n\n"
  • 不匹配任何 SSE 模式的纯文本行
  • 末尾缺失换行符的输入

这些是可选的增强,当前覆盖已足够验证主要功能。

src/app/v1/_lib/proxy/response-handler.ts (1)

61-75: 建议优化错误日志消息的通用性

第 66 行的错误消息中包含了 ResponseFixer 的内部实现细节(getCachedSystemSettings/processNonStream)。如果 ResponseFixer 的内部实现发生变化,这条消息可能会产生误导。

建议使用更通用的描述,例如:"[ResponseHandler] ResponseFixer failed to process response",以提高代码的可维护性。

♻️ 可选的优化建议
       logger.error(
-        "[ResponseHandler] ResponseFixer failed (getCachedSystemSettings/processNonStream)",
+        "[ResponseHandler] ResponseFixer failed to process response",
         {
           error: error instanceof Error ? error.message : String(error),
           sessionId: session.sessionId ?? null,

不过,当前的错误处理逻辑是正确的:捕获异常后记录详细上下文,并安全回退到原始响应。这确保了即使 ResponseFixer 出错,请求处理也能继续进行。

src/app/v1/_lib/proxy/response-fixer/json-fixer.ts (1)

36-48: 考虑添加配置参数校验

当前构造函数直接接受配置值,未验证 maxDepthmaxSize 是否为正数。虽然这是内部配置,但添加基本校验可防止潜在的配置错误(例如负值或零值可能导致意外行为)。

♻️ 可选的参数校验建议
 constructor(config: JsonFixerConfig) {
+  if (config.maxDepth <= 0 || config.maxSize <= 0) {
+    throw new Error("maxDepth and maxSize must be positive numbers");
+  }
   this.maxDepth = config.maxDepth;
   this.maxSize = config.maxSize;
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1f64c7a and 071d2f7.

📒 Files selected for processing (10)
  • biome.json
  • src/actions/my-usage.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts
  • src/app/v1/_lib/proxy/response-handler.ts
✅ Files skipped from review due to trivial changes (1)
  • biome.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/v1/_lib/proxy/response-fixer/index.ts
  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

No emoji characters in any code, comments, or string literals

Files:

  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/actions/my-usage.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,jsx,js}: All user-facing strings must use i18n (5 languages supported: zh-CN, en, ja, ko, de). Never hardcode display text
Use path alias @/ to map to ./src/
Use Biome for code formatting with configuration: double quotes, trailing commas, 2-space indent, 100 character line width
Prefer named exports over default exports
Use next-intl for internationalization
Use Next.js 16 App Router with Hono for API routes

Files:

  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/actions/my-usage.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place unit tests in tests/unit/, integration tests in tests/integration/, and source-adjacent tests in src/**/*.test.ts

Files:

  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
🧠 Learnings (4)
📚 Learning: 2026-01-07T17:05:36.362Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T17:05:36.362Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Place unit tests in tests/unit/, integration tests in tests/integration/, and source-adjacent tests in src/**/*.test.ts

Applied to files:

  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
📚 Learning: 2026-01-05T03:01:39.354Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/types/user.ts:158-170
Timestamp: 2026-01-05T03:01:39.354Z
Learning: In TypeScript interfaces, explicitly document and enforce distinct meanings for null and undefined. Example: for numeric limits like limitTotalUsd, use 'number | null | undefined' when null signifies explicitly unlimited (e.g., matches DB schema or special UI logic) and undefined signifies 'inherit default'. This pattern should be consistently reflected in type definitions across related fields to preserve semantic clarity between database constraints and UI behavior.

Applied to files:

  • src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
  • src/actions/my-usage.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts
  • src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts
📚 Learning: 2026-01-07T17:05:36.362Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T17:05:36.362Z
Learning: Applies to **/*.{ts,tsx,jsx,js} : All user-facing strings must use i18n (5 languages supported: zh-CN, en, ja, ko, de). Never hardcode display text

Applied to files:

  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
📚 Learning: 2026-01-05T03:02:06.594Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 539
File: src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx:66-66
Timestamp: 2026-01-05T03:02:06.594Z
Learning: In the claude-code-hub project, the translations.actions.addKey field in UserKeyTableRowProps is defined as optional for backward compatibility, but all actual callers in the codebase provide the complete translations object. The field has been added to all 5 locale files (messages/{locale}/dashboard.json).

Applied to files:

  • src/app/v1/_lib/proxy/response-fixer/json-fixer.ts
🧬 Code graph analysis (6)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts (1)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts (1)
  • EncodingFixer (62-95)
src/app/v1/_lib/proxy/response-fixer/json-fixer.ts (2)
src/lib/polyfills/file.ts (1)
  • bytes (28-30)
src/app/v1/_lib/proxy/response-fixer/types.ts (1)
  • FixResult (1-5)
src/actions/my-usage.ts (1)
src/repository/usage-logs.ts (1)
  • UsageLogFilters (10-27)
src/app/v1/_lib/proxy/response-handler.ts (2)
src/app/v1/_lib/proxy/response-fixer/index.ts (1)
  • ResponseFixer (211-558)
src/lib/logger.ts (1)
  • logger (168-187)
src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts (2)
src/app/v1/_lib/proxy/response-fixer/index.ts (1)
  • ResponseFixer (211-558)
src/lib/polyfills/file.ts (2)
  • stream (36-38)
  • text (40-42)
src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts (3)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.ts (1)
  • fix (69-94)
src/app/v1/_lib/proxy/response-fixer/json-fixer.ts (1)
  • fix (54-83)
src/app/v1/_lib/proxy/response-fixer/sse-fixer.ts (2)
  • fix (200-311)
  • SseFixer (171-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🌐 API Tests
  • GitHub Check: Docker Build Test
  • GitHub Check: dev-build-deploy
🔇 Additional comments (13)
src/app/v1/_lib/proxy/response-fixer/encoding-fixer.test.ts (3)

6-15: 测试逻辑正确,验证了有效 UTF-8 的透传行为。

该测试正确验证了 EncodingFixer 对有效 UTF-8 输入不做修改,且 applied 标志为 false。测试覆盖了基本的正向场景。


17-26: 正确验证了 UTF-8 BOM 的移除。

测试准确构造了 UTF-8 BOM 前缀 (0xef 0xbb 0xbf),并验证了修复器能够正确移除 BOM 并标记 applied=true


39-47: 空字节移除测试实现正确。

测试清晰验证了 EncodingFixer 能够识别并移除数据中的空字节(0x00),输出结果符合预期。

src/app/v1/_lib/proxy/response-fixer/sse-fixer.test.ts (2)

5-10: 辅助函数设计合理

fix() 辅助函数很好地简化了测试代码,将编码、修复和解码步骤封装起来,使测试用例更易读。不过它隐藏了 applied 标志,对于需要明确验证该字段的场景,仍需直接调用 fixer.fix()(如 lines 17-25 所示)。


1-81: 测试结构和覆盖率符合要求

该测试文件遵循了项目的测试模式和编码规范:

  • 正确放置在源代码相邻位置(src/**/*.test.ts
  • 使用 Vitest 框架和命名导入
  • 测试覆盖了 SseFixer 的主要修复场景
  • 测试结构清晰,每个用例聚焦单一行为

测试套件与 encoding-fixer.test.tsjson-fixer.test.ts 的模式保持一致,为响应修复器管道提供了可靠的单元测试保障。

src/app/v1/_lib/proxy/response-handler.ts (2)

1-1: 导入语句正确

ResponseFixer 的导入使用了路径别名 @/,符合项目规范。


77-84: 正确使用了修复后的响应

代码正确地将 fixedResponse 用于:

  1. 内容类型检测(第 77 行)
  2. 传递给非流式和流式处理器(第 81、84 行)

这确保了所有下游处理都基于 ResponseFixer 处理后的响应,符合 PR 的设计意图:在格式转换之前先进行响应修复。

src/app/v1/_lib/proxy/response-fixer/json-fixer.ts (3)

1-24: 实现合理,逻辑正确

工具函数实现简洁高效,looksLikeJson 的早期返回优化得当,removeTrailingComma 的原地修改策略适合修复场景。


50-83: 三层策略设计合理

快速路径、大小检查和修复路径的分层设计清晰高效,applied 标志和 details 字段便于调用方审计。快速路径优化减少了对有效 JSON 的不必要处理。


85-184: 修复算法实现严谨且覆盖面广

状态机设计清晰,正确处理了多种截断场景:

  • 转义序列、未闭合字符串、尾随逗号
  • 缺失值的键(补 null)、未闭合的嵌套结构
  • 深度限制防止栈溢出,不匹配的闭合符被安全忽略

字节级解析对 UTF-8 多字节字符透明兼容(字符串内容原样保留,仅处理 ASCII 结构字符)。

src/app/v1/_lib/proxy/response-fixer/response-fixer.test.ts (3)

1-43: 测试基础设施配置合理

使用 vi.hoisted 进行 mock 提升符合 vitest 最佳实践,测试 1 和测试 5 通过 mockResolvedValueOnce 实现了适当的隔离,createSession 辅助函数简化了测试用例编写。


45-134: 前 4 个测试用例覆盖核心场景

测试用例设计全面:

  • 功能禁用时的透传行为(测试 1)
  • 非流式响应的编码修复与持久化(测试 2)
  • 流式 SSE 跨块缓冲与 JSON 修复(测试 3)
  • 有效输入不触发审计记录(测试 4)

断言充分验证了响应内容、HTTP 头部和 specialSettings 的写入行为。


136-182: 边界测试设计巧妙,有效验证降级机制

测试 5 通过保持流不关闭模拟病态场景,使用 Promise.race 验证降级策略能及时输出而非无限缓冲,清理逻辑(reader.cancelreadPromise.catch)确保测试不会悬挂。这正确验证了 PR 目标中提到的 maxFixSize: 1MB 安全限制。

Comment on lines +398 to +407
const parsedStart = filters.startDate
? new Date(`${filters.startDate}T00:00:00`).getTime()
: Number.NaN;
const parsedEnd = filters.endDate
? new Date(`${filters.endDate}T00:00:00`).getTime()
: Number.NaN;

const startTime = Number.isFinite(parsedStart) ? parsedStart : undefined;
// endTime 使用“次日零点”作为排他上界(created_at < endTime),避免 23:59:59.999 的边界问题
const endTime = Number.isFinite(parsedEnd) ? parsedEnd + 24 * 60 * 60 * 1000 : undefined;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its size
wc -l src/actions/my-usage.ts

Repository: ding113/claude-code-hub

Length of output: 94


🏁 Script executed:

# Read the specific lines mentioned in the review (398-407)
sed -n '395,410p' src/actions/my-usage.ts

Repository: ding113/claude-code-hub

Length of output: 745


🏁 Script executed:

# Check lines 544-553 for the claimed duplication
sed -n '540,555p' src/actions/my-usage.ts

Repository: ding113/claude-code-hub

Length of output: 716


🏁 Script executed:

# Search for timezone or date parsing related code
rg -n "timezone|timeZone|TZ|UTC|Date.*T00:00:00" src/actions/my-usage.ts -C 2

Repository: ding113/claude-code-hub

Length of output: 613


🏁 Script executed:

# Search for date utility functions across the codebase
fd -e ts -e js src/lib | head -20
rg -l "date|time" src/lib --type ts | head -10

Repository: ding113/claude-code-hub

Length of output: 742


🏁 Script executed:

# Check for existing date parsing helpers
rg -n "parseDateString|parseDate|dateString" --type ts

Repository: ding113/claude-code-hub

Length of output: 2123


🏁 Script executed:

# Search for hardcoded Chinese strings or comments in the file
rg -n "次日零点|边界问题|startDate|endDate" src/actions/my-usage.ts -C 1

Repository: ding113/claude-code-hub

Length of output: 1378


🏁 Script executed:

# Check the existing date utility file
cat src/lib/webhook/utils/date.ts

Repository: ding113/claude-code-hub

Length of output: 534


🏁 Script executed:

# Check the dateStringToTimestamp in usage-logs-filters.tsx
sed -n '270,310p' src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx

Repository: ding113/claude-code-hub

Length of output: 1960


🏁 Script executed:

# Search for UTC usage in the codebase
rg -n "UTC|utc" src/actions/my-usage.ts -C 2

Repository: ding113/claude-code-hub

Length of output: 49


🏁 Script executed:

# Check if there are timezone configuration or constants
rg -n "dailyResetTime|timezone|timeZone" src --type ts -C 2 | head -50

Repository: ding113/claude-code-hub

Length of output: 2471


🏁 Script executed:

# Check the complete parseDate function in date-picker-field
sed -n '44,60p' src/components/form/date-picker-field.tsx

Repository: ding113/claude-code-hub

Length of output: 593


🏁 Script executed:

# Search for i18n usage in the file to verify guideline compliance
rg -n "useTranslations|t\(" src/actions/my-usage.ts

Repository: ding113/claude-code-hub

Length of output: 1835


🏁 Script executed:

# Check how the parsed timestamps are used - are they stored or compared?
sed -n '408,430p' src/actions/my-usage.ts

Repository: ding113/claude-code-hub

Length of output: 737


🏁 Script executed:

# Check the database schema or repository function to understand timestamp handling
rg -n "created_at|createdAt" src/repository/statistics.ts -C 2 | head -30

Repository: ding113/claude-code-hub

Length of output: 1700


🏁 Script executed:

# Check if there are other server-side date parsing that use UTC
rg -n "new Date.*getTime|Date\.UTC" src/actions --type ts | head -20

Repository: ding113/claude-code-hub

Length of output: 1161


🏁 Script executed:

# Verify if the app actually runs on a specific timezone or is timezone-agnostic
rg -n "process\.env|TZ|timezone" src --type ts -g '!node_modules' | grep -i timezone

Repository: ding113/claude-code-hub

Length of output: 15680


🏁 Script executed:

# Check the repository function that processes these timestamps
fd -name "statistics.ts" src/repository

Repository: ding113/claude-code-hub

Length of output: 297


🏁 Script executed:

# Check how findUsageLogsWithDetails handles the timestamps
grep -n "findUsageLogsWithDetails\|export.*findUsageLogsWithDetails" src/repository -r --include="*.ts" -A 10 | head -40

Repository: ding113/claude-code-hub

Length of output: 1312


🏁 Script executed:

# Verify the complete function signature and how it uses startTime/endTime
rg -n "interface UsageLogFilters|type UsageLogFilters" src/repository --type ts -A 10

Repository: ding113/claude-code-hub

Length of output: 710


🏁 Script executed:

# Check if there are environment variable configs showing TZ setup
rg -n "getEnvConfig|process\.env\.TZ" src/lib/config --type ts -A 5 | head -30

Repository: ding113/claude-code-hub

Length of output: 1317


🏁 Script executed:

# Check the EnvSchema definition to see TZ configuration
sed -n '1,126p' src/lib/config/env.schema.ts | grep -A 20 "TZ\|timezone"

Repository: ding113/claude-code-hub

Length of output: 924


🏁 Script executed:

# Get more context on how timestamps are actually used in the query
sed -n '303,400p' src/repository/usage-logs.ts

Repository: ding113/claude-code-hub

Length of output: 3970


🏁 Script executed:

# Check if there are comments explaining the timestamp semantics
sed -n '14,20p' src/repository/usage-logs.ts

Repository: ding113/claude-code-hub

Length of output: 263


提取日期解析逻辑为共享函数,并改进时区处理

getMyUsageLogsgetMyStatsSummary 中的日期解析代码重复(第 398-407 行和第 544-553 行完全相同)。更重要的是,当前实现存在时区处理问题:

new Date(\${date}T00:00:00`).getTime()在 Node.js 中会根据**服务器的进程时区**解析日期字符串。虽然环境变量TZ默认设置为Asia/Shanghai`,但这意味着:

  • 若服务器实际运行的时区与 TZ 环境变量不一致,或用户在不同时区,就会导致日期边界错误
  • 例如:美国用户选择 2024-01-15,如果服务器不在 Asia/Shanghai 时区,生成的时间戳可能偏差一天

建议提取为共享的日期解析函数,并改用显式的 UTC 时间戳或时区感知的工具(如 date-fns-tz)确保跨时区的正确性。参考 src/lib/rate-limit/time-utils.ts 中的时区处理模式。

@ding113 ding113 merged commit 33c5247 into dev Jan 9, 2026
11 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 9, 2026
@ding113 ding113 deleted the feat/response-fixer branch January 27, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:i18n enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant