Skip to content

feat: multi-module improvements batch#646

Merged
ding113 merged 11 commits intodevfrom
feat/multi-improvements-batch
Jan 22, 2026
Merged

feat: multi-module improvements batch#646
ding113 merged 11 commits intodevfrom
feat/multi-improvements-batch

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 22, 2026

Summary

This PR contains multiple improvements across different modules, organized into logical commits:

1. i18n: Availability Dashboard & Provider Network Settings (25 files)

  • Add availability dashboard i18n keys for overview, provider, and endpoint sections
  • Add network section translations for provider form (proxy settings)
  • Update customs and dashboard translations for all 5 languages

2. fix(proxy): SSL Certificate Error Detection & Endpoint Retry Limiting

  • Add isSSLCertificateError() function to detect SSL/TLS errors
  • Limit endpoint candidates to maxRetryAttempts to prevent excessive retries
  • Use Agent Pool for cached HTTP/2 connections to prevent memory leaks

3. fix(proxy): Thinking Signature Rectifier Enhancement

  • Handle "signature: Extra inputs are not permitted" error for third-party APIs
  • Support channels that don't accept signature field in thinking blocks

4. feat(session): Batch Concurrent Count Query

  • Add SessionTracker.getConcurrentCountBatch() for efficient N+1 query avoidance
  • Add concurrentCount field to ActiveSessionInfo type
  • Create session-status.ts with status calculation utilities

5. refactor(proxy): Agent Pool for Connection Management

  • Add AgentPool class for centralized agent lifecycle management
  • Implement LRU-style eviction with configurable TTL and max size
  • Support health tracking for SSL certificate errors

6. feat(dashboard): Availability Dashboard Components

  • Add AvailabilityDashboard with tab navigation
  • Create overview section with GaugeCard for metrics
  • Add provider tab with ConfidenceBadge, LaneChart, LatencyChart
  • Add endpoint tab with LatencyCurve, ProbeGrid, ProbeTerminal
  • Implement shared components: FloatingProbeButton, TimeRangeSelector

7. refactor(dashboard): Live Sessions Panel UX

  • Optimize LiveSessionsPanel component structure
  • Add dynamic item rendering with better state management

8. fix(ui): Chart and Tag-Input Components

  • Fix chart component tooltip styling
  • Enhance tag-input with better keyboard navigation

Test Plan

  • Run bun run build - Production build passes
  • Run bun run lint - Biome check passes
  • Run bun run typecheck - TypeScript check passes
  • Run bun run test - All unit tests pass
  • Verify availability dashboard renders correctly
  • Verify live sessions panel shows correct status
  • Test proxy retry behavior with multiple endpoints

Generated with Claude Code

Greptile Overview

Greptile Summary

This PR implements multiple improvements across proxy management, session tracking, and dashboard components:

Proxy & Connection Management:

  • Agent Pool with proper stats invariants: Resolves the previous stats counter bug where concurrent requests waiting for pending agent creation would break the totalRequests = cacheHits + cacheMisses invariant. The fix correctly increments cacheHits for concurrent waiters instead of decrementing cacheMisses.
  • Endpoint retry limiting: Truncates endpoint candidates to maxRetryAttempts count to prevent excessive retries beyond configured limits. Endpoints are pre-sorted by latency (ascending) before truncation.
  • SSL error detection & handling: Adds isSSLCertificateError() function and marks agents as unhealthy when SSL errors occur, triggering agent replacement on next request.
  • Thinking signature rectifier: Handles "Extra inputs are not permitted" errors from third-party APIs that don't accept the signature field in thinking blocks.

Session Tracking:

  • Adds getConcurrentCountBatch() method to avoid N+1 queries when fetching concurrent counts for multiple sessions
  • Creates session-status.ts utility with getSessionDisplayStatus() for consistent status calculation across components
  • Adds concurrentCount field to ActiveSessionInfo type

Dashboard & UI:

  • New availability dashboard with provider/endpoint tabs, time range selection, and auto-refresh functionality
  • Refactored live sessions panel to use new session status utilities and concurrent count field
  • Enhanced chart component tooltip styling and tag-input keyboard navigation

i18n:

  • Comprehensive translations for availability dashboard and network settings across all 5 languages (en, ja, ru, zh-CN, zh-TW)

Test Coverage:

  • Comprehensive test suites for agent pool (race conditions, stats counters, concurrent requests)
  • Tests for endpoint retry limiting and thinking signature rectifier enhancements
  • Unit tests for session status utilities and dashboard components

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The PR addresses the previously identified stats counter bug correctly, includes comprehensive test coverage (467 lines for agent pool tests alone), follows established patterns, and has already undergone one round of review with the critical bug fixed
  • No files require special attention

Important Files Changed

Filename Overview
src/lib/proxy-agent/agent-pool.ts Implements agent connection pooling with LRU eviction, TTL-based expiration, race condition prevention, and proper stats counter invariants
src/app/v1/_lib/proxy/forwarder.ts Adds endpoint retry limiting, SSL error detection with agent pool health tracking, and HTTP/2 fallback improvements
src/app/v1/_lib/proxy/errors.ts Adds isSSLCertificateError() function to detect SSL/TLS certificate validation errors for agent health tracking
src/lib/session-tracker.ts Adds getConcurrentCountBatch() method for efficient N+1 query avoidance when fetching concurrent counts
src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx Refactored to use concurrentCount field and getSessionDisplayStatus() for improved session status rendering

Sequence Diagram

sequenceDiagram
    participant Client
    participant Forwarder
    participant AgentPool
    participant Provider
    participant EndpointCB as Endpoint Circuit Breaker
    
    Client->>Forwarder: POST /v1/messages
    Forwarder->>Forwarder: getPreferredProviderEndpoints()
    Note over Forwarder: Truncate endpoints to maxRetryAttempts
    
    loop For each endpoint (max N attempts)
        Forwarder->>AgentPool: getAgent(endpoint, proxy, http2)
        alt Agent exists in cache
            AgentPool-->>Forwarder: Return cached agent
        else Agent creation needed
            alt Pending creation exists
                AgentPool->>AgentPool: Wait for pending creation
                Note over AgentPool: Increment cacheHits (reuse pending)
                AgentPool-->>Forwarder: Return shared agent
            else Create new agent
                AgentPool->>AgentPool: Create agent & cache
                Note over AgentPool: Increment cacheMisses
                AgentPool-->>Forwarder: Return new agent
            end
        end
        
        Forwarder->>Provider: Forward request with agent
        
        alt Success
            Provider-->>Forwarder: 200 OK
            Forwarder->>EndpointCB: recordEndpointSuccess()
            Forwarder-->>Client: Return response
        else SSL Error
            Provider-->>Forwarder: SSL Certificate Error
            Forwarder->>AgentPool: markUnhealthy(cacheKey)
            Note over AgentPool: Mark agent unhealthy for eviction
            Forwarder->>EndpointCB: recordEndpointFailure()
            Note over Forwarder: Retry with next endpoint
        else HTTP/2 Error
            Provider-->>Forwarder: HTTP/2 Protocol Error
            Forwarder->>AgentPool: markUnhealthy(cacheKey)
            Forwarder->>Forwarder: Retry with HTTP/1.1 fallback
        else Provider Error
            Provider-->>Forwarder: 4xx/5xx Error
            alt Signature error detected
                Forwarder->>Forwarder: detectThinkingSignatureRectifierTrigger()
                Note over Forwarder: Handle "Extra inputs" error
                Forwarder->>Forwarder: rectifyAnthropicRequestMessage()
                Forwarder->>Provider: Retry without signature field
            else Other error
                Forwarder->>EndpointCB: recordEndpointFailure()
                Note over Forwarder: Try next endpoint
            end
        end
    end
Loading

ding113 and others added 9 commits January 22, 2026 23:01
…k settings

- Add availability dashboard i18n keys for overview, provider, and endpoint sections
- Add network section translations for provider form (proxy settings)
- Update customs and dashboard translations for all 5 languages (en, ja, ru, zh-CN, zh-TW)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…miting

- Add isSSLCertificateError() function to detect SSL/TLS errors (hostname mismatch, expired certs, self-signed)
- Limit endpoint candidates to maxRetryAttempts to prevent excessive retries
- Use Agent Pool for cached HTTP/2 connections to prevent memory leaks
- Track directConnectionCacheKey for SSL error handling
- Add unit tests for SSL error detection and retry limit logic

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…rectifier

- Add detection for "signature: Extra inputs are not permitted" error
- Support third-party API channels that don't accept signature field
- Reuse existing rectifier logic for consistency
- Add unit tests for extra signature field detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ilities

