Skip to content

release v0.3.42#553

Closed
ding113 wants to merge 27 commits intomainfrom
dev
Closed

release v0.3.42#553
ding113 wants to merge 27 commits intomainfrom
dev

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 6, 2026

Summary

Release v0.3.42 from dev to main. This release includes significant new features, bug fixes, and UI improvements across the proxy pipeline, provider management, session handling, and admin panel.

Highlights

🆕 New Features

🔧 Bug Fixes

🎨 UI/UX Improvements

🧪 Testing

Related Issues

Fixed:

Related:

Database Migrations

This release includes 4 new migrations:

  • 0044_uneven_donald_blake.sql - Add intercept_anthropic_warmup_requests to system_settings
  • 0045_mushy_human_torch.sql - Add provider total limit columns
  • 0046_woozy_dark_phoenix.sql - Add Codex override preference columns
  • 0047_fix_system_settings_columns.sql - System settings column fixes

Breaking Changes

None. All changes are backward compatible.

Included PRs

PR Title
#552 fix: ensure /v1/responses and /v1/chat/completions use GuardPipeline
#551 fix: add error rule for tool_use missing tool_result
#547 feat(error-rules): add Gemini non-streaming error rule
#546 fix: provider list not refreshing after adding new provider
#545 docs: fix Codex model config in usage-doc
#544 fix: add error rule for Codex reasoning effort mismatch
#539 refactor: 拆分 unified-edit-dialog 为专用对话框组件
#538 fix(ui): admin panel UI improvements
#537 fix: Session Messages 提升内容阈值并导出完整请求
#536 feat: Codex 供应商级覆写 reasoning/text/parallel_tool_calls
#535 fix: provider total limit editable + enforce + reset
#534 fix: Key 清除到期时间使用空字符串传参
#533 fix: 清除用户/Key 到期时间后保存不生效
#532 fix: 修复 my-usage 今日统计与只读 API 自助查询
#531 fix: 用户限额未设置时 Key 限额仍生效
#528 test(models): 为 available-models 工具函数添加单元测试
#527 fix: 修复 CodeRabbit webhook 反馈
#525 feat: 拦截 Anthropic Warmup 请求

Files Changed

99 files changed across:

  • Core proxy pipeline (src/app/v1/_lib/)
  • Admin dashboard components (src/app/[locale]/dashboard/)
  • Settings pages (src/app/[locale]/settings/)
  • Repository layer (src/repository/)
  • Rate limiting service (src/lib/rate-limit/)
  • i18n translations (5 locales: en, ja, ru, zh-CN, zh-TW)

Checklist

  • All CI checks pass
  • No breaking changes
  • Database migrations included
  • i18n translations complete

Description enhanced by Claude AI

ding113 and others added 20 commits January 4, 2026 11:32
* fix: my-usage stats and readonly usage API

* fix: bearer-only my-usage auth
* feat: add Codex provider-level overrides (#404)

* fix: align context1m default and tooltip provider scope

* chore(db): regenerate drizzle migration for codex provider overrides
* docs: fix Codex model config in usage-doc (#542)

* test: fix Biome formatting in system-config update test
Add new error rule to handle the case where a `tool_use` block is missing
its corresponding `tool_result` in the next message. This is the reverse
direction of the existing rule for `tool_result` missing `tool_use`.

The new pattern matches:
- `tool_use` ids were found without `tool_result` blocks immediately after
- Each `tool_use` block must have a corresponding `tool_result` block

Closes #550
* feat(error-rules): add Gemini non-streaming error rule

部分服务商仅支持流式的 gemini 请求, 这种情况下需要返回 400 状态码, 以便 gemini 检测到 generateContent 不可用时, 自动切换到 streamGenerateContent

Signed-off-by: Kevin Cui <bh@bugs.cc>

* fix: lint

Signed-off-by: Kevin Cui <bh@bugs.cc>

---------

Signed-off-by: Kevin Cui <bh@bugs.cc>
Move AddProviderDialog creation from Server Component (page.tsx) to
Client Component (ProviderManagerLoaderContent) to ensure useQueryClient()
correctly accesses the QueryClient instance within QueryClientProvider.

This fixes the issue where invalidateQueries() calls did not trigger
React Query to refetch data after adding a new provider.
* refactor: 拆分 unified-edit-dialog 为专用对话框组件 (#413)

将 1120 行的 unified-edit-dialog.tsx 拆分为 4 个单一职责组件:
- CreateUserDialog: 创建用户 + 首个密钥
- EditUserDialog: 编辑用户信息
- AddKeyDialog: 添加新密钥
- EditKeyDialog: 编辑单个密钥

主要改动:
- 提取共享逻辑到 hooks/ (翻译、模型建议)
- 提取工具函数到 utils/ (表单处理、provider group)
- 新增 getModelSuggestionsByProviderGroup action
- 修复 expiresAt 清除时传参问题 (使用空字符串)
- 添加单元测试覆盖新组件

BREAKING CHANGE: 删除 unified-edit-dialog.tsx,引用需更新为新组件

* fix: 为 Key Dialog 组件定义精确类型,消除 as any

- 新增 KeyDialogUserContext 类型,替代过于宽泛的 User 类型
- 移除 4 处 as any 类型断言
- 清理 key-list-header.tsx 中伪造的字段

Addresses Gemini Code Review feedback on type safety
…552)

* fix: ensure openai endpoints use GuardPipeline

* fix: avoid concurrent count mismatch on pipeline early return
@greptile-apps
Copy link

greptile-apps bot commented Jan 6, 2026

Skipped: This PR changes more files than the configured file change limit: (146 files found, 100 file limit)

@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

引入数据库迁移与 schema 字段(提供商总消费、Codex 偏好、预热拦截等)、广泛在查询/统计中排除 warmup、代理路径加入 warmup 拦截与管道、提供商级别覆盖与限制、用户管理 UI 拆分、新 i18n 文案与大量测试/工具脚本。

Changes

内聚组 / File(s) 变更摘要
仓库元数据 / 忽略与脚本
/.gitignore, package.json
增加三个 coverage 脚本并将对应输出目录加入 .gitignore。
DB 迁移与 schema 快照
drizzle/*.sql, drizzle/meta/*.json, src/drizzle/schema.ts
新增列 intercept_anthropic_warmup_requestslimit_total_usdtotal_cost_reset_at、若干 codex_* 偏好列,并新增 message_request 部分索引与多个快照文件。
系统设置与类型
src/types/system-config.ts, src/repository/system-config.ts, src/lib/config/system-settings-cache.ts
interceptAnthropicWarmupRequests 纳入 SystemSettings、读写与兼容回退逻辑,测试覆盖新增字段。
预热 (warmup) 拦截与排除
src/app/v1/_lib/proxy/warmup-guard.ts, src/app/v1/_lib/proxy/session.ts, src/app/v1/_lib/proxy/guard-pipeline.ts, src/repository/_shared/message-request-conditions.ts
新增 ProxyWarmupGuard、ProxySession.isWarmupRequest、在 guard-pipeline 注入 warmup 步骤;新增 EXCLUDE_WARMUP_CONDITION 并在查询/统计处复用以排除 warmup。
统计/仓库层调整(排除 warmup)
src/repository/* (message, statistics, usage-logs, key, leaderboard, overview, provider 等)
在多处聚合与统计查询中应用 EXCLUDE_WARMUP_CONDITION,新增 sumProviderTotalCost、provider 统计与 reset 等函数。
提供商功能与 Codex 覆盖
src/repository/provider.ts, src/actions/providers.ts, src/lib/codex/provider-overrides.ts, src/app/v1/_lib/proxy/forwarder.ts, src/lib/rate-limit/service.ts
提供商暴露 limitTotalUsd/totalCostResetAt 与 codex_* 偏好字段;新增 applyCodexProviderOverrides 并在 forwarder 应用;新增跨实例缓存失效广播与重置总用量接口;扩展总费限流检查与缓存键。
API / 认证 / my-usage
src/app/api/actions/[...route]/route.ts, src/lib/api/action-adapter-openapi.ts, src/lib/auth.ts, src/actions/my-usage.ts
注册 my-usage 只读路由、支持 Authorization: Bearer(bearerAuth)与 allowReadOnlyAccess 选项,并在 my-usage 查询中排除 warmup,替换部分查询表达式。
前端:用户管理 UI 拆分
src/app/[locale]/dashboard/_components/user/*
拆分 UnifiedEditDialog 为 AddKeyDialog/CreateUserDialog/EditKeyDialog/EditUserDialog;新增 hooks(use-key-translations、use-user-translations、use-model-suggestions)、表单/列表与若干 util。
前端:会话/消息导出与 CodeDisplay
src/app/.../sessions/*, src/components/ui/code-display.tsx, src/app/[locale]/dashboard/sessions/.../session-messages-client.tsx
将消息导出聚焦为请求导出(copy/download request JSON);CodeDisplay 支持可配置 maxContentBytes/maxLines 并在超限时提供内联下载。
前端:提供商表单与校验工具
src/app/[locale]/settings/providers/_components/*, src/lib/utils/validation/provider.ts, src/lib/utils/index.ts
Provider 表单新增 limit_total_usd 与 codex 覆盖 UI、GROUP_TAG_MAX_TOTAL_LENGTH 校验;新增 validatePositiveDecimalField 并导出。
国际化与文案
messages/*/dashboard.json, messages/*/settings.json, 多语言文件
增加 skipped、hardLimit、生成密钥成功、codexOverrides、resetUsage 与 headers 校验相关文案(多语种)。
测试与工具
tests/**, tests/api/*, tests/integration/*
新增/扩展大量单元与集成测试(my-usage、action-adapter-openapi、session messages、code-display、validatePositiveDecimalField 等);新增测试脚本入口并更新测试用例以覆盖新功能。
小幅前端调整与文档
多个前端文件(provider-manager、provider-rich-list-item、user 表等)、README.md, README.en.md, CLAUDE.md, AGENTS.md
UI 行为与按钮(如重置总用量)、文档及 README 更新(赞助/折扣/新文档文件)。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #535:提供商总消费限制与重置功能(limit_total_usd、total_cost_reset_at)及相关成本聚合/UI 改动,代码层高度重叠。
  • PR #536:Codex 覆盖相关改动(codex_* 偏好、applyCodexProviderOverrides、forwarder 集成)直接相关。
  • PR #539:用户管理 UI 拆分(移除 unified-edit-dialog,新增多 dialog 与 hooks)与本次拆分实现相同改动。
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.95% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题明确指出这是release v0.3.42,与changeset的发布版本内容相关,但过于简洁。
Description check ✅ Passed PR描述详细说明了新功能、错误修复、UI改进、测试和数据库迁移,与changeset内容高度相关。

✏️ 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 dev

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 6, 2026

🧪 测试结果

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

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

@github-actions github-actions bot added the size/XL Extra Large PR (> 1000 lines) label Jan 6, 2026
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: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/v1/_lib/converters/openai-to-codex/request.ts (1)

17-17: 更新文档注释以反映 parallel_tool_calls 的可配置性

Line 17 的注释声明"强制设置:...parallel_tool_calls=true",但实际代码(lines 129-131)现在允许客户端覆盖此值。建议将注释更新为更准确的描述。

🔎 建议的文档修正
- * - 强制设置:stream=true, store=false, parallel_tool_calls=true
+ * - 强制设置:stream=true, store=false
+ * - 默认设置:parallel_tool_calls=true(可由客户端覆盖)
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)

155-170: 标签文本与反转逻辑不匹配,应调整以提高可读性

翻译文本在所有 5 个语言环境中都一致,但标签 t("canLoginWebUi.label") 的措辞与反转的开关逻辑产生了认知冲突:

  • 当前标签:"允许登录 Web UI"(所有语言版本遵循此逻辑)
  • 实际行为:打开开关 → canLoginWebUi=false禁用 Web UI 登录
  • 期望行为:标签应清晰表达"启用独立余额页面"或"禁用 Web UI 访问"

描述文本 t("canLoginWebUi.description") 已准确解释了行为("关闭后,此 Key 仅可用于 API 调用,无法登录管理后台"),但用户可能不会立即阅读描述。建议重新命名标签以消除歧义,使用户能够直观理解开关的实际作用。

🤖 Fix all issues with AI Agents
In @.gitignore:
- Around line 15-16: The .gitignore is missing the coverage directory produced
by the new npm script; add the entry "/coverage-my-usage" to .gitignore so the
output from the newly added "test:coverage:my-usage" script is ignored; update
the file by adding that line alongside the existing /coverage-quota and
/coverage-proxy-guard-pipeline entries.

In @messages/ru/dashboard.json:
- Around line 755-760: The Russian copy is inconsistent: generatedKey.hint ("Вы
можете просмотреть и скопировать этот ключ в любое время из списка ключей")
contradicts keyCreatedDialog.description ("так как он будет shown только один
раз"); confirm actual behavior with product/backend and then make the texts
consistent—either update generatedKey.hint to a non-misleading phrase like "Вы
можете управлять этим ключом в списке ключей (полный ключ показывается только
один раз)" or, if keys can be viewed again, update keyCreatedDialog.description
to reflect that; ensure you modify the string keys generatedKey.hint and/or
keyCreatedDialog.description accordingly across all locales.

In @messages/zh-CN/dashboard.json:
- Around line 773-778: The generatedKey.hint currently contradicts the one-time
display messages (keyCreatedDialog.description and warningText); update
generatedKey.hint to reflect that the full key is shown only once and instruct
the user to copy/save it now (e.g., "此密钥仅显示一次,请立即复制并妥善保存"), and apply the same
correction across all five language variants (en/ja/ru/zh-CN/zh-TW) so the hint
aligns with keyCreatedDialog.description and warningText.

In @messages/zh-TW/dashboard.json:
- Around line 753-758: The UI messages are contradictory:
keyCreatedDialog.description says the key is shown only once while
generatedKey.hint claims it can be viewed anytime; update the localized strings
to one consistent policy. Edit the zh-TW entries (keys:
keyCreatedDialog.description, generatedKey.hint, and generatedKey.label if
needed) to state that the full API key is shown only once and should be copied
immediately, but the key can be managed later in the key list (enable/disable,
quotas); make the same change in the zh-CN locale to keep both translations
consistent.

In @src/actions/my-usage.ts:
- Around line 23-24: Extract the duplicate warmup-filter constant and logic into
a shared DB query utility (e.g., create a function/constant like
EXCLUDE_WARMUP_CONDITION or buildExcludeWarmupFilter in a new shared module) and
replace all in-file duplicates (references to EXCLUDE_WARMUP_CONDITION and
inline excludeWarmup logic that uses messageRequest.blockedBy) to import and
reuse that utility so the same SQL fragment is centralized and consistent;
additionally, add a migration that creates an index on the
message_request.blocked_by column (matching deleted_at IS NULL) to improve query
performance as suggested.

In @src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx:
- Around line 153-185: The three async handlers (handleDisableUser,
handleEnableUser, handleDeleteUser) currently throw errors but lack internal
try-catch; wrap each handler body in try { await toggleUserEnabled(...) or
removeUser(...); check res.ok and show toast.success, call onSuccess?.(),
queryClient.invalidateQueries(["users"]), router.refresh(), and for delete also
call onOpenChange(false) } catch (err) { show a user-facing toast.error using
err.message or t("editDialog.operationFailed") / t("editDialog.deleteFailed") }
to prevent unhandled Promise rejections and ensure failures are surfaced to the
UI instead of throwing.

In @src/app/[locale]/dashboard/_components/user/utils/form-utils.ts:
- Around line 5-9: The getFirstErrorMessage function should accept an immutable,
defensive type: change the parameter to a readonly/partial map (e.g.,
Readonly<Partial<Record<string, string | undefined>>>) so callers cannot mutate
it and so values may be undefined; update the implementation to treat values as
possibly undefined (keep checking errors._form defensively and when searching
entries use a predicate that ensures msg !== undefined && msg !== "") so the
function still returns the first non-empty string or null while satisfying the
stricter type.

In @src/app/v1/_lib/codex/chat-completions-handler.ts:
- Around line 160-170: The Guard pipeline order is wrong: move the sensitive
word check earlier so SensitiveWordGuard runs immediately after authentication
and before session checks; update the CHAT_PIPELINE (the pipeline that
GuardPipelineBuilder.fromRequestType uses) to reorder steps so
ProxyAuthenticator → SensitiveWordGuard (step named "sensitive") → VersionGuard
→ ProxySessionGuard (step named "session") → ProxyRateLimitGuard →
ProxyProviderResolver, ensuring the 'sensitive' step appears before the
'session' step.

In @src/app/v1/_lib/proxy/warmup-guard.ts:
- Around line 73-94: The JOIN in proxy-status-tracker.ts's loadLastRequests()
currently includes requests with providerId=0 (warmup/sensitive-guard) which
have no matching providers row; update the loadLastRequests() query WHERE clause
to exclude those intercepted requests by adding the same filter used elsewhere:
AND (mr.blocked_by IS NULL OR mr.blocked_by <> 'warmup') so provider JOINs only
run on real provider rows and results aren't lost or errored.

In @src/lib/api/action-adapter-openapi.ts:
- Around line 18-25: getBearerTokenFromAuthHeader currently returns string |
null and uses token ? token : null; change its signature to return string |
undefined, simplify the final return to token || undefined, and make its
behavior match parseBearerToken; then audit callers of
getBearerTokenFromAuthHeader and adjust any null checks to undefined checks (or
accept both) so the authentication flow is consistent with parseBearerToken.

In @src/lib/utils/validation/provider.ts:
- Around line 19-24: The validatePositiveDecimalField function currently checks
num > 0 before rounding which allows tiny positives like 0.004 to pass but then
rounds to 0 and returns an invalid zero; change the logic to parse and round
first (const rounded = Math.round(num * 100) / 100) and then validate that
rounded is a finite number and > 0, returning null if not, otherwise return the
rounded value; ensure you still treat empty/whitespace input as null and keep
Number.parseFloat/Number.isFinite usage as in validatePositiveDecimalField.

In @tests/api/auth.unit.test.ts:
- Around line 1-19: This test performs real DB inserts via createTestUser and
createTestKey so move the file from tests/api/auth.unit.test.ts into the
integration test suite (e.g., tests/integration/) and update test setup so it
runs against the test database (ensure the configured DB name contains the
substring "test"); adjust any fixtures or environment variables used by
getSession, setAuthCookie, validateKey and db initialization so they point to
the integration DB, and remove it from unit test runs to keep unit tests
isolated.
🧹 Nitpick comments (26)
tests/cleanup-utils.ts (1)

56-68: 考虑使用事务包装两个更新操作。

当前实现先软删除 Keys,再软删除 Users。如果第一个操作成功但第二个失败,会导致数据不一致(Keys 被标记删除但 Users 未删除)。建议使用 db.transaction() 包装这两个操作以确保原子性。

🔎 建议的事务包装实现
    const testUserIds = testUsers.map((u) => u.id);

-    // 2. 软删除关联的 Keys
     const now = new Date();
-    const deletedKeys = await db
-      .update(keysTable)
-      .set({ deletedAt: now, updatedAt: now })
-      .where(and(inArray(keysTable.userId, testUserIds), isNull(keysTable.deletedAt)))
-      .returning({ id: keysTable.id });
-
-    // 3. 软删除测试用户
-    await db
-      .update(users)
-      .set({ deletedAt: now, updatedAt: now })
-      .where(and(inArray(users.id, testUserIds), isNull(users.deletedAt)))
-      .returning({ id: users.id });
+    
+    // 2-3. 在事务中软删除 Keys 和 Users
+    const deletedKeys = await db.transaction(async (tx) => {
+      const deletedKeys = await tx
+        .update(keysTable)
+        .set({ deletedAt: now, updatedAt: now })
+        .where(and(inArray(keysTable.userId, testUserIds), isNull(keysTable.deletedAt)))
+        .returning({ id: keysTable.id });
+
+      await tx
+        .update(users)
+        .set({ deletedAt: now, updatedAt: now })
+        .where(and(inArray(users.id, testUserIds), isNull(users.deletedAt)))
+        .returning({ id: users.id });
+
+      return deletedKeys;
+    });
drizzle/0045_mushy_human_torch.sql (1)

1-2: 数据库迁移结构合理。

新增的两个字段设计符合预期:

  • limit_total_usd 使用 numeric(10, 2) 精度足够存储美元金额
  • total_cost_reset_at 使用带时区的时间戳,适合跨时区场景
  • 可为空的设计与"null 表示无限制"的语义一致
💡 可选优化:添加列注释

考虑在后续迁移中为这两个字段添加数据库注释,提升可维护性:

COMMENT ON COLUMN "providers"."limit_total_usd" IS '供应商总费用限额(美元),NULL 表示无限制';
COMMENT ON COLUMN "providers"."total_cost_reset_at" IS '费用重置时间(带时区)';
tests/nextjs.mock.ts (1)

29-31: Mock 实现合理,符合测试需求。

新增的 headers() mock 遵循了与 cookies() 相同的模式,实现简洁清晰。目前始终返回 null 的设计对于大多数测试场景已足够。

如果后续测试需要验证特定请求头(如 User-AgentAuthorization 等),可以考虑像 cookies() mock 那样添加条件判断逻辑。

src/components/ui/__tests__/badge.test.tsx (3)

11-27: 建议将自定义渲染辅助函数提取到共享测试工具中。

这个 render 辅助函数是一个通用的测试工具,可能会在其他组件测试中重复使用。建议将其提取到共享的测试工具文件(如 src/test/utils.tsx)中,以提高代码复用性并保持测试代码的一致性。


30-38: 测试描述建议使用英文。

虽然中文描述能够清晰表达测试意图,但为了与代码库中的其他测试保持一致性,建议使用英文描述。这也有利于国际化团队的协作。

🔎 建议的修改
-  test("默认应渲染为 span", () => {
+  test("should render as span by default", () => {
     const { container, unmount } = render(<Badge>hi</Badge>);
 
     const el = container.querySelector('[data-slot="badge"]');

40-54: 测试描述建议使用英文。

同上,建议将测试描述改为英文以保持一致性。

🔎 建议的修改
-  test("asChild=true 时应透传到子元素(覆盖 Slot 分支)", () => {
+  test("should pass through to child element when asChild=true", () => {
     const { container, unmount } = render(
       <Badge asChild>
         <a href="/x">hi</a>
src/types/models.ts (2)

7-17: 建議為回應介面的屬性加上 readonly 修飾符。

這些介面表示來自外部 API 的回應資料,屬於不可變的資料結構。根據編碼規範,應使用 readonly 修飾符以確保類型安全。

🔎 建議的修改
 export interface OpenAIModelsResponse {
-  object: "list";
-  data: OpenAIModel[];
+  readonly object: "list";
+  readonly data: readonly OpenAIModel[];
 }
 
 export interface OpenAIModel {
-  id: string;
-  object: "model";
-  created: number;
-  owned_by: string;
+  readonly id: string;
+  readonly object: "model";
+  readonly created: number;
+  readonly owned_by: string;
 }

33-41: 為 Gemini 介面屬性加上 readonly 修飾符。

與 OpenAI 介面相同,這些屬性代表不可變的 API 回應資料,應標記為 readonly

🔎 建議的修改
 export interface GeminiModelsResponse {
-  models: GeminiModel[];
+  readonly models: readonly GeminiModel[];
 }
 
 export interface GeminiModel {
-  name: string;
-  displayName: string;
-  supportedGenerationMethods: string[];
+  readonly name: string;
+  readonly displayName: string;
+  readonly supportedGenerationMethods: readonly string[];
 }
src/app/v1/_lib/proxy/warmup-guard.ts (1)

110-131: 响应体构造符合 Anthropic API 格式

buildWarmupResponseBody 函数构造的响应格式正确:

  • 包含必需的字段:id, type, role, content, stop_reason, usage
  • usage 字段的 token 计数全部为 0(符合 warmup 不消耗 token 的语义)
  • 使用 crypto.randomBytes 生成唯一 ID

轻微建议:考虑为 id 添加时间戳前缀(如 msg_cch_warmup_{timestamp}_{random}),便于日志追踪和问题排查。

可选:为 warmup 响应 ID 添加时间戳
 function buildWarmupResponseBody(model: string | null): Record<string, unknown> {
+  const timestamp = Date.now();
   return {
     model: model ?? "unknown",
-    id: `msg_cch_${crypto.randomBytes(8).toString("hex")}`,
+    id: `msg_cch_warmup_${timestamp}_${crypto.randomBytes(4).toString("hex")}`,
     type: "message",
drizzle/0046_woozy_dark_phoenix.sql (1)

1-4: 考虑为新列添加默认值

新增的 4 个 codex 偏好设置列没有指定 DEFAULT 值,这意味着现有的 provider 记录中这些字段将为 NULL。建议考虑以下选项:

  1. 如果应用代码已正确处理 NULL 值(TypeScript 类型定义中这些字段为可选),则当前实现可接受
  2. 如果有合理的默认值(如空字符串或特定的枚举值),建议在迁移中添加 DEFAULT 子句以提高数据一致性

示例(如果需要默认值):

ALTER TABLE "providers" ADD COLUMN "codex_reasoning_effort_preference" varchar(20) DEFAULT NULL;

或者,请验证应用层代码是否正确处理这些字段的 NULL 值情况。

src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts (1)

14-24: 建议添加清理函数以防止内存泄漏。

当前的 useEffect 没有清理函数,如果组件在请求完成前卸载,可能会导致在已卸载的组件上调用 setModelSuggestions,触发 React 警告。

🔎 建议的修复方案
 useEffect(() => {
+  let isMounted = true;
+
   getModelSuggestionsByProviderGroup(providerGroup)
     .then((res) => {
-      if (res.ok && res.data) {
+      if (isMounted && res.ok && res.data) {
         setModelSuggestions(res.data);
       }
     })
     .catch(() => {
       // Silently fail - model suggestions are optional enhancement
     });
+
+  return () => {
+    isMounted = false;
+  };
 }, [providerGroup]);
src/lib/auth.ts (1)

130-137: Bearer token 解析的边缘情况处理可以更明确

parseBearerToken 函数的逻辑基本正确,但存在一个潜在的边缘情况:

  • Line 135: match?.[1]?.trim() 可能返回空字符串
  • Line 136: token || undefined 会正确将空字符串转为 undefined

建议使代码意图更明确:

🔎 建议的重构
 function parseBearerToken(raw: string | null | undefined): string | undefined {
   const trimmed = raw?.trim();
   if (!trimmed) return undefined;
 
   const match = /^Bearer\s+(.+)$/i.exec(trimmed);
   const token = match?.[1]?.trim();
-  return token || undefined;
+  return token && token.length > 0 ? token : undefined;
 }
src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)

10-23: 考虑大小写敏感性和额外验证

normalizeProviderGroup 函数的基本逻辑正确,但存在以下潜在问题:

  1. 大小写敏感:Line 22 的 .sort() 和去重逻辑是大小写敏感的,"Azure""azure" 会被视为不同的组
  2. 缺少格式验证:没有验证单个组名的长度、特殊字符或格式
🔎 建议的改进

如果需要大小写不敏感的处理:

 export function normalizeProviderGroup(value: unknown): string {
   if (value === null || value === undefined) return PROVIDER_GROUP.DEFAULT;
   if (typeof value !== "string") return PROVIDER_GROUP.DEFAULT;
   const trimmed = value.trim();
   if (trimmed === "") return PROVIDER_GROUP.DEFAULT;
 
   const groups = trimmed
     .split(",")
     .map((g) => g.trim())
+    .map((g) => g.toLowerCase())  // 统一转换为小写
     .filter(Boolean);
   if (groups.length === 0) return PROVIDER_GROUP.DEFAULT;
 
   return Array.from(new Set(groups)).sort().join(",");
 }

或者添加组名格式验证以确保数据质量。

tests/api/action-adapter-openapi.unit.test.ts (1)

1-197: 测试覆盖全面

单元测试覆盖了 action adapter 的核心功能:

  • 自动包装返回值
  • 单字段和多字段参数推断
  • 显式 argsMapper 支持
  • 错误处理的多种场景
  • JSON 解析异常的降级处理

测试结构清晰,用例完整。

可选改进:考虑为 mock context 定义更具体的类型,减少 as any 断言的使用。例如:

🔎 可选的类型安全改进
// 定义更具体的 mock context 类型
type MockContext = {
  req: {
    json: () => Promise<unknown>;
  };
  json: (payload: unknown, status?: number) => Response;
};

function createMockContext(options?: { 
  body?: unknown; 
  jsonThrows?: boolean 
}): MockContext {
  // ... 现有实现
}

// 使用时可以减少 as any
const response = await handler(createMockContext({ body: {} }));

这样可以在测试中保持更好的类型安全性。

src/app/v1/_lib/proxy/provider-selector.ts (1)

597-631: 会话复用的成本限制检查逻辑正确

在会话复用路径中添加成本限制检查是必要的,防止通过复用绕过限额控制。实现包括:

  • ✅ 周期性限额检查(5h、每日、每周、每月)
  • ✅ 总消费限额检查(支持可选的 resetAt)
  • ✅ 超限时正确拒绝复用(返回 null)

根据相关代码片段,RateLimitService 在错误时会 fail-open(返回 allowed: true),这是合理的容错策略。

考虑两个小优化:

  1. 提升日志级别:当前使用 logger.debug,在生产环境中可能不可见。考虑对限额拒绝使用 logger.info 以便监控:
   if (!costCheck.allowed) {
-    logger.debug("ProviderSelector: Session provider cost limit exceeded, reject reuse", {
+    logger.info("ProviderSelector: Session provider cost limit exceeded, reject reuse", {
       sessionId: session.sessionId,
       providerId: provider.id,
     });
     return null;
   }
  1. 性能优化(可选):如果这两个检查成为瓶颈,可以考虑并行执行:
const [costCheck, totalCheck] = await Promise.all([
  RateLimitService.checkCostLimits(...),
  RateLimitService.checkTotalCostLimit(...)
]);
src/actions/notifications.ts (1)

66-91: 建议统一 testWebhookAction 的返回类型

updateNotificationSettingsAction 已经迁移到新的 ActionResult<T> 返回类型模式,但 testWebhookAction 仍然使用旧的 { success: boolean; error?: string } 格式。为了保持代码库的一致性,建议也将 testWebhookAction 迁移到 ActionResult 模式。

🔎 建议的重构方案
 export async function testWebhookAction(
   webhookUrl: string,
   type: NotificationJobType
-): Promise<{ success: boolean; error?: string }> {
+): Promise<ActionResult<undefined>> {
   const session = await getSession();
   if (!session || session.user.role !== "admin") {
-    return { success: false, error: "无权限执行此操作" };
+    return { ok: false, error: "无权限执行此操作" };
   }
 
   if (!webhookUrl || !webhookUrl.trim()) {
-    return { success: false, error: "Webhook URL 不能为空" };
+    return { ok: false, error: "Webhook URL 不能为空" };
   }
 
   const trimmedUrl = webhookUrl.trim();
 
   try {
     const notifier = new WebhookNotifier(trimmedUrl, { maxRetries: 1 });
     const testMessage = buildTestMessage(type);
-    return notifier.send(testMessage);
+    const result = await notifier.send(testMessage);
+    if (result.success) {
+      return { ok: true, data: undefined };
+    }
+    return { ok: false, error: result.error || "测试连接失败" };
   } catch (error) {
     return {
-      success: false,
+      ok: false,
       error: error instanceof Error ? error.message : "测试连接失败",
     };
   }
 }
src/lib/codex/provider-overrides.ts (1)

43-90: 函数逻辑正确,建议增强类型安全

applyCodexProviderOverrides 函数的实现逻辑合理:

优点

  • 使用惰性克隆模式避免不必要的对象创建
  • 正确处理嵌套对象的合并(reasoningtext
  • 通过 null 值实现"继承客户端设置"的语义
  • 非 Codex 提供商早期返回,性能良好

💡 建议改进
考虑将 request 参数标记为 readonly 以明确表达不应修改输入的意图:

🔎 建议的类型安全改进
 export function applyCodexProviderOverrides(
   provider: CodexProviderOverrideConfig,
-  request: Record<string, unknown>
+  request: Readonly<Record<string, unknown>>
 ): Record<string, unknown> {
   if (provider.providerType !== "codex") {
     return request;
   }

这样可以:

  1. 在类型层面防止意外修改输入对象
  2. 使函数签名更清晰地表达"输入不变,输出可能不同"的语义
  3. 提高代码的可维护性
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (2)

68-68: open 属性未被使用

EditUserDialogInner 接收 EditUserDialogProps 类型,但 open 属性在组件内部未被使用。建议从解构中移除或使用更精确的类型定义。

🔎 建议的修改
-function EditUserDialogInner({ onOpenChange, user, onSuccess }: EditUserDialogProps) {
+function EditUserDialogInner({ onOpenChange, user, onSuccess }: Omit<EditUserDialogProps, "open">) {

128-151: 类型安全改进建议

handleUserChange 中多处使用 as any 类型断言,降低了类型安全性。考虑使用更精确的类型定义来避免运行时错误。

🔎 建议的类型安全改进
 const handleUserChange = (field: string | Record<string, any>, value?: any) => {
   const prev = form.values || defaultValues;
-  const next = { ...prev } as EditUserValues;
+  const next: EditUserValues = { ...prev };

   if (typeof field === "object") {
     Object.entries(field).forEach(([key, val]) => {
       const mappedField = key === "description" ? "note" : key;
-      (next as any)[mappedField] = mappedField === "expiresAt" ? (val ?? undefined) : val;
+      if (mappedField in next) {
+        (next as Record<string, unknown>)[mappedField] = mappedField === "expiresAt" ? (val ?? undefined) : val;
+      }
     });
tests/api/auth.unit.test.ts (1)

113-120: 管理员令牌测试依赖环境变量

测试假设 ADMIN_TOKEN 环境变量已设置。如果未设置,expect(adminToken).toBeTruthy() 将失败但错误信息不够清晰。

🔎 建议添加更清晰的断言消息
 test("admin token:应返回 admin session(无需 DB)", async () => {
   const adminToken = process.env.ADMIN_TOKEN;
-  expect(adminToken).toBeTruthy();
+  expect(adminToken, "ADMIN_TOKEN 环境变量必须设置").toBeTruthy();

   const session = await validateKey(adminToken as string);
   expect(session?.user.role).toBe("admin");
   expect(session?.key.canLoginWebUi).toBe(true);
 });
src/app/[locale]/dashboard/users/users-page-client.tsx (1)

32-39: queryClient 变量名冲突

模块级 queryClient 常量(第 32 行)与组件内 useQueryClient() 返回值(第 70 行)同名,可能造成混淆。建议重命名以区分。

🔎 建议的修改
-const queryClient = new QueryClient({
+const staticQueryClient = new QueryClient({
   defaultOptions: {
     queries: {
       refetchOnWindowFocus: false,
       staleTime: 30000,
     },
   },
 });

 // ...

 export function UsersPageClient(props: UsersPageClientProps) {
   return (
-    <QueryClientProvider client={queryClient}>
+    <QueryClientProvider client={staticQueryClient}>
       <UsersPageContent {...props} />
     </QueryClientProvider>
   );
 }

Also applies to: 70-70

src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx (1)

379-396: 到期时间区域条件渲染

showExpireTime 控制的条件渲染实现正确。但第 387 行 DatePickerFieldlabel 使用了 translations.sections.expireTime(区域标题),而第 384 行 <h4> 已显示相同文本,可能造成视觉重复。

🔎 建议移除重复标签
         <DatePickerField
-          label={translations.sections.expireTime}
+          label=""
           value={expiresAtValue}
           onChange={(val) => onChange("expiresAt", parseDateStringEndOfDay(val))}
         />

或者使用专门的字段标签翻译键。

src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (2)

35-37: onAddKeytranslations.actions.addKey 使用合理,但可考虑防御性回退

  • 新增 onAddKey?: () => void; 并在展开区渲染 “Add Key” 按钮,点击时 stopPropagation() 避免误触展开/收起,交互正确。
  • translations.actions.addKey?: string; 保持类型向后兼容;按钮文本直接使用 translations.actions.addKey

基于长期记忆,目前 5 个 locale 都已提供该键值,因此运行时缺失的风险很小。不过为了防御性更好,可以在渲染按钮时增加一个简单回退,例如:

  • !translations.actions.addKey 则不渲染该按钮,或
  • 回退到一个内置短文案(如 "Add Key")。

在现有调用场景下,此实现可以接受。

Based on learnings, 所有调用方都已经补全了 actions.addKey 翻译。

Also applies to: 62-67, 480-495


525-533: 从 User 向 EditKeyDialog 传递限额字段时建议保留 null 语义

这里将用户级限额字段通过 ?? undefined 传入:

limit5hUsd: user.limit5hUsd ?? undefined,
limitWeeklyUsd: user.limitWeeklyUsd ?? undefined,
limitMonthlyUsd: user.limitMonthlyUsd ?? undefined,
limitTotalUsd: user.limitTotalUsd ?? undefined,
limitConcurrentSessions: user.limitConcurrentSessions ?? undefined,

结合项目中对这些字段的约定(null 通常表示“显式无限制”,undefined 常表示“未设置/继承默认”),这样会把「显式无限制」压缩成「未设置」,可能让下游表单或校验逻辑无法区分两种状态。

建议改为直接透传原值,让 EditKeyDialog / EditKeyForm 继续按既有语义处理,例如:

limit5hUsd: user.limit5hUsd,
limitWeeklyUsd: user.limitWeeklyUsd,
limitMonthlyUsd: user.limitMonthlyUsd,
limitTotalUsd: user.limitTotalUsd,
limitConcurrentSessions: user.limitConcurrentSessions,

或者在 EditKeyDialogProps 中显式使用 number | null | undefined 并在此处保持同一含义。

Based on learnings, 这些限额字段在类型层面区分 nullundefined 语义,应尽量保持一致。

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

214-215: 首次初始化 system_settings 的降级读取逻辑更稳健

  • 先插入一条基础配置记录并 onConflictDoNothing(),随后再次通过 selectSettingsRow() 读取,而不是依赖 INSERT ... RETURNING
  • 结合 selectSettingsRow 的列缺失降级逻辑,即使旧库缺部分新列,也能成功读到基础字段并映射为 SystemSettings

在单实例场景下可以很好地避免「未返回任何记录」的边缘情况。若将来需要进一步强化多实例并发下的唯一性控制,可以考虑在 schema 层增加约束,但这超出了本次修改范围。

Also applies to: 222-224

src/app/api/actions/[...route]/route.ts (1)

621-805: 建议统一 my-usage 路由的标签分类

新增的 6 个 my-usage 路由功能完善,但标签使用不一致:

  • getMyUsageMetadata:标签为 "概览"
  • getMyQuota:标签为 "密钥管理"
  • getMyTodayStats:标签为 "统计分析"
  • getMyUsageLogsgetMyAvailableModelsgetMyAvailableEndpoints:标签为 "使用日志"

建议考虑:

  1. 为所有 my-usage 相关路由创建统一的标签(如 "我的用量"),便于 API 文档中集中展示
  2. 或者在现有标签的 description 中注明这些是用户自助查询接口
🔎 建议的标签统一方案

在 tags 数组中新增一个 "我的用量" 标签:

 tags: [
   {
     name: "用户管理",
     description: "用户账号的创建、编辑、删除和限额配置,支持 RPM、金额限制和并发会话控制",
   },
+  {
+    name: "我的用量",
+    description: "用户自助查询接口,允许只读 Key 访问,查询自己的配额、用量、日志等信息",
+  },
   // ... 其他标签
 ],

然后将所有 my-usage 路由的 tags 统一为 ["我的用量"]

Comment on lines +160 to 170
const type = session.isCountTokensRequest() ? RequestType.COUNT_TOKENS : RequestType.CHAT;
const pipeline = GuardPipelineBuilder.fromRequestType(type);

// 4. 限流检查
await ProxyRateLimitGuard.ensure(session);

// 5. 供应商选择(根据模型自动匹配)
const providerUnavailable = await ProxyProviderResolver.ensure(session);
if (providerUnavailable) {
// 创建失败记录(供应商不可用)
await ProxyMessageService.ensureContext(session);

// 解析错误响应
const errorBody = await providerUnavailable
.clone()
.json()
.catch(() => null);
const errorMessage = errorBody?.error?.message || "供应商不可用";

// 记录失败消息
if (session.messageContext) {
const { updateMessageRequestDetails } = await import("@/repository/message");
await updateMessageRequestDetails(session.messageContext.id, {
statusCode: providerUnavailable.status,
errorMessage: JSON.stringify(errorBody?.error || { message: errorMessage }),
model: session.getCurrentModel() ?? undefined,
context1mApplied: session.getContext1mApplied(),
});
}
const early = await pipeline.run(session);
if (early) return early;

return providerUnavailable;
// 增加并发计数(在所有检查通过后,请求开始前)- 跳过 count_tokens
if (session.sessionId && !session.isCountTokensRequest()) {
await SessionTracker.incrementConcurrentCount(session.sessionId);
concurrentCountIncremented = true;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# 查找 guard-pipeline.ts 文件
fd -t f "guard-pipeline.ts" src/

Repository: ding113/claude-code-hub

Length of output: 106


🏁 Script executed:

# 搜索 fromRequestType 方法和管道配置
rg -n "fromRequestType|CHAT_PIPELINE|COUNT_TOKENS_PIPELINE" --type=ts src/app/v1/_lib/

Repository: ding113/claude-code-hub

Length of output: 787


🏁 Script executed:

# 查看 GuardPipelineBuilder 的完整实现
fd -t f "*guard-pipeline*" src/ -x cat -n {}

Repository: ding113/claude-code-hub

Length of output: 362


🏁 Script executed:

# 读取 guard-pipeline.ts 文件查看管道配置
cat -n src/app/v1/_lib/proxy/guard-pipeline.ts | head -220

Repository: ding113/claude-code-hub

Length of output: 6492


守卫管道执行顺序不符合规定要求

CHAT_PIPELINE 的执行顺序为:auth → client → model → version → probe → session → warmup → requestFilter → sensitive → rateLimit → provider → ...

根据编码规范要求,管道必须按照以下顺序执行:ProxyAuthenticator → SensitiveWordGuard → VersionGuard → ProxySessionGuard → ProxyRateLimitGuard → ProxyProviderResolver

问题: 敏感词检查(SensitiveWordGuard)当前位于管道的第 9 步,应该在会话检查(ProxySessionGuard)之前、认证之后的第 2 步执行。当前配置使敏感词检查被推迟到会话和预热检查之后,违反了安全性和性能顺序要求。

需要调整 CHAT_PIPELINE 中的步骤顺序,将 sensitive 步骤移至 session 之前。

🤖 Prompt for AI Agents
In @src/app/v1/_lib/codex/chat-completions-handler.ts around lines 160 - 170,
The Guard pipeline order is wrong: move the sensitive word check earlier so
SensitiveWordGuard runs immediately after authentication and before session
checks; update the CHAT_PIPELINE (the pipeline that
GuardPipelineBuilder.fromRequestType uses) to reorder steps so
ProxyAuthenticator → SensitiveWordGuard (step named "sensitive") → VersionGuard
→ ProxySessionGuard (step named "session") → ProxyRateLimitGuard →
ProxyProviderResolver, ensuring the 'sensitive' step appears before the
'session' step.

Comment on lines 18 to 25
function getBearerTokenFromAuthHeader(raw: string | undefined): string | null {
const trimmed = raw?.trim();
if (!trimmed) return null;

const match = /^Bearer\s+(.+)$/i.exec(trimmed);
const token = match?.[1]?.trim();
return token ? token : null;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Bearer token 提取逻辑存在返回类型不一致

此函数与 src/lib/auth.ts 中的 parseBearerToken 逻辑相似但存在差异:

  1. 返回类型不一致:这里返回 string | null,而 auth.ts 返回 string | undefined
  2. Line 24token ? token : null 可简化为 token || null

虽然根据 learnings,authentication flows 之间的代码重复可能是有意的,但返回类型的不一致可能导致混淆。

🔎 建议统一返回类型

建议统一使用 undefined 作为"无值"的表示(更符合 TypeScript 惯例):

-function getBearerTokenFromAuthHeader(raw: string | undefined): string | null {
+function getBearerTokenFromAuthHeader(raw: string | undefined): string | undefined {
   const trimmed = raw?.trim();
-  if (!trimmed) return null;
+  if (!trimmed) return undefined;
 
   const match = /^Bearer\s+(.+)$/i.exec(trimmed);
   const token = match?.[1]?.trim();
-  return token ? token : null;
+  return token || undefined;
 }

并更新 Line 306:

-        const authToken =
-          getCookie(c, "auth-token") ?? getBearerTokenFromAuthHeader(c.req.header("authorization"));
+        const authToken = getCookie(c, "auth-token") ?? getBearerTokenFromAuthHeader(c.req.header("authorization"));
📝 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
function getBearerTokenFromAuthHeader(raw: string | undefined): string | null {
const trimmed = raw?.trim();
if (!trimmed) return null;
const match = /^Bearer\s+(.+)$/i.exec(trimmed);
const token = match?.[1]?.trim();
return token ? token : null;
}
function getBearerTokenFromAuthHeader(raw: string | undefined): string | undefined {
const trimmed = raw?.trim();
if (!trimmed) return undefined;
const match = /^Bearer\s+(.+)$/i.exec(trimmed);
const token = match?.[1]?.trim();
return token || undefined;
}
🤖 Prompt for AI Agents
In @src/lib/api/action-adapter-openapi.ts around lines 18 - 25,
getBearerTokenFromAuthHeader currently returns string | null and uses token ?
token : null; change its signature to return string | undefined, simplify the
final return to token || undefined, and make its behavior match
parseBearerToken; then audit callers of getBearerTokenFromAuthHeader and adjust
any null checks to undefined checks (or accept both) so the authentication flow
is consistent with parseBearerToken.

Comment on lines +1 to +19
import { afterAll, beforeEach, describe, expect, test, vi } from "vitest";
import { inArray } from "drizzle-orm";
import { db } from "@/drizzle/db";
import { keys, users } from "@/drizzle/schema";
import {
clearAuthCookie,
getAuthCookie,
getLoginRedirectTarget,
getSession,
setAuthCookie,
validateKey,
} from "@/lib/auth";

/**
* 说明:
* - 本文件用于覆盖 auth.ts 的权限边界与 Cookie 行为
* - 重点验证:allowReadOnlyAccess 白名单语义
* - 以及 getSession/cookie 的读写一致性
*/
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:

#!/bin/bash
# 查看完整的测试文件
echo "=== Reading full test file ==="
wc -l tests/api/auth.unit.test.ts
echo ""
cat -n tests/api/auth.unit.test.ts

Repository: ding113/claude-code-hub

Length of output: 9613


🏁 Script executed:

#!/bin/bash
# 检查数据库配置文件
echo "=== Looking for database configuration ==="
find . -name "*.env*" -o -name "*database*" -o -name "*db*" | grep -v node_modules | head -20

Repository: ding113/claude-code-hub

Length of output: 1551


🏁 Script executed:

#!/bin/bash
# 查找测试数据库配置
echo "=== Searching for database configuration in code ==="
rg "DATABASE_URL|database.*test|test.*database" -t ts -t tsx -A 2 --max-count 20

Repository: ding113/claude-code-hub

Length of output: 164


🏁 Script executed:

#!/bin/bash
# 检查是否有数据库安全验证
echo "=== Looking for test database safety validation ==="
rg "test.*keyword|'test'|\"test\"" src/ tests/ -t ts -t tsx | grep -i database

Repository: ding113/claude-code-hub

Length of output: 163


🏁 Script executed:

#!/bin/bash
# Find environment and database configuration files
find . -type f \( -name ".env*" -o -name "*.env*" \) ! -path "./.git/*" ! -path "./node_modules/*" 2>/dev/null | head -20

Repository: ding113/claude-code-hub

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Search for database configuration in source code
rg "DATABASE|database|drizzle" --type ts -l | head -10

Repository: ding113/claude-code-hub

Length of output: 389


🏁 Script executed:

#!/bin/bash
# Check database configuration file
if [ -f "src/drizzle/db.ts" ]; then
  echo "=== src/drizzle/db.ts ==="
  cat -n src/drizzle/db.ts
fi

Repository: ding113/claude-code-hub

Length of output: 1791


🏁 Script executed:

#!/bin/bash
# Look for database initialization and validation
rg "process\.env\.DATABASE|process\.env\." src/ -A 2 --max-count 15

Repository: ding113/claude-code-hub

Length of output: 10749


🏁 Script executed:

#!/bin/bash
# Check environment configuration example
echo "=== .env.example ==="
cat -n .env.example | head -50

Repository: ding113/claude-code-hub

Length of output: 1816


🏁 Script executed:

#!/bin/bash
# Find environment schema configuration
find . -name "env.schema*" -o -name "config*" -path "*/lib/*" | grep -v node_modules | head -10

Repository: ding113/claude-code-hub

Length of output: 165


🏁 Script executed:

#!/bin/bash
# Look for env schema to see DSN validation
rg "DSN|DATABASE" src/lib/config/ -A 2 | head -40

Repository: ding113/claude-code-hub

Length of output: 386


🏁 Script executed:

#!/bin/bash
# Check if test setup exists
find tests/ -name "*.ts" -o -name "setup*" | head -20

Repository: ding113/claude-code-hub

Length of output: 834


测试文件应移至集成测试目录

此测试文件在 createTestUser(第 51-58 行)和 createTestKey(第 60-84 行)中执行真实数据库插入操作,属于集成测试范畴。根据编码规范,应将其移至 tests/integration/ 目录,并确保测试数据库名称包含 'test' 关键字以符合安全验证要求。

🤖 Prompt for AI Agents
In @tests/api/auth.unit.test.ts around lines 1 - 19, This test performs real DB
inserts via createTestUser and createTestKey so move the file from
tests/api/auth.unit.test.ts into the integration test suite (e.g.,
tests/integration/) and update test setup so it runs against the test database
(ensure the configured DB name contains the substring "test"); adjust any
fixtures or environment variables used by getSession, setAuthCookie, validateKey
and db initialization so they point to the integration DB, and remove it from
unit test runs to keep unit tests isolated.

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

This pull request introduces several significant enhancements across the application, primarily focusing on database schema evolution, advanced provider configuration, and user interface improvements. Key changes include adding total usage limits and Codex-specific overrides for providers, implementing a system setting to intercept Anthropic warmup requests, and refining the user and key management experience with better dialogs and export functionalities. These updates aim to provide more granular control over provider behavior, optimize resource usage, and enhance the overall usability and auditability of the system.

Highlights

  • Database Schema Updates: The database schema has been updated to include new columns in the system_settings table for Anthropic warmup request interception and in the providers table for total usage limits and Codex-specific override preferences.
  • Anthropic Warmup Request Interception: A new system setting allows intercepting Anthropic warmup requests, preventing them from being forwarded to upstream providers, thus saving costs and resources. These intercepted requests are logged but not billed or rate-limited.
  • Provider Total Usage Limit & Codex Overrides: Providers can now have a limit_total_usd for overall spending, which can be manually reset. Additionally, new preferences have been added for Codex-type providers to override reasoning effort, summary, text verbosity, and parallel tool calls.
  • Enhanced User & Key Management UI: The user and key management interfaces have been refactored into more focused dialogs (CreateUserDialog, EditUserDialog, AddKeyDialog, EditKeyDialog) for improved usability and clarity.
  • Improved Session Log Details: Session log details now offer enhanced export options for request headers and body, and include handling for large content with hard limits and download capabilities.
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.

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 is a large release PR (v0.3.42) that consolidates 18 sub-PRs from dev to main. The changes span proxy pipeline security fixes, new features (Anthropic warmup interception, Codex provider overrides), bug fixes, and comprehensive test additions.

PR Size: XL

  • Lines changed: 22,801 (21,110 additions + 1,691 deletions)
  • Files changed: 146

Split Suggestions (For Future PRs)

Given the XL size, future releases would benefit from smaller, more focused PRs:

  1. Security fixes (GuardPipeline enforcement) should be separate, urgent PRs
  2. New features (warmup interception, Codex overrides) could be individual PRs
  3. UI refactoring (dialog components) as a standalone PR
  4. Test additions as a follow-up PR after feature PRs

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 1 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

Medium Priority Issue (Should Fix)

src/app/v1/_lib/codex/chat-completions-handler.ts (main branch lines 115, 128)

  • Issue: Duplicate session.setOriginalFormat("openai") call
  • Details: The function calls setOriginalFormat("openai") twice in the OpenAI format branch - once at line 115 and again at line 128. The second call appears to be a refactoring artifact.
  • Impact: No runtime bug (same value overwritten), but indicates incomplete cleanup. The diff shows line 128 was supposed to be removed (it had a comment removed at the same location in the diff).
  • Suggested fix: Remove the duplicate call on line 128.

Positive Observations (Not Reported as Comments Per Instructions)

The security-critical fix (#552) properly integrates the GuardPipeline pattern into chat-completions-handler.ts, ensuring authentication, session allocation, rate limiting, and provider resolution are consistently enforced. The warmup guard implementation correctly:

  • Uses Promise.allSettled for non-critical Redis operations
  • Logs audit trails with blockedBy: "warmup" for proper filtering
  • Excludes warmup requests from concurrent session tracking

Review Coverage

  • Logic and correctness - Clean (except for minor duplicate call)
  • Security (OWASP Top 10) - Clean, improved with GuardPipeline fix
  • Error handling - Clean, proper error logging patterns used
  • Type safety - Clean, proper type narrowing throughout
  • Documentation accuracy - Clean
  • Test coverage - Comprehensive new tests added (14+ new test files)
  • Code clarity - Good, refactored dialog components improve maintainability

Automated review by Claude AI

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

This is a substantial release PR that introduces several significant features, bug fixes, and major refactorings. The changes are well-executed and improve the overall quality of the codebase.
Key highlights include:

  • New Features: Anthropic warmup request interception, Codex provider parameter overrides, and new error rules are valuable additions.
  • Bug Fixes: The security fix for the GuardPipeline and the rate limit fixes are critical improvements. The fixes for UI state and data handling are also well-implemented.
  • Refactoring: The refactoring of the unified-edit-dialog into smaller, focused components is a great improvement for maintainability. Similarly, the introduction of the GuardPipelineBuilder simplifies the request handling logic significantly.
  • Database: The database migrations are well-structured, especially the idempotent migration for fixing system_settings.

My review has identified a few minor issues related to file formatting in the SQL migration files. I've provided suggestions to ensure consistency and prevent potential issues with tooling. Overall, this is an excellent pull request.

@@ -0,0 +1 @@
ALTER TABLE "system_settings" ADD COLUMN "intercept_anthropic_warmup_requests" boolean DEFAULT false NOT NULL; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This SQL file is missing a final newline character. It's a good practice to end all text files with a newline for better compatibility with various tools and to avoid issues with file concatenation. Please add a newline at the end of the file.

ALTER TABLE "system_settings" ADD COLUMN "intercept_anthropic_warmup_requests" boolean DEFAULT false NOT NULL;

@@ -0,0 +1,2 @@
ALTER TABLE "providers" ADD COLUMN "limit_total_usd" numeric(10, 2);--> statement-breakpoint
ALTER TABLE "providers" ADD COLUMN "total_cost_reset_at" timestamp with time zone; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This SQL file is missing a final newline character. It's a good practice to end all text files with a newline for better compatibility with various tools and to avoid issues with file concatenation. Please add a newline at the end of the file.

ALTER TABLE "providers" ADD COLUMN "total_cost_reset_at" timestamp with time zone;

ALTER TABLE "providers" ADD COLUMN "codex_reasoning_effort_preference" varchar(20);--> statement-breakpoint
ALTER TABLE "providers" ADD COLUMN "codex_reasoning_summary_preference" varchar(20);--> statement-breakpoint
ALTER TABLE "providers" ADD COLUMN "codex_text_verbosity_preference" varchar(10);--> statement-breakpoint
ALTER TABLE "providers" ADD COLUMN "codex_parallel_tool_calls_preference" varchar(10); No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This SQL file is missing a final newline character. It's a good practice to end all text files with a newline for better compatibility with various tools and to avoid issues with file concatenation. Please add a newline at the end of the file.

ALTER TABLE "providers" ADD COLUMN "codex_parallel_tool_calls_preference" varchar(10);

setModelSuggestions(res.data);
}
})
.catch(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

[High] [ERROR-SILENT] Swallowed error in useModelSuggestions

Why this is a problem: The promise rejection from getModelSuggestionsByProviderGroup() is swallowed with an empty .catch(), so failures (network/session/server errors) leave no trace for debugging and silently disable the UI enhancement.

Project docs explicitly call out the need for error logging (see docs/architecture-claude-code-hub-2025-11-29.md:588: - Error logging).

Suggested fix:

getModelSuggestionsByProviderGroup(providerGroup)
  .then((res) => {
    if (res.ok && res.data) {
      setModelSuggestions(res.data);
    }
  })
  .catch((error) => {
    if (process.env.NODE_ENV !== "production") {
      console.warn("[useModelSuggestions] Failed to fetch model suggestions", error);
    }
    setModelSuggestions([]);
  });

// Rollback: delete the user since key creation failed
try {
await removeUser(newUserId);
} catch (rollbackError) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[Medium] [ERROR-NO-USER-FEEDBACK] Rollback failure can leave an orphan user without telling the operator

Why this is a problem: If key creation fails and removeUser(newUserId) also fails, the dialog only logs to console and then reports the key failure. This can leave a partially-created user record behind and make retries confusing (e.g., “username already exists”) with no actionable hint.

Suggested fix:

let rollbackFailed = false;
try {
  await removeUser(newUserId);
} catch (rollbackError) {
  rollbackFailed = true;
  console.error("[CreateUserDialog] rollback failed", rollbackError);
}

toast.error(
  rollbackFailed
    ? `${t("createDialog.keyCreateFailed", { name: data.key.name })} (rollback failed; userId=${newUserId})`
    : keyRes.error || t("createDialog.keyCreateFailed", { name: data.key.name })
);

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

XL release PR merging dev into main with a very large surface area across proxy pipeline, rate limiting, dashboard UI, DB migrations, and translations. The size makes targeted review difficult and increases regression risk; this review focuses on high-confidence issues found in newly-added/modified lines.

PR Size: XL

  • Lines changed: 22801
  • Files changed: 146

Split suggestions (for future PRs):

  • Split DB migrations/snapshots vs app logic vs UI refactors vs i18n/test-only changes.
  • Keep proxy pipeline + auth/rate-limit changes in a dedicated PR with a smaller diff to reduce risk.

Issues Found

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

Critical Issues (Must Fix)

None.

High Priority Issues (Should Fix)

  • (Confidence 80) src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts:21 swallows promise rejections; add non-intrusive logging (dev-only) to avoid undiagnosed failures.
  • (Confidence 85) src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx:190 rollback failure (delete user after key-create failure) is only logged; surface a clear operator-facing warning to avoid orphaned users and confusing retries.

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.

Applied PR size label size/XL on #553 and completed the multi-perspective review using local git diff (GitHub diff API hit the 20k-line limit).

Posted 2 inline review comments (on new/modified lines only) and submitted the required “Code Review Summary” via gh pr review --comment.

- Add YesCode as fourth sponsor with logo
- Update Cubence discount from 20% to 10%
@github-actions
Copy link
Contributor

github-actions bot commented Jan 6, 2026

🧪 测试结果

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

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 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

🤖 Fix all issues with AI agents
In @src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx:
- Around line 310-322: The copy icon button (Button with onClick={handleCopy},
showing {copied ? <Check /> : <Copy />}) lacks an accessible label; add an
aria-label prop to that Button and make it dynamic (e.g., aria-label={copied ?
"Copied" : "Copy to clipboard"}) so screen readers announce the action and
reflect the copied state.
🧹 Nitpick comments (6)
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (2)

70-72: 临时 ID 生成逻辑可以简化

当前公式 -Math.floor(Date.now() + Math.random() * 1000) 中,Math.random() * 1000 的贡献相对于 Date.now()(毫秒级)非常小,实际上不增加唯一性。建议简化为使用 Date.now()crypto.randomUUID() 替代。

🔎 建议的简化方案
 function getNextTempKeyId() {
-  return -Math.floor(Date.now() + Math.random() * 1000);
+  return -Date.now();
 }

337-337: 重复的 max-h 属性

CSS 中存在两个 max-h 属性:max-h-[90vh]max-h-[90dvh]。后者会覆盖前者。如果是为了浏览器兼容性的回退,建议添加注释说明意图。

src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (1)

200-200: 重复的 max-h 属性

create-user-dialog.tsx 相同,存在重复的 max-h-[90vh]max-h-[90dvh] 属性。建议保持一致的处理方式。

src/lib/proxy-status-tracker.ts (1)

189-189: 考虑统一 warmup 过滤条件的维护策略。

第 189 行硬编码的 warmup 排除条件 (mr.blocked_by IS NULL OR mr.blocked_by <> 'warmup')src/repository/_shared/message-request-conditions.ts 中定义的 EXCLUDE_WARMUP_CONDITION 逻辑重复。虽然原始 SQL 上下文难以直接复用 Drizzle 的 sql 模板常量,但多处硬编码相同逻辑会增加维护成本。

建议

考虑以下方案之一:

  1. message-request-conditions.ts 中额外导出原始 SQL 字符串片段供原始 SQL 查询使用
  2. 在代码注释中明确标注所有使用该条件的位置,确保未来修改时能够同步更新
  3. 添加集成测试验证所有查询的 warmup 过滤行为一致
src/repository/statistics.ts (1)

49-49: 原始 SQL 中的 warmup 过滤条件存在重复,建议加强维护策略。

在多个原始 SQL 查询中硬编码了 warmup 排除逻辑 AND (mr.blocked_by IS NULL OR mr.blocked_by <> 'warmup'),与 EXCLUDE_WARMUP_CONDITION 常量逻辑重复。虽然复杂的 generate_series 时间序列查询使用原始 SQL 是可以理解的,但多处重复增加了维护成本。

建议

考虑以下方案提升可维护性:

  1. message-request-conditions.ts 中导出原始 SQL 字符串常量(如 WARMUP_FILTER_SQL = "(blocked_by IS NULL OR blocked_by <> 'warmup')"),供原始 SQL 查询使用
  2. 添加集成测试验证所有统计查询正确排除 warmup 请求
  3. 在文件顶部注释中明确列出所有使用该条件的位置

Also applies to: 85-85, 121-121, 157-157, 233-233, 274-274, 315-315, 356-356, 438-438, 468-468, 510-510, 540-540, 582-582, 612-612, 654-654, 684-684, 731-731

src/repository/message.ts (1)

567-593: 小提示:用户信息查询未过滤 warmup。

用户信息查询获取每个 session 的第一条请求元数据,未应用 EXCLUDE_WARMUP_CONDITION。这对于 userNameuserIdkeyName 等字段是合理的,因为这些值在整个 session 中保持一致。

但如果第一条请求恰好是 warmup 请求,apiType 可能与实际业务请求不同。如果 apiType 用于 UI 展示或分析,可考虑从非 warmup 请求中获取。

📜 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 8b4bfc5 and 272c3e9.

📒 Files selected for processing (34)
  • .gitignore
  • drizzle/0044_uneven_donald_blake.sql
  • drizzle/0045_mushy_human_torch.sql
  • drizzle/0046_woozy_dark_phoenix.sql
  • drizzle/0048_add_message_request_blocked_by_index.sql
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • messages/ja/dashboard.json
  • messages/ru/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/zh-TW/dashboard.json
  • src/actions/my-usage.ts
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/app/v1/_lib/proxy/guard-pipeline.ts
  • src/app/v1/_lib/proxy/sensitive-word-guard.ts
  • src/lib/api/action-adapter-openapi.ts
  • src/lib/proxy-status-tracker.ts
  • src/lib/utils/validation/provider.test.ts
  • src/lib/utils/validation/provider.ts
  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/repository/leaderboard.ts
  • src/repository/message.ts
  • src/repository/overview.ts
  • src/repository/statistics.ts
  • src/repository/usage-logs.ts
  • tests/integration/auth.test.ts
  • tests/unit/proxy/chat-completions-handler-guard-pipeline.test.ts
  • tests/unit/proxy/guard-pipeline-warmup.test.ts
  • vitest.integration.config.ts
  • vitest.my-usage.config.ts
✅ Files skipped from review due to trivial changes (1)
  • src/app/v1/_lib/proxy/sensitive-word-guard.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/actions/my-usage.ts
  • src/repository/overview.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/repository/usage-logs.ts
  • src/app/v1/_lib/proxy/guard-pipeline.ts
  • drizzle/0044_uneven_donald_blake.sql
  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/lib/api/action-adapter-openapi.ts
  • drizzle/0046_woozy_dark_phoenix.sql
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use 2-space indentation in all code files

Files:

  • src/lib/utils/validation/provider.test.ts
  • src/lib/proxy-status-tracker.ts
  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • src/repository/leaderboard.ts
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • messages/ja/dashboard.json
  • src/lib/utils/validation/provider.ts
  • messages/zh-CN/dashboard.json
  • src/repository/message.ts
  • messages/zh-TW/dashboard.json
  • src/repository/statistics.ts
  • messages/ru/dashboard.json
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias @/* to reference files from ./src/* directory

**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias @/* to reference files in ./src/* directory

Files:

  • src/lib/utils/validation/provider.test.ts
  • src/lib/proxy-status-tracker.ts
  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/repository/leaderboard.ts
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/lib/utils/validation/provider.ts
  • src/repository/message.ts
  • src/repository/statistics.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures

Files:

  • src/lib/utils/validation/provider.test.ts
  • src/lib/proxy-status-tracker.ts
  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/repository/leaderboard.ts
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/lib/utils/validation/provider.ts
  • src/repository/message.ts
  • src/repository/statistics.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages

Files:

  • src/lib/utils/validation/provider.test.ts
  • src/lib/proxy-status-tracker.ts
  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/repository/leaderboard.ts
  • src/lib/utils/validation/provider.ts
  • src/repository/message.ts
  • src/repository/statistics.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Files:

  • src/lib/utils/validation/provider.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Ensure test database names contain 'test' keyword for safety validation

Files:

  • src/lib/utils/validation/provider.test.ts
src/**/*provider*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Set provider circuit breaker failure threshold, open duration, and half-open success threshold in configuration

Files:

  • src/lib/utils/validation/provider.test.ts
  • src/lib/utils/validation/provider.ts
src/lib/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use connection pooling for database and Redis connections

Files:

  • src/lib/utils/validation/provider.test.ts
  • src/lib/proxy-status-tracker.ts
  • src/lib/utils/validation/provider.ts
src/repository/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use Repository pattern in src/repository/ to wrap Drizzle queries

Files:

  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/repository/leaderboard.ts
  • src/repository/message.ts
  • src/repository/statistics.ts
src/**/*{message,response,log}*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Log request duration, token usage, and cost to message_request table for analytics

Files:

  • src/repository/_shared/message-request-conditions.ts
  • src/repository/message.ts
src/{repository,actions}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Avoid N+1 queries by using eager loading and batch queries for statistics

Files:

  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/repository/leaderboard.ts
  • src/repository/message.ts
  • src/repository/statistics.ts
src/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{tsx,jsx}: Use lucide-react for icons, no custom SVGs
Use React's automatic escaping to prevent XSS vulnerabilities

Files:

  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
**/*.{tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Files:

  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • messages/ja/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/zh-TW/dashboard.json
  • messages/ru/dashboard.json
messages/**/*.json

📄 CodeRabbit inference engine (CLAUDE.md)

Support 5 locales via next-intl: en, ja, ru, zh-CN, zh-TW with messages in messages/{locale}/*.json

Store message translations in messages/{locale}/*.json files

Files:

  • messages/en/dashboard.json
  • messages/ja/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/zh-TW/dashboard.json
  • messages/ru/dashboard.json
🧠 Learnings (12)
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Applied to files:

  • .gitignore
📚 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/lib/utils/validation/provider.test.ts
  • src/lib/proxy-status-tracker.ts
  • src/repository/_shared/message-request-conditions.ts
  • src/repository/key.ts
  • src/repository/leaderboard.ts
  • src/lib/utils/validation/provider.ts
  • src/repository/message.ts
  • src/repository/statistics.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)

Applied to files:

  • src/lib/proxy-status-tracker.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Use Drizzle ORM with parameterized queries to prevent SQL injection

Applied to files:

  • src/lib/proxy-status-tracker.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/drizzle/**/*.ts : Use Drizzle ORM with PostgreSQL for database operations

Applied to files:

  • src/lib/proxy-status-tracker.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Use JSON columns in PostgreSQL for flexible data structures (modelRedirects, providerChain, etc.)

Applied to files:

  • src/lib/proxy-status-tracker.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/{repository,actions}/**/*.ts : Avoid N+1 queries by using eager loading and batch queries for statistics

Applied to files:

  • src/repository/key.ts
  • src/repository/message.ts
  • src/repository/statistics.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/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • messages/en/dashboard.json
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • messages/ja/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/zh-TW/dashboard.json
  • messages/ru/dashboard.json
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Implement proper indexing strategy for common queries and foreign keys

Applied to files:

  • drizzle/0048_add_message_request_blocked_by_index.sql
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*{message,response,log}*.ts : Log request duration, token usage, and cost to message_request table for analytics

Applied to files:

  • src/repository/message.ts
  • src/repository/statistics.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/lib/rate-limit/**/*.ts : Rate Limiting must track dimensions: RPM, cost (5h/week/month), concurrent sessions at User, Key, and Provider levels using Redis Lua scripts

Applied to files:

  • src/repository/statistics.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to messages/**/*.json : Support 5 locales via next-intl: en, ja, ru, zh-CN, zh-TW with messages in `messages/{locale}/*.json`

Applied to files:

  • messages/ru/dashboard.json
🧬 Code graph analysis (8)
src/lib/utils/validation/provider.test.ts (1)
src/lib/utils/validation/provider.ts (1)
  • validatePositiveDecimalField (19-27)
src/repository/_shared/message-request-conditions.ts (1)
src/drizzle/schema.ts (1)
  • messageRequest (274-355)
src/repository/key.ts (2)
src/repository/_shared/message-request-conditions.ts (1)
  • EXCLUDE_WARMUP_CONDITION (9-9)
src/drizzle/schema.ts (1)
  • messageRequest (274-355)
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (6)
src/lib/constants/provider.constants.ts (1)
  • PROVIDER_GROUP (25-30)
src/lib/hooks/use-zod-form.tsx (1)
  • useZodForm (17-100)
src/actions/users.ts (2)
  • createUserOnly (899-1063)
  • removeUser (1205-1228)
src/actions/keys.ts (1)
  • addKey (84-278)
src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)
  • normalizeProviderGroup (10-23)
