Skip to content

release v0.6.3#897

Open
ding113 wants to merge 39 commits intomainfrom
dev
Open

release v0.6.3#897
ding113 wants to merge 39 commits intomainfrom
dev

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Mar 10, 2026

Summary

Release v0.6.3 introducing provider hedge racing for optimized first-byte timeout handling, user insights dashboard, soft user limit reset, and comprehensive bug fixes and UI improvements.

Problem

This release addresses several key areas:

  1. First-byte timeout handling - When a provider exceeded its first-byte timeout, requests failed immediately. Users wanted better failover behavior
  2. User usage analytics - Admin lacked visibility into per-model usage distribution and trends for individual users
  3. User limit management - Resetting user limits required destructive deletion of all historical data
  4. API compatibility - Various API compatibility issues with /v1/responses, auth tokens, and request headers

Solution

This release implements multiple features and fixes:

  1. Provider Hedge Racing - Converts first-byte timeout into a hedge-based provider race that keeps earlier attempts alive until a winner is chosen, then aborts losers
  2. User Insights Page - New admin-only dashboard at /dashboard/leaderboard/user/[userId] with per-model breakdown and trend charts
  3. Soft User Limit Reset - New costResetAt mechanism allows resetting limits without deleting historical usage data
  4. Response Input Rectifier - Normalizes /v1/responses input format (string/object to array) before guard pipeline
  5. GPT Long-Context Pricing - Added priority long-context token pricing fields for billing calculations

Changes

Core Features

Feature Description PRs
Provider Hedge Racing Converts first-byte timeout into a race that aborts losers when winner emerges #894
User Insights Dashboard Per-model usage breakdown, trend charts, overview metrics for individual users #886
Soft User Limit Reset Reset user cost limits without deleting historical data #890
Response Input Rectifier Auto-normalizes /v1/responses input formats (string/object → array) #888
GPT Long-Context Pricing Added priority long-context token pricing for accurate billing #873

Bug Fixes

Fix Description PR/Issue
Admin token auth Restored admin token access in opaque session mode #884
Log cleanup Fixed log cleanup not actually deleting records #885
Transfer-encoding Strip transfer-encoding from forwarded upstream requests #880
Anthropic model routing Fixed Anthropic provider routing without model whitelist #832
Cached tokens Fixed cached tokens recording from chat completions usage #889
User search performance Fixed user management page search performance -
Request header parsing Fixed request header parsing for new-api compatibility -

UI Improvements

Database Changes

Migration Description
0079_easy_zeigeist.sql Added enable_response_input_rectifier system setting
0080_fresh_clint_barton.sql Added cost_reset_at column for soft user limit reset

Breaking Changes

Change Impact Migration
Provider first-byte timeout behavior Timeout now triggers hedge race instead of immediate failure. Recommend setting first-byte timeout to 10-15s for optimal experience No database migration required
Anthropic provider without model whitelist Behavior changes from routing only claude-* models to routing all models using Anthropic API format. Set model whitelist to maintain previous behavior No database migration required

Testing

Automated Tests

  • Unit tests added for hedge racing, user insights, soft reset, and response input rectifier
  • Integration tests for proxy flow and API compatibility

Manual Testing

  1. Test provider hedge racing with multiple providers

  2. Verify user insights page displays correct per-model statistics

  3. Test soft reset preserves historical data

  4. Test /v1/responses with string/object input formats

  5. Verify Anthropic provider model routing with and without whitelist

Related


Description enhanced by Claude AI

Greptile Summary

This release (v0.6.3) introduces five substantial features — provider hedge racing, a user insights dashboard, soft user limit reset (costResetAt), a Response API input rectifier, and priority long-context pricing — alongside a collection of bug fixes (admin token opaque fallback, log cleanup row-count fix, transfer-encoding stripping, Anthropic model routing, cached token recording). The changes are well-tested with a broad unit and integration test suite.

Key highlights and concerns:

  • Hedge racing (sendStreamingWithHedge, ~700 lines): The core logic is well-structured. One critical bug: settleFailure sets settled = true then awaits a Redis call (clearSessionProviderBinding). If that call throws, resolveResult is never invoked and resultPromise hangs indefinitely, leaving the client connection stalled with no response.
  • Log cleanup VACUUM: The refactored service.ts fixes the real delete bug (RETURNING 1 for driver-agnostic row counting) and adds FOR UPDATE SKIP LOCKED for concurrent safety. However, the new VACUUM ANALYZE call runs from application code on every non-empty cleanup — this is generally discouraged in production because it can conflict with autovacuum and relies on the connection not being inside a transaction block.
  • Shadow session cloning: createStreamingShadowSession uses Object.assign to copy ProxySession private fields. This is safe today because TypeScript private produces regular JS properties, but it would silently break if any field is ever converted to native #private syntax.
  • costResetAt / soft reset: The implementation is consistent across users.ts, key-quota.ts, my-usage.ts, and the statistics repository. The full-delete path correctly wraps DB mutations in a transaction.
  • Provider selector simplification: The new providerSupportsModel logic correctly removes modelRedirects from routing decisions, delegating format checks to checkFormatProviderTypeCompatibility. The breaking change is documented.

Confidence Score: 3/5

  • Mostly safe to merge with one logic bug that can cause client connections to hang under Redis failure in the new hedge path.
  • The PR is large (215 files) but well-tested. The hedge racing logic is correct in the happy path and most error paths. The identified bug — settleFailure not guarding against clearSessionProviderBinding throwing — is a real reliability issue under Redis unavailability. The VACUUM ANALYZE concern is operational rather than functional. All other changes (soft reset, rectifier, pricing, auth fix) appear correct and consistent.
  • src/app/v1/_lib/proxy/forwarder.ts (settleFailure hang bug), src/lib/log-cleanup/service.ts (VACUUM ANALYZE from application code)

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/forwarder.ts Core hedge racing implementation (~700 new lines). Well-structured with winner/loser tracking, threshold timers, and session syncing. Contains a potential hang in settleFailure if clearSessionProviderBinding throws before resolveResult is called.
src/lib/log-cleanup/service.ts Refactored log cleanup with soft-delete purging, SKIP LOCKED for concurrency safety, and RETURNING 1 for driver-agnostic row counting. Adds VACUUM ANALYZE after deletions — calling VACUUM from application code in production can conflict with autovacuum and cause lock contention.
src/app/v1/_lib/proxy/provider-selector.ts Simplified providerSupportsModel logic — removes Claude-specific branching and modelRedirects routing participation. The simplification is correct per the updated comments, with format-compatibility checks delegated to checkFormatProviderTypeCompatibility.
src/app/v1/_lib/proxy/response-input-rectifier.ts New Response API input normalizer. Correctly handles string, object, and array input cases. Enabled by system setting. No issues found.
src/lib/auth.ts Restores admin token access in opaque session mode by falling back to validateKey when the raw bearer token matches ADMIN_TOKEN env var. Clean, tested regression fix.
src/repository/statistics.ts Added costResetAt parameter to sumUserTotalCost, sumKeyTotalCost, and batch variants. Correctly picks the more recent of the maxAge cutoff and resetAt. Batch variant falls back to individual queries for users with costResetAt — potential N+1 at scale but acceptable for correctness.
src/actions/users.ts Refactored reset logic into resetUserLimitsOnly (sets costResetAt) and retained resetUserAllStatistics (full delete). The full-delete path now wraps DB changes in a transaction and invalidates cache outside the transaction. costResetAt clipping is applied consistently across all cost queries.
src/actions/admin-user-insights.ts New admin-only server actions for per-user insights. Proper admin role guard, date validation with regex and range check. No issues found.
src/lib/utils/cost-calculation.ts Added resolvePriorityAwareLongContextRate helper that applies priority pricing for input, output, and cache-read long-context tokens. Cache-creation long-context tokens remain non-priority, consistent with the absence of priority fields in the schema for that token type.

Sequence Diagram

sequenceDiagram
    participant Client
    participant ProxyForwarder
    participant Provider1
    participant Provider2

    Client->>ProxyForwarder: Streaming request
    ProxyForwarder->>ProxyForwarder: shouldUseStreamingHedge()
    ProxyForwarder->>Provider1: doForward() (firstByteTimeoutMs=0)
    ProxyForwarder->>ProxyForwarder: startThresholdTimer(firstByteTimeoutMs)

    alt Provider1 responds in time
        Provider1-->>ProxyForwarder: First chunk received
        ProxyForwarder->>ProxyForwarder: commitWinner(attempt, firstChunk)
        ProxyForwarder->>ProxyForwarder: abortAllAttempts(loser)
        ProxyForwarder-->>Client: Buffered stream response
    else Threshold timer fires (first-byte timeout)
        ProxyForwarder->>ProxyForwarder: hedge_triggered → launchAlternative()
        ProxyForwarder->>Provider2: doForward() (hedged attempt)
        ProxyForwarder->>ProxyForwarder: addProviderToChain(hedge_launched)

        alt Provider2 wins race
            Provider2-->>ProxyForwarder: First chunk received
            ProxyForwarder->>ProxyForwarder: commitWinner(Provider2, firstChunk)
            ProxyForwarder->>Provider1: abort(hedge_loser)
            ProxyForwarder->>ProxyForwarder: addProviderToChain(hedge_winner / hedge_loser_cancelled)
            ProxyForwarder-->>Client: Buffered stream response
        else Both fail
            Provider1-->>ProxyForwarder: Error
            Provider2-->>ProxyForwarder: Error
            ProxyForwarder->>ProxyForwarder: settleFailure(ProxyError 503)
            ProxyForwarder-->>Client: 503 No available providers
        end
    end
Loading

Last reviewed commit: d021fc8

Greptile also left 3 inline comments on this PR.

miraserver and others added 30 commits March 8, 2026 23:18
…#876)

* feat(providers): add sub-navigation and Options tab to provider form sidebar

Add Scheduling, Circuit Breaker, Timeout sub-items under their parent tabs
in the desktop sidebar for quick scroll access. Promote Options to a
top-level tab. Includes scroll tracking, i18n (5 langs), and 13 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(providers): add Active Time sub-item under Options tab

Add activeTime sub-navigation for quick scroll access to Scheduled Active
Time section. Also add variant="highlight" to Options SectionCard for
consistent visual styling with other main sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(providers): show green ShieldCheck icon when proxy is configured

Separate icon with tooltip next to endpoint count on provider list.
Visible only when proxyUrl is set. i18n for 5 languages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(providers): fix 7 review issues in sub-nav and form architecture

- Derive TAB_CONFIG, TAB_ORDER, NAV_ORDER, PARENT_MAP from NAV_CONFIG (DRY)
- Add sub-nav to tablet and mobile breakpoints
- Move activeSubTab from useState into form reducer
- Compute Options tab status from routing state instead of hardcoding
- Lift TooltipProvider from per-item to list container level
- Fix RU i18n: singular form for timeout label
- Add 8 new tests covering derived constants and responsive sub-nav

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(providers): extract OptionsSection, perf fixes, batch dialog fix

- Extract OptionsSection from RoutingSection (963 -> 432 lines)
- Throttle scroll handler via requestAnimationFrame
- Merge double dispatch into single SET_ACTIVE_NAV action
- Derive sectionRefs from NAV_ORDER instead of manual record
- Add NAV_BY_ID lookup map for O(1) tablet/mobile nav access
- Add excludeTabs prop to FormTabNav, hide Options in batch dialog
- Clean up setTimeout/rAF refs on unmount

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(providers): add Options tab to batch edit dialog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(providers): show proxy badge for providers without resolved vendor

Move ShieldCheck proxy indicator out of the vendor-specific branch
so it renders for all providers with a configured proxyUrl.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(providers): remove dead SET_ACTIVE_SUB_TAB, add status grouping comments

Address Gemini Code Assist review findings:
- Remove unused SET_ACTIVE_SUB_TAB action type and reducer case (superseded by SET_ACTIVE_NAV)
- Add grouping comments to options tab status conditional for readability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(providers): address CodeRabbit review findings

- Fix zh-CN/zh-TW i18n terminology consistency (供应商/供應商 instead of 提供者)
- Add activeTimeEnd check to options tab status indicator
- Add focus-visible ring to tablet/mobile sub-nav buttons for accessibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(providers): use i18n fallback for unknown tag errors and scrollend event for scroll lock

- Replace raw reason string fallback with tUI("unknownError") i18n key
  in tag validation callbacks (routing-section.tsx)