- Add SessionTracker.getConcurrentCountBatch() for efficient N+1 query avoidance
- Add concurrentCount field to ActiveSessionInfo type
- Create session-status.ts with status calculation utilities
- Update active-sessions action to support batch queries
- Add unit tests for session status calculation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add AgentPool class for centralized agent lifecycle management
- Implement LRU-style eviction with configurable TTL and max size
- Support health tracking for detecting SSL certificate errors
- Add cache key generation based on endpoint + proxy + http2 config
- Re-export pool utilities from proxy-agent.ts
- Add comprehensive unit tests for agent pool behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…mponents

- Add AvailabilityDashboard main component with tab navigation
- Create overview section with GaugeCard for metrics visualization
- Add provider tab with ConfidenceBadge, LaneChart, LatencyChart
- Add endpoint tab with LatencyCurve, ProbeGrid, ProbeTerminal
- Implement shared components: FloatingProbeButton, TimeRangeSelector
- Improve AvailabilitySkeleton with realistic loading states
- Add comprehensive unit tests for all new components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Optimize LiveSessionsPanel component structure
- Add dynamic item rendering with better state management
- Update DashboardBento integration
- Add unit tests for dynamic item behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix chart component tooltip styling
- Enhance tag-input with better keyboard navigation
- Update network-section form styling consistency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

本PR新增/重构了可用性仪表板与探测组件、引入代理连接池与SSL错误检测、将会话并发计数纳入显示逻辑,并扩展各语言本地化与大量单元测试以覆盖新功能与核心库变更。(50词内)

Changes

内聚 / 文件(s) 变更概要
本地化 - 会话状态(多语言)
messages/en/customs.json, messages/ja/customs.json, messages/ru/customs.json, messages/zh-CN/customs.json, messages/zh-TW/customs.json
将 activeSessions.status 的状态键从具体枚举(running、init、idle、error、done)替换为 tooltip 类型键(inProgressTooltip、initializingTooltip、idleTooltip、errorTooltip)
本地化 - 仪表板/可用性(多语言)
messages/*/dashboard.json (en, ja, ru, zh-CN, zh-TW)
新增 availability 下全面本地化条目:tabs、overview、timeRange、laneChart、latencyChart/curve、terminal、probeGrid、endpoint、confidence、actions 等
本地化 - 设置导入与成功文案(多语言)
messages/*/settings/index.ts, messages/*/settings/providers/form/success.json
在 providers 导出中新增 batchEdit 引入;在 provider success 文案中新增 updatedupdatedDesc 文本
本地化 - 超时说明更新(多语言)
messages/*/settings/providers/form/sections.json
超时说明与 placeholder 改为“填 0 禁用(默认不限制)”,并将 streamingFirstByte 最大值从 120s 扩展至 180s
会话并发与状态计算
src/actions/active-sessions.ts, src/lib/session-tracker.ts, src/types/session.ts, src/lib/session-status.ts
新增 SessionTracker.getConcurrentCountBatch 批量并发计数查询;ActiveSessionInfo 增加 concurrentCount;根据并发数引入 getSessionDisplayStatus 以决定展示状态与 tooltipKey
Live Sessions 面板适配
src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx, src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx
用 getSessionDisplayStatus 替换内部状态逻辑,移除 maxItems 传参,更新 sessions 类型签名
可用性仪表板 - 核心与路由替换
src/app/[locale]/dashboard/availability/page.tsx, src/app/[locale]/dashboard/availability/_components/availability-dashboard.tsx, .../availability-skeleton.tsx
新增 AvailabilityDashboard 及重做的 Skeleton;页面采用新仪表板组件并调整 Suspense fallback
可用性仪表板 - 提供商视图组件
src/app/[locale]/dashboard/availability/_components/provider/*
(lane-chart.tsx, latency-chart.tsx, provider-tab.tsx, confidence-badge.tsx)
新增 LaneChart、LatencyChart、ProviderTab、ConfidenceBadge 等数据可视化与交互组件
可用性仪表板 - 端点/探测组件
src/app/[locale]/dashboard/availability/_components/endpoint/*
(endpoint-tab.tsx, probe-grid.tsx, probe-terminal.tsx, latency-curve.tsx)
新增 EndpointTab、ProbeGrid、ProbeTerminal、LatencyCurve,支持端点选择、探测、日志、曲线图与自动刷新
可用性仪表板 - 共享UI组件
src/app/[locale]/dashboard/availability/_components/overview/*, .../shared/*
(overview-section.tsx, gauge-card.tsx, time-range-selector.tsx, floating-probe-button.tsx)
新增 OverviewSection、GaugeCard、TimeRangeSelector、FloatingProbeButton 等复用组件
代理连接池与相关 API
src/lib/proxy-agent/agent-pool.ts, src/lib/proxy-agent.ts
新增 AgentPool 实现(缓存、TTL、LRU、健康标记、全局 singleton);新增 getProxyAgentForProvider 返回含 cacheKey 的 ProxyConfigWithCacheKey 并导出相关类型与工具
转发与 SSL 错误检测
src/app/v1/_lib/proxy/forwarder.ts, src/app/v1/_lib/proxy/errors.ts
forwarder 改为使用代理/全局池(getProxyAgentForProvider、getGlobalAgentPool);新增 isSSLCertificateError,失败时标记 agent 不健康并记录诊断字段(endpointCount/selectedEndpoints 等)
思考签名整流增强
src/app/v1/_lib/proxy/thinking-signature-rectifier.ts, src/app/v1/_lib/proxy/thinking-signature-rectifier.test.ts
新增对包含 signature 且返回 “extra inputs are not permitted” 类错误的检测,并在测试中覆盖该路径
其他 UI 与交互改进
src/components/ui/chart.tsx, src/components/ui/tag-input.tsx, src/app/[locale]/settings/providers/_components/forms/provider-form/sections/network-section.tsx
Recharts 初始尺寸调整;Tag input 建议列表改为 portal 渲染并处理定位/外部点击;表单中非流式超时最小值允许为 0
测试覆盖大幅增加
tests/unit/...
新增并扩展大量单元测试:AvailabilityDashboard 计算、GaugeCard、ConfidenceBadge、ProbeTerminal、LiveSessionsPanel 动态项、AgentPool 与 getProxyAgentForProvider、ProxyForwarder 重试/签名整流、SSL 错误检测等

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题「feat: multi-module improvements batch」清晰准确地概括了本次变更的主要内容,涵盖了跨多个模块的改进(i18n、代理、会话、仪表板等),与提供的详细变更摘要相符。
Description check ✅ Passed PR 描述涵盖了变更集的多个方面,包括 i18n 更新、代理基础设施改进、会话跟踪、仪表板 UI 组件和 UI 修复,与文件变更内容相关。

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

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

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 github-actions bot added enhancement New feature or request area:UI area:i18n area:provider area:session size/XL Extra Large PR (> 1000 lines) labels Jan 22, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

… path

Remove erroneous cacheMisses-- in pending wait branch. The waiter request
never incremented cacheMisses (it exits early at pending check), so decrementing
would subtract from a different request's miss count, violating the invariant:
totalRequests === cacheHits + cacheMisses

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

🤖 Fix all issues with AI agents
In `@messages/ru/dashboard.json`:
- Around line 936-942: The JSON contains duplicate top-level "timeRange" objects
so the later "timeRange" overwrites the earlier one and keys like "15min", "1h",
"6h", "24h", "7d" become inaccessible; merge both sets of translations into a
single "timeRange" object (or rename one of them) so all keys (e.g.,
"timeRange", "15min", "1h", "6h", "24h", "7d") coexist, ensuring the new
component can read the intended strings; update any references to the renamed
object if you choose renaming instead of merging.

In `@messages/zh-TW/dashboard.json`:
- Around line 934-998: The JSON contains duplicated nested keys under
"availability" (e.g., "timeRange", "laneChart", "latencyChart", "latencyCurve",
"terminal", "probeGrid", "endpoint", "confidence", "actions") which will cause
overwrites or parse errors; locate the multiple "availability" blocks in
messages/zh-TW/dashboard.json and merge their entries into a single
"availability" object keeping the most complete/accurate strings, remove the
duplicate block(s), and ensure no duplicate child keys remain (verify entries
like "timeRange", "laneChart.requests", "latencyChart.p95", "terminal.download",
"confidence.highTooltip", "actions.probeNow" are preserved).

In
`@src/app/`[locale]/dashboard/availability/_components/endpoint/endpoint-tab.tsx:
- Around line 63-78: When getProviderVendors() fails inside the useEffect, we
only console.error and never surface the failure to the user; add an error state
(e.g., vendorError with setter setVendorError) and set it in the catch block of
fetchVendors, then ensure the component rendering for the vendors tab shows a
friendly error message or triggers a toast when vendorError is set; update the
fetchVendors catch to call setVendorError(error or a user-friendly string) and
keep setLoadingVendors(false) in finally so the UI can show the error UI instead
of an empty list (refer to functions/useEffect: getProviderVendors,
fetchVendors, setVendors, setSelectedVendorId, setLoadingVendors).

In
`@src/app/`[locale]/dashboard/availability/_components/endpoint/latency-curve.tsx:
- Around line 20-25: chartConfig currently hardcodes user-facing strings
(latency label and status text "OK"/"FAIL"); change chartConfig.latency.label to
use the i18n translator t(...) and update anywhere the tooltip/legend displays
status text (e.g. code that renders "OK"/"FAIL") to call t('...') instead; add
corresponding keys to the locale bundles for each language (e.g. chart.latency,
chart.status.ok, chart.status.fail) so translations exist; update references to
ChartConfig.satisfies if needed to keep types intact (ensure imports of the i18n
t function are added where chartConfig is defined).

In `@src/app/`[locale]/dashboard/availability/_components/endpoint/probe-grid.tsx:
- Around line 44-57: The functions formatLatency and formatTime currently use
hard-coded user-facing strings ("-", "ms", "s"); update them to use the app i18n
system (e.g., a translate function or react-intl) and locale-aware number
formatting: replace the literal "-" placeholder with a translated empty value,
format millisecond values with Intl.NumberFormat for the current locale, and
append localized unit strings for milliseconds/seconds (or return a localized
combined string), and ensure formatTime uses the same i18n placeholder when date
is null while still relying on toLocaleTimeString for time formatting; locate
and change formatLatency and formatTime in probe-grid.tsx to call the
translation utilities and Intl.NumberFormat instead of hard-coded literals.
- Around line 101-105: The code currently calls new URL(endpoint.url).hostname
directly in the JSX (endpoint.label || new URL(endpoint.url).hostname) which can
throw and break rendering; wrap that logic in a safe helper (e.g.,
getHostnameFromUrl or try/catch inline) to return endpoint.url or an empty
string on error and use endpoint.label first; also update formatLatency() and
formatTime() to remove hard-coded visible strings ("-", "ms", "s") and instead
return localized strings via the app's translation function (e.g.,
useTranslations or t) so UI text is i18n-safe—replace "-" with
t('common.notAvailable') and unit suffixes with
t('units.millisecond')/t('units.second') or similar keys and ensure
probe-grid.tsx imports and uses the translation function where formatLatency and
formatTime are defined.

In
`@src/app/`[locale]/dashboard/availability/_components/endpoint/probe-terminal.tsx:
- Around line 39-61: The levelConfig object in probe-terminal.tsx currently uses
hardcoded English labels (levelConfig.label) and the download filename prefix
("probe-logs-"); change label to be a key (e.g.,
"status.ok"/"status.fail"/"status.warn") and update all places that render
levelConfig.label and build the download filename to call the next-intl
translator t(...) instead of using raw strings, and add the corresponding
translation keys for all five locales; update the download logic that produces
"probe-logs-" to use t('probeLogs.filenamePrefix') so localized filenames are
produced and ensure the rendering of icons/status uses t(levelConfig.label)
rather than the raw label.

In `@src/app/`[locale]/dashboard/availability/_components/overview/gauge-card.tsx:
- Around line 32-47: The invertColors branch in getGaugeColor incorrectly checks
<= critical before <= warning, causing the amber state to be skipped when
warning < critical; update getGaugeColor (invertColors branch) to first compare
value <= thresholds.warning returning "text-emerald-500", then check value <=
thresholds.critical returning "text-amber-500", and finally return
"text-rose-500" so the warning/critical ordering is correct for metrics where
lower is better.

In `@src/app/`[locale]/dashboard/availability/_components/provider/lane-chart.tsx:
- Around line 30-39: getStatusColor currently only handles "green" and "red" so
a "yellow" currentStatus falls into the default branch; update getStatusColor to
handle the "yellow" status and return the matching color class (use the same
orange styling as getAvailabilityColor, e.g., the bg-orange-500 equivalent for
text such as "text-orange-500") so that "green" | "yellow" | "red" | "unknown"
map to appropriate classes and "unknown" can keep the existing default
("text-slate-400").
- Around line 163-175: The provider info panel's status dot only handles
"green", "red", and "unknown", so update the conditional classes where
provider.currentStatus is used (the span with className using cn in
lane-chart.tsx) to include the "yellow" case (e.g., map "yellow" to a
yellow/amber background such as bg-amber-500) and ensure this matches the color
mapping used by getStatusColor so the UI and helper remain consistent.

In
`@src/app/`[locale]/dashboard/availability/_components/provider/provider-tab.tsx:
- Around line 34-239: Add unit and integration tests for the ProviderTab
component to bring coverage ≥80%: write tests for ProviderTab that (1) assert
sortedProviders ordering when selecting sort options via the Select (test
"availability", "name", "requests" by interacting with setSortBy/Select and
verifying rendered order), (2) render and verify loading, error, and refreshing
UI states including the Retry/Refresh button invoking onRefresh, (3) simulate
TimeRangeSelector changes and ensure onTimeRangeChange is called, and (4) mount
ProviderTab with stubbed/mocked children LaneChart and LatencyChart to verify
they receive correct props (providers, bucketSizeMinutes, startTime, endTime)
and integrate with the parent rendering; target edge cases like unknown
currentStatus in availability sorting and empty data?. Use your testing toolkit
(React Testing Library + Jest) to mock translations and pass test data to
ProviderTab, and include snapshot/assertion checks for key UI pieces to ensure
stable coverage.

In
`@src/app/`[locale]/dashboard/availability/_components/shared/floating-probe-button.tsx:
- Around line 38-52: The floating probe button currently renders a plain
<button> in FloatingProbeButton (file floating-probe-button.tsx) which can
trigger form submission when nested inside a form; update the button element
used in the component to include an explicit type="button" attribute (leave
existing props like onClick={handleProbeAll}, disabled={isProbing},
className={cn(...)} unchanged) so it will not act as a submit button in any
surrounding form context.

In
`@src/app/`[locale]/dashboard/availability/_components/shared/time-range-selector.tsx:
- Around line 20-29: The buttons rendered in TIME_RANGE_OPTIONS currently lack
an explicit type which can cause unintended form submissions; update the button
element used in the map inside time-range-selector.tsx to include type="button"
so clicks call onChange(option) without submitting a surrounding form (look for
the JSX button using key={option}, onClick={() => onChange(option)}, value and
className).

In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/sections/network-section.tsx:
- Line 249: The input currently allows a minimum of 0 via min="0" in the Network
section (see the input element in network-section.tsx) but the localized help
text still reads "范围 60-1200 秒" / "範囲60~1200秒"; update the localized strings
used by this form section to reflect "范围 0-1200 秒,填 0 禁用" (or equivalent in each
locale) so the description matches the actual allowed range and explicitly
mentions that 0 disables the timeout; locate and modify the localized keys
referenced by the NetworkSection component (or the label/description props used
by that input) to change the copy for zh-CN, ja, and other affected locales.

In `@src/components/ui/tag-input.tsx`:
- Around line 103-113: The dropdown position calculation in the React.useEffect
is adding window.scrollY/window.scrollX to values from
containerRef.current.getBoundingClientRect(), which double-applies scroll
offsets when the dropdown uses position: fixed; update the effect used by
showSuggestions to setDropdownPosition using rect.bottom, rect.left, and
rect.width directly (remove addition of window.scrollY/window.scrollX) so
coordinates remain viewport-relative; ensure you update the effect that
references containerRef, setDropdownPosition, and showSuggestions accordingly.
- Around line 119-127: updatePosition currently adds window.scrollY/scrollX when
computing top/left which causes inconsistent positioning vs the initial
calculation; update the function so that when containerRef.current exists you
call getBoundingClientRect() and setDropdownPosition using rect.bottom + 4 for
top and rect.left for left (no window.scrollY/scrollX) and keep rect.width for
width so positioning matches the initial calculation; refer to updatePosition,
containerRef, and setDropdownPosition to locate the change.

In `@src/lib/proxy-agent/agent-pool.ts`:
- Around line 183-191: The pending-hit branch in agent-pool.ts incorrectly
decrements this.stats.cacheMisses when returning a pending creation; remove the
decrement so awaiting a pending result only increments this.stats.cacheHits (or
alternatively add a separate counter for pendingHits if you want to track them)
— modify the block handling this.pendingCreations.get(cacheKey) where it awaits
pending and returns { ...result, isNew: false } to stop mutating
stats.cacheMisses.
- Around line 113-118: The generateAgentCacheKey function currently includes
params.proxyUrl in plaintext which can leak credentials; update
generateAgentCacheKey to sanitize the proxy portion by either parsing
params.proxyUrl with URL and removing username/password before using it, or
replace the proxyUrl with a fixed-length hash (e.g., SHA-256) of the full
proxyUrl to preserve uniqueness; ensure the returned key still uses origin and
protocol and references generateAgentCacheKey so subsequent callers (e.g., any
markUnhealthy logging of the cacheKey) will never contain raw credentials.

In `@src/lib/session-status.ts`:
- Around line 19-92: The returned labels in getSessionDisplayStatus are
hard-coded English; change SessionStatusInfo to replace the label:string field
with labelKey:string (e.g., labelKey values like "status.errorLabel",
"status.initializingLabel", "status.inProgressLabel", "status.idleLabel") and
update getSessionDisplayStatus to return labelKey instead of label/ERROR for the
error and each branch (use SESSION_DISPLAY_STATUS constants only for the status
field). Also update any callers/UI to render visible text via i18n (e.g.,
t(labelKey)) and add the 5 required localized strings into the translations
resource.

In `@tests/unit/dashboard/live-sessions-panel-dynamic-items.test.tsx`:
- Around line 29-42: The createMockSession helper is missing required
ActiveSessionInfo fields causing type errors; update the createMockSession
function to return all mandatory properties from ActiveSessionInfo (e.g., add
userId, keyId, providerId, apiType) with appropriate mock values/types, and
ensure the return type still matches ActiveSessionInfo & { lastActivityAt?:
number } so tests compile; locate and modify createMockSession to include those
fields.
🧹 Nitpick comments (13)
src/app/[locale]/dashboard/availability/_components/provider/confidence-badge.tsx (1)

19-26: 阈值逻辑清晰,但 low 阈值未被使用

当前的 getConfidenceLevel 函数中,thresholds.low 参数在判断逻辑中未被使用。函数仅检查 count >= highcount >= medium,其他情况默认返回 "low"。这种实现是正确的,但 low 阈值的存在可能会让其他开发者误以为它参与了判断逻辑。

可以考虑从接口中移除 low 阈值,或者添加注释说明其用途(如文档目的)。

可选:简化阈值接口
 interface ConfidenceBadgeProps {
   requestCount: number;
   thresholds?: {
-    low: number;
     medium: number;
     high: number;
   };
   className?: string;
 }

 // ...

 function getConfidenceLevel(
   count: number,
-  thresholds: { low: number; medium: number; high: number }
+  thresholds: { medium: number; high: number }
 ): ConfidenceLevel {
   if (count >= thresholds.high) return "high";
   if (count >= thresholds.medium) return "medium";
   return "low";
 }

 // ...

 export function ConfidenceBadge({
   requestCount,
-  thresholds = { low: 10, medium: 50, high: 200 },
+  thresholds = { medium: 50, high: 200 },
   className,
 }: ConfidenceBadgeProps) {
src/app/[locale]/dashboard/availability/_components/provider/latency-chart.tsx (1)

86-92: 时间格式化未使用应用的语言环境

formatTime 函数使用 undefined 作为 toLocaleTimeString 的 locale 参数,这将使用浏览器的默认语言环境,而不是应用当前选择的语言环境。这可能导致时间格式与应用的其他本地化内容不一致。

建议:使用 next-intl 的 locale
+"use client";
+
+import { useLocale, useTranslations } from "next-intl";
 import { useMemo } from "react";
 // ...

 export function LatencyChart({ providers, className }: LatencyChartProps) {
   const t = useTranslations("dashboard.availability.latencyChart");
+  const locale = useLocale();

   // ...

   const formatTime = (time: string) => {
     const date = new Date(time);
-    return date.toLocaleTimeString(undefined, {
+    return date.toLocaleTimeString(locale, {
       hour: "2-digit",
       minute: "2-digit",
     });
   };
src/app/[locale]/dashboard/availability/_components/shared/time-range-selector.tsx (1)

3-6: 请按项目规范使用 @/ 路径别名引用 src 目录文件。
当前相对路径会偏离仓库约定,建议改为别名引用。根据编码规范。

修改建议
-import type { TimeRangeOption } from "../availability-dashboard";
+import type { TimeRangeOption } from "@/app/[locale]/dashboard/availability/_components/availability-dashboard";
tests/unit/proxy/proxy-forwarder-retry-limit.test.ts (1)

506-510: 避免将 null 强制断言为 number,保持 providerVendorId 的真实类型。
如果 Provider.providerVendorId 为 number | null,可直接赋值 null;当前断言会掩盖类型问题,请确认类型定义。

修改建议
-        providerVendorId: null as unknown as number,
+        providerVendorId: null,
src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx (1)

36-111: 避免用 label 字符串驱动错误态逻辑。

当前依赖 label === "ERROR" 决定图标/颜色,后续 label 调整会让逻辑失效。建议用 session.status 或显式 isError 标志驱动 UI。

建议修改
 function SessionItem({ session }: { session: ActiveSessionInfo }) {
   const router = useRouter();
   const t = useTranslations("customs.activeSessions");
+  const isError = session.status === "error";
   const statusInfo = getSessionDisplayStatus({
     concurrentCount: session.concurrentCount,
     requestCount: session.requestCount,
     status: session.status,
   });

   // Determine ping animation color based on status
-  const getPingColor = (info: SessionStatusInfo) => {
+  const getPingColor = (info: SessionStatusInfo, error: boolean) => {
     if (info.status === SESSION_DISPLAY_STATUS.IN_PROGRESS) {
-      return info.label === "ERROR" ? "bg-rose-500" : "bg-emerald-500";
+      return error ? "bg-rose-500" : "bg-emerald-500";
     }
     if (info.status === SESSION_DISPLAY_STATUS.INITIALIZING) {
       return "bg-amber-500";
     }
     return "";
   };

   // Determine user name color based on status
-  const getUserNameColor = (info: SessionStatusInfo) => {
+  const getUserNameColor = (info: SessionStatusInfo, error: boolean) => {
     if (info.status === SESSION_DISPLAY_STATUS.IN_PROGRESS) {
-      return info.label === "ERROR"
+      return error
         ? "text-rose-500 dark:text-rose-400"
         : "text-blue-500 dark:text-blue-400";
     }
     if (info.status === SESSION_DISPLAY_STATUS.INITIALIZING) {
       return "text-amber-600 dark:text-amber-300";
     }
     return "text-muted-foreground";
   };
@@
         {statusInfo.pulse && (
           <span
             className={cn(
               "absolute inset-0 rounded-full animate-ping opacity-75",
-              getPingColor(statusInfo)
+              getPingColor(statusInfo, isError)
             )}
             style={{ animationDuration: "1.5s" }}
           />
         )}
-        {statusInfo.label === "ERROR" ? (
+        {isError ? (
           <XCircle className={cn("h-2.5 w-2.5 relative", statusInfo.color)} fill="currentColor" />
         ) : (
           <Circle className={cn("h-2.5 w-2.5 relative", statusInfo.color)} fill="currentColor" />
         )}
@@
-      <span className={cn("text-xs font-medium truncate", getUserNameColor(statusInfo))}>
+      <span className={cn("text-xs font-medium truncate", getUserNameColor(statusInfo, isError))}>
         {userName}
       </span>
tests/unit/dashboard/availability/confidence-badge.test.tsx (1)

13-20: 测试逻辑与生产实现重复,容易漂移

当前用本地函数与配置对象断言结果,无法覆盖组件实际逻辑/样式映射,后续改动可能导致测试仍通过。建议从组件/共享模块导出 getConfidenceLevel 与配置,或直接渲染组件进行断言。

Also applies to: 84-103

src/app/v1/_lib/proxy/errors.ts (1)

873-950: 避免使用过宽的 SSL/TLS 关键词导致误判。

当前模式包含 ssl/tls,可能把纯协议或握手错误误判为证书问题,进而错误标记代理为不健康。建议仅保留证书相关模式(或将 ssl/tls 与证书关键字组合判断)以降低误判率。

建议调整
-const SSL_ERROR_PATTERNS = [
-  "certificate",
-  "ssl",
-  "tls",
-  "cert_",
+const SSL_ERROR_PATTERNS = [
+  "certificate",
+  "cert_",
   "unable to verify",
   "self signed",
   "hostname mismatch",
   "unable_to_get_issuer_cert",
   "cert_has_expired",
   "depth_zero_self_signed_cert",
   "unable_to_verify_leaf_signature",
   "err_tls_cert_altname_invalid",
   "cert_untrusted",
   "altnames",
 ];
tests/unit/dashboard/availability/availability-dashboard.test.tsx (3)

9-27: 测试中存在与生产代码的逻辑重复。

TIME_RANGE_MAPTARGET_BUCKETScalculateBucketSize 直接复制了 availability-dashboard.tsx 中的实现。这种做法:

  • 优点:测试隔离性好,不依赖生产模块导出
  • 风险:生产代码修改时测试可能不会同步更新,导致测试与实际行为脱节

建议从生产模块导出这些常量和工具函数,测试直接导入使用,以确保测试验证的是实际代码行为。

建议的重构方式
// 在 availability-dashboard.tsx 中导出
export const TIME_RANGE_MAP: Record<TimeRangeOption, number> = { ... };
export const TARGET_BUCKETS = 60;
export function calculateBucketSize(timeRangeMs: number): number { ... }

// 在测试中导入
import { TIME_RANGE_MAP, TARGET_BUCKETS, calculateBucketSize } from "@/app/[locale]/dashboard/availability/_components/availability-dashboard";

140-184: 测试中定义的 Provider 接口与生产类型存在差异。

测试中的 Provider 接口只包含 timeBucketstotalRequestscurrentStatus 三个字段,而实际的 ProviderAvailabilitySummary 类型(见 src/lib/availability/types.ts)包含更多字段如 providerIdproviderNameavgLatencyMs 等。

当前测试的计算逻辑 (calculateAvgLatency, calculateErrorRate) 也是本地定义的,未测试实际生产代码。建议将这些计算函数提取到独立模块并直接测试。


314-325: 自动刷新间隔测试价值有限。

这段测试仅验证了一个简单的三元表达式逻辑,并未测试实际的 setInterval 行为或组件的自动刷新机制。

如果目标是验证刷新间隔配置,建议直接导出常量;如果要测试自动刷新行为,应使用 vi.useFakeTimers() 模拟定时器并验证 fetchData 的调用频率。

src/app/[locale]/dashboard/availability/_components/endpoint/endpoint-tab.tsx (1)

36-43: PROVIDER_TYPES 硬编码可能导致与后端不同步。

提供商类型列表直接硬编码在组件中。如果后端支持的类型发生变化,需要手动更新此列表。

考虑从共享常量文件或 API 获取支持的类型列表,以保持前后端一致性。

src/app/[locale]/dashboard/availability/_components/availability-dashboard.tsx (2)

84-113: Overview 指标计算逻辑复杂,建议提取为独立函数。

avgLatencyerrorRate 的计算逻辑直接内联在组件中,包含多层嵌套的 reduce 和 filter 操作。这降低了代码可读性,且难以单独测试。

建议将这些计算逻辑提取到独立的工具函数中,便于单元测试和复用。这也与测试文件中定义的 calculateAvgLatencycalculateErrorRate 函数逻辑对应。

建议的重构
// 提取到 utils 或 hooks 文件
export function calculateAvgLatency(providers: ProviderAvailabilitySummary[]): number {
  if (providers.length === 0) return 0;
  
  const providersWithLatency = providers.filter((p) =>
    p.timeBuckets.some((b) => b.avgLatencyMs > 0)
  );
  
  if (providersWithLatency.length === 0) return 0;
  
  const totalLatency = providersWithLatency.reduce((sum, p) => {
    const latencies = p.timeBuckets
      .filter((b) => b.avgLatencyMs > 0)
      .map((b) => b.avgLatencyMs);
    if (latencies.length === 0) return sum;
    return sum + latencies.reduce((a, b) => a + b, 0) / latencies.length;
  }, 0);
  
  return totalLatency / providersWithLatency.length;
}

// 组件中使用
const overviewMetrics = {
  systemAvailability: data?.systemAvailability ?? 0,
  avgLatency: calculateAvgLatency(providers),
  errorRate: calculateErrorRate(providers),
  // ...
};

41-71: fetchData 缺少请求取消机制。

当组件卸载或 timeRange 快速变化时,之前的 fetch 请求可能仍在进行中。这可能导致:

  1. 组件卸载后尝试更新状态
  2. 旧请求的响应覆盖新请求的结果

建议使用 AbortController 取消未完成的请求。

建议的改进
  const fetchData = useCallback(async () => {
+   const controller = new AbortController();
    try {
      setRefreshing(true);
      // ...
-     const res = await fetch(`/api/availability?${params}`);
+     const res = await fetch(`/api/availability?${params}`, {
+       signal: controller.signal,
+     });
      // ...
    } catch (err) {
+     if (err instanceof Error && err.name === "AbortError") return;
      console.error("Failed to fetch availability data:", err);
      // ...
    }
+   return () => controller.abort();
  }, [timeRange, t]);

Comment on lines +63 to +78
useEffect(() => {
const fetchVendors = async () => {
try {
const vendors = await getProviderVendors();
setVendors(vendors);
if (vendors.length > 0) {
setSelectedVendorId(vendors[0].id);
}
} catch (error) {
console.error("Failed to fetch vendors:", error);
} finally {
setLoadingVendors(false);
}
};
fetchVendors();
}, []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

供应商加载失败时缺少用户反馈。

getProviderVendors() 失败时,错误仅记录到控制台,但用户界面未显示任何错误提示。用户可能会看到空的供应商列表而不知道发生了什么。

建议添加错误状态并向用户显示友好的错误消息,或使用 toast 通知。

建议的改进
+ const [vendorError, setVendorError] = useState<string | null>(null);

  useEffect(() => {
    const fetchVendors = async () => {
      try {
        const vendors = await getProviderVendors();
        setVendors(vendors);
        if (vendors.length > 0) {
          setSelectedVendorId(vendors[0].id);
        }
+       setVendorError(null);
      } catch (error) {
        console.error("Failed to fetch vendors:", error);
+       setVendorError(t("errors.vendorLoadFailed"));
      } finally {
        setLoadingVendors(false);
      }
    };
    fetchVendors();
  }, []);
📝 Committable suggestion

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

Suggested change
useEffect(() => {
const fetchVendors = async () => {
try {
const vendors = await getProviderVendors();
setVendors(vendors);
if (vendors.length > 0) {
setSelectedVendorId(vendors[0].id);
}
} catch (error) {
console.error("Failed to fetch vendors:", error);
} finally {
setLoadingVendors(false);
}
};
fetchVendors();
}, []);
useEffect(() => {
const fetchVendors = async () => {
try {
const vendors = await getProviderVendors();
setVendors(vendors);
if (vendors.length > 0) {
setSelectedVendorId(vendors[0].id);
}
setVendorError(null);
} catch (error) {
console.error("Failed to fetch vendors:", error);
setVendorError(t("errors.vendorLoadFailed"));
} finally {
setLoadingVendors(false);
}
};
fetchVendors();
}, []);
🤖 Prompt for AI Agents
In
`@src/app/`[locale]/dashboard/availability/_components/endpoint/endpoint-tab.tsx
around lines 63 - 78, When getProviderVendors() fails inside the useEffect, we
only console.error and never surface the failure to the user; add an error state
(e.g., vendorError with setter setVendorError) and set it in the catch block of
fetchVendors, then ensure the component rendering for the vendors tab shows a
friendly error message or triggers a toast when vendorError is set; update the
fetchVendors catch to call setVendorError(error or a user-friendly string) and
keep setLoadingVendors(false) in finally so the UI can show the error UI instead
of an empty list (refer to functions/useEffect: getProviderVendors,
fetchVendors, setVendors, setSelectedVendorId, setLoadingVendors).

Comment on lines +20 to +25
const chartConfig = {
latency: {
label: "Latency",
color: "hsl(var(--primary))",
},
} satisfies ChartConfig;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

用户可见文本需接入 i18n(Latency/OK/FAIL)。
目前图例与 tooltip 的文案为硬编码,按项目规则应统一走多语言。建议将 label 与状态文案改为 t(...),并在各语言包中补充对应 key。根据编码规范。

建议修改
-const chartConfig = {
-  latency: {
-    label: "Latency",
-    color: "hsl(var(--primary))",
-  },
-} satisfies ChartConfig;
-
-export function LatencyCurve({ logs, className }: LatencyCurveProps) {
-  const t = useTranslations("dashboard.availability.latencyCurve");
+export function LatencyCurve({ logs, className }: LatencyCurveProps) {
+  const t = useTranslations("dashboard.availability.latencyCurve");
+  const chartConfig = {
+    latency: {
+      label: t("latency"),
+      color: "hsl(var(--primary))",
+    },
+  } satisfies ChartConfig;
@@
-                    {data?.statusCode || (data?.ok ? "OK" : "FAIL")}
+                    {data?.statusCode ?? (data?.ok ? t("statusOk") : t("statusFail"))}

Also applies to: 127-133

🤖 Prompt for AI Agents
In
`@src/app/`[locale]/dashboard/availability/_components/endpoint/latency-curve.tsx
around lines 20 - 25, chartConfig currently hardcodes user-facing strings
(latency label and status text "OK"/"FAIL"); change chartConfig.latency.label to
use the i18n translator t(...) and update anywhere the tooltip/legend displays
status text (e.g. code that renders "OK"/"FAIL") to call t('...') instead; add
corresponding keys to the locale bundles for each language (e.g. chart.latency,
chart.status.ok, chart.status.fail) so translations exist; update references to
ChartConfig.satisfies if needed to keep types intact (ensure imports of the i18n
t function are added where chartConfig is defined).

Comment on lines +44 to +57
function formatLatency(ms: number | null): string {
if (ms === null) return "-";
if (ms < 1000) return `${Math.round(ms)}ms`;
return `${(ms / 1000).toFixed(2)}s`;
}

function formatTime(date: Date | string | null): string {
if (!date) return "-";
const d = typeof date === "string" ? new Date(date) : date;
return d.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

延迟与空值占位符需本地化。

"-", "ms", "s" 是硬编码展示文本,建议通过 i18n 或 Intl.NumberFormat 处理,并在 5 语言中补齐文案/单位。

建议修改
 export function ProbeGrid({
   endpoints,
   selectedEndpointId,
   onEndpointSelect,
   className,
 }: ProbeGridProps) {
   const t = useTranslations("dashboard.availability.probeGrid");
+
+  const formatLatency = (ms: number | null): string => {
+    if (ms === null) return t("notAvailable");
+    return ms < 1000
+      ? t("latencyMs", { value: Math.round(ms) })
+      : t("latencySeconds", { value: (ms / 1000).toFixed(2) });
+  };
+
+  const formatTime = (date: Date | string | null): string => {
+    if (!date) return t("notAvailable");
+    const d = typeof date === "string" ? new Date(date) : date;
+    return d.toLocaleTimeString(undefined, {
+      hour: "2-digit",
+      minute: "2-digit",
+      second: "2-digit",
+    });
+  };

As per coding guidelines, 用户可见文本必须走 i18n。

🤖 Prompt for AI Agents
In `@src/app/`[locale]/dashboard/availability/_components/endpoint/probe-grid.tsx
around lines 44 - 57, The functions formatLatency and formatTime currently use
hard-coded user-facing strings ("-", "ms", "s"); update them to use the app i18n
system (e.g., a translate function or react-intl) and locale-aware number
formatting: replace the literal "-" placeholder with a translated empty value,
format millisecond values with Intl.NumberFormat for the current locale, and
append localized unit strings for milliseconds/seconds (or return a localized
combined string), and ensure formatTime uses the same i18n placeholder when date
is null while still relying on toLocaleTimeString for time formatting; locate
and change formatLatency and formatTime in probe-grid.tsx to call the
translation utilities and Intl.NumberFormat instead of hard-coded literals.

Comment on lines +19 to +92
export interface SessionStatusInfo {
status: SessionDisplayStatus;
label: string;
tooltipKey: string;
color: string;
pulse: boolean;
}

/**
* Input type for session status calculation
*/
export interface SessionStatusInput {
concurrentCount?: number;
requestCount?: number;
status?: "in_progress" | "completed" | "error";
}

/**
* Determine session display status based on request state
*
* Logic:
* - IN_PROGRESS: concurrentCount > 0 AND requestCount > 1 (has active requests, not first)
* - INITIALIZING: requestCount <= 1 AND concurrentCount > 0 (first request still running)
* - IDLE: concurrentCount === 0 (all requests completed)
*
* @param session - Session data with concurrent and request counts
* @returns SessionStatusInfo for UI rendering
*/
export function getSessionDisplayStatus(session: SessionStatusInput): SessionStatusInfo {
const { concurrentCount = 0, requestCount = 0, status } = session;

logger.trace("getSessionDisplayStatus", { concurrentCount, requestCount, status });

// Error status takes priority
if (status === "error") {
return {
status: SESSION_DISPLAY_STATUS.IN_PROGRESS,
label: "ERROR",
tooltipKey: "status.errorTooltip",
color: "text-rose-500 dark:text-rose-400",
pulse: true,
};
}

// INITIALIZING: first request still running
if (requestCount <= 1 && concurrentCount > 0) {
return {
status: SESSION_DISPLAY_STATUS.INITIALIZING,
label: SESSION_DISPLAY_STATUS.INITIALIZING,
tooltipKey: "status.initializingTooltip",
color: "text-amber-500 dark:text-amber-400",
pulse: true,
};
}

// IN_PROGRESS: has active requests
if (concurrentCount > 0) {
return {
status: SESSION_DISPLAY_STATUS.IN_PROGRESS,
label: SESSION_DISPLAY_STATUS.IN_PROGRESS,
tooltipKey: "status.inProgressTooltip",
color: "text-emerald-500 dark:text-emerald-400",
pulse: true,
};
}

// IDLE: no active requests
return {
status: SESSION_DISPLAY_STATUS.IDLE,
label: SESSION_DISPLAY_STATUS.IDLE,
tooltipKey: "status.idleTooltip",
color: "text-muted-foreground/50",
pulse: false,
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

状态 label 为硬编码英文,违反本地化要求。

label 被 UI 直接展示,当前返回的是英文常量/ERROR 字符串。建议改为返回 labelKey 并在 UI 中 t(labelKey) 渲染,同时补齐 5 语言文案。

建议修改
 export interface SessionStatusInfo {
   status: SessionDisplayStatus;
-  label: string;
+  labelKey: string;
   tooltipKey: string;
   color: string;
   pulse: boolean;
+  isError: boolean;
 }

 // Error status takes priority
 if (status === "error") {
   return {
     status: SESSION_DISPLAY_STATUS.IN_PROGRESS,
-    label: "ERROR",
+    labelKey: "status.error",
     tooltipKey: "status.errorTooltip",
     color: "text-rose-500 dark:text-rose-400",
     pulse: true,
+    isError: true,
   };
 }

 // INITIALIZING: first request still running
 if (requestCount <= 1 && concurrentCount > 0) {
   return {
     status: SESSION_DISPLAY_STATUS.INITIALIZING,
-    label: SESSION_DISPLAY_STATUS.INITIALIZING,
+    labelKey: "status.initializing",
     tooltipKey: "status.initializingTooltip",
     color: "text-amber-500 dark:text-amber-400",
     pulse: true,
+    isError: false,
   };
 }
@@
   return {
     status: SESSION_DISPLAY_STATUS.IDLE,
-    label: SESSION_DISPLAY_STATUS.IDLE,
+    labelKey: "status.idle",
     tooltipKey: "status.idleTooltip",
     color: "text-muted-foreground/50",
     pulse: false,
+    isError: false,
   };

As per coding guidelines, 需要使用 i18n 渲染用户可见文本。

📝 Committable suggestion

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

Suggested change
export interface SessionStatusInfo {
status: SessionDisplayStatus;
label: string;
tooltipKey: string;
color: string;
pulse: boolean;
}
/**
* Input type for session status calculation
*/
export interface SessionStatusInput {
concurrentCount?: number;
requestCount?: number;
status?: "in_progress" | "completed" | "error";
}
/**
* Determine session display status based on request state
*
* Logic:
* - IN_PROGRESS: concurrentCount > 0 AND requestCount > 1 (has active requests, not first)
* - INITIALIZING: requestCount <= 1 AND concurrentCount > 0 (first request still running)
* - IDLE: concurrentCount === 0 (all requests completed)
*
* @param session - Session data with concurrent and request counts
* @returns SessionStatusInfo for UI rendering
*/
export function getSessionDisplayStatus(session: SessionStatusInput): SessionStatusInfo {
const { concurrentCount = 0, requestCount = 0, status } = session;
logger.trace("getSessionDisplayStatus", { concurrentCount, requestCount, status });
// Error status takes priority
if (status === "error") {
return {
status: SESSION_DISPLAY_STATUS.IN_PROGRESS,
label: "ERROR",
tooltipKey: "status.errorTooltip",
color: "text-rose-500 dark:text-rose-400",
pulse: true,
};
}
// INITIALIZING: first request still running
if (requestCount <= 1 && concurrentCount > 0) {
return {
status: SESSION_DISPLAY_STATUS.INITIALIZING,
label: SESSION_DISPLAY_STATUS.INITIALIZING,
tooltipKey: "status.initializingTooltip",
color: "text-amber-500 dark:text-amber-400",
pulse: true,
};
}
// IN_PROGRESS: has active requests
if (concurrentCount > 0) {
return {
status: SESSION_DISPLAY_STATUS.IN_PROGRESS,
label: SESSION_DISPLAY_STATUS.IN_PROGRESS,
tooltipKey: "status.inProgressTooltip",
color: "text-emerald-500 dark:text-emerald-400",
pulse: true,
};
}
// IDLE: no active requests
return {
status: SESSION_DISPLAY_STATUS.IDLE,
label: SESSION_DISPLAY_STATUS.IDLE,
tooltipKey: "status.idleTooltip",
color: "text-muted-foreground/50",
pulse: false,
};
export interface SessionStatusInfo {
status: SessionDisplayStatus;
labelKey: string;
tooltipKey: string;
color: string;
pulse: boolean;
isError: boolean;
}
/**
* Input type for session status calculation
*/
export interface SessionStatusInput {
concurrentCount?: number;
requestCount?: number;
status?: "in_progress" | "completed" | "error";
}
/**
* Determine session display status based on request state
*
* Logic:
* - IN_PROGRESS: concurrentCount > 0 AND requestCount > 1 (has active requests, not first)
* - INITIALIZING: requestCount <= 1 AND concurrentCount > 0 (first request still running)
* - IDLE: concurrentCount === 0 (all requests completed)
*
* `@param` session - Session data with concurrent and request counts
* `@returns` SessionStatusInfo for UI rendering
*/
export function getSessionDisplayStatus(session: SessionStatusInput): SessionStatusInfo {
const { concurrentCount = 0, requestCount = 0, status } = session;
logger.trace("getSessionDisplayStatus", { concurrentCount, requestCount, status });
// Error status takes priority
if (status === "error") {
return {
status: SESSION_DISPLAY_STATUS.IN_PROGRESS,
labelKey: "status.error",
tooltipKey: "status.errorTooltip",
color: "text-rose-500 dark:text-rose-400",
pulse: true,
isError: true,
};
}
// INITIALIZING: first request still running
if (requestCount <= 1 && concurrentCount > 0) {
return {
status: SESSION_DISPLAY_STATUS.INITIALIZING,
labelKey: "status.initializing",
tooltipKey: "status.initializingTooltip",
color: "text-amber-500 dark:text-amber-400",
pulse: true,
isError: false,
};
}
// IN_PROGRESS: has active requests
if (concurrentCount > 0) {
return {
status: SESSION_DISPLAY_STATUS.IN_PROGRESS,
labelKey: "status.inProgress",
tooltipKey: "status.inProgressTooltip",
color: "text-emerald-500 dark:text-emerald-400",
pulse: true,
isError: false,
};
}
// IDLE: no active requests
return {
status: SESSION_DISPLAY_STATUS.IDLE,
labelKey: "status.idle",
tooltipKey: "status.idleTooltip",
color: "text-muted-foreground/50",
pulse: false,
isError: false,
};
}
🤖 Prompt for AI Agents
In `@src/lib/session-status.ts` around lines 19 - 92, The returned labels in
getSessionDisplayStatus are hard-coded English; change SessionStatusInfo to
replace the label:string field with labelKey:string (e.g., labelKey values like
"status.errorLabel", "status.initializingLabel", "status.inProgressLabel",
"status.idleLabel") and update getSessionDisplayStatus to return labelKey
instead of label/ERROR for the error and each branch (use SESSION_DISPLAY_STATUS
constants only for the status field). Also update any callers/UI to render
visible text via i18n (e.g., t(labelKey)) and add the 5 required localized
strings into the translations resource.

Comment on lines +29 to +42
function createMockSession(id: number): ActiveSessionInfo & { lastActivityAt?: number } {
return {
sessionId: `session_${id}`,
userName: `User ${id}`,
keyName: `key_${id}`,
model: "claude-sonnet-4-5-20250929",
providerName: "anthropic",
status: "in_progress",
startTime: Date.now() - 1000,
inputTokens: 100,
outputTokens: 50,
costUsd: 0.01,
lastActivityAt: Date.now(),
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

createMockSession 缺少 ActiveSessionInfo 必填字段,测试会被类型检查阻断。
请补齐 userId、keyId、providerId、apiType 等必填字段。

修改建议
   return {
     sessionId: `session_${id}`,
+    userId: id,
     userName: `User ${id}`,
+    keyId: id,
     keyName: `key_${id}`,
+    providerId: 1,
     model: "claude-sonnet-4-5-20250929",
     providerName: "anthropic",
+    apiType: "chat",
     status: "in_progress",
     startTime: Date.now() - 1000,
📝 Committable suggestion

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

Suggested change
function createMockSession(id: number): ActiveSessionInfo & { lastActivityAt?: number } {
return {
sessionId: `session_${id}`,
userName: `User ${id}`,
keyName: `key_${id}`,
model: "claude-sonnet-4-5-20250929",
providerName: "anthropic",
status: "in_progress",
startTime: Date.now() - 1000,
inputTokens: 100,
outputTokens: 50,
costUsd: 0.01,
lastActivityAt: Date.now(),
};
function createMockSession(id: number): ActiveSessionInfo & { lastActivityAt?: number } {
return {
sessionId: `session_${id}`,
userId: id,
userName: `User ${id}`,
keyId: id,
keyName: `key_${id}`,
providerId: 1,
model: "claude-sonnet-4-5-20250929",
providerName: "anthropic",
apiType: "chat",
status: "in_progress",
startTime: Date.now() - 1000,
inputTokens: 100,
outputTokens: 50,
costUsd: 0.01,
lastActivityAt: Date.now(),
};
}
🤖 Prompt for AI Agents
In `@tests/unit/dashboard/live-sessions-panel-dynamic-items.test.tsx` around lines
29 - 42, The createMockSession helper is missing required ActiveSessionInfo
fields causing type errors; update the createMockSession function to return all
mandatory properties from ActiveSessionInfo (e.g., add userId, keyId,
providerId, apiType) with appropriate mock values/types, and ensure the return
type still matches ActiveSessionInfo & { lastActivityAt?: number } so tests
compile; locate and modify createMockSession to include those fields.

Copy link
Contributor

Choose a reason for hiding this comment

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

[CRITICAL] [SECURITY-VULNERABILITY] cacheKey includes raw proxyUrl (can leak credentials into logs)

Evidence (src/lib/proxy-agent/agent-pool.ts:116):

const proxy = params.proxyUrl || "direct";
return `${origin}|${proxy}|${protocol}`;

If proxyUrl is like http://user:pass@proxy.example.com:8080, the password becomes part of cacheKey.

cacheKey is logged in new code paths (e.g. src/app/v1/_lib/proxy/forwarder.ts:1668, src/app/v1/_lib/proxy/forwarder.ts:1827) and in src/lib/proxy-agent/agent-pool.ts:246, so credentials can end up in application logs.

Why this is a problem: Proxy credentials are secrets. Logging them is a direct data exposure risk.

Suggested fix: Make cacheKey safe-by-construction by hashing the proxy URL (including credentials) or stripping credentials and adding a stable hash so different creds don't collide.

import { createHash } from "node:crypto";

function hashForKey(value: string): string {
  return createHash("sha256").update(value).digest("hex").slice(0, 12);
}

export function generateAgentCacheKey(params: GetAgentParams): string {
  const origin = new URL(params.endpointUrl).origin;
  const proxy = params.proxyUrl ? `proxy#${hashForKey(params.proxyUrl)}` : "direct";
  const protocol = params.enableHttp2 ? "h2" : "h1";
  return `${origin}|${proxy}|${protocol}`;
}

Also update tests/unit/lib/proxy-agent/agent-pool.test.ts expectations to avoid asserting raw proxy URLs.


/**
* Session Display Status Constants
* English uppercase abbreviations (no i18n for status labels)
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 session status labels break i18n + encode errors via label

Evidence:

  • src/lib/session-status.ts:4:
/**
 * Session Display Status Constants
 * English uppercase abbreviations (no i18n for status labels)
 */
  • src/lib/session-status.ts:7:
export const SESSION_DISPLAY_STATUS = {
  IN_PROGRESS: "IN_PROGRESS",
  IDLE: "IDLE",
  INITIALIZING: "INITIALIZING",
} as const;
  • src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx:39 and src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx:110:
return info.label === "ERROR" ? ...
...
{statusInfo.label}

This violates CLAUDE.md:14: **i18n Required** - All user-facing strings must use i18n (5 languages supported). Never hardcode display text.

Why this is a problem: Non-English locales will see untranslated status badges. Also, UI logic becomes stringly-typed (label === "ERROR") even though status exists, making refactors risky.

Suggested fix:

  1. Add an ERROR member to SESSION_DISPLAY_STATUS and return it for status === "error".
  2. Replace label: string with labelKey: string (or derive label in the component via i18n), and render t(statusInfo.labelKey) instead of raw label.
  3. Update LiveSessionsPanel comparisons to check statusInfo.status === SESSION_DISPLAY_STATUS.ERROR.
// src/lib/session-status.ts
export const SESSION_DISPLAY_STATUS = {
  IN_PROGRESS: "IN_PROGRESS",
  IDLE: "IDLE",
  INITIALIZING: "INITIALIZING",
  ERROR: "ERROR",
} as const;

export interface SessionStatusInfo {
  status: SessionDisplayStatus;
  labelKey: string;
  tooltipKey: string;
  color: string;
  pulse: boolean;
}

if (status === "error") {
  return {
    status: SESSION_DISPLAY_STATUS.ERROR,
    labelKey: "status.error",
    tooltipKey: "status.errorTooltip",
    color: "text-rose-500 dark:text-rose-400",
    pulse: true,
  };
}

Then add customs.activeSessions.status.{error,inProgress,initializing,idle} keys for all locales and update tests/unit/lib/session-status.test.ts.

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 i18n, proxy/agent networking, session tracking, and dashboard UI in a single change set. Two issues are must-address before merge: a credential leakage risk via AgentPool cache keys/logging and hardcoded (non-i18n) session status labels coupled to string comparisons.

PR Size: XL

  • Lines changed: 7557
  • Files changed: 66
  • Split suggestions: (1) i18n-only changes (messages/*, provider form network section) (2) proxy forwarder + AgentPool + SSL detection + thinking-rectifier (3) session tracker/status + active sessions action (4) availability dashboard UI + tests (5) misc UI fixes (src/components/ui/chart.tsx, src/components/ui/tag-input.tsx)

Issues Found

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

Critical Issues (Must Fix)

  1. (Confidence 98/100) Proxy credentials can leak via AgentPool cacheKey generation/logging (src/lib/proxy-agent/agent-pool.ts:116, logged from src/app/v1/_lib/proxy/forwarder.ts:1668).

High Priority Issues (Should Fix)

  1. (Confidence 88/100) Session status labels are hardcoded (non-i18n) and error state is encoded via label string comparisons (src/lib/session-status.ts:5, used in src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx:110).

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.

Code Review Summary

This PR implements comprehensive improvements across proxy infrastructure, session tracking, and dashboard components. After thorough multi-perspective review, no significant issues were identified that meet the reporting threshold.

PR Size: XL

  • Lines changed: 7,557 (7,305 additions + 252 deletions)
  • Files changed: 66

Recommendation for Large PRs: Consider splitting future PRs of this size into smaller, focused changes:

  • Commit 1-2: i18n translations (25 files) - could be separate PR
  • Commit 3-5: Proxy infrastructure improvements (SSL detection, Agent Pool, retry limiting)
  • Commit 6: Dashboard components (availability dashboard)
  • Commit 7-8: UI component fixes and session panel refactoring

Smaller PRs are easier to review, test, and revert if issues arise.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate (80%+ coverage for new features)
  • Code clarity - Good

Key Improvements Validated

Proxy Infrastructure:

  • ✅ Agent Pool implementation with LRU eviction and health tracking
  • ✅ SSL certificate error detection with comprehensive pattern matching
  • ✅ Endpoint retry limiting to prevent excessive attempts
  • ✅ HTTP/2 connection pooling with proper resource management

Session Tracking:

  • ✅ Batch concurrent count query prevents N+1 queries
  • ✅ Session status calculation utilities for consistent UI state
  • ✅ Redis pipeline usage for efficient batch operations

Dashboard & UI:

  • ✅ Availability dashboard with provider/endpoint metrics
  • ✅ Chart component initialization fixes
  • ✅ Tag-input portal rendering and keyboard navigation
  • ✅ Live sessions panel optimization

Testing:

  • ✅ Comprehensive test coverage for Agent Pool (unit tests)
  • ✅ SSL error detection tests with multiple error patterns
  • ✅ Thinking signature rectifier tests
  • ✅ Dashboard component tests with proper mocking

Notes

  1. Agent Pool Design: The pending creation race condition handling is correct - rejected promises are removed to allow retry, which is the intended behavior.

  2. Error Handling: Client-side components appropriately use for browser DevTools visibility. Server-side code uses the logger utility.

  3. Type Safety: Type assertions in Redis operations are protected by ternary fallbacks, preventing runtime errors.

  4. i18n Compliance: All user-facing strings properly use i18n across 5 languages (en, ja, ru, zh-CN, zh-TW).


Automated review by Claude 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.

  • Detected PR #646 and applied the size/XL label.
  • Left 2 inline review comments:
    • [CRITICAL] [SECURITY-VULNERABILITY] src/lib/proxy-agent/agent-pool.ts:116 builds cacheKey from raw proxyUrl, which can include credentials and get logged (e.g. src/app/v1/_lib/proxy/forwarder.ts:1668).
    • [HIGH] [STANDARD-VIOLATION] src/lib/session-status.ts:5 hardcodes user-facing status labels (non-i18n) and src/app/[locale]/dashboard/_components/bento/live-sessions-panel.tsx:110 renders/branches on string labels like "ERROR".
  • Submitted the required summary review on the PR with XL split suggestions and the issue matrix.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR implements comprehensive improvements across proxy infrastructure, session tracking, and dashboard components. After thorough multi-perspective review, no significant issues were identified that meet the reporting threshold.

PR Size: XL

  • Lines changed: 7,557 (7,305 additions + 252 deletions)
  • Files changed: 66

Recommendation for Large PRs: Consider splitting future PRs of this size into smaller, focused changes:

  • Commit 1-2: i18n translations (25 files) - could be separate PR
  • Commit 3-5: Proxy infrastructure improvements (SSL detection, Agent Pool, retry limiting)
  • Commit 6: Dashboard components (availability dashboard)
  • Commit 7-8: UI component fixes and session panel refactoring

Smaller PRs are easier to review, test, and revert if issues arise.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate (80%+ coverage for new features)
  • Code clarity - Good

Key Improvements Validated

Proxy Infrastructure:

  • ✅ Agent Pool implementation with LRU eviction and health tracking
  • ✅ SSL certificate error detection with comprehensive pattern matching
  • ✅ Endpoint retry limiting to prevent excessive attempts
  • ✅ HTTP/2 connection pooling with proper resource management

Session Tracking:

  • ✅ Batch concurrent count query prevents N+1 queries
  • ✅ Session status calculation utilities for consistent UI state
  • ✅ Redis pipeline usage for efficient batch operations

Dashboard & UI:

  • ✅ Availability dashboard with provider/endpoint metrics
  • ✅ Chart component initialization fixes
  • ✅ Tag-input portal rendering and keyboard navigation
  • ✅ Live sessions panel optimization

Testing:

  • ✅ Comprehensive test coverage for Agent Pool (unit tests)
  • ✅ SSL error detection tests with multiple error patterns
  • ✅ Thinking signature rectifier tests
  • ✅ Dashboard component tests with proper mocking

Notes

  1. Agent Pool Design: The pending creation race condition handling is correct - rejected promises are removed to allow retry, which is the intended behavior.

  2. Error Handling: Client-side components appropriately use console.error() for browser DevTools visibility. Server-side code uses the logger utility.

  3. Type Safety: Type assertions in Redis operations are protected by ternary fallbacks, preventing runtime errors.

  4. i18n Compliance: All user-facing strings properly use i18n across 5 languages (en, ja, ru, zh-CN, zh-TW).


Automated review by Claude AI

- Fix duplicate JSON keys in ru/zh-TW dashboard.json (availability.timeRange, laneChart, etc.)
- Fix tag-input.tsx scroll offset bug: use viewport coords for fixed positioning
- Fix agent-pool.ts credential exposure: only use proxy origin in cache key
- Add missing type="button" to shared availability components

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ding113 ding113 merged commit 25f0f38 into dev Jan 22, 2026
7 of 10 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 22, 2026
@github-actions github-actions bot mentioned this pull request Jan 25, 2026
6 tasks
@ding113 ding113 deleted the feat/multi-improvements-batch branch January 27, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant

Comments