src/app/[locale]/dashboard/_components/user/utils/form-utils.ts (1)
  • getFirstErrorMessage (5-15)
src/repository/leaderboard.ts (2)
src/drizzle/schema.ts (1)
  • messageRequest (274-355)
src/repository/_shared/message-request-conditions.ts (1)
  • EXCLUDE_WARMUP_CONDITION (9-9)
src/lib/utils/validation/provider.ts (2)
src/lib/utils/index.ts (1)
  • validatePositiveDecimalField (35-35)
src/lib/utils/validation/index.ts (1)
  • validatePositiveDecimalField (7-7)
src/repository/message.ts (2)
src/repository/_shared/message-request-conditions.ts (1)
  • EXCLUDE_WARMUP_CONDITION (9-9)
src/drizzle/schema.ts (1)
  • messageRequest (274-355)
src/repository/statistics.ts (2)
src/repository/_shared/message-request-conditions.ts (1)
  • EXCLUDE_WARMUP_CONDITION (9-9)
src/drizzle/schema.ts (1)
  • messageRequest (274-355)
⏰ 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). (4)
  • GitHub Check: 🌐 API Tests
  • GitHub Check: Docker Build Test
  • GitHub Check: 🌐 API Tests
  • GitHub Check: dev-build-deploy
🔇 Additional comments (64)
.gitignore (1)

15-17: 覆盖率目录条目已完整。

所有三个新增的测试覆盖率目录条目都已正确添加到 .gitignore 中,与 package.json 中新增的 npm 测试脚本相对应:

  • /coverage-quotatest:coverage:quota
  • /coverage-my-usagetest:coverage:my-usage
  • /coverage-proxy-guard-pipelinetest:coverage:proxy-guard-pipeline

条目的放置位置合理,与现有的测试配置保持一致。之前关于缺失 /coverage-my-usage 的反馈已得到解决。

src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)

186-199: 回滚失败处理已改进

当前实现正确处理了回滚失败的情况:在密钥创建失败时尝试删除用户,如果回滚也失败,会显示单独的 toast 通知用户。这解决了之前评审中提到的问题。

src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (2)

153-185: 启用/禁用用户的错误处理已完善

handleDisableUserhandleEnableUser 现在都包含了完整的 try-catch 错误处理,符合最佳实践。


274-279: Dialog 组件结构清晰

使用 key={props.user.id} 确保用户切换时组件正确重置状态,条件渲染避免了不必要的内部组件挂载。实现合理。

src/lib/utils/validation/provider.ts (1)

19-27: 验证逻辑已修复,实现正确!

先前评审中指出的问题(先检查 num > 0 再四舍五入可能导致返回 0)已得到解决。当前实现先执行四舍五入,然后验证四舍五入后的值是否大于 0,确保了极小正数(如 0.004)会正确返回 null

逻辑流程清晰且符合预期:

  • 解析并验证有效性
  • 四舍五入到两位小数
  • 验证四舍五入后的值 > 0
  • 返回有效值或 null
src/lib/utils/validation/provider.test.ts (1)

1-30: 测试覆盖全面且准确!

测试用例完整涵盖了 validatePositiveDecimalField 的所有边界情况和核心行为:

  • 空值/空白输入处理
  • 非法输入拒绝
  • 零值和负数处理(对齐 "≤0 表示不限额" 的语义)
  • 极小正数四舍五入后为零的情况(验证了修复后的逻辑)
  • 正常正数的四舍五入行为

特别是第 21-23 行的测试用例,有效验证了先前评审中指出的问题已被修复。

src/repository/leaderboard.ts (2)

174-180: 预热请求排除逻辑正确应用于用户排行榜。

EXCLUDE_WARMUP_CONDITION 已正确添加到 where 条件中,与 deletedAt 过滤条件一起使用,确保预热请求不会被计入用户消耗统计。


316-316: 预热请求排除逻辑已一致应用于所有排行榜查询。

EXCLUDE_WARMUP_CONDITION 已系统性地添加到所有排行榜查询类型中:

  • 供应商消耗排行榜(line 316)
  • 供应商缓存命中率排行榜(line 416)
  • 模型调用排行榜(lines 559-565)

条件逻辑正确:(blockedBy IS NULL OR blockedBy <> 'warmup') 准确地排除了 blockedBy = 'warmup' 的记录,同时保留正常请求和其他被拦截的请求。这与 PR 目标(排除 Anthropic 预热请求统计)完全一致。

Also applies to: 416-416, 559-565

drizzle/meta/_journal.json (1)

313-347: 迁移日志条目格式正确且顺序合理。

新增的 5 个迁移条目(0044-0048)结构完整,包含所有必需字段(idx、version、when、tag、breakpoints),时间戳按时间顺序排列,与 PR 描述中的数据库迁移变更一致。

drizzle/0048_add_message_request_blocked_by_index.sql (1)

1-7: 索引设计合理,支持 warmup 过滤查询优化。

该部分索引(partial index)设计恰当,通过 WHERE deleted_at IS NULL 过滤软删除记录,减少索引体积的同时匹配了代码中 EXCLUDE_WARMUP_CONDITION 的常见查询模式。IF NOT EXISTS 子句确保了迁移的幂等性。

src/lib/proxy-status-tracker.ts (1)

4-4: 密钥掩码处理符合安全最佳实践。

在代理状态追踪中对密钥字符串进行掩码处理是良好的安全实践,避免在日志或状态接口中暴露完整的 API 密钥。

Also applies to: 100-100, 126-126

src/repository/_shared/message-request-conditions.ts (1)

1-9: 共享条件定义清晰,实现良好。

EXCLUDE_WARMUP_CONDITION 常量的设计遵循 DRY 原则,为 Drizzle 查询提供了统一的 warmup 过滤逻辑。文档清晰说明了语义:warmup 抢答请求仅用于探测/预热,不应计入聚合统计和限额计算。

src/repository/key.ts (1)

9-9: warmup 过滤条件一致应用于所有密钥统计查询。

EXCLUDE_WARMUP_CONDITION 在所有密钥相关的统计和消费查询中得到一致应用,包括单用户和批量查询路径。这确保了 warmup 探测请求被正确排除在今日用量、最后使用时间、模型统计等指标之外,符合 PR 目标。

Also applies to: 299-299, 344-344, 569-569, 583-589, 605-605, 691-691, 714-720, 746-746

src/repository/statistics.ts (1)

820-851: 新增的 sumProviderTotalCost 函数设计合理,语义清晰。

该函数为供应商总限额(limit_total_usd)检查提供了准确的成本计算。重要设计决策:

  • 不进行 365 天时间截断,避免达到总限额后"自动恢复",符合禁用语义
  • 支持 resetAt 参数,允许手动重置供应商成本累计起点
  • 正确应用 EXCLUDE_WARMUP_CONDITION 排除探测请求
  • 文档清晰说明了与用户/密钥总成本函数的语义差异
src/repository/message.ts (5)

9-9: LGTM!

导入 EXCLUDE_WARMUP_CONDITION 共享条件符合仓储层的模块化设计。


300-318: 实现正确,设计合理。

