Skip to content

feat(rate-limit): 多层级并发 UA 限制#851

Open
tesgth032 wants to merge 10 commits intoding113:devfrom
tesgth032:feat/ua-concurrent-limit
Open

feat(rate-limit): 多层级并发 UA 限制#851
tesgth032 wants to merge 10 commits intoding113:devfrom
tesgth032:feat/ua-concurrent-limit

Conversation

@tesgth032
Copy link
Contributor

@tesgth032 tesgth032 commented Mar 1, 2026

变更摘要

实现多层级并发 UA(User-Agent)限制,维度覆盖 User / Key / Provider,并与现有并发 Session 逻辑语义对齐:

  • 统计口径:限制“活跃 UA 去重数量”(不是并发请求数),且只阻止“新 UA”进入;已存在 UA 在达到上限后仍允许继续请求
  • Key 继承:Key 未设置或为 0 时继承 User 的并发 UA 上限;Key > 0 时作为 Key 维度上限(并校验不超过 User)
  • Provider 独立上限:Provider 的 limit_concurrent_uas 为 0 表示不限制;超限时自动 fallback 到下一个可用 Provider
  • 回滚:Provider UA 先 check+track,若后续 Provider Session 并发检查失败并触发 fallback,会回滚本次新增的 Provider UA 追踪,避免“幽灵 UA”占用名额
  • Redis Cluster:active_uas 相关 key 统一使用 {active_uas} hash tag;UA member 使用 clientType 分桶并 sha256,避免 UA 过长

新增错误码:

  • RATE_LIMIT_CONCURRENT_UAS_EXCEEDED
  • KEY_LIMIT_CONCURRENT_UAS_EXCEEDS_USER_LIMIT

数据库

迁移 drizzle/0078_wild_scourge.sql

  • users.limit_concurrent_uas:可空(null/<=0 视为不限制)
  • keys.limit_concurrent_uas / providers.limit_concurrent_uas:默认 0
  • 增加非负 CHECK 约束,避免负数写入破坏语义

主要改动点

  • src/lib/rate-limit/concurrent-ua-limit.ts:UA limit 归一化、Key/User 有效上限解析、UA identity(bucket + sha256)
  • src/lib/redis/active-ua-keys.ts:UA 维度的 Redis key builders(含 cluster hash tag)
  • src/lib/rate-limit/service.ts:Key/User UA 与 Provider UA 原子 check+track(复用 Lua 脚本别名),Provider UA untrack 回滚
  • src/app/v1/_lib/proxy/rate-limit-guard.ts:在并发 Session 之前增加 Key/User UA 并发保护
  • src/app/v1/_lib/proxy/provider-selector.ts:Provider UA check + fallback,且在 Session fallback 时回滚 UA
  • UI/i18n:User/Key/Provider 表单与 quota 展示新增 limitConcurrentUas,并补齐 5 语言文案(含 legacy provider form 折叠摘要)

测试

  • 新增/覆盖 UA 归一化、identity 解析、服务层 UA check+track、provider fallback、guard 集成等单测
  • 本地通过:bun run lint:fixbun run lintbun run typecheckbun run testbun run build

Greptile Summary

This PR implements multi-level concurrent User-Agent (UA) limiting at User, Key, and Provider dimensions to prevent resource exhaustion and fix issue #769 where keys bypassed user-level concurrent session limits.

Key Implementation Details:

  • UA Tracking vs Session Tracking: Unlike sessions which track active connections, UA limits track unique client identifiers (deduplicated by User-Agent string using SHA256 hashing)
  • Redis Cluster Compatible: Uses {active_uas} hash tag for all UA tracking keys to avoid CROSSSLOT errors in clustered environments
  • Inheritance Model: Keys automatically inherit user's limitConcurrentUas when not explicitly set (0 value)
  • Provider Fallback: When provider UA limit is exceeded, the system automatically falls back to the next available provider
  • Leak Prevention: UA check occurs before session tracking at the provider level; if session tracking fails after UA is tracked, the UA tracking is rolled back atomically

Technical Highlights:

  • Reuses existing Lua scripts (CHECK_AND_TRACK_KEY_USER_SESSION) for both session and UA tracking by adding generic aliases
  • Proper fail-open behavior when Redis is unavailable
  • Comprehensive test coverage (4 new test files with 130+ test cases)
  • Complete i18n support across all 5 languages
  • Database migration with proper constraints and defaults

No Critical Issues Found - The implementation is well-architected with proper atomicity guarantees, comprehensive error handling, and thorough test coverage.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation demonstrates excellent software engineering practices: atomic operations via Lua scripts prevent race conditions, comprehensive test coverage validates all critical paths, Redis cluster compatibility ensures production readiness, and the rollback mechanism prevents resource leaks. All database constraints are properly defined, i18n is complete, and the code follows existing patterns consistently.
  • No files require special attention

Important Files Changed

Filename Overview
src/lib/rate-limit/concurrent-ua-limit.ts Implements UA identity parsing and limit normalization. Logic for inheritance, bucketing by clientType, and SHA256 hashing is sound.
src/lib/redis/active-ua-keys.ts Redis key generators with {active_uas} hash tag for cluster compatibility. Includes proper type checking.
src/lib/rate-limit/service.ts Adds checkAndTrackKeyUserUa, checkAndTrackProviderUa, and untrackProviderUa methods. Fail-open behavior preserved.
src/app/v1/_lib/proxy/provider-selector.ts Provider UA check before session tracking prevents leaks. Rollback on session failure is correct. Fallback logic properly excludes failed providers.
src/app/v1/_lib/proxy/rate-limit-guard.ts Adds UA concurrent limit check (step 3) before session check (step 4). Key inherits user limit when not set. Proper error handling.
drizzle/0078_wild_scourge.sql Adds limit_concurrent_uas columns with proper constraints. Default 0 for keys/providers, nullable for users.
src/actions/keys.ts Adds validation to ensure key limitConcurrentUas doesn't exceed user limit. Follows existing pattern for other limits.
tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts Tests provider fallback when UA limit exceeded and rollback when session tracking fails. Verifies correct sequencing.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Request arrives] --> B[Rate Limit Guard]
    B --> C{Check Total Limits}
    C -->|Failed| X[Reject: Total limit]
    C -->|Pass| D{Check UA Concurrent}
    D -->|Failed| Y[Reject: UA limit]
    D -->|Pass| E{Check Session Concurrent}
    E -->|Failed| Z[Reject: Session limit]
    E -->|Pass| F{Check RPM/Cost Limits}
    F -->|Failed| W[Reject: Cost/RPM limit]
    F -->|Pass| G[Provider Selection]
    
    G --> H{Check Provider UA}
    H -->|Exceeded| I[Try Next Provider]
    I --> H
    H -->|Pass| J{Check Provider Session}
    J -->|Exceeded| K[Rollback Provider UA]
    K --> I
    J -->|Pass| L[Track Session]
    L --> M[Proxy Request]
    
    I -->|No Fallback| N[Reject: No providers]
    
    subgraph "Key/User Inheritance"
    D1[Key limit=0?]
    D1 -->|Yes| D2[Inherit User limit]
    D1 -->|No| D3[Use Key limit]
    end
Loading

Last reviewed commit: e136a0f

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

在数据库、后端、前端、Redis 与速率限制服务中新增并接入“并发 UA(User‑Agent)上限”字段与逻辑:包括迁移、类型/验证、API/表单、代理层并发检查、Redis 原子追踪、补丁契约以及单元测试覆盖(均为增量更改)。

Changes