- Add "unknownError" key to tagInput namespace in all 5 locales
- Use scrollend event with { once: true } + 1000ms fallback timer
  instead of fixed 500ms setTimeout for scroll lock release (index.tsx)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(providers): add unit tests for OptionsSection component (25 tests)

Covers rendering, conditional display by provider type (claude/codex/gemini),
batch mode, dispatch actions, active time UI, disabled state, edit mode IDs,
and batch-only badges.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(providers): remove stale scrollend listener on rapid tab clicks

Store previous scrollend listener in a ref and remove it at the start
of each scrollToSection call, preventing premature unlock when multiple
smooth scrolls overlap during fast sequential tab clicks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(providers): fix 3 style-level review findings

- Remove dead subSectionRefs.options property from OptionsSection
  (parent div in index.tsx already tracks this ref)
- Use filteredNav.find() instead of NAV_BY_ID for tablet/mobile
  sub-row lookup so excludeTabs is respected; remove unused NAV_BY_ID
- Replace non-null assertion with guarded clearTimeout in scrollend
  handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): resolve 3 pre-existing test failures from PR #873

All caused by commit 2e663cd which changed billing model and session
API without updating tests/translations:

- i18n: add missing `prices.badges.multi` key to ja/ru/zh-TW locales
- tests: update cost-calculation expectations to match full-request
  pricing (all tokens at premium rate when context > 200K threshold)
- tests: fix lease-decrement session mock to use
  getResolvedPricingByBillingSource instead of removed method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(types): narrow Recharts v3 dataKey type for React Key/ReactNode compat

Recharts v3 widened `dataKey` to `string | number | ((obj: any) => any) | undefined`,
which is incompatible with React `Key` and `ReactNode`. Wrap with `String()` in 2 files
to satisfy tsgo in CI. Pre-existing issue from main, not introduced by this branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: format code (feat-providers-list-3-0732ba6)

* ci: retrigger CI after auto-format fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(types): narrow Recharts ValueType for formatter call in chart.tsx

Removing the explicit .map() parameter type exposed ValueType
(string | number | readonly (string|number)[]) which is too wide
for the formatter's (string | number) parameter. Cast item.value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(types): restore narrowing annotation for Recharts v3 tooltip map

Bring back the explicit type annotation on .map() callback but extend
dataKey to include ((obj: any) => any) to match Recharts v3 DataKey<any>.
This keeps value/name narrowed for formatter compatibility while making
the annotation assignable from TooltipPayloadEntry.

Replaces the previous approach of removing the annotation entirely,
which exposed ValueType and NameType width issues one by one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(types): use inline casts instead of annotation for Recharts v3 compat

Remove the .map() parameter annotation entirely (it causes TS2345 due to
contravariance — our narrower type is not assignable from TooltipPayloadEntry).
Instead, let TS infer the full type and cast only at the two call sites where
formatter needs narrower types: item.value as string|number, item.name as string.

All other usages of item.dataKey, item.name, item.value are compatible with
the wider Recharts v3 types (String() wraps, template literals, ReactNode).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* Red: 复现透传请求体回归

* fix: strip transfer-encoding from forwarded upstream requests

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: preserve original request body bytes for raw passthrough endpoints

When bypassForwarderPreprocessing=true and session.request.buffer is
available, use the original ArrayBuffer directly instead of
JSON.stringify(messageToSend). This preserves whitespace, key ordering,
and trailing newlines in the forwarded request body.

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Simplify providerSupportsModel() to treat all provider types uniformly.
Previously, claude-* models were hardcoded to only route to claude/claude-auth
providers, even when other providers explicitly declared them in allowedModels.
Now the logic is: explicit allowedModels/modelRedirects match -> accept;
empty allowedModels -> wildcard; otherwise reject. Format compatibility
remains enforced by checkFormatProviderTypeCompatibility independently.
- Export providerSupportsModel for direct unit testing
- Add table-driven tests (13 cases) covering all provider/model combinations
- Add pickRandomProvider path tests verifying format+model check interaction
- Add findReusable integration tests for session reuse scenarios
- Add invariant comment in findReusable documenting format-safety assumption
In opaque mode, validateAuthToken() rejected raw ADMIN_TOKEN passed via
cookie or Authorization header, breaking programmatic API access that
worked in legacy/dual mode. Add a constantTimeEqual check for the admin
token before returning null, so server-side env secret still authenticates
while regular API keys remain correctly rejected.
* feat: add response input rectifier for /v1/responses input normalization

OpenAI Responses API input field supports string shortcut, single object,
and array formats. This rectifier normalizes non-array input to standard
array format before the guard pipeline, with audit trail via special
settings persisted to message_requests.

- Normalize string input to [{role:"user",content:[{type:"input_text",text}]}]
- Wrap single object input (with role/type) into array
- Convert empty string to empty array
- Passthrough array input unchanged
- Default enabled, toggleable via system settings UI
- Broadened format-mapper body detection for string/object input
- Full i18n support (en, zh-CN, zh-TW, ja, ru)
- 14 unit tests covering all normalization paths

* fix: address bugbot review comments on response-input-rectifier PR

- Remove redundant ternary in rectifyResponseInput (both branches "other")
- Revert body-based detection broadening in detectClientFormat to prevent
  misrouting /v1/embeddings and other endpoints with string input fields
- Add missing enableBillingHeaderRectifier to returning clause (pre-existing)
- Refine i18n descriptions to specify "single message object" (5 locales)
- Add 4 normalizeResponseInput tests covering settings gate and audit trail
…861) (#886)

* feat(leaderboard): add user model drill-down and user insights page (#861)

- Add user-level modelStats data layer with null model preservation
- Cache key isolation for user scope includeModelStats
- Admin-only includeUserModelStats API gate (403 for non-admin)
- Extract shared ModelBreakdownColumn/Row UI primitive
- Admin-only expandable user rows with per-model breakdown
- User name links to /dashboard/leaderboard/user/[userId]
- Admin-only user insights page with overview cards, key trend chart, and model breakdown
- Server actions for getUserInsightsOverview, KeyTrend, ModelBreakdown
- Full i18n support (zh-CN, zh-TW, en, ja, ru)
- 54 new tests across 6 test files

* chore: format code (feat-861-user-leaderboard-drill-down-81697a4)

* fix(leaderboard): resolve bugbot review findings for user insights (#886)

- Fix critical bug: overview card labeled "Cache Hit Rate" was showing
  todayErrorRate; renamed to "Error Rate" with correct i18n (5 locales)
- Fix security: Cache-Control now private when allowGlobalUsageView=false
- Add date validation (YYYY-MM-DD regex + range check) in
  getUserInsightsModelBreakdown server action
- Normalize key trend date field to ISO string to prevent client crash
  when cache returns Date objects
- Replace hardcoded "OK" button with tCommon("ok") i18n key
- Replace hardcoded "Calls" chart label with tStats("requests")
- Tighten userId validation to positive integers only
- Add NULLIF(TRIM()) to model field for consistency with leaderboard.ts
- Use machine-readable error code for 403 response
- Strengthen return type from unknown to DatabaseKeyStatRow[]
- Update tests: assert errorRate metric, date normalization, date
  validation, and error code assertions

* test: use realistic DatabaseKeyStatRow fields in key trend test mock

Mock data now matches the actual DatabaseKeyStatRow interface with
key_id, key_name, api_calls, and total_cost fields instead of generic
cost/requests. Assertions verify the full data contract.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Remove `deletedAt IS NULL` filter from buildWhereConditions: cleanup
  should delete ALL matching records regardless of soft-delete status
- Add `RETURNING 1` to DELETE SQL for driver-agnostic row counting
  (result.length instead of fragile count/rowCount properties)
- Add `FOR UPDATE SKIP LOCKED` to prevent deadlocks with concurrent jobs
- Add purgeSoftDeleted: batched hard-delete of soft-deleted records
  as fallback after main cleanup loop
- Add VACUUM ANALYZE after deletions to reclaim disk space
  (failure is non-fatal)
- Update CleanupResult with softDeletedPurged and vacuumPerformed
- Pass new fields through API route and show in UI toast
- Add i18n keys for 5 languages
- 19 tests covering all new behavior
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Wrap DISTINCT ON subquery so the outer ORDER BY sorts by updatedAt DESC,
showing recently updated models first in the paginated price table.
…hart colors

- Fix chart colors: replace hsl(var(--chart-N)) with var(--chart-N) to
  resolve oklch incompatibility causing gray fallback
- Fix leaderboard username link styling: remove hyperlink appearance
  while keeping clickability
- Add provider breakdown component with repository, server action, and
  side-by-side layout alongside model breakdown
- Add unified filter bar (time preset, key, provider, model) controlling
  key trend chart and both breakdown panels
- Lift filter state to UserInsightsView, refactor child components to
  accept filter props instead of managing internal state
- Extend repository layer with filter params (keyId, providerId, model)
  for both model and provider breakdown queries
- Add i18n keys for all 5 languages (en, zh-CN, zh-TW, ja, ru)
- Add comprehensive tests for provider breakdown action and filter utils
#894)

* fix(proxy): hedge first-byte timeout failover and clear stale bindings

* docs(schema): clarify timeout field comments

* fix(proxy): handle hedge launcher failures and endpoint errors

* test(validation): add zero timeout disable case
…chain

Implements comprehensive observability for hedge (speculative execution) and client abort scenarios:

Backend (forwarder.ts):
- Record hedge_triggered when threshold timer fires and alternative provider launches
- Record hedge_winner when a provider wins the hedge race (first byte received)
- Record hedge_loser_cancelled when a provider loses and gets aborted
- Record client_abort when client disconnects (replaces generic system_error)

Frontend (provider-chain-popover.tsx, LogicTraceTab.tsx):
- Add icons and status colors for all 4 new reason types
- Correctly count hedge_triggered as informational (not actual request)
- Display hedge flow with GitBranch/CheckCircle/XCircle/MinusCircle icons

Langfuse (trace-proxy-request.ts):
- Add hedge_winner to SUCCESS_REASONS set
- Add client_abort to ERROR_REASONS set
- Create hedge-trigger event observation with WARNING level

i18n:
- Add translations for 4 new reasons across 5 languages (en, zh-CN, zh-TW, ja, ru)
- Include timeline, description, and reason label translations

Tests:
- Add 22 new tests covering all new reason types
- Test isActualRequest(), getItemStatus(), isSuccessReason(), isErrorReason()

Related improvements:
- Add isProviderFinalized() utility to detect when provider info is reliable
- Show in-progress state in logs table and big-screen for unfinalised requests
- Prevent displaying stale provider names during hedge/fallback transitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… logs table

Consolidate the isActualRequest function that was duplicated across three
files (provider-chain-formatter, provider-chain-popover, virtualized-logs-table).
Export from provider-chain-formatter.ts as the single source of truth.

Fixes two bugs in virtualized-logs-table.tsx:
- successfulProvider lookup was missing hedge_winner, causing incorrect
  costMultiplier display for hedge-won requests
- Inline isActualRequest was missing hedge_winner, hedge_loser_cancelled,
  client_abort and other newer reasons, causing incorrect request count
  and cost badge visibility
When the hedge timer fires and no alternative provider is found,
let the sole in-flight request continue instead of aborting it
with a 524 error. Only call finishIfExhausted() when all attempts
have already completed (edge case).
…tives

This commit addresses three issues with hedge race tracking:

1. Decision chain missing hedge participants
   - Add hedge_launched reason to record alternative provider launches
   - Record hedge_launched in forwarder.ts when launchedProviderCount > 1
   - Add timeline formatting for hedge_launched events

2. Hedge races mislabeled as retries in UI
   - Add isHedgeRace() and getRetryCount() helper functions
   - Display "Hedge Race" badge instead of retry count
   - Update provider-chain-popover and virtualized-logs-table

3. hedge_winner false positives
   - Fix isActualHedgeWin logic to only check launchedProviderCount
   - Prevent marking single-provider requests as hedge_winner

Additional improvements:
   - Strengthen addProviderToChain deduplication logic
   - Add comprehensive JSDoc for getRetryCount design decisions
   - Add 6 edge case tests (empty chain, incomplete hedge, mixed scenarios)
   - Test coverage: 54 → 60 tests, all passing

i18n: Add hedge_launched translations for 5 languages (en, zh-CN, zh-TW, ja, ru)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove text label from hedge race badge, keeping only the GitBranch icon
for a more compact display. The aria-label still contains the full text
for accessibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
实现批量编辑供应商时自动预填充已有设置的功能:

核心功能:
- 创建 deep-equals.ts 实现深度比较工具
- 创建 analyze-batch-settings.ts 分析批量供应商设置
- 修改 provider-form-context.tsx 在批量模式下预填充表单

分析逻辑:
- uniform: 所有供应商值相同时显示该值
- mixed: 供应商值不同时使用默认值
- empty: 所有供应商未设置时使用默认值

测试覆盖:
- 单元测试:deepEquals 深度比较(13个测试)
- 单元测试:analyzeBatchProviderSettings 分析器(14个测试)
- 集成测试:批量编辑预填充(3个测试)

所有测试通过,类型检查通过,构建成功。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2 implementation: UI enhancements for batch edit prefill

Changes:
- Created MixedValueIndicator component to show when selected providers have different values
- Added i18n translations for mixed value indicators (5 languages: zh-CN, zh-TW, en, ja, ru)
- Modified provider-form-context to export batch analysis results
- Integrated mixed value indicators in routing-section (priority, weight, cost multiplier, model redirects)
- Integrated mixed value indicators in limits-section (all rate limits, circuit breaker settings)
- Enhanced LimitCard component to support mixed value display

The mixed value indicator appears below fields when:
- Batch mode is active
- Selected providers have different values for that field
- Shows up to 5 different values with "...and N more" for additional values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing translation keys for settings.providers.batchEdit namespace:
- Batch operation toolbar (selectedCount, actions, enterMode, etc.)
- Edit/delete/reset circuit dialogs (dialog.*)
- Preview step with field labels (preview.*, fields.*)
- Toast notifications (toast.*)
- Undo operations (undo.*)
- Confirmation buttons (confirm.*)

Covers all UI strings used in provider-batch-actions.tsx,
provider-batch-dialog.tsx, provider-batch-preview-step.tsx,
and provider-batch-toolbar.tsx components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…#890)

* feat: add costResetAt for soft user limit reset without deleting data

Add costResetAt timestamp column to users table that clips all cost
calculations to start from reset time instead of all-time. This enables
admins to reset a user's rate limits without destroying historical
usage data (messageRequest/usageLedger rows are preserved).

Key changes:
- Schema: new cost_reset_at column on users table
- Repository: costResetAt propagated through all select queries, key
  validation, and statistics aggregation (with per-user batch support)
- Rate limiting: all 12 proxy guard checks pass costResetAt; service
  and lease layers clip time windows accordingly
- Auth cache: hydrate costResetAt from Redis cache as Date; invalidate
  auth cache on reset to avoid stale costResetAt
- Actions: resetUserLimitsOnly sets costResetAt + clears cost cache;
  getUserLimitUsage/getUserAllLimitUsage/getKeyLimitUsage/getMyQuota
  clip time ranges by costResetAt
- UI: edit-user-dialog with separate Reset Limits Only (amber) vs
  Reset All Statistics (red) with confirmation dialogs
- i18n: all 5 languages (en, zh-CN, zh-TW, ja, ru)
- Tests: 10 unit tests for resetUserLimitsOnly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden costResetAt handling -- repo layer, DRY Redis cleanup, Date validation

- Extract resetUserCostResetAt repo function with updatedAt + auth cache invalidation
- Extract clearUserCostCache helper to deduplicate Redis cleanup between reset functions
- Use instanceof Date checks in lease-service and my-usage for costResetAt validation
- Remove dead hasActiveSessions variable in cost-cache-cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: unify costResetAt guards to instanceof Date, add last-reset badge in user edit dialog

R3: Replace truthiness checks with `instanceof Date` in 3 places (users.ts clipStart, quotas page).
R4: Show last reset timestamp in edit-user-dialog Reset Limits section (5 langs).
Add 47 unit tests covering costResetAt across key-quota, redis cleanup, statistics, and auth cache.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: apply costResetAt clipStart to key-quota usage queries

Clip all period time ranges by user's costResetAt and replace getTotalUsageForKey
with sumKeyTotalCost supporting resetAt parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: format branch files with biome, suppress noThenProperty in thenable mock

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR #853 review findings -- guard consistency, window clipping, error handling

- keys.ts: eliminate redundant findUserById call, use joined costResetAt + instanceof Date guard
- users.ts: handle resetUserCostResetAt return value (false = soft-deleted user)
- service.ts: add instanceof Date guard to costResetAt comparison
- statistics.ts: fix sumKeyTotalCost/sumUserTotalCost to use max(resetAt, maxAgeCutoff) instead
  of replacing maxAgeDays; refactor nested ternaries to if-blocks in quota functions
- cost-cache-cleanup.ts: wrap pipeline.exec() in try/catch to honor never-throws contract
- Update test for pipeline.exec throw now caught inside clearUserCostCache

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: tighten key-quota clipStart assertions with toHaveBeenNthCalledWith

Verify each window (5h/daily/weekly/monthly) is clipped individually
instead of checking unordered calledWith matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: repair CI test failures caused by costResetAt feature changes

- ja/dashboard.json: replace fullwidth parens with halfwidth
- api-key-auth-cache-reset-at.test: override CI env so shouldUseRedisClient() works
- key-quota-concurrent-inherit.test: add logger.info mock, sumKeyTotalCost mock, userCostResetAt field
- my-usage-concurrent-inherit.test: add logger.info/debug mocks
- total-usage-semantics.test: update call assertions for new costResetAt parameter
- users-reset-all-statistics.test: mock resetUserCostResetAt, update pipeline.exec error expectations
- rate-limit-guard.test: add cost_reset_at: null to expected checkCostLimitsWithLease args

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR #890 review comments for costResetAt feature

Critical:
- Wrap resetUserAllStatistics DB writes in transaction for atomicity
- Change sumKeyTotalCost maxAgeDays from 365 to Infinity for unbounded
  accumulation from costResetAt
- Add costResetAtMs to BudgetLease cache with stale detection

Medium:
- Add logger.warn to silent Redis scan failure handlers
- Add fail-open documentation for costResetAt validation
- Fix test mock leak (vi.resetAllMocks + re-establish defaults)

Minor:
- Rename i18n "Reset Data" to "Reset Options" across 5 languages
- Remove brittle source code string assertion tests
- Update test assertions for transaction and Infinity changes

* chore: format code (fix-user-reset-stats-e5002db)

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* fix: resolve client restriction checkboxes not working in create user dialog

Use useRef to track latest nested form state synchronously, preventing
stale state overwrites when AccessRestrictionsSection fires multiple
onChange calls within the same event handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add wildcard glob pattern support for custom client restrictions

Patterns with '*' use full-string glob matching (case-insensitive,
literal characters). Patterns without '*' retain existing substring
matching with normalization for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add popover multi-select for Claude Code sub-client restrictions

Adds granular selection of individual Claude Code sub-clients (CLI,
VS Code, SDK-TS, SDK-PY, CLI-SDK, GitHub Action) via a popover
dropdown on the Claude Code preset row. Auto-consolidates to parent
"claude-code" when all 6 sub-clients are selected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: delegate client restrictions UI to shared editor component

Remove ~200 lines of duplicated preset/popover logic from
AccessRestrictionsSection by reusing ClientRestrictionsEditor.
Fixes i18n bug where sub-client "All" label used wrong translation key
(presetClients["sub-all"] instead of subClients?.all).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: replace regex glob with linear matcher to prevent ReDoS

Replace globToRegex (regex-based, vulnerable to catastrophic
backtracking on patterns like *a*a*a*a*c) with globMatch
(two-pointer linear algorithm). Remove globCache since regex
compilation is no longer needed.

Add adversarial tests: consecutive wildcards, regex metacharacters
as literals, and a performance guard for pathological patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(i18n): correct parentheses and restrict wildcard to client editors

- ja/dashboard.json: fullwidth -> halfwidth parentheses (CI test)
- zh-TW/dashboard.json: halfwidth -> fullwidth in subClients (CI test)
- tag-input.tsx: remove * from DEFAULT_TAG_PATTERN (was leaking to all consumers)
- client-restrictions-editor.tsx: explicit validateTag with * for client inputs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ui): disable inactive preset popover and allow dots/slashes in client patterns

- Disable sub-client popover button when preset is neither allowed nor blocked
- Extend validateTag regex to accept . and / for real-world UA patterns (e.g. my.tool/1.0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ui): require subClients/nSelected props and document glob anchoring

- Make subClients and nSelected required in component interfaces (all callers
  already provide them; removes hardcoded "All" fallbacks per i18n rules)
- Update customHelp in all 10 i18n files to document glob anchoring behavior
  (use *foo* to match anywhere)
- Update test fixtures with required translation props

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: format code (fix-ui-client-restriction-4-8d4ad51)

* style: fix formatting and extract shared client tag pattern constant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
ding113 and others added 8 commits March 10, 2026 14:39
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
UsageLogsDataSection was not forwarding billingModelSource and
currencyCode to the client component, causing it to default to
"original" and skip the client-side settings fetch. Now reads
system settings server-side via getCachedSystemSettings() and
passes both props, matching the pattern used by my-usage page.
… polling

- Add shared shouldShowCostBadgeInCell() helper to deduplicate cost
  multiplier badge logic across virtualized and non-virtualized tables,
  preventing duplicate badges on hedge/retry requests
- Unify hedge icon style: plain GitBranch icon in indigo with separate
  count badge, matching reuse icon visual baseline
- Implement Redis-backed live chain snapshots for real-time decision
  chain display during in-flight requests, replacing generic spinner
  with provider name and phase indicators (retrying, hedge_racing, etc.)
- Make polling interval configurable via DASHBOARD_LOGS_POLL_INTERVAL_MS
  env var (250-60000ms, default 5000ms) with function-based
  refetchInterval to prevent concurrent polls
- Fix pre-existing bug: getPricingSourceLabel missing template literal
  interpolation for billing source
- Fix pre-existing bug: hedge_winner missing from successful provider
  finder in usage-logs-table
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Important

Review skipped

Too many files!

This PR contains 215 files, which is 65 over the limit of 150.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7347076d-b2d6-41e4-a5ce-9724beef99be

📥 Commits

Reviewing files that changed from the base of the PR and between 2b60af9 and d021fc8.

📒 Files selected for processing (215)
  • .env.example
  • drizzle/0079_easy_zeigeist.sql
  • drizzle/0080_fresh_clint_barton.sql
  • drizzle/meta/0079_snapshot.json
  • drizzle/meta/0080_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/dashboard.json
  • messages/en/provider-chain.json
  • messages/en/settings/config.json
  • messages/en/settings/data.json
  • messages/en/settings/providers/batchEdit.json
  • messages/en/settings/providers/form/common.json
  • messages/en/settings/providers/form/sections.json
  • messages/en/settings/providers/list.json
  • messages/en/ui.json
  • messages/ja/dashboard.json
  • messages/ja/provider-chain.json
  • messages/ja/settings/config.json
  • messages/ja/settings/data.json
  • messages/ja/settings/prices.json
  • messages/ja/settings/providers/batchEdit.json
  • messages/ja/settings/providers/form/common.json
  • messages/ja/settings/providers/form/sections.json
  • messages/ja/settings/providers/list.json
  • messages/ja/ui.json
  • messages/ru/dashboard.json
  • messages/ru/provider-chain.json
  • messages/ru/settings/config.json
  • messages/ru/settings/data.json
  • messages/ru/settings/prices.json
  • messages/ru/settings/providers/batchEdit.json
  • messages/ru/settings/providers/form/common.json
  • messages/ru/settings/providers/form/sections.json
  • messages/ru/settings/providers/list.json
  • messages/ru/ui.json
  • messages/zh-CN/dashboard.json
  • messages/zh-CN/provider-chain.json
  • messages/zh-CN/settings/config.json
  • messages/zh-CN/settings/data.json
  • messages/zh-CN/settings/providers/batchEdit.json
  • messages/zh-CN/settings/providers/form/common.json
  • messages/zh-CN/settings/providers/form/sections.json
  • messages/zh-CN/settings/providers/list.json
  • messages/zh-CN/ui.json
  • messages/zh-TW/dashboard.json
  • messages/zh-TW/provider-chain.json
  • messages/zh-TW/settings/config.json
  • messages/zh-TW/settings/data.json
  • messages/zh-TW/settings/prices.json
  • messages/zh-TW/settings/providers/batchEdit.json
  • messages/zh-TW/settings/providers/form/common.json
  • messages/zh-TW/settings/providers/form/sections.json
  • messages/zh-TW/settings/providers/list.json
  • messages/zh-TW/ui.json
  • src/actions/admin-user-insights.ts
  • src/actions/dashboard-realtime.ts
  • src/actions/key-quota.ts
  • src/actions/keys.ts
  • src/actions/my-usage.ts
  • src/actions/system-config.ts
  • src/actions/usage-logs.ts
  • src/actions/users.ts
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/access-restrictions-section.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/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/user-limit-badge.tsx
  • src/app/[locale]/dashboard/availability/_components/provider/latency-chart.tsx
  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/types.ts
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/user-insights-filter-bar.tsx
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-insights-view.tsx
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-key-trend-chart.tsx
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-model-breakdown.tsx
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-provider-breakdown.tsx
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/page.tsx
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-sections.test.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
  • src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/[locale]/dashboard/quotas/users/page.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/internal/dashboard/big-screen/page.tsx
  • src/app/[locale]/my-usage/_components/statistics-summary-card.tsx
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/[locale]/settings/data/_components/log-cleanup-panel.tsx
  • src/app/[locale]/settings/providers/_components/batch-edit/analyze-batch-settings.ts
  • src/app/[locale]/settings/providers/_components/batch-edit/deep-equals.ts
  • src/app/[locale]/settings/providers/_components/batch-edit/mixed-value-indicator.tsx
  • src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-dialog.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.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/[locale]/settings/providers/_components/forms/provider-form/sections/network-section.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/options-section.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
  • src/app/[locale]/settings/providers/_components/provider-list-item.legacy.tsx
  • src/app/[locale]/settings/providers/_components/provider-list.tsx
  • src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx
  • src/app/api/admin/log-cleanup/manual/route.ts
  • src/app/api/leaderboard/route.ts
  • src/app/v1/_lib/proxy-handler.ts
  • src/app/v1/_lib/proxy/client-detector.ts
  • src/app/v1/_lib/proxy/format-mapper.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/provider-selector.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/response-input-rectifier.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/app/v1/_lib/proxy/stream-finalization.ts
  • src/components/analytics/model-breakdown-column.tsx
  • src/components/form/client-restrictions-editor.test.tsx
  • src/components/form/client-restrictions-editor.tsx
  • src/components/ui/chart.tsx
  • src/drizzle/schema.ts
  • src/lib/auth.ts
  • src/lib/client-restrictions/client-presets.test.ts
  • src/lib/client-restrictions/client-presets.ts
  • src/lib/config/env.schema.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/dashboard/user-limit-usage-cache.test.ts
  • src/lib/dashboard/user-limit-usage-cache.ts
  • src/lib/dashboard/user-usage-loader.test.ts
  • src/lib/dashboard/user-usage-loader.ts
  • src/lib/langfuse/trace-proxy-request.test.ts
  • src/lib/langfuse/trace-proxy-request.ts
  • src/lib/log-cleanup/service.ts
  • src/lib/rate-limit/lease-service.ts
  • src/lib/rate-limit/lease.ts
  • src/lib/rate-limit/service.ts
  • src/lib/redis/cost-cache-cleanup.ts
  • src/lib/redis/leaderboard-cache.ts
  • src/lib/redis/live-chain-store.test.ts
  • src/lib/redis/live-chain-store.ts
  • src/lib/security/api-key-auth-cache.ts
  • src/lib/utils/cost-calculation.ts
  • src/lib/utils/price-data.ts
  • src/lib/utils/pricing-resolution.ts
  • src/lib/utils/provider-chain-display.test.ts
  • src/lib/utils/provider-chain-display.ts
  • src/lib/utils/provider-chain-formatter.test.ts
  • src/lib/utils/provider-chain-formatter.ts
  • src/lib/utils/provider-display.ts
  • src/lib/utils/special-settings.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.test.ts
  • src/repository/_shared/transformers.ts
  • src/repository/admin-user-insights.ts
  • src/repository/key.ts
  • src/repository/leaderboard.ts
  • src/repository/model-price.ts
  • src/repository/provider.ts
  • src/repository/statistics.ts
  • src/repository/system-config.ts
  • src/repository/usage-logs.ts
  • src/repository/user.ts
  • src/types/message.ts
  • src/types/model-price.ts
  • src/types/special-settings.ts
  • src/types/system-config.ts
  • src/types/user.ts
  • tests/integration/batch-edit-prefill.test.ts
  • tests/integration/billing-model-source.test.ts
  • tests/unit/actions/admin-user-insights.test.ts
  • tests/unit/actions/key-quota-concurrent-inherit.test.ts
  • tests/unit/actions/key-quota-cost-reset.test.ts
  • tests/unit/actions/my-usage-concurrent-inherit.test.ts
  • tests/unit/actions/total-usage-semantics.test.ts
  • tests/unit/actions/users-reset-all-statistics.test.ts
  • tests/unit/actions/users-reset-limits-only.test.ts
  • tests/unit/api/leaderboard-route.test.ts
  • tests/unit/auth/admin-token-opaque-fallback.test.ts
  • tests/unit/batch-edit/analyze-batch-settings.test.ts
  • tests/unit/batch-edit/deep-equals.test.ts
  • tests/unit/components/model-breakdown-column.test.tsx
  • tests/unit/dashboard/user-insights-page.test.tsx
  • tests/unit/lib/cost-calculation-breakdown.test.ts
  • tests/unit/lib/cost-calculation-priority.test.ts
  • tests/unit/lib/log-cleanup/service-count.test.ts
  • tests/unit/lib/redis/cost-cache-cleanup.test.ts
  • tests/unit/lib/security/api-key-auth-cache-reset-at.test.ts
  • tests/unit/lib/utils/provider-display.test.ts
  • tests/unit/proxy/client-detector.test.ts
  • tests/unit/proxy/extract-usage-metrics.test.ts
  • tests/unit/proxy/hedge-winner-dedup.test.ts
  • tests/unit/proxy/provider-selector-cross-type-model.test.ts
  • tests/unit/proxy/proxy-forwarder-hedge-first-byte.test.ts
  • tests/unit/proxy/proxy-forwarder-raw-passthrough-regression.test.ts
  • tests/unit/proxy/proxy-forwarder.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • tests/unit/proxy/response-handler-endpoint-circuit-isolation.test.ts
  • tests/unit/proxy/response-handler-gemini-stream-passthrough-timeouts.test.ts
  • tests/unit/proxy/response-handler-lease-decrement.test.ts
  • tests/unit/proxy/response-input-rectifier.test.ts
  • tests/unit/repository/leaderboard-user-model-stats.test.ts
  • tests/unit/repository/statistics-reset-at.test.ts
  • tests/unit/settings/providers/form-tab-nav.test.tsx
  • tests/unit/settings/providers/options-section.test.tsx
  • tests/unit/settings/providers/provider-form-total-limit-ui.test.tsx
  • tests/unit/settings/providers/provider-rich-list-item-endpoints.test.tsx
  • tests/unit/usage-ledger/cleanup-immunity.test.ts
  • tests/unit/user-insights-filters.test.ts
  • tests/unit/validation/provider-timeout-schemas.test.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@github-actions
Copy link
Contributor

🧪 测试结果

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

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

@github-actions github-actions bot added size/XL Extra Large PR (> 1000 lines) enhancement New feature or request area:UI area:provider area:core labels Mar 10, 2026
@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!

This release introduces significant performance and monitoring enhancements, primarily through concurrent provider racing and a new user insights dashboard. It also refines administrative controls with improved UI for client restrictions and provider management, alongside critical bug fixes for API routing and billing accuracy. The update aims to provide a more robust, efficient, and transparent system for managing AI service consumption.

Highlights

  • Concurrent Provider Racing: Introduced concurrent provider racing to optimize first-byte timeout, significantly improving user experience. It is recommended to adjust provider first-byte timeouts to 10-15s to maximize this feature's effectiveness.
  • Enhanced Billing & Model Support: Completed billing support for GPT series long context and fast modes, and fixed an issue where Anthropic providers couldn't route to non-Claude models without a whitelist.
  • Improved UI/UX for Administration: Optimized the user interface for client restrictions, provider batch editing, and request records, making administrative tasks more intuitive.
  • New User Insights & Leaderboard Features: Added a new user insights page and sub-model statistics to the user leaderboard, providing deeper analytics into user consumption patterns.
  • User Limit Management: Implemented functionality to reset user limits, allowing administrators to clear accumulated cost counters without deleting historical logs.
  • API Input Normalization: Added automatic adjustment for non-array input in /v1/responses requests, ensuring compatibility with standard API formats.
  • Configurable Log Polling: Enabled adjustment of the request log polling interval via environment variables, offering more control over dashboard real-time updates.
Changelog
  • .env.example
    • Added DASHBOARD_LOGS_POLL_INTERVAL_MS environment variable for log polling interval.
  • drizzle/0079_easy_zeigeist.sql
    • Added 'enable_response_input_rectifier' boolean column to 'system_settings' table.
  • drizzle/0080_fresh_clint_barton.sql
    • Added 'cost_reset_at' timestamp column to 'users' table.
  • drizzle/meta/_journal.json
    • Updated Drizzle migration journal with new migration entries.
  • messages/en/dashboard.json
    • Added 'retrying' status and 'unknownModel' label.
    • Introduced new 'userInsights' section with various labels for user analytics.
    • Updated client restriction placeholders and added new user reset options.
  • messages/en/provider-chain.json
    • Added new provider chain reasons including 'hedgeTriggered', 'hedgeLaunched', 'hedgeWinner', 'hedgeLoserCancelled', and 'clientAbort'.
  • messages/en/settings/config.json
    • Added new setting for 'enableResponseInputRectifier' with description.
  • messages/en/settings/data.json
    • Added new messages for log cleanup success, including 'softDeletePurged' and 'vacuumComplete'.
  • messages/en/settings/providers/batchEdit.json
    • Refactored batch edit translations, reordered actions, and added mixed value indicators.
    • Updated dialog descriptions for clarity.
  • messages/en/settings/providers/form/common.json
    • Added new tab labels for provider form sections: 'scheduling', 'options', 'activeTime', 'circuitBreaker', and 'timeout'.
  • messages/en/settings/providers/form/sections.json
    • Updated client restriction help text and placeholders.
    • Added sub-client selection options and increased non-streaming total timeout range to 1800 seconds.
  • messages/en/settings/providers/list.json
    • Added 'proxyEnabled' label.
  • messages/en/ui.json
    • Added 'unknownError' message for invalid tag input.
  • messages/ja/dashboard.json
    • Added 'Retrying' status and '不明' model label.
    • Introduced new 'ユーザーインサイト' section with various labels for user analytics.
    • Updated client restriction placeholders and added new user reset options.
  • messages/ja/provider-chain.json
    • Added new provider chain reasons including 'Hedge 発動', 'Hedge 代替起動済み', 'Hedge 競争勝者', 'Hedge 競争敗者(キャンセル)', and 'クライアント中断'.
  • messages/ja/settings/config.json
    • Added new setting for 'Response Input 整流器を有効化' with description.
  • messages/ja/settings/data.json
    • Added new messages for log cleanup success, including '論理削除レコードも物理削除しました' and 'データベース領域を回収しました'.
  • messages/ja/settings/prices.json
    • Added 'マルチ' badge.
  • messages/ja/settings/providers/batchEdit.json
    • Refactored batch edit translations, reordered actions, and added mixed value indicators.
    • Updated dialog descriptions for clarity.
  • messages/ja/settings/providers/form/common.json
    • Added new tab labels for provider form sections: 'スケジューリング', 'オプション', 'アクティブ時間', 'サーキットブレーカー', and 'タイムアウト'.
  • messages/ja/settings/providers/form/sections.json
    • Updated client restriction help text and placeholders.
    • Added sub-client selection options and increased non-streaming total timeout range to 1800 seconds.
  • messages/ja/settings/providers/list.json
    • Added 'プロキシ有効' label.
  • messages/ja/ui.json
    • Added '無効な入力' error message for tag input.
  • messages/ru/dashboard.json
    • Added 'Retrying' status and 'Неизвестно' model label.
    • Introduced new 'Аналитика пользователя' section with various labels for user analytics.
    • Updated client restriction placeholders and added new user reset options.
  • messages/ru/provider-chain.json
    • Added new provider chain reasons including 'Hedge запущен', 'Hedge альтернатива запущена', 'Победитель Hedge-гонки', 'Проигравший Hedge-гонки (отменён)', and 'Клиент прервал запрос'.
  • messages/ru/settings/config.json
    • Added new setting for 'Включить исправление Response Input' with description.
  • messages/ru/settings/data.json
    • Added new messages for log cleanup success, including 'Также удалено мягко удаленных записей' and 'Дисковое пространство БД освобождено'.
  • messages/ru/settings/prices.json
    • Added 'Мульти' badge.
  • messages/ru/settings/providers/batchEdit.json
    • Refactored batch edit translations, reordered actions, and added mixed value indicators.
    • Updated dialog descriptions for clarity.
  • messages/ru/settings/providers/form/common.json
    • Added new tab labels for provider form sections: 'Планирование', 'Параметры', 'Активное время', 'Автовыключатель', and 'Таймаут'.
  • messages/ru/settings/providers/form/sections.json
    • Updated client restriction help text and placeholders.
    • Added sub-client selection options and increased non-streaming total timeout range to 1800 seconds.
  • messages/ru/settings/providers/list.json
    • Added 'Прокси включен' label.
  • messages/ru/ui.json
    • Added 'Некорректный ввод' error message for tag input.
  • messages/zh-CN/dashboard.json
    • Added 'Retrying' status and '未知' model label.
    • Introduced new '用户洞察' section with various labels for user analytics.
    • Updated client restriction placeholders and added new user reset options.
  • messages/zh-CN/provider-chain.json
    • Added new provider chain reasons including 'Hedge 已触发', 'Hedge 备选已启动', 'Hedge 竞速赢家', 'Hedge 竞速输家(已取消)', and '客户端中断'.
  • messages/zh-CN/settings/config.json
    • Added new setting for '启用 Response Input 整流器' with description.
  • messages/zh-CN/settings/data.json
    • Added new messages for log cleanup success, including '另外清除了软删除记录' and '数据库空间已回收'.
  • messages/zh-CN/settings/providers/batchEdit.json
    • Refactored batch edit translations, reordered actions, and added mixed value indicators.
    • Updated dialog descriptions for clarity.
  • messages/zh-CN/settings/providers/form/common.json
    • Added new tab labels for provider form sections: '调度', '选项', '活跃时间', '断路器', and '超时'.
  • messages/zh-CN/settings/providers/form/sections.json
    • Updated client restriction help text and placeholders.
    • Added sub-client selection options and increased non-streaming total timeout range to 1800 seconds.
  • messages/zh-CN/settings/providers/list.json
    • Added '已启用代理' label.
  • messages/zh-CN/ui.json
    • Added '输入无效' error message for tag input.
  • messages/zh-TW/dashboard.json
    • Added 'Retrying' status and '未知' model label.
    • Introduced new '使用者洞察' section with various labels for user analytics.
    • Updated client restriction placeholders and added new user reset options.
  • messages/zh-TW/provider-chain.json
    • Added new provider chain reasons including 'Hedge 已觸發', 'Hedge 備選已啟動', 'Hedge 競速贏家', 'Hedge 競速輸家(已取消)', and '客戶端中斷'.
  • messages/zh-TW/settings/config.json
    • Added new setting for '啟用 Response Input 整流器' with description.
  • messages/zh-TW/settings/data.json
    • Added new messages for log cleanup success, including '另外清除了軟刪除記錄' and '資料庫空間已回收'.
  • messages/zh-TW/settings/prices.json
    • Added '多供應商' badge.
  • messages/zh-TW/settings/providers/batchEdit.json
    • Refactored batch edit translations, reordered actions, and added mixed value indicators.
    • Updated dialog descriptions for clarity.
  • messages/zh-TW/settings/providers/form/common.json
    • Added new tab labels for provider form sections: '排程', '選項', '活躍時間', '斷路器', and '逾時'.
  • messages/zh-TW/settings/providers/form/sections.json
    • Updated client restriction help text and placeholders.
    • Added sub-client selection options and increased non-streaming total timeout range to 1800 seconds.
  • messages/zh-TW/settings/providers/list.json
    • Added '已啟用代理' label.
  • messages/zh-TW/ui.json
    • Added '輸入無效' error message for tag input.
  • src/actions/admin-user-insights.ts
    • Added new server actions for fetching user insights data, including overview, key trend, model breakdown, and provider breakdown.
  • src/actions/dashboard-realtime.ts
    • Modified real-time dashboard data to handle unfinalized provider and status, using live chain data.
  • src/actions/key-quota.ts
    • Modified key quota usage calculation to respect 'userCostResetAt' for time range clipping.
  • src/actions/keys.ts
    • Modified key limit usage calculation to respect 'userCostResetAt' for time range clipping.
  • src/actions/my-usage.ts
    • Modified user quota calculation to respect 'costResetAt' for time range clipping.
  • src/actions/system-config.ts
    • Added 'enableResponseInputRectifier' to system settings save action.
  • src/actions/usage-logs.ts
    • Modified 'getUsageLogsBatch' to merge Redis live chain data for unfinalized rows.
  • src/actions/users.ts
    • Added 'costResetAt' to user display data.
    • Introduced 'resetUserLimitsOnly' action to reset user cost limits without deleting logs.
    • Updated 'resetUserAllStatistics' to use 'clearUserCostCache' and invalidate user auth cache.
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
    • Updated user creation dialog to use refs for latest user and key drafts, improving form state management.
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
    • Added a 'Reset Limits' option to the user edit dialog, allowing selective reset of cost counters.
  • src/app/[locale]/dashboard/_components/user/forms/access-restrictions-section.tsx
    • Replaced direct preset handling with a dedicated 'ClientRestrictionsEditor' component.
  • src/app/[locale]/dashboard/_components/user/forms/user-edit-section.tsx
    • Updated 'UserEditSection' to pass new sub-client translations to the client restrictions editor.
  • src/app/[locale]/dashboard/_components/user/forms/user-form.tsx
    • Added sub-client translations to the user form.
  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
    • Added sub-client translations to the user translations hook.
  • src/app/[locale]/dashboard/_components/user/user-limit-badge.tsx
    • Updated user limit badge to use a shared cache and peek function for usage data.
  • src/app/[locale]/dashboard/availability/_components/provider/latency-chart.tsx
    • Fixed dataKey type in latency chart tooltip for better compatibility.
  • src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx
    • Added 'includeUserModelStats' parameter to leaderboard queries and integrated user insights link.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/types.ts
    • Added types for user insights filters and time range resolution utilities.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/user-insights-filter-bar.tsx
    • Added a filter bar component for user insights.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-insights-view.tsx
    • Added a new user insights view component to display detailed user analytics.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-key-trend-chart.tsx
    • Added a user key trend chart component for visualizing usage over time.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-model-breakdown.tsx
    • Added a user model breakdown component to show model-level usage.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx
    • Added user overview cards component to display key metrics.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-provider-breakdown.tsx
    • Added a user provider breakdown component to show provider-level usage.
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/page.tsx
    • Added the user insights page.
  • src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx
    • Updated logic trace tab to handle new hedge and client abort reasons.
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.test.tsx
    • Added tests for hedge/abort reason handling in provider chain popover.
  • src/app/[locale]/dashboard/logs/_components/provider-chain-popover.tsx
    • Updated provider chain popover to display hedge race status and client abort events.
  • src/app/[locale]/dashboard/logs/_components/usage-logs-sections.test.tsx
    • Added tests for 'UsageLogsDataSection' props, including logs refresh interval.
  • src/app/[locale]/dashboard/logs/_components/usage-logs-sections.tsx
    • Added 'logsRefreshIntervalMs' and billing model source/currency code to 'UsageLogsViewVirtualized'.
  • src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx
    • Updated usage logs table to handle hedge winner and use 'shouldShowCostBadgeInCell' for multiplier display.
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.test.tsx
    • Added tests for live chain display in virtualized logs table.
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
    • Updated virtualized logs table to display live chain status and handle hedge winner/retrying states.
  • src/app/[locale]/dashboard/quotas/users/page.tsx
    • Modified user quota page to handle 'costResetAt' for total cost calculation.
  • src/app/[locale]/dashboard/users/users-page-client.tsx
    • Updated user page client to use sequential usage loading and clear usage cache on refresh.
  • src/app/[locale]/internal/dashboard/big-screen/page.tsx
    • Updated activity stream to handle unfinalized provider and status, displaying '...' for pending states.
  • src/app/[locale]/my-usage/_components/statistics-summary-card.tsx
    • Removed local 'ModelBreakdownColumn' and 'ModelBreakdownRow' components, now imported from 'src/components/analytics/model-breakdown-column.tsx'.
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
    • Added 'enableResponseInputRectifier' to the system settings form.
  • src/app/[locale]/settings/config/page.tsx
    • Added 'enableResponseInputRectifier' to the system settings page.
  • src/app/[locale]/settings/data/_components/log-cleanup-panel.tsx
    • Updated log cleanup panel to display soft-deleted purged count and vacuum status in success message.
  • src/app/[locale]/settings/providers/_components/batch-edit/analyze-batch-settings.ts
    • Added new file for analyzing batch provider settings, determining uniform, mixed, or empty values.
  • src/app/[locale]/settings/providers/_components/batch-edit/deep-equals.ts
    • Added new file for deep equality comparison, used in batch settings analysis.
  • src/app/[locale]/settings/providers/_components/batch-edit/mixed-value-indicator.tsx
    • Added new file for a mixed value indicator component in batch edit forms.
  • src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-dialog.tsx
    • Added 'OptionsSection' to the provider batch dialog.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
    • Refactored form tab navigation to support sub-tabs and filtering of tabs.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
    • Updated provider form to support sub-tabs and integrated the new 'OptionsSection'.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-context.tsx
    • Updated provider form context to support batch analysis and sub-tabs for navigation.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts
    • Added 'SubTabId' and 'NavTargetId' types for enhanced navigation.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/limits-section.tsx
    • Added mixed value indicators to limit cards in the limits section.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/network-section.tsx
    • Increased the maximum value for non-streaming total timeout to 1800 seconds.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/options-section.tsx
    • Added new file for the provider options section, including advanced settings and overrides.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
    • Removed advanced settings, Codex/Anthropic/Gemini overrides, and active time sections, moving them to 'OptionsSection'.
  • src/app/[locale]/settings/providers/_components/provider-list-item.legacy.tsx
    • Updated provider list item to use 'PROVIDER_TIMEOUT_DEFAULTS' and display proxy enabled status.
  • src/app/[locale]/settings/providers/_components/provider-list.tsx
    • Wrapped provider list with 'TooltipProvider' for consistent tooltip behavior.
  • src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx
    • Displayed proxy enabled status and updated timeout summary to use default values.
  • src/app/api/admin/log-cleanup/manual/route.ts
    • Added 'softDeletedPurged' and 'vacuumPerformed' to log cleanup API response.
  • src/app/api/leaderboard/route.ts
    • Added 'includeUserModelStats' parameter and logic for user leaderboard, requiring admin access.
  • src/app/v1/_lib/proxy-handler.ts
    • Added 'normalizeResponseInput' for '/v1/responses' endpoint to handle non-array inputs.
  • src/app/v1/_lib/proxy/client-detector.ts
    • Added glob matching functionality for client patterns in User-Agent strings.
  • src/app/v1/_lib/proxy/format-mapper.ts
    • Clarified 'detectClientFormat' for Response API, noting input normalization happens later.
  • src/app/v1/_lib/proxy/forwarder.ts
    • Implemented streaming hedge (concurrent racing) logic to improve first-byte performance.
    • Updated client abort reason to 'client_abort' and blacklisted more outbound headers.
  • src/app/v1/_lib/proxy/provider-selector.ts
    • Updated 'providerSupportsModel' logic for model selection and removed explicit modelRedirects check from this stage.
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
    • Modified rate limit checks to incorporate 'costResetAt' for accurate usage tracking.
  • src/app/v1/_lib/proxy/response-handler.ts
    • Added live chain deletion upon request finalization and updated deferred streaming finalization logic.
  • src/app/v1/_lib/proxy/response-input-rectifier.ts
    • Added new file for Response API input rectifier, normalizing non-array inputs.
  • src/app/v1/_lib/proxy/session.ts
    • Added live chain persistence and new reasons for provider chain items related to hedging and client aborts.
  • src/components/analytics/model-breakdown-column.tsx
    • Added new file for a reusable model breakdown column component.
  • src/components/form/client-restrictions-editor.test.tsx
    • Added tests for sub-client selection functionality in the client restrictions editor.
  • src/components/form/client-restrictions-editor.tsx
    • Added sub-client selection functionality to the client restrictions editor.
  • src/components/ui/chart.tsx
    • Fixed dataKey type in chart tooltip for better type safety.
  • src/drizzle/schema.ts
    • Added 'cost_reset_at' column to the 'users' table and updated timeout defaults for 'providers' table.
  • src/lib/auth.ts
    • Added opaque mode fallback for raw admin tokens, allowing programmatic API access.
  • src/lib/client-restrictions/client-presets.test.ts
    • Added tests for child selection helpers in client restriction presets.
  • src/lib/client-restrictions/client-presets.ts
    • Added child selection logic for client restriction presets.
  • src/lib/config/env.schema.ts
    • Added 'DASHBOARD_LOGS_POLL_INTERVAL_MS' environment variable for dashboard log polling.
  • src/lib/config/system-settings-cache.ts
    • Added 'enableResponseInputRectifier' to default system settings.
  • src/lib/dashboard/user-limit-usage-cache.test.ts
    • Added tests for the new user limit usage cache.
  • src/lib/dashboard/user-limit-usage-cache.ts
    • Added new file for a shared user limit usage cache.
  • src/lib/dashboard/user-usage-loader.test.ts
    • Added tests for the new user usage loader.
  • src/lib/dashboard/user-usage-loader.ts
    • Added new file for a sequential user usage loader.
  • src/lib/langfuse/trace-proxy-request.test.ts
    • Added tests for trace proxy request reason classification, including hedge events.
  • src/lib/langfuse/trace-proxy-request.ts
    • Added 'hedge_winner' to success reasons and 'client_abort' to error reasons for Langfuse tracing.
  • src/lib/log-cleanup/service.ts
    • Implemented purging of soft-deleted records and VACUUM ANALYZE for database space reclamation.
  • src/lib/rate-limit/lease-service.ts
    • Modified lease service to respect 'costResetAt' for accurate rate limiting.
  • src/lib/rate-limit/lease.ts
    • Added 'costResetAtMs' to the 'BudgetLease' interface.
  • src/lib/rate-limit/service.ts
    • Modified rate limit service to respect 'cost_reset_at' in cost limit checks.
  • src/lib/redis/cost-cache-cleanup.ts
    • Added new file for Redis cost cache cleanup utilities.
  • src/lib/redis/leaderboard-cache.ts
    • Updated leaderboard cache key to include 'includeModelStats' for user scope.
  • src/lib/redis/live-chain-store.test.ts
    • Added tests for live chain store phase inference.
  • src/lib/redis/live-chain-store.ts
    • Added new file for a Redis-based live chain store.
  • src/lib/security/api-key-auth-cache.ts
    • Added 'costResetAt' to the cached user payload.
  • src/lib/utils/cost-calculation.ts
    • Updated cost calculation to handle priority-aware long context rates for GPT models.
  • src/lib/utils/price-data.ts
    • Added priority long-context pricing fields to model price data.
  • src/lib/utils/pricing-resolution.ts
    • Added priority long-context pricing fields to pricing resolution logic.
  • src/lib/utils/provider-chain-display.test.ts
    • Added tests for 'shouldShowCostBadgeInCell' utility.
  • src/lib/utils/provider-chain-display.ts
    • Added new file for provider chain display utilities.
  • src/lib/utils/provider-chain-formatter.test.ts
    • Added tests for hedge and client abort reason handling in provider chain formatter.
  • src/lib/utils/provider-chain-formatter.ts
    • Updated provider chain formatter to handle hedge and client abort reasons, and improved retry counting.
  • src/lib/utils/provider-display.test.ts
    • Added tests for 'isProviderFinalized' utility.
  • src/lib/utils/provider-display.ts
    • Added new file for provider display utilities, including 'isProviderFinalized'.
  • src/lib/utils/special-settings.ts
    • Added 'response_input_rectifier' to special settings.
  • src/lib/validation/schemas.ts
    • Added 'enableResponseInputRectifier' to the system settings schema.
  • src/repository/_shared/transformers.test.ts
    • Updated provider timeout defaults in transformers test and added 'costResetAt' to user transformer.
  • src/repository/_shared/transformers.ts
    • Updated provider timeout defaults and added 'costResetAt' to user transformer.
  • src/repository/admin-user-insights.ts
    • Added new file for admin user insights repository functions.
  • src/repository/key.ts
    • Added 'costResetAt' to key validation logic.
  • src/repository/leaderboard.ts
    • Added 'UserModelStat' interface and 'includeModelStats' parameter to leaderboard queries.
  • src/repository/model-price.ts
    • Updated 'findAllLatestPricesPaginated' to order by 'updatedAt' DESC for better sorting.
  • src/repository/provider.ts
    • Updated provider creation to use 'PROVIDER_TIMEOUT_DEFAULTS' for consistent timeout settings.
  • src/repository/statistics.ts
    • Modified total cost calculation to respect 'resetAt' for accurate usage aggregation.
  • src/repository/system-config.ts
    • Added 'enableResponseInputRectifier' to system settings.
  • src/repository/usage-logs.ts
    • Added '_liveChain' to 'UsageLogRow' interface for real-time log updates.
  • src/repository/user.ts
    • Added 'costResetAt' to user queries and introduced 'resetUserCostResetAt' function.
  • src/types/message.ts
    • Added new reasons for provider chain items, including 'hedge_triggered', 'hedge_launched', 'hedge_winner', 'hedge_loser_cancelled', and 'client_abort'.
  • src/types/model-price.ts
    • Added priority long-context pricing fields to the model price data interface.
  • src/types/special-settings.ts
    • Added 'ResponseInputRectifierSpecialSetting' for auditing input normalization.
  • src/types/system-config.ts
    • Added 'enableResponseInputRectifier' to system configuration.
  • src/types/user.ts
    • Added 'costResetAt' to user interfaces.
  • tests/integration/batch-edit-prefill.test.ts
    • Added new file for integration tests of batch edit prefill logic.
  • tests/integration/billing-model-source.test.ts
    • Added test case for long-context priority pricing in billing model source.
  • tests/unit/actions/admin-user-insights.test.ts
    • Added new file for unit tests of admin user insights actions.
  • tests/unit/actions/key-quota-concurrent-inherit.test.ts
    • Updated mocks and added 'userCostResetAt' to key quota tests.
  • tests/unit/actions/key-quota-cost-reset.test.ts
    • Added new file for unit tests of key quota cost reset functionality.
  • tests/unit/actions/my-usage-concurrent-inherit.test.ts
    • Updated logger mocks.
  • tests/unit/actions/total-usage-semantics.test.ts
    • Removed source code verification tests.
  • tests/unit/actions/users-reset-all-statistics.test.ts
    • Updated mocks for 'db.transaction' and 'invalidateCachedUser' in user statistics reset tests.
  • tests/unit/actions/users-reset-limits-only.test.ts
    • Added new file for unit tests of user reset limits only functionality.
  • tests/unit/auth/admin-token-opaque-fallback.test.ts
    • Added new file for unit tests of admin token opaque fallback in authentication.
  • tests/unit/batch-edit/analyze-batch-settings.test.ts
    • Added new file for unit tests of batch edit settings analysis.
  • tests/unit/batch-edit/deep-equals.test.ts
    • Added new file for unit tests of deep equality comparison.
  • tests/unit/components/model-breakdown-column.test.tsx
    • Added new file for unit tests of the model breakdown column component.
  • tests/unit/lib/cost-calculation-breakdown.test.ts
    • Updated cost calculation tests for long-context priority pricing.
  • tests/unit/lib/cost-calculation-priority.test.ts
    • Added tests for long-context priority pricing in cost calculation.
  • tests/unit/lib/log-cleanup/service-count.test.ts
    • Updated log cleanup service count tests for soft-delete purge and vacuum.
  • tests/unit/lib/redis/cost-cache-cleanup.test.ts
    • Added new file for unit tests of Redis cost cache cleanup.
  • tests/unit/lib/redis/live-chain-store.test.ts
    • Added new file for unit tests of live chain store phase inference.
  • tests/unit/lib/security/api-key-auth-cache-reset-at.test.ts
    • Added new file for unit tests of API key auth cache reset-at handling.
  • tests/unit/lib/utils/provider-display.test.ts
    • Added new file for unit tests of provider display utilities.
  • tests/unit/proxy/client-detector.test.ts
    • Added glob matching tests for client detector.
  • tests/unit/proxy/extract-usage-metrics.test.ts
    • Added tests for extracting cached tokens from Chat Completions format.
  • tests/unit/proxy/provider-selector-cross-type-model.test.ts
    • Added new file for unit tests of provider selector cross-type model routing.
  • tests/unit/proxy/response-handler-live-chain.test.ts
    • Added new file for unit tests of response handler live chain updates.
  • tests/unit/proxy/response-handler-streaming-hedge.test.ts
    • Added new file for unit tests of response handler streaming hedge logic.
  • tests/unit/proxy/response-input-rectifier.test.ts
    • Added new file for unit tests of Response API input rectifier.
  • tests/unit/proxy/session-live-chain.test.ts
    • Added new file for unit tests of session live chain persistence.
  • tests/unit/repository/admin-user-insights.test.ts
    • Added new file for unit tests of admin user insights repository functions.
  • tests/unit/repository/leaderboard-model-stats.test.ts
    • Added new file for unit tests of leaderboard model statistics.
  • tests/unit/repository/model-price-pagination.test.ts
    • Added new file for unit tests of model price pagination.
  • tests/unit/repository/user-reset-cost-at.test.ts
    • Added new file for unit tests of user reset cost at functionality.
  • tests/unit/repository/user-total-cost-batch.test.ts
    • Added new file for unit tests of user total cost batch calculation.
  • tests/unit/repository/user-total-cost-reset-at.test.ts
    • Added new file for unit tests of user total cost with reset at.
  • tests/unit/utils/provider-chain-formatter-hedge.test.ts
    • Added new file for unit tests of provider chain formatter hedge logic.
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.

Comment on lines +3144 to +3158
launchedProviderIds.add(provider.id);
launchedProviderCount += 1;

let endpointSelection: {
endpointId: number | null;
baseUrl: string;
endpointUrl: string;
};
try {
endpointSelection = await ProxyForwarder.resolveStreamingHedgeEndpoint(session, provider);
} catch (endpointError) {
lastError = endpointError as Error;
await launchAlternative();
await finishIfExhausted();
return;
Copy link

Choose a reason for hiding this comment

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

launchedProviderCount inflated before endpoint resolution succeeds

launchedProviderIds and launchedProviderCount are both incremented at lines 3144–3145, before resolveStreamingHedgeEndpoint is awaited (line 3153). If that call throws (e.g. the vendor-type circuit is open or no endpoint candidates exist), the count is already 1 and the function immediately calls launchAlternative(), which will increment the count to 2.

Later, inside commitWinner, the winning alternative is classified using:

const isActualHedgeWin = launchedProviderCount > 1;

Because the count is 2 due to the failed endpoint-resolution step (not a real in-flight network race), the provider chain entry will be logged with reason: "hedge_winner" even though no concurrent race actually took place — it was a plain failover triggered by endpoint selection failure. This can make the audit trail misleading for operators reviewing the decision chain.

Consider only incrementing launchedProviderCount after successful endpoint resolution, or tracking a separate boolean anyRealAttemptLaunched to gate the isActualHedgeWin check:

launchedProviderIds.add(provider.id);

let endpointSelection: { ... };
try {
  endpointSelection = await ProxyForwarder.resolveStreamingHedgeEndpoint(session, provider);
} catch (endpointError) {
  lastError = endpointError as Error;
  await launchAlternative();
  await finishIfExhausted();
  return;
}

// Only count after a real network attempt is about to launch
launchedProviderCount += 1;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/forwarder.ts
Line: 3144-3158

Comment:
**`launchedProviderCount` inflated before endpoint resolution succeeds**

`launchedProviderIds` and `launchedProviderCount` are both incremented at lines 3144–3145, *before* `resolveStreamingHedgeEndpoint` is awaited (line 3153). If that call throws (e.g. the vendor-type circuit is open or no endpoint candidates exist), the count is already 1 and the function immediately calls `launchAlternative()`, which will increment the count to 2.

Later, inside `commitWinner`, the winning alternative is classified using:
```typescript
const isActualHedgeWin = launchedProviderCount > 1;
```
Because the count is 2 due to the failed *endpoint-resolution* step (not a real in-flight network race), the provider chain entry will be logged with `reason: "hedge_winner"` even though no concurrent race actually took place — it was a plain failover triggered by endpoint selection failure. This can make the audit trail misleading for operators reviewing the decision chain.

Consider only incrementing `launchedProviderCount` *after* successful endpoint resolution, or tracking a separate boolean `anyRealAttemptLaunched` to gate the `isActualHedgeWin` check:

```typescript
launchedProviderIds.add(provider.id);

let endpointSelection: { ... };
try {
  endpointSelection = await ProxyForwarder.resolveStreamingHedgeEndpoint(session, provider);
} catch (endpointError) {
  lastError = endpointError as Error;
  await launchAlternative();
  await finishIfExhausted();
  return;
}

// Only count after a real network attempt is about to launch
launchedProviderCount += 1;
```

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

Comment on lines +3531 to +3538
const { done, value } = await reader.read();
if (done) {
controller.close();
return;
}
if (value && value.byteLength > 0) {
controller.enqueue(value);
}
Copy link

Choose a reason for hiding this comment

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

Zero-length chunk not enqueued and stream not closed

When reader.read() returns a chunk with value.byteLength === 0, neither controller.enqueue nor controller.close is called before pull returns. Per the ReadableStream spec, the runtime will call pull again when the consumer's queue is empty, triggering another await reader.read() round-trip.

In practice, AI streaming providers never emit zero-length chunks after the first valid byte, so this is unlikely to cause problems. However, if any upstream ever produces a burst of empty frames (e.g. keep-alive padding), this could result in a loop of no-op reads that delay real data delivery to the client without any observable error.

A defensive guard would make the intent explicit:

const { done, value } = await reader.read();
if (done) {
  controller.close();
  return;
}
if (value && value.byteLength > 0) {
  controller.enqueue(value);
}
// else: skip empty chunk; pull will be called again

The comment on the else branch would at least document that the silent skip is intentional.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/forwarder.ts
Line: 3531-3538

Comment:
**Zero-length chunk not enqueued and stream not closed**

When `reader.read()` returns a chunk with `value.byteLength === 0`, neither `controller.enqueue` nor `controller.close` is called before `pull` returns. Per the `ReadableStream` spec, the runtime will call `pull` again when the consumer's queue is empty, triggering another `await reader.read()` round-trip.

In practice, AI streaming providers never emit zero-length chunks after the first valid byte, so this is unlikely to cause problems. However, if any upstream ever produces a burst of empty frames (e.g. keep-alive padding), this could result in a loop of no-op reads that delay real data delivery to the client without any observable error.

A defensive guard would make the intent explicit:

```typescript
const { done, value } = await reader.read();
if (done) {
  controller.close();
  return;
}
if (value && value.byteLength > 0) {
  controller.enqueue(value);
}
// else: skip empty chunk; pull will be called again
```
The comment on the `else` branch would at least document that the silent skip is intentional.

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

Comment on lines 113 to 121
function providerSupportsModel(provider: Provider, requestedModel: string): boolean {
const isClaudeModel = requestedModel.startsWith("claude-");
const isClaudeProvider =
provider.providerType === "claude" || provider.providerType === "claude-auth";

// Case 1: Claude 模型请求
if (isClaudeModel) {
// 1a. Anthropic 提供商
if (isClaudeProvider) {
// 未设置 allowedModels 或为空数组:允许所有 claude 模型
if (!provider.allowedModels || provider.allowedModels.length === 0) {
return true;
}
// 检查白名单
return provider.allowedModels.includes(requestedModel);
}

// 1b. 非 Anthropic 提供商不支持 Claude 模型调度
return false;
}

// Case 2: 非 Claude 模型请求(gpt-*, gemini-*, 或其他任意模型)
// 2a. 优先检查显式声明(支持跨类型代理)
// 原因:允许 Claude 类型供应商通过 allowedModels/modelRedirects 声明支持非 Claude 模型
// 场景:Claude 供应商配置模型重定向,将 gemini-* 请求转发到真实的 Gemini 上游
const explicitlyDeclared = !!(
provider.allowedModels?.includes(requestedModel) || provider.modelRedirects?.[requestedModel]
);

if (explicitlyDeclared) {
return true; // 显式声明优先级最高,允许跨类型代理
}

// 2b. Anthropic 提供商不支持非声明的非 Claude 模型
// 保护机制:防止将非 Claude 模型误路由到 Anthropic API
if (isClaudeProvider) {
return false;
}

// 2c. 非 Anthropic 提供商(codex, gemini, gemini-cli, openai-compatible)
// allowedModels 是声明列表,用于调度器匹配提供商
// 用户可以手动填写任意模型名称(不限于真实模型),用于声明该提供商"支持"哪些模型

// 未设置 allowedModels 或为空数组:接受任意模型(由上游提供商判断)
// 1. 未设置 allowedModels(null 或空数组):接受任意模型
if (!provider.allowedModels || provider.allowedModels.length === 0) {
return true;
}

// 不在声明列表中且无重定向配置(前面已检查过 explicitlyDeclared)
return false;
// 2. 设置了 allowedModels:只按原始请求模型做白名单匹配
return provider.allowedModels.includes(requestedModel);
}
Copy link

Choose a reason for hiding this comment

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

modelRedirects no longer participates in provider routing

The previous implementation of providerSupportsModel treated a modelRedirects entry as an explicit declaration that the provider supports a given source model, so it was factored into the routing decision alongside allowedModels:

// Old code
const explicitlyDeclared = !!(
  provider.allowedModels?.includes(requestedModel) || provider.modelRedirects?.[requestedModel]
);
if (explicitlyDeclared) return true;

With the new simplified logic, modelRedirects is no longer consulted here. A provider that has:

  • allowedModels: ["gpt-4o"]
  • modelRedirects: { "gpt-3.5-turbo": "gpt-4o" }

…will now return false for a gpt-3.5-turbo request because the source model is absent from allowedModels. Requests that used to be silently redirected will fail to route at all until the operator explicitly adds the redirect source model to allowedModels.

This is noted in the PR's BREAKING CHANGE section, but it is a silent behavioral regression — no error is surfaced to users; requests simply fall through to a 503. Consider adding a debug-level log when a provider is skipped because of model mismatch and a modelRedirects entry exists for the requested model, to help operators detect misconfigured providers:

// After the allowedModels check fails:
if (provider.modelRedirects?.[requestedModel]) {
  logger.debug(
    "providerSupportsModel: model in modelRedirects but not in allowedModels — skipping provider",
    { providerId: provider.id, requestedModel }
  );
}
return false;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/provider-selector.ts
Line: 113-121

Comment:
**`modelRedirects` no longer participates in provider routing**

The previous implementation of `providerSupportsModel` treated a `modelRedirects` entry as an explicit declaration that the provider supports a given source model, so it was factored into the routing decision alongside `allowedModels`:

```typescript
// Old code
const explicitlyDeclared = !!(
  provider.allowedModels?.includes(requestedModel) || provider.modelRedirects?.[requestedModel]
);
if (explicitlyDeclared) return true;
```

With the new simplified logic, `modelRedirects` is no longer consulted here. A provider that has:
- `allowedModels: ["gpt-4o"]`
- `modelRedirects: { "gpt-3.5-turbo": "gpt-4o" }`

…will now return `false` for a `gpt-3.5-turbo` request because the source model is absent from `allowedModels`. Requests that used to be silently redirected will fail to route at all until the operator explicitly adds the redirect source model to `allowedModels`.

This is noted in the PR's BREAKING CHANGE section, but it is a silent behavioral regression — no error is surfaced to users; requests simply fall through to a 503. Consider adding a debug-level log when a provider is skipped because of model mismatch *and* a `modelRedirects` entry exists for the requested model, to help operators detect misconfigured providers:

```typescript
// After the allowedModels check fails:
if (provider.modelRedirects?.[requestedModel]) {
  logger.debug(
    "providerSupportsModel: model in modelRedirects but not in allowedModels — skipping provider",
    { providerId: provider.id, requestedModel }
  );
}
return false;
```

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

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

这是一个重要的版本发布,引入了多项重大功能,例如供应商并发竞速(hedging)、用户洞察页面和限额重置,同时还包含了大量的 UI/UX 优化和错误修复。这些功能的实现看起来相当稳健且经过深思熟虑。proxy-forwarder.ts 中的并发竞速逻辑虽然复杂,但妥善处理了包括客户端中止在内的各种边缘情况。用于重置用户限额的 costResetAt 功能在不同的用量和配额计算模块中得到了一致的应用。提供商模型支持逻辑的简化,提高了代码的清晰度和正确性。为客户端限制添加的 glob 模式匹配也是一个实用的增强功能。总的来说,这是一个高质量的拉取请求,在各个方面都有实质性的改进。

Note: Security Review did not run due to the size of the PR.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR introduces release v0.6.3 with Provider Hedge Racing, User Insights Dashboard, Soft User Limit Reset, Response Input Rectifier, and GPT Long-Context Pricing. The implementation is well-structured with comprehensive test coverage for all new features.

PR Size: XL

  • Lines changed: 26,378 (23,587 additions + 2,791 deletions)
  • Files changed: 213

Note: This is an extra-large PR. For future releases, consider splitting into smaller PRs by feature area:

  1. Core proxy changes (hedge racing, response rectifier) - ~8,000 lines
  2. User insights dashboard - ~4,000 lines
  3. User limit management (costResetAt) - ~3,000 lines
  4. UI improvements and i18n - ~6,000 lines
  5. Database migrations and schema changes - ~1,000 lines

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 (SQL injection protected via Drizzle parameterized queries, admin routes properly authorized, input validated)
  • Error handling - Clean (no empty catch blocks, errors logged or propagated)
  • Type safety - Clean (TypeScript used throughout, no unsafe any in critical paths)
  • Documentation accuracy - Clean (comments match implementation)
  • Test coverage - Adequate (tests exist for hedge racing, response rectifier, user insights, cost reset)
  • Code clarity - Good

Notable Implementation Quality

  1. Hedge Racing (src/app/v1/_lib/proxy/forwarder.ts): Well-implemented concurrent provider racing with proper cleanup of losing attempts and session synchronization.

  2. Response Input Rectifier (src/app/v1/_lib/proxy/response-input-rectifier.ts): Clean normalization of /v1/responses input formats with proper type guards.

  3. User Insights Authorization (src/app/[locale]/dashboard/leaderboard/user/[userId]/page.tsx): Proper admin-only access control with userId validation.

  4. SQL Safety (src/repository/admin-user-insights.ts): All queries use Drizzle ORM with parameterized statements, preventing SQL injection.

  5. Soft Reset Pattern (src/actions/users.ts): The costResetAt mechanism provides a clean way to reset limits without destroying historical data.


Automated review by Claude AI

> {
const session = await getSession();
if (!session || session.user.role !== "admin") {
return { ok: false, error: "Unauthorized" };
Copy link
Contributor

Choose a reason for hiding this comment

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

[HIGH] [STANDARD-VIOLATION] Hardcoded ActionResult errors bypass i18n

Evidence: src/actions/admin-user-insights.ts:40 contains return { ok: false, error: "Unauthorized" };; similar hardcoded user-facing strings exist at lines 45, 72, 78, 100, 103, 106, 127, 163.

Why this is a problem: CLAUDE.md requires: i18n Required - All user-facing strings must use i18n (5 languages supported). Never hardcode display text.

Suggested fix:

import { getTranslations } from "next-intl/server";
import { ERROR_CODES } from "@/lib/utils/error-messages";

const tError = await getTranslations("errors");

if (!session || session.user.role !== "admin") {
  return {
    ok: false,
    error: tError("UNAUTHORIZED"),
    errorCode: ERROR_CODES.UNAUTHORIZED,
  };
}

if (!isValidTimeRange(timeRange)) {
  return {
    ok: false,
    error: tError("INVALID_FORMAT", { field: "timeRange" }),
    errorCode: ERROR_CODES.INVALID_FORMAT,
    errorParams: { field: "timeRange" },
  };
}

export function UserOverviewCards({ userId }: UserOverviewCardsProps) {
const t = useTranslations("dashboard.leaderboard.userInsights");

const { data, isLoading } = useQuery({
Copy link
Contributor

Choose a reason for hiding this comment

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

[HIGH] [ERROR-NO-USER-FEEDBACK] React Query failures render as blank/"no data"

Evidence: src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx:23-47 throws on failure (if (!result.ok) throw new Error(result.error);) but then hides the error with if (!data) return null;.

Same pattern (throw, but no isError/error render path) in:

  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-key-trend-chart.tsx:41-52 (falls through to noData)
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-model-breakdown.tsx:36-69 (falls through to noData)
  • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-provider-breakdown.tsx:36-69 (falls through to noData)

Why this is a problem: Network/session errors become indistinguishable from empty datasets, and the UI provides no actionable feedback.

Suggested fix:

const tError = useTranslations("errors");
const { data, isLoading, isError, error } = useQuery({
  // ...
});

if (isError) {
  return (
    <div
      role="alert"
      className="rounded-lg border border-destructive/50 bg-destructive/5 p-3 text-sm text-destructive"
    >
      {error instanceof Error ? error.message : tError("OPERATION_FAILED")}
    </div>
  );
}

usageCache.set(userId, { data: result.data, timestamp: Date.now() });
return result.data;
})
.catch(() => 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] [ERROR-SILENT] getSharedUserLimitUsage swallows request errors

Evidence: src/lib/dashboard/user-limit-usage-cache.ts:59 uses .catch(() => null).

Why this is a problem: Exceptions (network/server) become indistinguishable from legitimate "no data" results, and the root cause is lost (no logging, no propagation). This makes the UI fall back silently and complicates debugging.

Suggested fix:

const request = getUserAllLimitUsage(userId)
  .then((result) => {
    if (!result.ok || !result.data) {
      return null;
    }

    usageCache.set(userId, { data: result.data, timestamp: Date.now() });
    return result.data;
  })
  .catch((error) => {
    console.error("[user-limit-usage-cache] getUserAllLimitUsage failed", error);
    return null;
  })
  .finally(() => {
    inFlightUsageRequests.delete(userId);
  });

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 is XL and spans proxy core, dashboard UI, settings, and database changes, which makes regression risk high and review/rollback difficult. The most actionable issues are (1) i18n rule violations in new server actions and (2) user-facing failures being rendered as blank/"no data" states due to missing React Query error UI.

PR Size: XL

  • Lines changed: 26378
  • Files changed: 213
  • Split suggestions (recommended):
    1. Proxy pipeline changes (hedge + rectifiers + response/log handling)
    2. Dashboard leaderboard/user-insights UI + supporting actions/repositories
    3. User quota reset/costResetAt + related DB migration
    4. Provider/settings UI + i18n message updates

Issues Found

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

Critical Issues (Must Fix)

  • None

High Priority Issues (Should Fix)

  • [STANDARD-VIOLATION] Hardcoded ActionResult errors bypass i18n: src/actions/admin-user-insights.ts:40 (and other lines noted inline)
  • [ERROR-NO-USER-FEEDBACK] User-insights widgets throw React Query errors but render blank/"no data": src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx:23 (and related components)
  • [ERROR-SILENT] Shared limit-usage cache swallows exceptions: src/lib/dashboard/user-limit-usage-cache.ts:59

Review Coverage

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

Automated review by Codex AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

  • Applied PR label: size/XL
  • Left 3 inline review comments at:
    • src/actions/admin-user-insights.ts:40
    • src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx:23
    • src/lib/dashboard/user-limit-usage-cache.ts:59
  • Submitted PR review summary (comment) with the following body:

Code Review Summary

This PR is XL and spans proxy core, dashboard UI, settings, and database changes, which makes regression risk high and review/rollback difficult. The most actionable issues are (1) i18n rule violations in new server actions and (2) user-facing failures being rendered as blank/"no data" states due to missing React Query error UI.

PR Size: XL

  • Lines changed: 26378
  • Files changed: 213
  • Split suggestions (recommended):
    1. Proxy pipeline changes (hedge + rectifiers + response/log handling)
    2. Dashboard leaderboard/user-insights UI + supporting actions/repositories
    3. User quota reset/costResetAt + related DB migration
    4. Provider/settings UI + i18n message updates

Issues Found

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

Critical Issues (Must Fix)

  • None

High Priority Issues (Should Fix)

  • [STANDARD-VIOLATION] Hardcoded ActionResult errors bypass i18n: src/actions/admin-user-insights.ts:40 (and other lines noted inline)
  • [ERROR-NO-USER-FEEDBACK] User-insights widgets throw React Query errors but render blank/"no data": src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx:23 (and related components)
  • [ERROR-SILENT] Shared limit-usage cache swallows exceptions: src/lib/dashboard/user-limit-usage-cache.ts:59

Review Coverage

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

Automated review by Codex AI

When a hedge winner streams, commitWinner() already logs the provider
to the chain with reason "hedge_winner" and updates session binding.
The deferred finalization was redundantly calling updateSessionBindingSmart,
updateSessionProvider, and addProviderToChain again with reason "retry_success",
causing the same provider to appear twice in dashboard logs.

Add isHedgeWinner flag to DeferredStreamingFinalization so finalization
skips these operations when commitWinner() has already performed them.
@github-actions
Copy link
Contributor

🧪 测试结果

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

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

Comment on lines +2873 to +2878

const launchedProviderIds = new Set<number>();
let launchedProviderCount = 0;
let settled = false;
let winnerCommitted = false;
let noMoreProviders = false;
Copy link

Choose a reason for hiding this comment

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

settleFailure can leave resultPromise permanently unresolved

settleFailure sets settled = true synchronously, then awaits clearSessionProviderBinding. If that Redis call throws (e.g., Redis is unavailable), resolveResult?.({ error }) is never reached, so resultPromise never resolves and the entire hedge call hangs indefinitely.

const settleFailure = async (error: Error) => {
  if (settled) return;
  settled = true;
  await ProxyForwarder.clearSessionProviderBinding(session).catch(() => {});  // swallow to guarantee resolve
  resolveResult?.({ error });
};

The same silent-swallow pattern already used in abortAttempt (attempt.responseController?.abort(...) inside try/catch) should be applied here. Without it, any Redis outage during a hedge failure leaves the client connection open with no response.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/forwarder.ts
Line: 2873-2878

Comment:
**`settleFailure` can leave `resultPromise` permanently unresolved**

`settleFailure` sets `settled = true` synchronously, then `await`s `clearSessionProviderBinding`. If that Redis call throws (e.g., Redis is unavailable), `resolveResult?.({ error })` is never reached, so `resultPromise` never resolves and the entire hedge call hangs indefinitely.

```typescript
const settleFailure = async (error: Error) => {
  if (settled) return;
  settled = true;
  await ProxyForwarder.clearSessionProviderBinding(session).catch(() => {});  // swallow to guarantee resolve
  resolveResult?.({ error });
};
```

The same silent-swallow pattern already used in `abortAttempt` (`attempt.responseController?.abort(...)` inside `try/catch`) should be applied here. Without it, any Redis outage during a hedge failure leaves the client connection open with no response.

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

Comment on lines +270 to +284
logger.info({
action: "log_cleanup_soft_delete_purge",
deletedInBatch: deleted,
totalPurged,
});

if (deleted === batchSize) {
await sleep(BATCH_SLEEP_MS);
}
}

return totalPurged;
}

/**
Copy link

Choose a reason for hiding this comment

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

VACUUM ANALYZE from application code may conflict with autovacuum

Manually calling VACUUM ANALYZE message_request after each cleanup run is typically unnecessary in production PostgreSQL — autovacuum already reclaims dead tuples after bulk deletes. Triggering it from application code has several downsides:

  • It can conflict with a concurrently running autovacuum on the same table, causing one to back off.
  • While VACUUM doesn't hold an exclusive lock during the heap scan, it does briefly lock during the analysis phase, which can affect query planning on a busy table.
  • If Drizzle's connection pool wraps connections in a transaction (or if a middleware does), the call will throw ERROR: VACUUM cannot run inside a transaction block — this is already acknowledged in the comment, but the implicit reliance on db.execute() being outside a transaction is fragile.

Consider removing the runVacuum() call and instead adjusting autovacuum_vacuum_cost_limit / autovacuum_vacuum_scale_factor on the message_request table to make autovacuum respond more aggressively after large deletes. If manual vacuuming is truly needed, a separate out-of-band maintenance job is safer.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/log-cleanup/service.ts
Line: 270-284

Comment:
**`VACUUM ANALYZE` from application code may conflict with autovacuum**

Manually calling `VACUUM ANALYZE message_request` after each cleanup run is typically unnecessary in production PostgreSQL — autovacuum already reclaims dead tuples after bulk deletes. Triggering it from application code has several downsides:

- It can conflict with a concurrently running autovacuum on the same table, causing one to back off.
- While VACUUM doesn't hold an exclusive lock during the heap scan, it does briefly lock during the analysis phase, which can affect query planning on a busy table.
- If Drizzle's connection pool wraps connections in a transaction (or if a middleware does), the call will throw `ERROR: VACUUM cannot run inside a transaction block` — this is already acknowledged in the comment, but the implicit reliance on `db.execute()` being outside a transaction is fragile.

Consider removing the `runVacuum()` call and instead adjusting `autovacuum_vacuum_cost_limit` / `autovacuum_vacuum_scale_factor` on the `message_request` table to make autovacuum respond more aggressively after large deletes. If manual vacuuming is truly needed, a separate out-of-band maintenance job is safer.

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

Comment on lines +3480 to +3505
const exists = mergedProviderChain.some(
(existing) =>
existing.id === item.id &&
existing.timestamp === item.timestamp &&
existing.reason === item.reason &&
existing.attemptNumber === item.attemptNumber
);
if (!exists) {
mergedProviderChain.push(item);
}
}
targetState.providerChain = mergedProviderChain;
targetState.specialSettings = [...sourceState.specialSettings];
targetState.originalModelName = sourceState.originalModelName;
targetState.originalUrlPathname = sourceState.originalUrlPathname;
targetState.clearResponseTimeout = sourceRuntime.clearResponseTimeout;
targetState.responseController = sourceRuntime.responseController;
}

private static async clearSessionProviderBinding(session: ProxySession): Promise<void> {
if (!session.sessionId) return;
await SessionManager.clearSessionProvider(session.sessionId);
}

private static async readFirstReadableChunk(
reader: ReadableStreamDefaultReader<Uint8Array>
Copy link

Choose a reason for hiding this comment

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

Shadow session cloning relies on TypeScript-private fields being JS-enumerable

createStreamingShadowSession creates the shadow via:

const shadow = Object.assign(
  Object.create(Object.getPrototypeOf(session)) as ProxySession,
  session
);

This works because TypeScript's private keyword produces regular, enumerable JS properties — so Object.assign copies them. However, if ProxySession ever introduces a field using the native JS #privateField syntax (truly private, non-enumerable), that field would silently not be copied and the shadow would be in a broken state with no compile-time or runtime warning.

The existing code has private readonly endpointPolicy and private originalModelName which are safe today, but this coupling to the TS-private implementation detail is fragile and worth a note or a factory method on ProxySession itself (ProxySession.createShadow(source, provider)) that can be kept in sync when fields change.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/forwarder.ts
Line: 3480-3505

Comment:
**Shadow session cloning relies on TypeScript-private fields being JS-enumerable**

`createStreamingShadowSession` creates the shadow via:

```typescript
const shadow = Object.assign(
  Object.create(Object.getPrototypeOf(session)) as ProxySession,
  session
);
```

This works because TypeScript's `private` keyword produces regular, enumerable JS properties — so `Object.assign` copies them. However, if `ProxySession` ever introduces a field using the native JS `#privateField` syntax (truly private, non-enumerable), that field would silently not be copied and the shadow would be in a broken state with no compile-time or runtime warning.

The existing code has `private readonly endpointPolicy` and `private originalModelName` which are safe today, but this coupling to the TS-private implementation detail is fragile and worth a note or a factory method on `ProxySession` itself (`ProxySession.createShadow(source, provider)`) that can be kept in sync when fields change.

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

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

Labels

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

Projects

Status: Backlog

2 participants