使用 totalCount 判断 session 存在性(包含 warmup),同时用 FILTER (WHERE ...) 从统计中排除 warmup 请求,这是 PostgreSQL 条件聚合的标准做法,既保证了 session 可被查询到,又确保统计数据的准确性。


321-371: 一致性良好。

供应商、模型、缓存 TTL 的去重查询均一致地应用了 EXCLUDE_WARMUP_CONDITION,确保聚合结果不包含 warmup 请求的数据。


458-470: 验证:单会话与批量会话 API 的行为差异。

aggregateSessionStats 使用 totalCount === 0 判断 session 是否存在,允许返回只有 warmup 请求的 session(此时 requestCount = 0)。

aggregateMultipleSessionStats 在第 603 行使用 stats.requestCount === 0 跳过 session,这意味着只包含 warmup 请求的 session 会被过滤掉。

请确认这种行为差异是否符合预期。如果需要保持一致,可以考虑在批量查询中也添加 totalCount 字段用于存在性判断。

Also applies to: 598-605


476-563: LGTM!

批量查询供应商、模型、缓存 TTL 时一致地应用了 EXCLUDE_WARMUP_CONDITION,并使用 Map 结构高效聚合,避免了 N+1 查询问题。基于 learnings,这符合仓储层的最佳实践。

messages/ja/dashboard.json (9)

123-136: 日志表新增“skipped”状态键看起来正确

"skipped": "スキップ" 与现有 "blocked" / "nonBilling" 并列,含义清晰,无占位符,结构与其它语言版本一致。


148-217: Warmup 跳过详情块结构与语义合理

新增 details.skipped 下的 title/reason/warmup/desc 结构完整,日文说明准确表达“由 CCH 抢答、不计费、不限流、不计统计”,与功能设计一致,占位符使用正确(无多余变量)。


392-421: 会话操作中文案更新为“请求头+体”更加精确

copyMessages / downloadMessages 改为明确说明“リクエスト(ヘッダーとボディ)” 与实际导出内容一致,有助于减少歧义。


422-446: SSE Data 与大内容 hardLimit 文案结构良好

新增 sseData 标签和 hardLimit 对象(title/size/maximum/hint/download)键名与其它 locale 一致,占位符 {sizeMB}/{sizeBytes}/{maxSizeMB}/{maxLines} 使用正确,无遗漏。


687-763: 新增密钥创建成功提示解决了一次性展示语义问题

successTitle/successDescription/generatedKey.hint 明确说明密钥只展示一次、需立即复制,并强调后续可在列表中进行启用/上限管理,已与 keyCreatedDialog 的“一次性展示”策略保持一致。


1087-1126: userManagement 表格 actions.addKey 翻译补齐

userManagement.table.actions 下新增 "addKey": "キーを追加",补齐了内联“新增密钥”操作的翻译,键名与其它语言文件保持一致,方便调用方统一使用。Based on learnings, 这也满足了所有 5 个 locale 都提供 actions.addKey 的要求。


1183-1200: 编辑对话框标题/描述收敛为“编辑用户”更贴合当前 UI

editDialog.title/description 更新为“ユーザーを編集 / ユーザー情報を編集”,与该对话框聚焦用户设置(而非混合用户+密钥)的一致,语义清晰。


1278-1297: 创建用户成功后的密钥提示文案清晰一致

createDialog 中新增的 rollbackFailedsuccessTitle/successDescription/generatedKey/keyHint 既覆盖了异常回滚场景,也明确指出密钥只显示一次并引导去用户管理页做后续管理,语义和其它 locale 对齐。


1379-1402: balanceQueryPage 关闭态说明与产品行为一致

keySettings.balanceQueryPage.descriptionDisabled 明确说明关闭后将进入“受限 Web UI” 而非个人用量页,和英文基准版本语义一致,能有效降低用户误解。

messages/ru/dashboard.json (9)

123-136: 日志表“Пропущено”状态键定义合理

在表格状态中增加 "skipped": "Пропущено" 与“Заблокировано/Не тарифицируется”并列,符合语义,也与其它语言版本的 key 对齐。


148-217: Warmup 跳过详情块结构与说明准确

details.skipped 中的 title/reason/warmup/desc 完整描述了 Warmup 探测由 CCH 本地处理、不计费、不计入限额和统计,俄文表述自然,占位符正确。


392-421: 会话操作文本改为复制/下载“请求(头+体)”更精确

copyMessagesdownloadMessages 现在明确说明作用于整个 HTTP 请求(заголовки и тело),与 UI 行为一致,没有占位符,安全可用。


422-446: SSE 数据与 hardLimit 提示键位和占位符正确

sseData 标签及 hardLimit 的各字段(title/size/maximum/hint/download)与英语版结构一致, {sizeMB}, {sizeBytes}, {maxSizeMB}, {maxLines} 均正确保留。


689-766: 新增密钥创建成功提示修复了一次性展示冲突

addKeyForm.successTitle/successDescription/generatedKey.hint 现在强调“полный ключ показывается только один раз”,并说明后续只能在列表中做管理(启用/停用、限额等),解决了此前与 keyCreatedDialog.description 的语义冲突。


1094-1133: userManagement actions.addKey 翻译齐全

userManagement.table.actions 下增加 "addKey": "Добавить ключ",与组件属性要求一致,并与其它 locale 保持相同结构。Based on learnings, 这满足了所有语言都提供 actions.addKey 的约定。


1194-1211: 编辑对话框标题/描述聚焦用户信息

editDialog.title/description 更新为“Редактировать пользователя / Редактирование данных пользователя”,与该对话框实际负责的范围(用户 + 下方 key section)更加契合,表达自然。


1289-1308: 用户创建成功的密钥提示统一为“一次展示”策略

createDialog 新增的 successTitle/successDescription/generatedKey/keyHint 明确提示密钥后续只能管理不可再次查看完整值,和 key 创建流程保持一致,避免用户误解。


1389-1402: balanceQueryPage 禁用说明与受限 Web UI 行为一致

keySettings.balanceQueryPage.descriptionDisabled 对关闭后进入“ограниченный Web UI” 的行为描述清楚,和英文版本逻辑一致,便于管理员理解开关影响。

messages/zh-CN/dashboard.json (9)

123-136: 新增“已跳过”状态键与业务语义匹配

"skipped": "已跳过" 与“被拦截/非计费”同级,简洁表达 Warmup 等跳过场景,key 名与其它语言一致,便于前端统一使用。


148-217: Warmup 跳过详情块中文描述清晰

details.skipped 中的说明准确表达“CCH 抢答、不转发上游、不计费、不限流、不计入统计”,字段结构与其它 locale 一致,便于前端展示统一的跳过原因。


392-421: 会话操作改为“请求头和请求体”避免歧义

copyMessages / downloadMessages 现在明确指出复制/下载的是“请求头和请求体”,比泛泛的“Messages” 更贴近实际导出内容,有利于用户理解。


423-447: SSE 数据及大内容 hardLimit 提示结构完备

sseDatahardLimit 均已本地化,占位符 {sizeMB}/{sizeBytes}/{maxSizeMB}/{maxLines} 保留正确,提示用户通过下载查看完整内容,符合 5MB/行数上限的产品设计。


697-784: 新增密钥创建成功提示与“一次显示”逻辑一致

addKeyForm.successTitle/successDescription/generatedKey.hint 明确强调“完整密钥仅显示一次,请立即复制并妥善保存”,同时说明后续仅在列表中进行启用/限额管理,彻底消除了之前“随时查看”类文案的歧义。


1118-1156: userManagement.actions.addKey 翻译补齐

userManagement.table.actions 中加入 "addKey": "新增密钥",与 UI 行为一致,也与其它 locale 均提供该键保持一致。Based on learnings, 这保证了调用方不再需要处理缺失翻译的情况。


1222-1239: 用户编辑对话框标题/描述简化为“编辑用户”更自然

editDialog.title/description 更新为“编辑用户 / 编辑用户信息”,用语规范,和英文本地化语义对齐。


1317-1336: 创建用户成功提示与回滚失败说明都很到位

rollbackFailed 为异常路径提供了明确的手动处理指引(含 userId),successTitle/successDescription/generatedKey/keyHint 则统一了“密钥仅显示一次”的策略说明,减少运维困惑。


1524-1564: balanceQueryPage 关闭态说明与英文保持一致

keyEditSection.balanceQueryPage.descriptionDisabled 关于关闭后进入“受限 Web UI” 的描述清楚,能帮助管理员理解该开关对登录入口和能力范围的影响。

messages/zh-TW/dashboard.json (9)

123-136: 新增“已跳過”狀態鍵語義正確

"skipped": "已跳過" 與“已攔截/非計費”並列,準確對應 Warmup 等跳過場景,key 名與其他語言版本一致。


148-217: Warmup 跳過詳情區塊結構與翻譯良好

details.skipped 中的 title/reason/warmup/desc 正確說明由 CCH 搶答、不轉發、不計費、不限流、不計入統計,結構與其他 locale 對齊,方便前端統一處理。


393-421: 會話操作改為“請求頭與請求體”更精準

copyMessages / downloadMessages 改為“複製/下載請求頭與請求體”,與實際導出內容匹配,避免誤認為僅針對 messages。


423-447: SSE Data 與 hardLimit 本地化完整

sseData 以及 hardLimit 的 title/size/maximum/hint/download 均已翻譯,占位符維持正確,提示使用者下載以檢視完整內容,符合大內容截斷行為。


687-764: 金鑰建立成功提示與“一次顯示”策略一致

addKeyForm.successTitle/successDescription/generatedKey.hint 清楚指出完整金鑰僅顯示一次,並強調之後只能在清單中做啟用/額度管理,修復了先前 reviewer 指出的文案矛盾。


1093-1131: userManagement 表格 actions.addKey 翻譯補齊

新增 "addKey": "新增金鑰",與元件屬性要求相符,並與其餘 4 個語言版本保持一致。Based on learnings, 這避免了某些 locale 下缺失 actions.addKey 的情況。


1192-1209: 編輯對話框標題/描述與英文保持同步

editDialog.title/description 更新為“編輯使用者 / 編輯使用者資訊”,更貼近實際行為,也與英文本地化語義一致。


1287-1306: 使用者建立成功的金鑰提示與回滾文案完善

rollbackFailed 提供了明確的手動清理指引,successTitle/successDescription/generatedKey/keyHint 則沿用“一次顯示”策略並告知可在使用者管理頁做後續管理,整體體驗完整。


1495-1534: balanceQueryPage 關閉態描述與受限 Web UI 行為一致

keyEditSection.balanceQueryPage.descriptionDisabled 清楚說明關閉後會進入受限 Web UI,而非個人用量頁,與其它語言版本保持一致,有助於管理員理解這一選項。

messages/en/dashboard.json (9)

123-136: New “Skipped” status key is consistent and clear

Adding "skipped": "Skipped" alongside "blocked" / "nonBilling" matches the new Warmup-skip state and keeps the schema aligned with other locales.


148-218: Warmup skip details block is well-structured

details.skipped with title/reason/warmup/desc clearly explains that CCH answered a Warmup probe locally (no billing, no rate limiting, excluded from stats); structure matches other languages and expected UI usage.


393-421: Session actions now accurately describe copying/downloading request

copyMessages / downloadMessages changed to “Copy/Download Request (Headers + Body)”, which better reflects actual behavior and avoids ambiguity about only copying messages.


423-447: SSE data label and hardLimit messaging look correct

sseData and the hardLimit object (title/size/maximum/hint/download) are added with correct placeholders {sizeMB}, {sizeBytes}, {maxSizeMB}, {maxLines}, matching the streaming viewer behavior.


696-783: Key creation success texts resolve the “shown once” vs “view anytime” conflict

addKeyForm.successTitle/successDescription/generatedKey.hint now state that the full key is only shown once and should be copied immediately, while management happens later in the key list, which aligns with keyCreatedDialog and backend behavior.


1117-1155: userManagement.table.actions.addKey added for inline key creation

Adding "actions": { "addKey": "Add Key" } completes the table actions API and lines up with how the row component expects translations across locales。Based on learnings, 这也保证了所有 5 个语言版本都有该键。


1221-1238: Edit dialog copy now clearly scoped to user information

editDialog.title/description updated to “Edit user / Edit user information”, which better reflects the dialog’s primary purpose and matches the UX refactor away from a unified edit dialog.


1316-1335: User creation success and rollback messages are comprehensive

rollbackFailed explains the partial-create edge case with userId, and the new successTitle/successDescription/generatedKey/keyHint block clearly communicates one-time key display and where to manage it, aligning with key-creation flows elsewhere.


1416-1428: Disabled balance/usage page description consistently mentions restricted Web UI

Both in keySettings.balanceQueryPage.descriptionDisabled and keyEditSection.balanceQueryPage.descriptionDisabled, the text now explains that disabling the dedicated usage page sends users to a restricted Web UI instead, which matches the product behavior and keeps copy consistent across contexts.

Also applies to: 1525-1564

Comment on lines +310 to +322
<Button
type="button"
variant="outline"
size="icon"
onClick={handleCopy}
className="shrink-0"
>
{copied ? (
<Check className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
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

复制按钮缺少无障碍标签

复制按钮仅包含图标,缺少 aria-label 属性,屏幕阅读器用户无法理解按钮用途。

🔎 建议添加 aria-label
               <Button
                 type="button"
                 variant="outline"
                 size="icon"
                 onClick={handleCopy}
                 className="shrink-0"
+                aria-label={copied ? tCommon("copied") : tCommon("copy")}
               >
🤖 Prompt for AI Agents
In @src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx around
lines 310 - 322, The copy icon button (Button with onClick={handleCopy}, showing
{copied ? <Check /> : <Copy />}) lacks an accessible label; add an aria-label
prop to that Button and make it dynamic (e.g., aria-label={copied ? "Copied" :
"Copy to clipboard"}) so screen readers announce the action and reflect the
copied state.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

🧪 测试结果

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

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

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Too many files changed for review.

1 similar comment
@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Too many files changed for review.

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Too many files changed for review.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 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

🤖 Fix all issues with AI agents
In @CLAUDE.md:
- Line 129: Update the language list in the CLAUDE.md line that currently reads
"zh-CN, en, ja, ko, de" to match the actual supported languages from
src/i18n/config.ts by replacing it with "zh-CN, zh-TW, en, ru, ja"; ensure the
document string exactly matches that new comma-separated list so the README
aligns with the code.
📜 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 3250f21 and cc32b47.

📒 Files selected for processing (3)
  • AGENTS.md
  • AGENTS.md
  • CLAUDE.md
✅ Files skipped from review due to trivial changes (2)
  • AGENTS.md
  • AGENTS.md
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/v1/_lib/**/*.ts : Guard pipeline must execute in order: ProxyAuthenticator, SensitiveWordGuard, VersionGuard, ProxySessionGuard, ProxyRateLimitGuard, ProxyProviderResolver

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)

Applied to files:

  • CLAUDE.md
⏰ 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). (4)
  • GitHub Check: 🌐 API Tests
  • GitHub Check: Docker Build Test
  • GitHub Check: 🌐 API Tests
  • GitHub Check: dev-build-deploy
🔇 Additional comments (4)
CLAUDE.md (4)

54-69: 数据库迁移工作流说明清晰准确

数据库迁移工作流部分提供了明确的指导,禁止手动创建 SQL 文件,必须通过 bun run db:generate 流程。这与 PR 中新增的 4 个迁移(0044–0047)一致。


24-52: 构建和开发命令参考完整

命令文档涵盖开发、构建、质量检查和测试的所有关键流程。建议所有贡献者在提交前遵循预提交清单(第 15-22 行)。


10-22: 关键规则明确定义

临界规则部分为贡献者设置了清晰的预期:禁止使用 emoji、强制 80% 测试覆盖、必须 i18n 处理,以及预提交检查清单。这些规则与 PR 目标(新增测试套件、i18n 完成)一致。


102-110: 修正守卫管道顺序和国际化语言列表

  1. 守卫管道顺序错误(第102-110行):文档中 sensitive 守卫的位置不准确。根据 src/app/v1/_lib/proxy/guard-pipeline.ts 的实现,实际执行顺序应为:
Request -> GuardPipeline -> [auth -> client -> model -> version -> probe ->
                            session -> warmup -> requestFilter -> sensitive ->
                            rateLimit -> provider -> providerRequestFilter ->
                            messageContext] ->
           ProxyForwarder -> ProxyResponseHandler -> Response

sensitive 守卫应该在 requestFilter 之后、rateLimit 之前执行,而非文档中所示的位置。

  1. 国际化语言列表错误(第129行):文档记载"5种语言:zh-CN、en、ja、ko、de",但实际支持的语言为 enjaruzh-CNzh-TW。需更正为:zh-CNzh-TWruenja
⛔ Skipped due to learnings
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/v1/_lib/**/*.ts : Guard pipeline must execute in order: ProxyAuthenticator, SensitiveWordGuard, VersionGuard, ProxySessionGuard, ProxyRateLimitGuard, ProxyProviderResolver
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/v1/_lib/proxy-handler.ts : Structure request flow in proxy handler through: ProxySession.fromContext() -> detectFormat() -> GuardPipelineBuilder.run() -> ProxyForwarder.send() -> ProxyResponseHandler.dispatch()
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/*handler*.ts : Stream responses with proper backpressure handling and chunked transfer encoding
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*guard*.ts : Implement fail-open strategy: allow requests when Redis is unavailable
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 517
File: src/app/v1/_lib/proxy/auth-guard.ts:205-232
Timestamp: 2026-01-03T09:10:02.182Z
Learning: In `src/app/v1/_lib/proxy/auth-guard.ts`, the `extractApiKeyFromHeaders` function is intentionally a standalone simplified version for non-Guard flows. It deliberately does not check for multi-source conflicts (unlike `ProxyAuthenticator.validate`), and the code duplication is intentional to avoid coupling between Guard and non-Guard authentication flows.
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/**/*.ts : Use Hono router for ultrafast, lightweight routing in proxy endpoints
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Use undici library for HTTP requests instead of node-fetch for better performance
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 517
File: src/app/v1/_lib/models/available-models.ts:45-70
Timestamp: 2026-01-03T09:09:37.748Z
Learning: The `authenticateRequest` function in `src/app/v1/_lib/models/available-models.ts` is intentionally a lightweight version of authentication, specifically designed for the `/v1/models` endpoint. It does not need to check for multi-source conflicts or use the full `ProxyAuthenticator` logic, as the scenario is simpler. The JSON error response format is intentional (more generic than `ProxyResponses.buildError`).

- **Path alias**: `@/` maps to `./src/`
- **Formatting**: Biome (double quotes, trailing commas, 2-space indent, 100 char width)
- **Exports**: Prefer named exports over default exports
- **i18n**: Use `next-intl` for internationalization (5 languages: zh-CN, en, ja, ko, de)
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:

#!/bin/bash
# Verify supported i18n languages in the project

echo "=== Checking messages directory ==="
if [ -d "src/messages" ]; then
    ls -1 src/messages/ | grep -v "^\." || echo "No locales found in src/messages"
else
    echo "src/messages directory not found"
fi

echo -e "\n=== Checking for i18n/locale configuration ==="
find . -name "i18n*" -o -name "*locale*" -o -name "*intl*" | head -20

echo -e "\n=== Checking next.config for locale settings ==="
find . -name "next.config*" -type f | while read f; do
    echo "File: $f"
    rg -A 10 "locale|i18n|intl" "$f" -i || echo "No matches"
done

echo -e "\n=== Checking package.json for next-intl ==="
if [ -f "package.json" ]; then
    rg "next-intl" package.json || echo "next-intl not found in package.json"
fi

Repository: ding113/claude-code-hub

Length of output: 908


🏁 Script executed:

#!/bin/bash
# Check i18n directory structure and locale definitions

echo "=== src/i18n directory structure ==="
find src/i18n -type f | head -20

echo -e "\n=== Search for locale definitions ==="
rg -r '$0' "locale|zh-CN|zh-TW|en|ja|ru|ko|de" src/i18n -t ts -t js -A 2 -B 2

echo -e "\n=== Check for middleware or locale config ==="
rg "locales|LOCALES|supportedLocales" src/ -t ts -t js -B 2 -A 2

echo -e "\n=== Check CLAUDE.md content around line 129 ==="
sed -n '125,135p' CLAUDE.md

Repository: ding113/claude-code-hub

Length of output: 13059


更新 i18n 语言列表

CLAUDE.md 第 129 行的语言列表与实际配置不符。根据 src/i18n/config.ts,项目实际支持的 5 种语言为:zh-CN、zh-TW、en、ru、ja。需将文档中的 "zh-CN, en, ja, ko, de" 更正为实际支持的语言列表。

🤖 Prompt for AI Agents
In @CLAUDE.md at line 129, Update the language list in the CLAUDE.md line that
currently reads "zh-CN, en, ja, ko, de" to match the actual supported languages
from src/i18n/config.ts by replacing it with "zh-CN, zh-TW, en, ru, ja"; ensure
the document string exactly matches that new comma-separated list so the README
aligns with the code.

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Too many files changed for review.

1 similar comment
@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Too many files changed for review.

Remove custom X button, keep browser native clear button from
type="search".

- Remove custom clear button code and unused X icon import
- Remove unnecessary right padding (pr-9)
@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

🧪 测试结果

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

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

hank9999 and others added 2 commits January 7, 2026 13:30
* feat: 添加供应商查询缓存, 改善性能

* fix: 让后台管理函数不经过缓存

* fix: 初始化 Redis 订阅时避免未正确设置 subscriptionInitialized
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: 0

🧹 Nitpick comments (1)
src/actions/providers.ts (1)

3164-3204: 建议使用 findAllProvidersFresh 以确保数据一致性。

此函数用于用户/密钥编辑时的模型下拉建议。当管理员刚添加或修改供应商后,如果继续使用缓存数据 (findAllProviders),用户可能看不到最新的模型列表。

🔎 建议修改
     // 获取所有启用的供应商
-    const providers = await findAllProviders();
+    const providers = await findAllProvidersFresh();
     const enabledProviders = providers.filter((p) => p.isEnabled);

注意:这会增加数据库查询负担。如果调用频率较高,可考虑:

  1. 保持当前实现,依赖 TTL 自然过期
  2. 或将此函数改为仅管理员可用
📜 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 cc32b47 and 9e8f9fe.

📒 Files selected for processing (6)
  • src/actions/providers.ts
  • src/app/[locale]/settings/providers/_components/provider-manager.tsx
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/lib/cache/provider-cache.ts
  • src/repository/provider.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use 2-space indentation in all code files

Files:

  • src/actions/providers.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias @/* to reference files from ./src/* directory

**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias @/* to reference files in ./src/* directory

Files:

  • src/actions/providers.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures

Files:

  • src/actions/providers.ts
src/actions/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/actions/**/*.ts: Validate all user inputs with Zod schemas before processing
Use Server Actions in next-safe-action with OpenAPI generation for admin API endpoints
Use Next.js API Routes and Server Actions for admin operations and REST endpoints

Files:

  • src/actions/providers.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages

Files:

  • src/actions/providers.ts
src/**/*provider*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Set provider circuit breaker failure threshold, open duration, and half-open success threshold in configuration

Files:

  • src/actions/providers.ts
src/{repository,actions}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Avoid N+1 queries by using eager loading and batch queries for statistics

Files:

  • src/actions/providers.ts
🧠 Learnings (1)
📚 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/actions/providers.ts
🧬 Code graph analysis (1)
src/actions/providers.ts (4)
src/lib/cache/provider-cache.ts (1)
  • publishProviderCacheInvalidation (79-83)
src/repository/provider.ts (3)
  • findAllProvidersFresh (201-265)
  • resetProviderTotalCostResetAt (514-525)
  • findAllProviders (276-278)
src/types/provider.ts (4)
  • CodexReasoningEffortPreference (17-24)
  • CodexReasoningSummaryPreference (26-26)
  • CodexTextVerbosityPreference (28-28)
  • CodexParallelToolCallsPreference (31-31)
src/lib/constants/provider.constants.ts (1)
  • PROVIDER_GROUP (25-30)
🔇 Additional comments (8)
src/actions/providers.ts (8)

107-128: LGTM! 缓存失效广播的容错设计良好。

函数正确地将缓存失效失败隔离在 try-catch 中,不影响主流程。日志上下文包含操作类型和 providerId,便于问题追踪。


148-149: LGTM! 管理端正确使用 findAllProvidersFresh 绕过缓存。

与 PR 目标一致:"ensured admin management functions bypass cache"。


239-257: LGTM! 新增字段与仓储层 schema 一致。

limitTotalUsd 和四个 Codex 偏好字段正确地从 findAllProvidersFresh 结果映射到前端展示结构。


485-490: LGTM! Codex 偏好默认值设置正确。

默认值 "inherit" 与类型定义一致,表示继承系统默认配置。


664-665: LGTM! 编辑操作后正确广播缓存失效。

addProviderremoveProvider 保持一致的缓存失效模式。


701-702: LGTM! 删除操作的缓存失效顺序正确。

先清理本地缓存,再广播到其他实例,确保本实例立即生效。


783-809: 考虑是否需要添加缓存失效广播。

addProvidereditProviderremoveProvider 不同,此函数未调用 broadcastProviderCacheInvalidation。如果其他实例缓存了供应商的 totalCostResetAt 字段,可能会导致配额计算不一致。

请确认:重置 totalCostResetAt 后,其他实例是否需要立即感知此变更?如果配额计算依赖于实时的 totalCostResetAt 值,建议添加缓存失效广播:

     revalidatePath("/settings/providers");
     revalidatePath("/dashboard/quotas/providers");
+
+    // 广播缓存更新(跨实例即时生效)
+    await broadcastProviderCacheInvalidation({ operation: "edit", providerId });
+
     return { ok: true };

3129-3153: LGTM! 分组匹配逻辑正确处理了通配符和默认情况。

checkProviderGroupMatch 正确实现了:

  1. PROVIDER_GROUP.ALL ("*") 通配符匹配所有供应商
  2. groupTag 回退到 PROVIDER_GROUP.DEFAULT
  3. 多标签场景下的 some 匹配

@ding113 ding113 closed this Jan 7, 2026
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

5 participants