Cohort / File(s) Summary
数据库迁移 & 模式
drizzle/0078_wild_scourge.sql, drizzle/meta/_journal.json, src/drizzle/schema.ts
keys(DEFAULT 0)、providers(DEFAULT 0)、users(无默认)新增 limit_concurrent_uas 列并记录迁移元信息。
速率限制核心 & Redis
src/lib/rate-limit/concurrent-ua-limit.ts, src/lib/redis/active-ua-keys.ts, src/lib/rate-limit/service.ts, src/lib/redis/lua-scripts.ts
新增并发 UA 工具/身份解析、Active‑UA Redis key 生成、RateLimitService 的 UA 原子检查/追踪方法及相关 Lua 脚本别名和 untrack 脚本。
代理层与选择/守卫
src/app/v1/_lib/proxy/provider-selector.ts, src/app/v1/_lib/proxy/rate-limit-guard.ts, src/app/v1/_lib/proxy/errors.ts, src/app/v1/_lib/proxy/error-handler.ts
在 provider 选择与 rate‑limit 守卫中加入并发 UA 检查、处理 UA 超限的降级/回退逻辑,并引入错误类型 "concurrent_uas"
后端动作、API 与存储
src/actions/keys.ts, src/actions/users.ts, src/actions/providers.ts, src/app/api/actions/[...route]/route.ts, src/repository/*, src/repository/_shared/transformers.ts
limit_concurrent_uas / limitConcurrentUas 在 create/update/read 路径中端到端传播;actions 增加校验(Key 不能超过 User 限额);API schema 与 transformer 更新。
类型、权限与验证
src/types/*, src/lib/validation/schemas.ts, src/lib/permissions/user-field-permissions.ts
扩展 User/Key/Provider 类型与请求类型,新增并发限值 Zod 模式,新增字段权限(admin 限制)。
前端表单、组件与页面
src/app/[locale]/dashboard/_components/user/..., src/app/[locale]/settings/providers/_components/forms/*, src/app/[locale]/dashboard/quotas/users/*
在用户/密钥/提供商表单、LimitRulePicker、KeyEditSection、UserEditSection、quota 列表/对话框等处加入 limitConcurrentUas 字段、默认值、UI 文案与提交逻辑。
提供商补丁契约 & 批量编辑
src/lib/provider-patch-contract.ts, src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts
limit_concurrent_uas 纳入 PATCH 字段、清理策略、归一化、补丁构建与批量更新检查与应用。
错误消息与本地化文案
messages/*/errors.json, messages/*/quota.json, messages/*/dashboard.json, messages/*/settings/providers/form/sections.json
新增错误码与本地化文案(RATE_LIMIT_CONCURRENT_UAS_EXCEEDED、KEY_LIMIT_CONCURRENT_UAS_EXCEEDS_USER_LIMIT)及多语言表单/配额翻译。
日志/链路错误呈现
src/app/[locale]/dashboard/logs/..., src/app/v1/_lib/proxy/session.ts, src/types/message.ts
在 provider chain item 中新增结构化错误字段(errorCode、errorParams),并新增 resolveChainItemErrorMessage 工具以翻译/解析链路错误显示。
Types 更新(前后端)
src/types/user.ts, src/types/key.ts, src/types/provider.ts, src/types/message.ts
在多处类型声明中加入 limitConcurrentUas / limit_concurrent_uas 字段,并更新相关 patch/patch-draft/patch-apply 类型。
单元测试
tests/unit/lib/rate-limit/*, tests/unit/proxy/*
新增/扩展大量测试:并发 UA 归一化、身份解析、RateLimitService UA 路径、provider selector 的 UA 降级场景以及 rate‑limit 守卫的 UA 超限用例。
工具/UI 文案解析
src/app/[locale]/dashboard/logs/_components/resolve-chain-item-error-message.ts, src/app/[locale]/dashboard/logs/_components/*
新增错误消息解析函数并在日志 UI 中优先使用翻译解析后的错误消息。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • ding113
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed PR实现了issue #769的所有目标:通过新增limitConcurrentUas字段、跨层级继承逻辑、API校验等,完整解决了Key并发限制无法继承User限制的问题。
Out of Scope Changes check ✅ Passed 所有变更均聚焦于实现多层级并发UA限制,包括数据库迁移、核心限流逻辑、UI/i18n更新、测试覆盖等,均在scope范围内,无超出范围的改动。
Title check ✅ Passed 标题清晰概括了本 PR 的主要目标——实现多层级并发 UA 限制功能,与整体变更内容高度相关。
Description check ✅ Passed PR 描述清晰完整,涵盖功能目标、实现细节、数据库变更、主要改动点、测试情况,并包含详细的技术摘要和流程图。

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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!

此拉取请求全面引入了一个新的多层级并发 User-Agent (UA) 限流系统,旨在更精细地控制不同实体(用户、密钥、提供商)的活跃 UA 数量。通过在数据库中添加相应字段、更新 Redis 存储和处理逻辑,以及在用户界面中提供配置选项,该功能增强了系统的资源管理能力,并确保在 UA 限制达到时能优雅地进行服务降级或回退,从而提升了系统的稳定性和可控性。

Highlights

  • 多层级并发 UA 限制: 新增了用户 (User)、密钥 (Key) 和提供商 (Provider) 三个层级的并发 User-Agent (UA) 限制功能。此限制旨在控制同时活跃的去重 UA 数量,而非并发请求数,主要用于阻止新的 UA 进入系统。
  • 限制继承与回退机制: Key 维度的并发 UA 限制在未设置或设置为 0 时,会自动继承其所属用户的限制。在提供商选择过程中,如果某个提供商的并发 UA 限制超限,系统会自动回退到下一个可用的提供商,以避免会话计数泄漏。
  • Redis 与数据库集成: Redis 中活跃 UA 的键统一使用 {active_uas} hash tag,以兼容 Redis Cluster。UA 成员通过 clientType 进行分桶并使用 SHA256 散列,以避免过长的 UA 字符串影响性能。数据库层面,userskeysproviders 表均新增了 limit_concurrent_uas 字段,并通过 Drizzle 迁移脚本 0078_wild_scourge.sql 完成了数据库更新。
  • 用户界面与国际化: 仪表盘和设置页面中,用户、密钥和提供商的表单及配额展示部分新增了 limitConcurrentUas 字段,允许管理员配置和查看这些限制。同时,补充了五种语言的国际化文案和新的错误码 RATE_LIMIT_CONCURRENT_UAS_EXCEEDED
  • 代理层限流逻辑更新: 代理层的限流守卫 (RateLimitGuard) 逻辑已更新,将 Key/User 并发 UA 限制检查置于并发会话限制之前,确保在建立上游连接前就能进行 UA 限制判断。提供商选择器 (ProviderSelector) 也增加了在提供商 UA 超限时进行回退的机制。
Changelog
  • drizzle/0078_wild_scourge.sql
    • Added limit_concurrent_uas column to keys, providers, and users tables.
  • drizzle/meta/_journal.json
    • Updated Drizzle migration journal to include 0078_wild_scourge.
  • messages/en/dashboard.json
    • Added translation keys for 'Concurrent UA Limit' and related descriptions.
    • Added 'Concurrent UAs' label for quota display.
  • messages/en/errors.json
    • Added error message for RATE_LIMIT_CONCURRENT_UAS_EXCEEDED.
    • Added error message for KEY_LIMIT_CONCURRENT_UAS_EXCEEDS_USER_LIMIT.
  • messages/en/quota.json
    • Added 'Concurrent UAs' label for quota display.
    • Added translation keys for 'Concurrent UA Limit' in quota forms.
  • messages/en/settings/providers/form/sections.json
    • Added translation keys for 'Concurrent UA Limit' in provider settings.
  • messages/ja/dashboard.json
    • Added Japanese translation keys for 'Concurrent UA Limit' and related descriptions.
    • Added Japanese 'Concurrent UAs' label for quota display.
  • messages/ja/errors.json
    • Added Japanese error message for RATE_LIMIT_CONCURRENT_UAS_EXCEEDED.
    • Added Japanese error message for KEY_LIMIT_CONCURRENT_UAS_EXCEEDS_USER_LIMIT.
  • messages/ja/quota.json
    • Added Japanese 'Concurrent UAs' label for quota display.
    • Added Japanese translation keys for 'Concurrent UA Limit' in quota forms.
  • messages/ja/settings/providers/form/sections.json
    • Added Japanese translation keys for 'Concurrent UA Limit' in provider settings.
  • messages/ru/dashboard.json
    • Added Russian translation keys for 'Concurrent UA Limit' and related descriptions.
    • Added Russian 'Concurrent UAs' label for quota display.
  • messages/ru/errors.json
    • Added Russian error message for RATE_LIMIT_CONCURRENT_UAS_EXCEEDED.
    • Added Russian error message for KEY_LIMIT_CONCURRENT_UAS_EXCEEDS_USER_LIMIT.
  • messages/ru/quota.json
    • Added Russian 'Concurrent UAs' label for quota display.
    • Added Russian translation keys for 'Concurrent UA Limit' in quota forms.
  • messages/ru/settings/providers/form/sections.json
    • Added Russian translation keys for 'Concurrent UA Limit' in provider settings.
  • messages/zh-CN/dashboard.json
    • Added Simplified Chinese translation keys for 'Concurrent UA Limit' and related descriptions.
    • Added Simplified Chinese 'Concurrent UAs' label for quota display.
  • messages/zh-CN/errors.json
    • Added Simplified Chinese error message for RATE_LIMIT_CONCURRENT_UAS_EXCEEDED.
    • Added Simplified Chinese error message for KEY_LIMIT_CONCURRENT_UAS_EXCEEDS_USER_LIMIT.
  • messages/zh-CN/quota.json
    • Added Simplified Chinese 'Concurrent UAs' label for quota display.
    • Added Simplified Chinese translation keys for 'Concurrent UA Limit' in quota forms.
  • messages/zh-CN/settings/providers/form/sections.json
    • Added Simplified Chinese translation keys for 'Concurrent UA Limit' in provider settings.
  • messages/zh-TW/dashboard.json
    • Added Traditional Chinese translation keys for 'Concurrent UA Limit' and related descriptions.
    • Added Traditional Chinese 'Concurrent UAs' label for quota display.
  • messages/zh-TW/errors.json
    • Added Traditional Chinese error message for RATE_LIMIT_CONCURRENT_UAS_EXCEEDED.
    • Added Traditional Chinese error message for KEY_LIMIT_CONCURRENT_UAS_EXCEEDS_USER_LIMIT.
  • messages/zh-TW/quota.json
    • Added Traditional Chinese 'Concurrent UAs' label for quota display.
    • Added Traditional Chinese translation keys for 'Concurrent UA Limit' in quota forms.
  • messages/zh-TW/settings/providers/form/sections.json
    • Added Traditional Chinese translation keys for 'Concurrent UA Limit' in provider settings.
  • src/actions/keys.ts
    • Updated addKey and editKey functions to include limitConcurrentUas parameter.
    • Implemented validation to ensure key.limitConcurrentUas does not exceed user.limitConcurrentUas.
    • Updated database operations to persist limit_concurrent_uas.
  • src/actions/providers.ts
    • Updated getProviders return type to include limitConcurrentUas.
    • Updated addProvider and editProvider functions to include limit_concurrent_uas parameter.
    • Updated mapApplyUpdatesToRepositoryFormat and PATCH_FIELD_TO_PROVIDER_KEY to handle limitConcurrentUas.
  • src/actions/users.ts
    • Updated user retrieval functions (getUsers, getUsersBatch, getUsersBatchCore) to include limitConcurrentUas.
    • Updated user creation and editing functions (addUser, createUserOnly, editUser) to include limitConcurrentUas parameter.
    • Updated database operations to persist limit_concurrent_uas.
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
    • Added limitConcurrentUas field to user and key creation forms.
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
    • Updated EditKeyDialogProps to include limitConcurrentUas.
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
    • Added limitConcurrentUas field to user editing forms.
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
    • Added limitConcurrentUas input field with label and description to the add key form.
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
    • Added limitConcurrentUas input field with label and description to the edit key form.
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
    • Updated KeyEditSectionProps to include limitConcurrentUas.
    • Modified limit rule handling to incorporate limitUas.
  • src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx
    • Added limitUas as a new LimitType.
    • Updated LIMIT_TYPE_OPTIONS to include '并发 UA'.
    • Adjusted input step and quick value options for limitUas.
  • src/app/[locale]/dashboard/_components/user/forms/limit-rules-display.tsx
    • Updated i18n string expectations to include limitUas.
  • src/app/[locale]/dashboard/_components/user/forms/user-edit-section.tsx
    • Updated UserEditSectionProps to include limitConcurrentUas.
    • Modified limit rule handling to incorporate limitUas.
  • src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
    • Added limitConcurrentUas input field to the user form.
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
    • Passed limitConcurrentUas from active user to AddKeyDialog.
  • src/app/[locale]/dashboard/_components/user/key-list.tsx
    • Updated hasQuota logic to consider limitConcurrentUas.
  • src/app/[locale]/dashboard/_components/user/user-key-manager.tsx
    • Passed limitConcurrentUas from active user to AddKeyDialog.
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
    • Passed limitConcurrentUas to EditKeyDialog.
  • src/app/[locale]/dashboard/quotas/users/_components/types.ts
    • Added limitConcurrentUas to UserKeyWithUsage and UserQuotaWithUsage interfaces.
  • src/app/[locale]/dashboard/quotas/users/_components/user-quota-list-item.tsx
    • Added display for limitConcurrentUas in the user quota list item.
  • src/app/[locale]/dashboard/quotas/users/_components/users-quota-client.tsx
    • Updated hasQuota function to include limitConcurrentUas.
  • src/app/[locale]/dashboard/quotas/users/page.tsx
    • Updated getUsersWithQuotas to fetch and include limitConcurrentUas for keys and users.
  • src/app/[locale]/dashboard/users/users-page-client.tsx
    • Passed limitConcurrentUas from current user to AddKeyDialog.
  • src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts
    • Added logic to build patch draft for limit_concurrent_uas.
  • src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx
    • Added state and UI for limitConcurrentUas in the legacy provider form.
    • Updated addProvider and editProvider calls to include limit_concurrent_uas.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
    • Updated updateProvider call to include limit_concurrent_uas.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx
    • Added SET_LIMIT_CONCURRENT_UAS action type and corresponding reducer logic.
    • Updated initial state creation to include limitConcurrentUas.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts
    • Added limitConcurrentUas to RateLimitState interface.
    • Added SET_LIMIT_CONCURRENT_UAS action type.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/limits-section.tsx
    • Added LimitCard component for limitConcurrentUas in the limits section.
  • src/app/v1/_lib/proxy/error-handler.ts
    • Updated getRateLimitStatusCode to return 429 for concurrent_uas limit type.
    • Updated error message documentation to include concurrent_uas.
  • src/app/v1/_lib/proxy/errors.ts
    • Added concurrent_uas to RateLimitError's limitType union.
  • src/app/v1/_lib/proxy/provider-selector.ts
    • Imported resolveConcurrentUaIdentity.
    • Implemented atomic concurrent UA check for providers before concurrent session check.
    • Added fallback logic if provider concurrent UA limit is exceeded.
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
    • Imported resolveConcurrentUaIdentity and resolveKeyUserConcurrentUaLimits.
    • Updated rate limit check order to include Key/User concurrent UA limit before concurrent session limit.
    • Implemented logic to check and track Key/User concurrent UA limits, throwing RateLimitError if exceeded.
  • src/drizzle/schema.ts
    • Added limitConcurrentUas column to users, keys, and providers tables in the Drizzle schema.
  • src/lib/auth.ts
    • Updated validateKey to include limitConcurrentUas in the default key object.
  • src/lib/permissions/user-field-permissions.ts
    • Added limitConcurrentUas to USER_FIELD_PERMISSIONS with requiredRole: "admin".
  • src/lib/provider-patch-contract.ts
    • Added limit_concurrent_uas to various provider batch patch related types and functions.
  • src/lib/rate-limit/concurrent-ua-limit.ts
    • Added new file defining types and utility functions for concurrent UA limits, including normalization, resolution of key/user limits, and UA identity resolution.
  • src/lib/rate-limit/service.ts
    • Imported new Redis active UA key functions.
    • Added checkAndTrackKeyUserUa method for atomic checking and tracking of Key/User concurrent UAs.
    • Added checkAndTrackProviderUa method for atomic checking and tracking of Provider concurrent UAs.
  • src/lib/redis/active-ua-keys.ts
    • Added new file defining functions to generate Redis keys for active UAs, using a hash tag for Redis Cluster compatibility.
  • src/lib/utils/error-messages.ts
    • Added RATE_LIMIT_CONCURRENT_UAS_EXCEEDED to ERROR_CODES and RATE_LIMIT_ERRORS.
  • src/lib/validation/schemas.ts
    • Added limitConcurrentUas validation to user, key, and provider creation/update schemas.
  • src/repository/_shared/transformers.ts
    • Updated toUser, toKey, and toProvider functions to include limitConcurrentUas.
  • src/repository/key.ts
    • Updated key repository functions to handle limitConcurrentUas in various operations like finding, creating, and updating keys.
  • src/repository/provider.ts
    • Updated provider repository functions to handle limitConcurrentUas in various operations like creating, finding, and updating providers.
  • src/repository/user.ts
    • Updated user repository functions to handle limitConcurrentUas in various operations like creating, finding, and updating users.
  • src/types/key.ts
    • Added limitConcurrentUas to Key, CreateKeyData, and UpdateKeyData interfaces.
  • src/types/provider.ts
    • Added limit_concurrent_uas to various provider related interfaces and types.
  • src/types/user.ts
    • Added limitConcurrentUas to various user related interfaces and types.
  • tests/unit/lib/rate-limit/concurrent-ua-limit.test.ts
    • Added new unit tests for concurrent-ua-limit utility functions.
  • tests/unit/lib/rate-limit/service-extra.test.ts
    • Added unit tests for checkAndTrackProviderUa and checkAndTrackKeyUserUa methods in RateLimitService.
  • tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts
    • Added new unit tests for provider selection fallback behavior when concurrent UA limit is exceeded.
  • tests/unit/proxy/rate-limit-guard.test.ts
    • Updated mocks to include checkAndTrackKeyUserUa.
    • Added unit tests for Key/User concurrent UA limit enforcement.
Activity
  • 此拉取请求自创建以来,尚未有任何人工评论或审查活动。
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

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

Choose a reason for hiding this comment

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

Code Review

这个 PR 增加了一个基于 User-Agent 的多层级并发限制功能,这是一个重要的特性增强。相关的代码变更遍及数据库、后端逻辑、前端组件和国际化文件,覆盖面很广。整体实现考虑得很周到,包括对 Redis Cluster 的兼容性处理和充分的单元测试。我的主要反馈集中在几个地方的代码重复问题,例如错误处理和回退逻辑。通过重构这些重复代码,可以提高代码的可维护性。除此之外,代码质量很高。

Comment on lines +288 to +346
if (!uaCheckResult.allowed) {
logger.warn("ProviderSelector: Provider concurrent UA limit exceeded, trying fallback", {
providerName: session.provider.name,
providerId: session.provider.id,
current: uaCheckResult.count,
limit: uaLimit,
attempt: attemptCount,
});

const failedContext = session.getLastSelectionContext();
session.addProviderToChain(session.provider, {
reason: "concurrent_limit_failed",
selectionMethod: failedContext?.groupFilterApplied
? "group_filtered"
: "weighted_random",
circuitState: getCircuitState(session.provider.id),
attemptNumber: attemptCount,
errorMessage: uaCheckResult.reason || "并发 UA 限制已达到",
decisionContext: failedContext
? {
...failedContext,
concurrentLimit: uaLimit,
currentConcurrent: uaCheckResult.count,
}
: {
totalProviders: 0,
enabledProviders: 0,
targetType: session.provider.providerType as NonNullable<
ProviderChainItem["decisionContext"]
>["targetType"],
requestedModel: session.getOriginalModel() || "",
groupFilterApplied: false,
beforeHealthCheck: 0,
afterHealthCheck: 0,
priorityLevels: [],
selectedPriority: 0,
candidatesAtPriority: [],
concurrentLimit: uaLimit,
currentConcurrent: uaCheckResult.count,
},
});

excludedProviders.push(session.provider.id);

const { provider: fallbackProvider, context: retryContext } =
await ProxyProviderResolver.pickRandomProvider(session, excludedProviders);

if (!fallbackProvider) {
logger.error("ProviderSelector: No fallback providers available", {
excludedCount: excludedProviders.length,
totalAttempts: attemptCount,
});
break;
}

session.setProvider(fallbackProvider);
session.setLastSelectionContext(retryContext);
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这段处理并发 UA 超限并回退到下一个供应商的逻辑,与后面的并发会话超限的回退逻辑(357-423行)非常相似。为了避免代码重复并提高可读性,建议将这部分回退逻辑提取成一个独立的辅助方法。这个方法可以接收失败的供应商、失败原因、当前尝试次数等作为参数,并返回下一个可用的供应商或 null。这样可以简化 ensure 方法中的主循环,使其更专注于业务流程。

Comment on lines +148 to +187
if (!uaConcurrentCheck.allowed) {
const rejectedBy = uaConcurrentCheck.rejectedBy ?? "key";
const fallbackCurrentUsage =
rejectedBy === "user" ? uaConcurrentCheck.userCount : uaConcurrentCheck.keyCount;
const fallbackLimitValue =
rejectedBy === "user" ? normalizedUserUaConcurrentLimit : effectiveKeyUaConcurrentLimit;
const currentUsage = Number(uaConcurrentCheck.reasonParams?.current);
const limitValue = Number(uaConcurrentCheck.reasonParams?.limit);
const resolvedCurrentUsage = Number.isFinite(currentUsage)
? currentUsage
: fallbackCurrentUsage;
const resolvedLimitValue = Number.isFinite(limitValue) ? limitValue : fallbackLimitValue;

logger.warn(
`[RateLimit] ${rejectedBy === "user" ? "User" : "Key"} UA limit exceeded: key=${key.id}, user=${user.id}, current=${resolvedCurrentUsage}, limit=${resolvedLimitValue}`
);

const resetTime = new Date().toISOString();

const { getLocale } = await import("next-intl/server");
const locale = await getLocale();
const message = await getErrorMessageServer(
locale,
uaConcurrentCheck.reasonCode ?? ERROR_CODES.RATE_LIMIT_CONCURRENT_UAS_EXCEEDED,
{
current: String(resolvedCurrentUsage),
limit: String(resolvedLimitValue),
}
);

throw new RateLimitError(
"rate_limit_error",
message,
"concurrent_uas",
resolvedCurrentUsage,
resolvedLimitValue,
resetTime,
null
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这部分错误处理逻辑与后面的并发会话检查(218-256行)几乎完全相同。为了提高代码的可维护性和减少重复,建议将这部分逻辑提取到一个私有辅助函数中。这个辅助函数可以接受检查结果、限制类型(concurrent_uasconcurrent_sessions)以及相关的限制值作为参数,并负责抛出相应的 RateLimitError。这样可以使 ensure 方法更简洁,也更容易在未来进行修改。

Comment on lines +691 to +693
const result = (await RateLimitService.redis.eval(
CHECK_AND_TRACK_KEY_USER_SESSION,
3, // KEYS count
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

checkAndTrackKeyUserUa 方法中复用 CHECK_AND_TRACK_KEY_USER_SESSION 这个 Lua 脚本,虽然从功能上可能是可行的,但从可读性和可维护性角度来看,可能会引起混淆。脚本的名称明确指向 'SESSION',但在这里却用于处理 'UA'。建议为这个 Lua 脚本起一个更通用的名称,例如 CHECK_AND_TRACK_CONCURRENT_MEMBER,以更准确地反映其通用性,避免未来维护者产生误解。

@github-actions github-actions bot added the size/XL Extra Large PR (> 1000 lines) label Mar 1, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR implements a multi-level concurrent User-Agent (UA) rate limiting feature across User/Key/Provider dimensions. The implementation is well-structured with proper atomic Redis operations for race condition prevention, comprehensive test coverage, and i18n support for all 5 languages.

PR Size: XL

  • Lines changed: 5342 (5319 additions, 23 deletions)
  • Files changed: 76

Note: This is a large PR. Consider splitting future PRs of this size into smaller, focused changes for easier review:

  • Core rate-limit logic (concurrent-ua-limit.ts, service.ts changes)
  • Redis integration (active-ua-keys.ts)
  • UI/Dashboard changes
  • i18n updates

Issues Found

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

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean (Fail Open pattern with logging)
  • Type safety - Clean (proper null/undefined handling)
  • Documentation accuracy - Clean
  • Test coverage - Adequate (new tests for all core functions)
  • Code clarity - Good

Notable Implementation Details

  1. Atomic Operations: Uses Lua scripts for atomic check-and-track operations to prevent race conditions in concurrent scenarios.

  2. Redis Cluster Compatibility: Uses hash tags ({active_uas}) for Key/User/Global UA keys to ensure same-slot placement in Redis Cluster.

  3. Fail Open Pattern: When Redis is unavailable, requests are allowed through (Fail Open) rather than blocked, ensuring availability.

  4. Provider Fallback: When a provider hits its concurrent UA limit, the system automatically falls back to the next available provider without session count leakage.

  5. UA Identity Resolution: Uses SHA256 hash of parsed client type to create stable UA identifiers, avoiding memory issues from long UA strings.


Automated review by Claude AI

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: 9

Caution

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

⚠️ Outside diff range comments (1)
src/app/v1/_lib/proxy/provider-selector.ts (1)

282-423: ⚠️ Potential issue | 🟠 Major

UA 先追踪后 Session 失败时会留下“幽灵 UA”计数。

当前流程先执行 checkAndTrackProviderUa,若随后 checkAndTrackProviderSession 失败并切换 fallback,前一个 provider 上新追踪到的 UA 没有回滚,会导致 UA 计数虚高并误触发限流。

🛠 建议修复(在 session 检查失败分支回滚 UA,或合并为单个原子脚本)
         const checkResult = await RateLimitService.checkAndTrackProviderSession(
           session.provider.id,
           session.sessionId,
           limit
         );

         if (!checkResult.allowed) {
+          // 回滚本次新追踪的 UA,避免 fallback 后遗留 UA 计数
+          if (uaCheckResult.tracked) {
+            void RateLimitService.untrackProviderUa(session.provider.id, uaId);
+          }
+
           logger.warn(
             "ProviderSelector: Provider concurrent session limit exceeded, trying fallback",
             {
               providerName: session.provider.name,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/provider-selector.ts` around lines 282 - 423, When
RateLimitService.checkAndTrackProviderUa succeeds but the subsequent
RateLimitService.checkAndTrackProviderSession fails, the UA counter on the
previous provider is never decremented causing "ghost UA" counts; update the
session failure branch (the block handling !checkResult.allowed) to undo the
prior UA tracking by calling the corresponding rollback/decrement on the UA
tracker (e.g., a new RateLimitService.rollbackProviderUa or
RateLimitService.decrementProviderUa with session.provider.id and uaId) before
adding provider to chain and switching provider, or alternatively replace the
two-step calls with a single atomic operation in RateLimitService (e.g.,
checkAndTrackProviderUaAndSession) that performs both checks/updates
transactionally; locate fixes around RateLimitService.checkAndTrackProviderUa,
RateLimitService.checkAndTrackProviderSession, and the failure handling code
that calls session.addProviderToChain/session.setProvider to ensure UA is
reverted on session-check failure.
🧹 Nitpick comments (5)
src/lib/redis/active-ua-keys.ts (1)

20-28: 可选:为 keyId/userId 增加整数边界保护。

当前签名是 number,若上游误传 NaN/Infinity/小数,会生成异常 key(如 :NaN:)。建议在此层或调用边界做 Number.isSafeInteger 校验,降低线上排查成本。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/redis/active-ua-keys.ts` around lines 20 - 28, The functions
getKeyActiveUasKey and getUserActiveUasKey currently accept a number and can
produce invalid keys if passed NaN/Infinity/floats; add input validation at the
top of each function using Number.isSafeInteger(keyId) /
Number.isSafeInteger(userId) and throw a clear TypeError (e.g.,
"getKeyActiveUasKey: keyId must be a safe integer" / "getUserActiveUasKey:
userId must be a safe integer") so callers surface incorrect values early;
alternatively, if you prefer coercion, explicitly coerce with Math.trunc after
checking finite and log a warning—ensure the chosen behavior is consistent for
both functions.
src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx (1)

1521-1535: 新增 UA 限制输入后,折叠摘要未展示该值。

Line [1521] 新增了输入项,但上方 rate-limit 摘要(Line [1320] 附近)仍未包含 limitConcurrentUas,用户折叠后无法快速确认该配置。

♻️ 建议补齐摘要展示
@@
                     if (limitConcurrentSessions)
                       limits.push(
                         t("sections.rateLimit.summary.concurrent", {
                           count: limitConcurrentSessions,
                         })
                       );
+                    if (limitConcurrentUas)
+                      limits.push(
+                        t("sections.rateLimit.summary.concurrentUas", {
+                          count: limitConcurrentUas,
+                        })
+                      );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form.legacy.tsx
around lines 1521 - 1535, The rate-limit collapsed summary does not include the
newly added limitConcurrentUas value; update the summary renderer (the JSX/logic
that builds the rate-limit summary near the existing summary code around where
other rate-limit fields are displayed) to read the limitConcurrentUas state and
include it in the collapsed text (use limitConcurrentUas?.toString() or a
fallback like "—"/placeholder when empty), ensuring the displayed label matches
t("sections.rateLimit.limitConcurrentUas.label") so users can see the configured
concurrent UA limit when the section is collapsed; update any summaryFormatting
helper or conditional that collects other fields (e.g., maxTokens,
requestsPerMinute) to also include limitConcurrentUas.
src/types/key.ts (1)

26-26: 建议补充 limitConcurrentUas 的语义注释(0 / undefined)

新增字段本身没问题,但建议在类型旁明确 0undefined 的业务含义,避免后续在继承逻辑里被误用。

Based on learnings: In TypeScript interfaces, explicitly document and enforce distinct meanings for null and undefined.

Also applies to: 59-60, 85-86

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/key.ts` at line 26, Add a clear JSDoc comment for the
limitConcurrentUas property in the interface explaining the distinct meanings:
explicitly state that 0 means “disable concurrency” (or “no concurrent UAs
allowed”), undefined means “inherit default or upstream setting”, and that
callers must not treat them interchangeably; update the type if desirable to
number | undefined (or a branded type) to make absence explicit; apply the same
JSDoc + type clarification to the other analogous fields mentioned in the review
so their semantics (0 vs undefined) are unambiguous in interfaces like
limitConcurrentUas.
tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts (1)

115-133: 建议:增强 mock session 对象的类型安全

当前 session 对象使用 any 类型。考虑从 ProxySession 类型中提取接口或使用 Partial<ProxySession> 来提高类型安全性,便于未来维护时捕获接口变更。

可选改进
// 可以考虑提取 session mock 工厂函数供其他测试复用
function createMockSession(overrides: Partial<{
  sessionId: string;
  userAgent: string;
  // ... other fields
}> = {}) {
  // ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts` around
lines 115 - 133, Replace the ad-hoc any-typed session mock with a typed mock
using Partial<ProxySession> (or an interface extracted from ProxySession) so
TypeScript will catch API changes; update the session declaration from "const
session: any = { ... }" to "const session: Partial<ProxySession> = { ... }" (or
use a createMockSession factory) and ensure methods like shouldReuseProvider,
getOriginalModel, getCurrentModel, setProvider, setLastSelectionContext,
getLastSelectionContext, and addProviderToChain match the ProxySession
signatures and return types; adjust any uses in the test to account for Partial
(e.g., cast where necessary) so the mock remains compatible with functions
expecting Provider and other concrete types.
src/app/v1/_lib/proxy/rate-limit-guard.ts (1)

48-50: 注释编号不一致

检查顺序注释中的编号存在不一致:

  • 第 48-50 行注释显示 3-5. 资源/频率保护6-9. 短期周期限额
  • 第 258 行 User RPM 标注为 5.
  • 第 288 行 Key 5h 限额标注为 5.(应为 6.

建议统一注释中的步骤编号,以便于代码维护。

Also applies to: 258-258, 288-288

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/rate-limit-guard.ts` around lines 48 - 50, The numbered
steps in the comment blocks are inconsistent; update the inline comment labels
so they follow the intended sequence (e.g., "3-5. 资源/频率保护", "6-9. 短期周期限额",
"10-13. 中长期周期限额") and fix the mismatched labels where "User RPM" and "Key 5h"
are currently misnumbered—locate the comment strings (e.g., the block containing
"资源/频率保护", the "User RPM" label, and the "Key 5h" label) and renumber them to
match the sequence (change the "User RPM" label to the correct step within 3-5
and change the "Key 5h" label from 5 to 6 so the short-period section reads
6-9).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx:
- Line 66: The fallbackLabel for the option with type "limitUas" is hardcoded as
"并发 UA"; replace it with an i18n lookup so the UI uses translated text instead
of a literal string: update the option entry in the list inside
limit-rule-picker.tsx to call the component's translation helper (e.g.
t('dashboard.limitUas') or the existing i18n helper used in this file) and add
the corresponding translation key "dashboard.limitUas" to all locale files
(zh-CN, zh-TW, en, ja, ru); ensure you use the same i18n accessor (function or
hook) that other options in this component use so fallback behavior remains
consistent.
- Around line 213-215: The step attribute only affects UI stepping but does not
prevent users from submitting decimals; update the form submit path in
handleSubmit to validate and coerce integer-only limits for the relevant types
(e.g., when type === "limitUas" and likewise for "limitSessions" / "limitRpm"):
check that the submitted value is a non-negative integer (e.g., Number.isInteger
and >= 0) and either coerce to an integer or return a field validation error so
the form cannot submit decimals like 1.5; reference the existing type variable
and handleSubmit function in limit-rule-picker.tsx to find where to insert this
validation and the error feedback.

In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/index.tsx:
- Line 352: The limits tab status logic is missing the limitConcurrentUas field
so updating only UA concurrency does not flip the tab out of default; update
getTabStatus() to include state.rateLimit.limitConcurrentUas in the
comparison/dirty check alongside the other rateLimit properties and ensure the
same key name (limit_concurrent_uas / limitConcurrentUas mapping) is considered
in the object built at the top where limit_concurrent_uas is set (refer to the
existing assignment limit_concurrent_uas: state.rateLimit.limitConcurrentUas and
the getTabStatus function) so the tab status reflects changes to UA concurrency.

In `@src/app/api/actions/`[...route]/route.ts:
- Line 94: The field description for limitConcurrentUas is hardcoded with
describe("并发UA上限"); update it to use the i18n lookup used across the project
(e.g., call the shared translator function like i18n.t or t with the appropriate
key) instead of a raw string so the OpenAPI/schema docs render localized text;
replace the describe argument in the z.number().nullable().describe(...) call
for limitConcurrentUas (and the similar hardcoded describe at the other
occurrence around line 129) with the i18n key lookup (e.g.,
i18n.t('schema.limitConcurrentUas')) and ensure the i18n key exists in all 5
language resource files (zh-CN, zh-TW, en, ja, ru).

In `@src/lib/provider-patch-contract.ts`:
- Around line 228-229: The switch branch handling "limit_concurrent_uas"
currently accepts any finite number and therefore permits negatives and
decimals; change the validation so that for the "limit_concurrent_uas" case you
only accept non-negative integers (e.g. use Number.isInteger(value) && value >=
0) instead of the generic isFinite check, update the rejection path/error
message accordingly, and keep the "circuit_breaker_failure_threshold" branch
behavior unchanged unless it also needs integer semantics—locate the switch on
the patch key (the case "limit_concurrent_uas") in provider-patch-contract.ts
and replace the permissive finite-number check with an integer-and-non-negative
check.

In `@src/lib/rate-limit/service.ts`:
- Around line 75-79: The code hardcodes the provider Redis key as
`provider:${providerId}:active_uas` instead of using the project's
key-builder/hashtag convention; add a new helper
getProviderActiveUasKey(providerId) in "@/lib/redis/active-ua-keys" that returns
the key using the same `{active_uas}` hash tag pattern, then replace all
occurrences of the hardcoded `provider:${providerId}:active_uas` in service.ts
(the spots near usages alongside getGlobalActiveUasKey, getKeyActiveUasKey,
getUserActiveUasKey and the occurrences around lines 818-829) to call
getProviderActiveUasKey(providerId) so the provider UA key is consistent and
cluster-hash-safe.

In `@src/lib/redis/active-ua-keys.ts`:
- Around line 5-7: Add and export a provider-dimension key builder in
src/lib/redis/active-ua-keys.ts (e.g., getProviderActiveUAKey(providerId:
string)) that constructs the provider active-UA Redis key using the same
hash-tagging scheme as the existing global/key/user helpers, then import and use
that function inside src/lib/rate-limit/service.ts to replace the currently
hard-coded provider key at the provider-key usage (the hardcoded key referenced
around the provider logic near the existing rate-limit handling). Ensure the
function signature and export match the module's style so callers in service.ts
can replace the literal string with getProviderActiveUAKey(providerId).

In `@src/lib/validation/schemas.ts`:
- Around line 155-157: The validation messages for the chained validators (.int,
.min, .max) are hardcoded Chinese strings; replace these literal messages with
i18n keys and use the i18n lookup function used elsewhere in the project (e.g.,
t('validation.concurrentUALimit.int')) when constructing the schema so messages
render per locale; update the same pattern for the other occurrences noted (the
other .int/.min/.max chains at the reported locations) and ensure the keys exist
in all supported locales (zh-CN, zh-TW, en, ja, ru).
- Around line 153-159: The schema for limitConcurrentUas uses z.coerce.number()
which converts null to 0 and breaks the intended semantics (null = unlimited,
undefined = inherit); change the schema to accept null explicitly by using
z.union([z.null(), z.coerce.number().int().min(0).max(1000)]) (and keep
.optional() if needed) so null is preserved, apply the same pattern to the other
affected fields referenced in the comment, and remove hardcoded Chinese
validation messages from the .int/.min/.max calls—replace them with keys that
reference the i18n translation strings so all messages are loaded from the
localization files.

---

Outside diff comments:
In `@src/app/v1/_lib/proxy/provider-selector.ts`:
- Around line 282-423: When RateLimitService.checkAndTrackProviderUa succeeds
but the subsequent RateLimitService.checkAndTrackProviderSession fails, the UA
counter on the previous provider is never decremented causing "ghost UA" counts;
update the session failure branch (the block handling !checkResult.allowed) to
undo the prior UA tracking by calling the corresponding rollback/decrement on
the UA tracker (e.g., a new RateLimitService.rollbackProviderUa or
RateLimitService.decrementProviderUa with session.provider.id and uaId) before
adding provider to chain and switching provider, or alternatively replace the
two-step calls with a single atomic operation in RateLimitService (e.g.,
checkAndTrackProviderUaAndSession) that performs both checks/updates
transactionally; locate fixes around RateLimitService.checkAndTrackProviderUa,
RateLimitService.checkAndTrackProviderSession, and the failure handling code
that calls session.addProviderToChain/session.setProvider to ensure UA is
reverted on session-check failure.

---

Nitpick comments:
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form.legacy.tsx:
- Around line 1521-1535: The rate-limit collapsed summary does not include the
newly added limitConcurrentUas value; update the summary renderer (the JSX/logic
that builds the rate-limit summary near the existing summary code around where
other rate-limit fields are displayed) to read the limitConcurrentUas state and
include it in the collapsed text (use limitConcurrentUas?.toString() or a
fallback like "—"/placeholder when empty), ensuring the displayed label matches
t("sections.rateLimit.limitConcurrentUas.label") so users can see the configured
concurrent UA limit when the section is collapsed; update any summaryFormatting
helper or conditional that collects other fields (e.g., maxTokens,
requestsPerMinute) to also include limitConcurrentUas.

In `@src/app/v1/_lib/proxy/rate-limit-guard.ts`:
- Around line 48-50: The numbered steps in the comment blocks are inconsistent;
update the inline comment labels so they follow the intended sequence (e.g.,
"3-5. 资源/频率保护", "6-9. 短期周期限额", "10-13. 中长期周期限额") and fix the mismatched labels
where "User RPM" and "Key 5h" are currently misnumbered—locate the comment
strings (e.g., the block containing "资源/频率保护", the "User RPM" label, and the
"Key 5h" label) and renumber them to match the sequence (change the "User RPM"
label to the correct step within 3-5 and change the "Key 5h" label from 5 to 6
so the short-period section reads 6-9).

In `@src/lib/redis/active-ua-keys.ts`:
- Around line 20-28: The functions getKeyActiveUasKey and getUserActiveUasKey
currently accept a number and can produce invalid keys if passed
NaN/Infinity/floats; add input validation at the top of each function using
Number.isSafeInteger(keyId) / Number.isSafeInteger(userId) and throw a clear
TypeError (e.g., "getKeyActiveUasKey: keyId must be a safe integer" /
"getUserActiveUasKey: userId must be a safe integer") so callers surface
incorrect values early; alternatively, if you prefer coercion, explicitly coerce
with Math.trunc after checking finite and log a warning—ensure the chosen
behavior is consistent for both functions.

In `@src/types/key.ts`:
- Line 26: Add a clear JSDoc comment for the limitConcurrentUas property in the
interface explaining the distinct meanings: explicitly state that 0 means
“disable concurrency” (or “no concurrent UAs allowed”), undefined means “inherit
default or upstream setting”, and that callers must not treat them
interchangeably; update the type if desirable to number | undefined (or a
branded type) to make absence explicit; apply the same JSDoc + type
clarification to the other analogous fields mentioned in the review so their
semantics (0 vs undefined) are unambiguous in interfaces like
limitConcurrentUas.

In `@tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts`:
- Around line 115-133: Replace the ad-hoc any-typed session mock with a typed
mock using Partial<ProxySession> (or an interface extracted from ProxySession)
so TypeScript will catch API changes; update the session declaration from "const
session: any = { ... }" to "const session: Partial<ProxySession> = { ... }" (or
use a createMockSession factory) and ensure methods like shouldReuseProvider,
getOriginalModel, getCurrentModel, setProvider, setLastSelectionContext,
getLastSelectionContext, and addProviderToChain match the ProxySession
signatures and return types; adjust any uses in the test to account for Partial
(e.g., cast where necessary) so the mock remains compatible with functions
expecting Provider and other concrete types.

ℹ️ Review info

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 2769a75 and 39c03a4.

📒 Files selected for processing (76)
  • drizzle/0078_wild_scourge.sql
  • drizzle/meta/0078_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • messages/en/errors.json
  • messages/en/quota.json
  • messages/en/settings/providers/form/sections.json
  • messages/ja/dashboard.json
  • messages/ja/errors.json
  • messages/ja/quota.json
  • messages/ja/settings/providers/form/sections.json
  • messages/ru/dashboard.json
  • messages/ru/errors.json
  • messages/ru/quota.json
  • messages/ru/settings/providers/form/sections.json
  • messages/zh-CN/dashboard.json
  • messages/zh-CN/errors.json
  • messages/zh-CN/quota.json
  • messages/zh-CN/settings/providers/form/sections.json
  • messages/zh-TW/dashboard.json
  • messages/zh-TW/errors.json
  • messages/zh-TW/quota.json
  • messages/zh-TW/settings/providers/form/sections.json
  • src/actions/keys.ts
  • src/actions/providers.ts
  • src/actions/users.ts
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
  • src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx
  • src/app/[locale]/dashboard/_components/user/forms/limit-rules-display.tsx
  • src/app/[locale]/dashboard/_components/user/forms/user-edit-section.tsx
  • src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  • src/app/[locale]/dashboard/_components/user/key-list.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-manager.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/quotas/users/_components/types.ts
  • src/app/[locale]/dashboard/quotas/users/_components/user-quota-list-item.tsx
  • src/app/[locale]/dashboard/quotas/users/_components/users-quota-client.tsx
  • src/app/[locale]/dashboard/quotas/users/page.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts
  • src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/limits-section.tsx
  • src/app/api/actions/[...route]/route.ts
  • src/app/v1/_lib/proxy/error-handler.ts
  • src/app/v1/_lib/proxy/errors.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • src/drizzle/schema.ts
  • src/lib/auth.ts
  • src/lib/permissions/user-field-permissions.ts
  • src/lib/provider-patch-contract.ts
  • src/lib/rate-limit/concurrent-ua-limit.ts
  • src/lib/rate-limit/service.ts
  • src/lib/redis/active-ua-keys.ts
  • src/lib/utils/error-messages.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.ts
  • src/repository/key.ts
  • src/repository/provider.ts
  • src/repository/user.ts
  • src/types/key.ts
  • src/types/provider.ts
  • src/types/user.ts
  • tests/unit/lib/rate-limit/concurrent-ua-limit.test.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
  • tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts

limitMonthlyUsd: z.number().nullable().describe("月消费上限"),
limitTotalUsd: z.number().nullable().describe("总消费上限"),
limitConcurrentSessions: z.number().nullable().describe("并发Session上限"),
limitConcurrentUas: z.number().nullable().describe("并发UA上限"),
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

新增字段描述仍为硬编码文本,未走 i18n。

这里新增的 describe("并发UA上限") 会直接进入接口文档展示,属于用户可见文本,建议改为从多语言资源注入,避免仅单语生效。

As per coding guidelines, "All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text".

Also applies to: 129-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/actions/`[...route]/route.ts at line 94, The field description
for limitConcurrentUas is hardcoded with describe("并发UA上限"); update it to use
the i18n lookup used across the project (e.g., call the shared translator
function like i18n.t or t with the appropriate key) instead of a raw string so
the OpenAPI/schema docs render localized text; replace the describe argument in
the z.number().nullable().describe(...) call for limitConcurrentUas (and the
similar hardcoded describe at the other occurrence around line 129) with the
i18n key lookup (e.g., i18n.t('schema.limitConcurrentUas')) and ensure the i18n
key exists in all 5 language resource files (zh-CN, zh-TW, en, ja, ru).

Comment on lines +155 to +157
.int("并发UA上限必须是整数")
.min(0, "并发UA上限不能为负数")
.max(1000, "并发UA上限不能超过1000")
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

新增校验提示文案是硬编码字符串,未走 i18n

这些新增提示是用户可见文本,建议改为 i18n key 映射,避免只支持单语言。

As per coding guidelines: All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text.

Also applies to: 289-291, 420-422, 540-542, 780-782

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/validation/schemas.ts` around lines 155 - 157, The validation
messages for the chained validators (.int, .min, .max) are hardcoded Chinese
strings; replace these literal messages with i18n keys and use the i18n lookup
function used elsewhere in the project (e.g.,
t('validation.concurrentUALimit.int')) when constructing the schema so messages
render per locale; update the same pattern for the other occurrences noted (the
other .int/.min/.max chains at the reported locations) and ensure the keys exist
in all supported locales (zh-CN, zh-TW, en, ja, ru).

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

76 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@@ -0,0 +1,3 @@
ALTER TABLE "keys" ADD COLUMN "limit_concurrent_uas" integer DEFAULT 0;--> statement-breakpoint
ALTER TABLE "providers" ADD COLUMN "limit_concurrent_uas" integer DEFAULT 0;--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "limit_concurrent_uas" integer; No newline at end of file
Copy link

Choose a reason for hiding this comment

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

SQL file missing newline at end

Suggested change
ALTER TABLE "users" ADD COLUMN "limit_concurrent_uas" integer;
ALTER TABLE "users" ADD COLUMN "limit_concurrent_uas" integer;
Prompt To Fix With AI
This is a comment left during a code review.
Path: drizzle/0078_wild_scourge.sql
Line: 3

Comment:
SQL file missing newline at end

```suggestion
ALTER TABLE "users" ADD COLUMN "limit_concurrent_uas" integer;
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

const globalKey = getGlobalActiveUasKey();
const keyKey = getKeyActiveUasKey(keyId);
const userKey = getUserActiveUasKey(userId);
const now = Date.now();
Copy link

Choose a reason for hiding this comment

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

Reusing CHECK_AND_TRACK_KEY_USER_SESSION Lua script for UA tracking is efficient, but the script name may cause confusion since it suggests session-specific logic. Consider adding a comment explaining this reuse is intentional and semantically correct.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/rate-limit/service.ts
Line: 689

Comment:
Reusing `CHECK_AND_TRACK_KEY_USER_SESSION` Lua script for UA tracking is efficient, but the script name may cause confusion since it suggests session-specific logic. Consider adding a comment explaining this reuse is intentional and semantically correct.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

🧹 Nitpick comments (2)
src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx (1)

51-51: JSDoc 注释遗漏了 limitRpm 翻译键。

LIMIT_TYPE_OPTIONS 包含 "limitRpm",但此处文档注释中 limitTypes.{...} 未列出该键,可能导致调用方遗漏翻译。

建议补充文档
-   * - limitTypes.{limit5h|limitDaily|limitWeekly|limitMonthly|limitTotal|limitSessions|limitUas}
+   * - limitTypes.{limitRpm|limit5h|limitDaily|limitWeekly|limitMonthly|limitTotal|limitSessions|limitUas}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx at
line 51, JSDoc for limitTypes in limit-rule-picker.tsx omits the "limitRpm"
translation key while LIMIT_TYPE_OPTIONS includes it; update the JSDoc comment
(the line with limitTypes.{...}) to include "limitRpm" so callers see the full
set of translation keys and won't miss providing a translation for limitRpm.
src/lib/rate-limit/service.ts (1)

797-852: Provider UA key 构建已修复,但 reason 字符串建议使用 i18n。

getProviderActiveUasKey(providerId) 的使用解决了之前评审中关于硬编码 Redis key 的问题,现在 Provider UA key 与其他维度保持一致。

Line 839 的 reason 字段使用了硬编码中文字符串 "供应商并发 UA 上限已达到"。虽然这与现有 checkAndTrackProviderSession(Line 782)的模式一致,但根据编码规范,用户可见的字符串应使用 i18n。建议后续统一重构这些 reason 字符串。

♻️ 建议修复(可选)
       if (allowed === 0) {
         return {
           allowed: false,
           count,
           tracked: false,
-          reason: `供应商并发 UA 上限已达到(${count}/${limit})`,
+          reasonCode: ERROR_CODES.RATE_LIMIT_CONCURRENT_UAS_EXCEEDED,
+          reasonParams: { current: count, limit, target: "provider" },
         };
       }

这样可以与 checkAndTrackKeyUserUa 的返回结构保持一致,由调用方根据 reasonCode 进行 i18n 处理。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/rate-limit/service.ts` around lines 797 - 852, The hard-coded Chinese
reason string in checkAndTrackProviderUa should be replaced with an
i18n-friendly code/field to match the pattern used by checkAndTrackKeyUserUa;
update the return when allowed === 0 to return a reasonCode (e.g.
"PROVIDER_UA_LIMIT_REACHED") instead of the literal `"供应商并发 UA
上限已达到(${count}/${limit})"`, keep or remove the human-readable reason field as
needed, and ensure callers of checkAndTrackProviderUa are adjusted to use the
new reasonCode for localization (reference: function checkAndTrackProviderUa and
parity with checkAndTrackKeyUserUa).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/app/`[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx:
- Line 51: JSDoc for limitTypes in limit-rule-picker.tsx omits the "limitRpm"
translation key while LIMIT_TYPE_OPTIONS includes it; update the JSDoc comment
(the line with limitTypes.{...}) to include "limitRpm" so callers see the full
set of translation keys and won't miss providing a translation for limitRpm.

In `@src/lib/rate-limit/service.ts`:
- Around line 797-852: The hard-coded Chinese reason string in
checkAndTrackProviderUa should be replaced with an i18n-friendly code/field to
match the pattern used by checkAndTrackKeyUserUa; update the return when allowed
=== 0 to return a reasonCode (e.g. "PROVIDER_UA_LIMIT_REACHED") instead of the
literal `"供应商并发 UA 上限已达到(${count}/${limit})"`, keep or remove the human-readable
reason field as needed, and ensure callers of checkAndTrackProviderUa are
adjusted to use the new reasonCode for localization (reference: function
checkAndTrackProviderUa and parity with checkAndTrackKeyUserUa).

ℹ️ Review info

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 39c03a4 and 7f5e3df.

📒 Files selected for processing (10)
  • src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
  • src/app/api/actions/[...route]/route.ts
  • src/lib/provider-patch-contract.ts
  • src/lib/rate-limit/service.ts
  • src/lib/redis/active-ua-keys.ts
  • src/lib/utils/error-messages.ts
  • src/lib/validation/schemas.ts
  • src/types/key.ts
  • tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
  • tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts
  • src/app/api/actions/[...route]/route.ts
  • src/lib/utils/error-messages.ts
  • src/lib/redis/active-ua-keys.ts

@tesgth032
Copy link
Contributor Author

已按 CodeRabbit / Gemini 审查意见修正(commit 7f5e3df):

  • Zod 校验:并发 Session/UA 上限改为 preprocess+union,避免空字符串/空值被 coerce 成 0;同时在 zodErrorToCode 中把 expected=int 映射到 MUST_BE_INTEGER,去除新增硬编码提示。
  • Provider patch contract:limit_concurrent_sessions/limit_concurrent_uas 的 {set} 值收紧为非负整数。
  • Redis key:补充 getProviderActiveUasKey() 并在 checkAndTrackProviderUa() 统一使用 {active_uas} hash tag。
  • UI/OpenAPI:provider 表单 limits tab 状态纳入 limitConcurrentUas;LimitRulePicker 去掉新增硬编码回退并补整数校验;OpenAPI 移除 limitConcurrentUas 的单语 describe。
  • 代码质量:Key.limitConcurrentUas 增加语义注释;测试中 session mock 改为 Partial。

如还有需要调整的点请继续指出。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/rate-limit/service.ts (1)

694-696: 建议在 Key/User UA 路径统一使用通用脚本别名,避免语义漂移。

当前 checkAndTrackKeyUserUa 仍调用 CHECK_AND_TRACK_KEY_USER_SESSION,建议改为 CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER,与同文件 Session 路径保持一致,降低后续误读成本。

建议修改
-      const result = (await RateLimitService.redis.eval(
-        CHECK_AND_TRACK_KEY_USER_SESSION,
+      const result = (await RateLimitService.redis.eval(
+        CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/rate-limit/service.ts` around lines 694 - 696, The call inside
checkAndTrackKeyUserUa is using the wrong script alias
CHECK_AND_TRACK_KEY_USER_SESSION; update the RateLimitService.redis.eval
invocation in the checkAndTrackKeyUserUa implementation to use
CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER instead of CHECK_AND_TRACK_KEY_USER_SESSION
so the UA path uses the same ZSET-member script alias as the Session path
(adjust the constant passed as the first argument to redis.eval where
checkAndTrackKeyUserUa is defined).
drizzle/0078_wild_scourge.sql (1)

1-3: 建议补充非负约束,增强数据完整性。

新增的 limit_concurrent_uas 字段目前缺少 CHECK 约束。虽然代码库中 limit_concurrent_sessions 等类似字段也未添加此类约束,但为了防止通过 SQL 直写绕过应用校验导致负数进入数据库破坏限流语义,建议在此迁移中补充非负验证。users 表的可空字段可允许 NULL。

建议的迁移补丁
 ALTER TABLE "keys" ADD COLUMN "limit_concurrent_uas" integer DEFAULT 0;--> statement-breakpoint
 ALTER TABLE "providers" ADD COLUMN "limit_concurrent_uas" integer DEFAULT 0;--> statement-breakpoint
 ALTER TABLE "users" ADD COLUMN "limit_concurrent_uas" integer;
+ALTER TABLE "keys"
+  ADD CONSTRAINT "keys_limit_concurrent_uas_non_negative"
+  CHECK ("limit_concurrent_uas" >= 0);--> statement-breakpoint
+ALTER TABLE "providers"
+  ADD CONSTRAINT "providers_limit_concurrent_uas_non_negative"
+  CHECK ("limit_concurrent_uas" >= 0);--> statement-breakpoint
+ALTER TABLE "users"
+  ADD CONSTRAINT "users_limit_concurrent_uas_non_negative"
+  CHECK ("limit_concurrent_uas" IS NULL OR "limit_concurrent_uas" >= 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@drizzle/0078_wild_scourge.sql` around lines 1 - 3, 为新增的 limit_concurrent_uas
字段添加非负约束以保证数据完整性:在 ALTER TABLE "keys" 和 ALTER TABLE "providers" 的列定义中增加非负
CHECK(例如 DEFAULT 0 且 CHECK (limit_concurrent_uas >= 0)),并为约束命名以便识别(例如
chk_keys_limit_concurrent_uas_nonnegative /
chk_providers_limit_concurrent_uas_nonnegative);对于 ALTER TABLE "users" 保持可空但添加
CHECK 以允许 NULL 或非负值(例如 CHECK (limit_concurrent_uas IS NULL OR
limit_concurrent_uas >= 0))以防止负数写入数据库。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/rate-limit/service.ts`:
- Around line 810-842: The rejection branch currently returns a hardcoded
Chinese reason string; change the returned payload where allowed === 0 to remove
the hardcoded text and instead return a machine-readable reason code and
parameters for i18n (e.g., reasonCode: 'provider.ua_concurrency_limit_reached'
and reasonParams: { count, limit }). Update the object returned in the same
place that currently references count/limit (the block reading result and
checking allowed === 0 in the function that calls RateLimitService.redis.eval
with CHECK_AND_TRACK_ZSET_MEMBER and SESSION_TTL_MS) so upper layers can perform
localization using the provided reasonCode/reasonParams. Ensure no user-facing
strings remain in this file.

---

Nitpick comments:
In `@drizzle/0078_wild_scourge.sql`:
- Around line 1-3: 为新增的 limit_concurrent_uas 字段添加非负约束以保证数据完整性:在 ALTER TABLE
"keys" 和 ALTER TABLE "providers" 的列定义中增加非负 CHECK(例如 DEFAULT 0 且 CHECK
(limit_concurrent_uas >= 0)),并为约束命名以便识别(例如
chk_keys_limit_concurrent_uas_nonnegative /
chk_providers_limit_concurrent_uas_nonnegative);对于 ALTER TABLE "users" 保持可空但添加
CHECK 以允许 NULL 或非负值(例如 CHECK (limit_concurrent_uas IS NULL OR
limit_concurrent_uas >= 0))以防止负数写入数据库。

In `@src/lib/rate-limit/service.ts`:
- Around line 694-696: The call inside checkAndTrackKeyUserUa is using the wrong
script alias CHECK_AND_TRACK_KEY_USER_SESSION; update the
RateLimitService.redis.eval invocation in the checkAndTrackKeyUserUa
implementation to use CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER instead of
CHECK_AND_TRACK_KEY_USER_SESSION so the UA path uses the same ZSET-member script
alias as the Session path (adjust the constant passed as the first argument to
redis.eval where checkAndTrackKeyUserUa is defined).

ℹ️ Review info

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 7f5e3df and c981bb8.

📒 Files selected for processing (3)
  • drizzle/0078_wild_scourge.sql
  • src/lib/rate-limit/service.ts
  • src/lib/redis/lua-scripts.ts

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.

Caution

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

⚠️ Outside diff range comments (1)
src/app/v1/_lib/proxy/provider-selector.ts (1)

280-357: ⚠️ Potential issue | 🟠 Major

Provider UA 与 Session 分步追踪会造成 UA 计数泄漏。

当前流程是“先追踪 UA,再追踪 Session”。当 UA 检查通过并完成追踪后,如果 Session 检查失败并触发回退,已写入的 UA 成员不会撤销,直到 TTL 过期前都会占用并发 UA 名额,可能导致后续请求被误判超限。

建议修复(最小止血 + 长期方案)
-        const uaCheckResult = await RateLimitService.checkAndTrackProviderUa(
+        const uaCheckResult = await RateLimitService.checkAndTrackProviderUa(
           session.provider.id,
           uaId,
           uaLimit
         );
+        const uaTrackedThisAttempt = uaCheckResult.tracked;
@@
         const checkResult = await RateLimitService.checkAndTrackProviderSession(
           session.provider.id,
           session.sessionId,
           limit
         );

         if (!checkResult.allowed) {
+          // 避免“UA 已追踪但 Session 未通过”导致的计数泄漏
+          if (uaTrackedThisAttempt) {
+            await RateLimitService.untrackProviderUa(session.provider.id, uaId);
+          }
           // === 并发限制失败 ===
           logger.warn(
             "ProviderSelector: Provider concurrent session limit exceeded, trying fallback",
// src/lib/rate-limit/service.ts
static async untrackProviderUa(providerId: number, uaId: string): Promise<void> {
  if (!RateLimitService.redis || RateLimitService.redis.status !== "ready") return;
  await RateLimitService.redis.zrem(getProviderActiveUasKey(providerId), uaId);
}

更稳妥的长期方案:把“provider UA + provider session”的检查与追踪合并到同一个 Lua 原子脚本,一次性 check+track,彻底消除双写不一致。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/provider-selector.ts` around lines 280 - 357, The code
first calls RateLimitService.checkAndTrackProviderUa(…) and then
RateLimitService.checkAndTrackProviderSession(…), which can leak UA entries when
the session check fails and the code falls back; fix by undoing the UA tracking
on failure (call a new RateLimitService.untrackProviderUa(providerId, uaId)
immediately before adding provider to chain / selecting fallback) or, better,
replace the two-step flow with a single atomic check+track in a Lua script
inside RateLimitService to perform provider UA+session checkAndTrack atomically;
update the failure path in ProviderSelector (around calls to
checkAndTrackProviderUa, checkAndTrackProviderSession,
session.addProviderToChain, ProxyProviderResolver.pickRandomProvider, and
session.setProvider) to call untrackProviderUa on session check failure so UA
slot is released.
🧹 Nitpick comments (1)
src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx (1)

54-71: 建议抽离 resolveChainItemErrorMessage 为共享工具。

该函数与 src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx 中实现几乎一致,后续容易出现 fallback 规则漂移,建议统一到单一 util。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
around lines 54 - 71, Extract the duplicate logic in
resolveChainItemErrorMessage into a shared utility function (e.g., export
function resolveChainItemErrorMessage(...) in a new util module) and replace the
inline implementations in both LogicTraceTab and provider-chain-popover to
import and use that single exported function; ensure the new util preserves the
same behavior (string errorMessage check, errorCode validation, tErrors call
with item.errorParams fallback and try/catch returning item.errorCode) and
update exports/imports accordingly so both components rely on the same
implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/app/v1/_lib/proxy/provider-selector.ts`:
- Around line 280-357: The code first calls
RateLimitService.checkAndTrackProviderUa(…) and then
RateLimitService.checkAndTrackProviderSession(…), which can leak UA entries when
the session check fails and the code falls back; fix by undoing the UA tracking
on failure (call a new RateLimitService.untrackProviderUa(providerId, uaId)
immediately before adding provider to chain / selecting fallback) or, better,
replace the two-step flow with a single atomic check+track in a Lua script
inside RateLimitService to perform provider UA+session checkAndTrack atomically;
update the failure path in ProviderSelector (around calls to
checkAndTrackProviderUa, checkAndTrackProviderSession,
session.addProviderToChain, ProxyProviderResolver.pickRandomProvider, and
session.setProvider) to call untrackProviderUa on session check failure so UA
slot is released.

---

Nitpick comments:
In
`@src/app/`[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx:
- Around line 54-71: Extract the duplicate logic in resolveChainItemErrorMessage
into a shared utility function (e.g., export function
resolveChainItemErrorMessage(...) in a new util module) and replace the inline
implementations in both LogicTraceTab and provider-chain-popover to import and
use that single exported function; ensure the new util preserves the same
behavior (string errorMessage check, errorCode validation, tErrors call with
item.errorParams fallback and try/catch returning item.errorCode) and update
exports/imports accordingly so both components rely on the same implementation.

ℹ️ Review info

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 c981bb8 and f0d9032.

📒 Files selected for processing (8)
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/lib/rate-limit/service.ts
  • src/types/message.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
  • tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/lib/rate-limit/service-extra.test.ts

- Provider Session 超限回退时回滚本次新增 UA(避免幽灵计数)\n- 增加 ZSET member 回滚 Lua 脚本并补齐单测\n- 抽离 ProviderChain 错误信息解析工具,移除重复实现\n- 修正文档注释遗漏的 limitRpm 翻译键
@tesgth032
Copy link
Contributor Author

已根据 AI review 的主要问题做了修正(commit 918bccb):

  • 修复 Provider UA 计数泄漏:Provider UA check+track 通过后若 Provider Session check 失败并回退,会回滚本次新增的 UA member(新增 UNTRACK_ZSET_MEMBER_IF_SCORE_MATCH + RateLimitService.untrackProviderUa,并补齐单测覆盖)。
  • 抽离 logs UI 的 resolveChainItemErrorMessage 为共享 util,避免两处实现漂移。
  • 补齐 LimitRulePicker 的 JSDoc:把 limitRpm 翻译键加入说明列表。

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: 2

🧹 Nitpick comments (3)
src/app/[locale]/dashboard/logs/_components/resolve-chain-item-error-message.ts (1)

11-18: 建议先标准化 errorCode 再查翻译。

Line 11 已用 trim() 判空,但 Line 16/18 仍直接使用原始 item.errorCode;如果上游带前后空格,会导致翻译键命中失败并回退到带空格的错误码。

建议修改
-  if (typeof item.errorCode !== "string" || !item.errorCode.trim()) {
+  const errorCode = typeof item.errorCode === "string" ? item.errorCode.trim() : "";
+  if (!errorCode) {
     return null;
   }

   try {
-    return tErrors(item.errorCode, item.errorParams ?? undefined);
+    return tErrors(errorCode, item.errorParams ?? undefined);
   } catch {
-    return item.errorCode;
+    return errorCode;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`[locale]/dashboard/logs/_components/resolve-chain-item-error-message.ts
around lines 11 - 18, Normalize item.errorCode to a trimmed string once and use
that normalized value for translation and fallback: check typeof item.errorCode
=== "string", set const code = item.errorCode.trim(), return null if !code, then
call tErrors(code, item.errorParams ?? undefined) inside the try and return code
in the catch; update references to use code instead of item.errorCode (symbols:
item.errorCode, tErrors).
src/lib/rate-limit/service.ts (1)

695-697: 建议统一 Lua 脚本别名,降低维护时的语义分叉风险。

这里建议与同文件的 Session 分支保持一致,统一使用 CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER,避免同一语义下出现两种常量名。

♻️ 建议修改
-        CHECK_AND_TRACK_KEY_USER_SESSION,
+        CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/rate-limit/service.ts` around lines 695 - 697, Replace the Lua script
alias CHECK_AND_TRACK_KEY_USER_SESSION with the unified constant
CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER in the Redis eval invocation so this branch
uses the same semantic name as the Session branch; update the call site that
constructs the eval (the one using RateLimitService.redis.eval and
CHECK_AND_TRACK_KEY_USER_SESSION) to pass CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER
instead and ensure any related parameter ordering or KEYS count remains
unchanged.
src/app/v1/_lib/proxy/provider-selector.ts (1)

290-349: 建议抽取统一的并发失败回退流程,减少分支漂移。

UA 超限与 Session 超限分支的“记录链路 → 加入排除 → 选 fallback → 切换重试”逻辑高度重复,后续维护很容易出现行为不一致。建议抽一个私有 helper 统一处理。

Also applies to: 366-430

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/provider-selector.ts` around lines 290 - 349, Extract
the repeated “record failure → add to excluded → pick fallback → switch and
retry” flow into a private helper (e.g., handleConcurrentLimitFallback) and call
it from both UA-limit and Session-limit branches to avoid divergence: the helper
should accept the Session instance, the failing provider info
(session.provider), the decision context pieces (uaCheckResult, uaLimit or
sessionLimit), attemptCount, excludedProviders array, and logger; inside it call
session.getLastSelectionContext(), session.addProviderToChain(...) with the same
structured payload, push the provider id into excludedProviders, call
ProxyProviderResolver.pickRandomProvider(session, excludedProviders), log and
return a result indicating whether a fallback was found (and the fallback
provider/context) so callers can call session.setProvider(...) and
session.setLastSelectionContext(...) or break if none. Ensure you reuse symbols
session.addProviderToChain, session.getLastSelectionContext,
ProxyProviderResolver.pickRandomProvider, session.setProvider, and
session.setLastSelectionContext so both branches (UA and Session) invoke
identical behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx:
- Around line 195-197: The SelectItem currently uses
getTranslation(translations, `limitTypes.${opt}`, opt) which falls back to the
internal enum key (opt) when a translation is missing; update the SelectItem to
call getTranslation(translations, `limitTypes.${opt}`) without the opt fallback
so user-facing text always comes from i18n, and add the missing limitTypes.*
entries across all five locales (zh-CN, zh-TW, en, ja, ru) to the translation
files so no fallback occurs; references: SelectItem, getTranslation,
translations, and the limitTypes.* keys.

In
`@src/app/`[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx:
- Line 29: 在 LogicTraceTab.tsx 中把相对导入改为仓库约定的别名导入:将当前的 import {
resolveChainItemErrorMessage } from "../../resolve-chain-item-error-message";
替换为使用 "@/..." 路径别名指向同一模块(保留导入的符号 resolveChainItemErrorMessage 不变),确保导入路径从 src
根目录开始并符合项目的别名配置(如 tsconfig/webpack 别名)。

---

Nitpick comments:
In
`@src/app/`[locale]/dashboard/logs/_components/resolve-chain-item-error-message.ts:
- Around line 11-18: Normalize item.errorCode to a trimmed string once and use
that normalized value for translation and fallback: check typeof item.errorCode
=== "string", set const code = item.errorCode.trim(), return null if !code, then
call tErrors(code, item.errorParams ?? undefined) inside the try and return code
in the catch; update references to use code instead of item.errorCode (symbols:
item.errorCode, tErrors).

In `@src/app/v1/_lib/proxy/provider-selector.ts`:
- Around line 290-349: Extract the repeated “record failure → add to excluded →
pick fallback → switch and retry” flow into a private helper (e.g.,
handleConcurrentLimitFallback) and call it from both UA-limit and Session-limit
branches to avoid divergence: the helper should accept the Session instance, the
failing provider info (session.provider), the decision context pieces
(uaCheckResult, uaLimit or sessionLimit), attemptCount, excludedProviders array,
and logger; inside it call session.getLastSelectionContext(),
session.addProviderToChain(...) with the same structured payload, push the
provider id into excludedProviders, call
ProxyProviderResolver.pickRandomProvider(session, excludedProviders), log and
return a result indicating whether a fallback was found (and the fallback
provider/context) so callers can call session.setProvider(...) and
session.setLastSelectionContext(...) or break if none. Ensure you reuse symbols
session.addProviderToChain, session.getLastSelectionContext,
ProxyProviderResolver.pickRandomProvider, session.setProvider, and
session.setLastSelectionContext so both branches (UA and Session) invoke
identical behavior.

In `@src/lib/rate-limit/service.ts`:
- Around line 695-697: Replace the Lua script alias
CHECK_AND_TRACK_KEY_USER_SESSION with the unified constant
CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER in the Redis eval invocation so this branch
uses the same semantic name as the Session branch; update the call site that
constructs the eval (the one using RateLimitService.redis.eval and
CHECK_AND_TRACK_KEY_USER_SESSION) to pass CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER
instead and ensure any related parameter ordering or KEYS count remains
unchanged.

ℹ️ Review info

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 f0d9032 and 918bccb.

📒 Files selected for processing (9)
  • src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
  • src/app/[locale]/dashboard/logs/_components/resolve-chain-item-error-message.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/lib/rate-limit/service.ts
  • src/lib/redis/lua-scripts.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
  • tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/redis/lua-scripts.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
  • tests/unit/proxy/provider-selector-concurrent-ua-fallback.test.ts

Comment on lines +195 to 197
<SelectItem key={opt} value={opt}>
{getTranslation(translations, `limitTypes.${opt}`, opt)}
</SelectItem>
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

避免把内部枚举值作为下拉文案回退值。

getTranslation(..., opt) 在缺失翻译时会把 limitUas 这类内部 key 直接展示给用户,属于用户可见硬编码文本路径。建议改为仅使用 i18n key,并确保 5 种语言都补齐 limitTypes.*

🔧 建议修改
- {getTranslation(translations, `limitTypes.${opt}`, opt)}
+ {getTranslation(
+   translations,
+   `limitTypes.${opt}`,
+   getTranslation(translations, "fields.type.placeholder", ""),
+ )}

As per coding guidelines "All user-facing strings must use i18n (5 languages supported: zh-CN, zh-TW, en, ja, ru). Never hardcode display text".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx
around lines 195 - 197, The SelectItem currently uses
getTranslation(translations, `limitTypes.${opt}`, opt) which falls back to the
internal enum key (opt) when a translation is missing; update the SelectItem to
call getTranslation(translations, `limitTypes.${opt}`) without the opt fallback
so user-facing text always comes from i18n, and add the missing limitTypes.*
entries across all five locales (zh-CN, zh-TW, en, ja, ru) to the translation
files so no fallback occurs; references: SelectItem, getTranslation,
translations, and the limitTypes.* keys.

import { cn } from "@/lib/utils";
import { formatProbability, formatProviderTimeline } from "@/lib/utils/provider-chain-formatter";
import type { ProviderChainItem } from "@/types/message";
import { resolveChainItemErrorMessage } from "../../resolve-chain-item-error-message";
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

请改为 @/ 路径别名导入。

Line 29 目前使用相对路径导入,不符合仓库的导入规范,建议统一改为 @/ 别名路径。

建议修改
-import { resolveChainItemErrorMessage } from "../../resolve-chain-item-error-message";
+import { resolveChainItemErrorMessage } from "@/app/[locale]/dashboard/logs/_components/resolve-chain-item-error-message";

As per coding guidelines: Use path alias @/ to reference files in ./src/ directory.

📝 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
import { resolveChainItemErrorMessage } from "../../resolve-chain-item-error-message";
import { resolveChainItemErrorMessage } from "@/app/[locale]/dashboard/logs/_components/resolve-chain-item-error-message";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
at line 29, 在 LogicTraceTab.tsx 中把相对导入改为仓库约定的别名导入:将当前的 import {
resolveChainItemErrorMessage } from "../../resolve-chain-item-error-message";
替换为使用 "@/..." 路径别名指向同一模块(保留导入的符号 resolveChainItemErrorMessage 不变),确保导入路径从 src
根目录开始并符合项目的别名配置(如 tsconfig/webpack 别名)。

- SelectItem 文案缺失时不再回退到内部枚举值\n- logs 详情页改为使用 @/ 别名导入共享 util
@tesgth032
Copy link
Contributor Author

补充处理了 CodeRabbit 最新两条意见(commit 77aa2c3):

  • LogicTraceTab 里改为使用 @/ 别名导入共享的 resolveChainItemErrorMessage util。
  • LimitRulePicker 的下拉项不再把内部枚举值作为回退文案(翻译缺失时回退到 placeholder,避免把 limitUas 等内部 key 展示给用户)。

- Key/User UA 原子检查改用通用 Lua 别名,避免语义误读\n- UNTRACK_ZSET_MEMBER_IF_SCORE_MATCH 改为数值比较,避免 score 字符串格式差异\n- 补齐 untrackProviderUa 单测覆盖
@tesgth032
Copy link
Contributor Author

补充做了两点“加固/一致性”修正(commit 8dc1a53),避免后续维护误读与边界问题:

  • Key/User UA 原子检查改用通用 Lua 别名 CHECK_AND_TRACK_KEY_USER_ZSET_MEMBER(与 Session 路径一致)。
  • UNTRACK_ZSET_MEMBER_IF_SCORE_MATCH 改为对 score 做数值比较,避免 Redis 返回 score 字符串格式差异导致回滚失效,并新增 untrackProviderUa 单测覆盖。

@tesgth032
Copy link
Contributor Author

补充修正(commit 372d37e):

  • 日志 UI:解析/翻译 provider chain 错误码时先对 errorCode 做 trim,避免上游带空格导致 i18n key 命中失败并回退显示原始码。

本地已通过:bun run lint:fix / bun run lint / bun run typecheck / bun run test / bun run build。
(仓库既有 2 条 biome warning 仍存在,非本 PR 引入)

CI checks 正在跑。

@tesgth032
Copy link
Contributor Author

补充修正(commit 8df8bd5):

  • provider-selector:抽取并统一“并发失败 → 记录链路 → 排除 provider → 选 fallback → 切换重试”的流程,避免 UA/Session 两条分支后续维护漂移(对应 CodeRabbit/Gemini 的重复代码建议)。

本地已通过:bun run lint:fix / bun run lint / bun run typecheck / bun run test / bun run build。
(仓库既有 2 条 biome warning 仍存在,非本 PR 引入)

CI checks 正在跑。

- Provider legacy 表单折叠摘要补齐 limitConcurrentUas\n- Provider session 并发回退链路改用 errorCode/errorParams(便于 i18n 展示)\n- 迁移补充 limit_concurrent_uas 非负 CHECK 约束\n- Key 并发限额文案明确 0 继承 User\n- active_uas Redis key builder 增加 safe integer 校验\n- 修正 rate-limit-guard 步骤编号注释
@tesgth032 tesgth032 changed the title feat(rate-limit): 多层级并发 UA 限制 [未完成] feat(rate-limit): 多层级并发 UA 限制 Mar 1, 2026
@tesgth032
Copy link
Contributor Author

已根据 CodeRabbit 等 AI review 补齐细节并推送最新提交 e136a0f

  • legacy provider form 折叠摘要补齐并发 UA
  • Provider session 并发回退链路记录改为 errorCode/errorParams,便于 dashboard i18n 展示
  • 迁移补充 limit_concurrent_uas 非负 CHECK 约束
  • Key 表单文案明确 0 = 继承 User(并修复 ja dashboard 半角括号约束)
  • active_uas Redis key builder 增加 safe integer 校验
  • 修正 rate-limit-guard 步骤编号注释

本地与 CI 已通过(除 Greptile Review 常驻 pending 外)。

@tesgth032
Copy link
Contributor Author

已逐条对照 CodeRabbit / Greptile / Gemini 的 review comments 复查当前实现:

  • i18n 回退/硬编码文案、provider fallback 复用、Lua 脚本语义命名与复用、Provider UA 回滚避免幽灵计数等建议均已落实(最新提交:e136a0f2)。
  • 当前 PR 的 Actions checks 均已通过(gh pr checks 851)。

如方便请再做一次人工 review/approve(历史 CodeRabbit 的 CHANGES_REQUESTED 可能不会自动消失)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant