Skip to content

fix#314

Merged
ding113 merged 6 commits intomainfrom
dev
Dec 10, 2025
Merged

fix#314
ding113 merged 6 commits intomainfrom
dev

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Dec 10, 2025

Summary

This PR brings multiple improvements from the dev branch: sessions pagination UI, upstream 404 handling without circuit breaker impact, empty response detection with automatic failover, correct output token extraction from Anthropic SSE streams, and rolling 24-hour window for daily leaderboard notifications.

Problem

1. Anthropic SSE Output Token Counting (Fixes #312)

The v0.3.27 update introduced 5m/1h cache differentiated billing, which changed usage extraction to use message_start. However, message_start appears at stream beginning when output_tokens is typically 1, before actual content is generated.

2. Daily Leaderboard Window (Fixes #308)

The daily leaderboard notification only included data from midnight to send time (e.g., 00:00-09:00 for 9am sends), not a full day's worth of data.

3. Upstream 404 Errors

Previously, 404 errors from upstream providers would trigger circuit breaker failures, potentially causing unnecessary provider unavailability for transient resource issues.

4. Empty Response Handling

Empty responses from providers (empty body, zero output tokens, missing content) were not detected, potentially causing silent failures.

5. Sessions Page Performance

No pagination on the sessions monitoring page could cause performance issues with many active/inactive sessions.

Solution

Anthropic SSE Fix (merged from PR #313)

  • Extract input tokens and cache fields from message_start (required for 5m/1h cache billing)
  • Extract final output_tokens from message_delta (accurate count at stream end)
  • Merge both metrics for complete usage data

Daily Leaderboard

  • Changed from calendar-day based to rolling 24-hour window
  • Added findLast24HoursLeaderboard() repository function
  • Updated UI labels to reflect "过去24小时" (last 24 hours)

404 Error Handling

  • Added RESOURCE_NOT_FOUND error category
  • 404 errors trigger failover to next provider without incrementing circuit breaker failure count
  • Prevents transient resource issues from cascading to provider unavailability

Empty Response Detection

  • Added EmptyResponseError class for structured error handling
  • Detects: empty body (Content-Length: 0), missing content fields, zero output tokens
  • Empty responses count toward circuit breaker and trigger failover

Sessions Pagination

  • Added pagination to getAllSessions() action with activePage, inactivePage, pageSize parameters
  • Client-side pagination controls with page size of 20
  • Added i18n support for pagination labels (EN, ZH-CN, ZH-TW, JA, RU)

Changes

Core Changes

  • src/app/v1/_lib/proxy/errors.ts: Added EmptyResponseError class and RESOURCE_NOT_FOUND category
  • src/app/v1/_lib/proxy/forwarder.ts: Empty response detection, 404 handling without circuit breaker
  • src/app/v1/_lib/proxy/response-handler.ts: Fixed Anthropic SSE usage parsing (message_start + message_delta merge)
  • src/app/v1/_lib/proxy/session.ts: Added resource_not_found reason type

Sessions Pagination

  • src/actions/active-sessions.ts: Added pagination parameters and return totals
  • src/app/[locale]/dashboard/sessions/page.tsx: Added pagination UI controls

Leaderboard

  • src/repository/leaderboard.ts: Added last24h period and findLast24HoursLeaderboard()
  • src/lib/notification/tasks/daily-leaderboard.ts: Use rolling 24h window
  • src/lib/wechat/message-templates.ts: Updated labels

i18n

  • messages/{en,ja,ru,zh-CN,zh-TW}/dashboard.json: Added pagination.total translations

Types

  • src/types/message.ts: Added resource_not_found reason

Testing

Automated Tests

  • TypeScript compilation passes
  • Biome lint passes

Manual Testing

  • Verify sessions page pagination works with many sessions
  • Test Anthropic SSE stream to confirm correct output token count
  • Test 404 response from provider to verify failover without circuit breaker increment
  • Verify daily leaderboard notification includes rolling 24h data

Checklist

  • Code follows project conventions
  • Self-review completed
  • PR targets devmain for release

Description enhanced by Claude AI

github-actions bot and others added 5 commits December 10, 2025 10:57
The previous implementation extracted output_tokens from message_start event
which appears at the beginning of the SSE stream, resulting in incorrect
(usually 1) output token counts since the main content hadn't been generated yet.

This fix:
- Extracts input tokens and cache fields (5m/1h) from message_start
- Extracts final output_tokens from message_delta (at stream end)
- Merges both to get accurate usage metrics

Closes #312

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…okens

Fix #312: Extract output_tokens from message_delta for Anthropic SSE streams
…ar day

- Add last24h period type to LeaderboardPeriod
- Add findLast24HoursLeaderboard() function using INTERVAL '24 hours'
- Update daily-leaderboard task to use rolling 24h window
- Update message templates to show 'past 24 hours' instead of 'today'

Fixes #308
- Add RESOURCE_NOT_FOUND to ErrorCategory enum
- Update categorizeErrorAsync to detect 404 status code
- Add handler in forwarder for 404: triggers failover but skips recordFailure
- Add 'resource_not_found' reason type to ProviderChainItem
- Add pagination params to getAllSessions() with input validation
- Add PaginationControls component with separate state for active/inactive
- Add i18n translations for pagination.total in all 5 locales
- Validate page bounds (min 1) and pageSize (1-200)
@gemini-code-assist
Copy link
Contributor

Summary of Changes

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

This pull request delivers a set of targeted enhancements aimed at improving both user experience and system robustness. It introduces pagination to the session management dashboard, making it more efficient to navigate and monitor active and inactive user sessions. Concurrently, the proxy layer benefits from more sophisticated error handling, allowing for better detection of problematic upstream responses and more intelligent failover strategies. Finally, the leaderboard functionality has been refined to offer a more timely view of user activity by shifting to a 24-hour rolling window.

Highlights

  • Session Pagination: Implemented pagination for active and inactive sessions on the dashboard, significantly improving performance and user experience for managing large numbers of sessions. This includes updates to the backend API and frontend UI.
  • Advanced Proxy Error Handling: Introduced a new EmptyResponseError to specifically detect and handle empty or malformed responses from upstream providers. Additionally, 404 errors (RESOURCE_NOT_FOUND) are now handled distinctly, triggering provider switching without impacting circuit breaker metrics.
  • Accurate Claude SSE Usage Parsing: Enhanced the logic for parsing usage metrics from Claude's Server-Sent Events (SSE) to correctly combine input tokens from message_start and output tokens from message_delta events, ensuring more precise cost tracking.
  • 24-Hour Rolling Leaderboard: The 'daily' leaderboard has been updated to reflect user consumption over a rolling 24-hour period instead of a fixed calendar day, providing more current and dynamic insights into usage trends.
Using Gemini Code Assist

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

Invoking Gemini

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

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

Customization

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

Limitations & Feedback

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

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

Footnotes

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

@github-actions github-actions bot added bug Something isn't working size/M Medium PR (< 500 lines) labels Dec 10, 2025
Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request introduces pagination for active and inactive sessions on the dashboard, allowing users to navigate through large lists of sessions. This involved modifying the getAllSessions action to accept pagination parameters (activePage, inactivePage, pageSize), update its return type with total counts and hasMore flags, and apply slice operations to paginate the data. The frontend ActiveSessionsPage was updated to manage pagination state, integrate new PaginationControls components, and adjust the useQuery hook to fetch paginated data. Additionally, the PR refactors the error handling logic in the proxy, introducing a new EmptyResponseError to detect and handle empty or content-less responses from upstream providers, and a RESOURCE_NOT_FOUND error category for 404 responses, both of which trigger provider switching but with different circuit breaker implications. The parseUsageFromResponseText function was enhanced to accurately extract usage metrics from Claude SSE responses by merging data from message_start and message_delta events. Finally, the daily leaderboard notification system was updated to report statistics for the 'past 24 hours' instead of a calendar day, changing function names and message templates accordingly. A review comment highlighted that the current pagination implementation in getAllSessions fetches all data into memory before slicing, suggesting that pagination should be pushed down to the data layer (Redis and database) for efficiency. Another comment pointed out duplicated pagination logic within getAllSessions, recommending extraction into a helper function to adhere to DRY principles.

Comment on lines +145 to 158
export async function getAllSessions(
activePage: number = 1,
inactivePage: number = 1,
pageSize: number = 20
): Promise<
ActionResult<{
active: ActiveSessionInfo[];
inactive: ActiveSessionInfo[];
totalActive: number;
totalInactive: number;
hasMoreActive: boolean;
hasMoreInactive: boolean;
}>
> {
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The current implementation of pagination fetches all session data from the database and Redis into memory and then performs pagination in the application layer. This can lead to significant performance issues and high memory consumption if the number of sessions is large.

To improve efficiency, pagination should be pushed down to the data layer:

  1. When fetching session IDs from Redis (SessionManager.getAllSessionIds), it should support pagination.
  2. The aggregateMultipleSessionStats function should be modified to accept limit and offset to perform pagination at the database level, rather than fetching all stats at once.

This would prevent fetching potentially thousands of session records just to display a small page.

Comment on lines +331 to +349
// 7. 应用分页
const totalActive = active.length;
const totalInactive = inactive.length;
const activeOffset = (safeActivePage - 1) * safePageSize;
const inactiveOffset = (safeInactivePage - 1) * safePageSize;
const paginatedActive = active.slice(activeOffset, activeOffset + safePageSize);
const paginatedInactive = inactive.slice(inactiveOffset, inactiveOffset + safePageSize);

return {
ok: true,
data: {
active: paginatedActive,
inactive: paginatedInactive,
totalActive,
totalInactive,
hasMoreActive: activeOffset + paginatedActive.length < totalActive,
hasMoreInactive: inactiveOffset + paginatedInactive.length < totalInactive,
},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This block of code for pagination is a duplicate of the logic found earlier in the function (around lines 234-252). To adhere to the Don't Repeat Yourself (DRY) principle and improve maintainability, this logic should be extracted into a helper function.

For example, you could create a function like this and call it from both places:

function paginateAndFormat(
  active: ActiveSessionInfo[],
  inactive: ActiveSessionInfo[],
  activePage: number,
  inactivePage: number,
  pageSize: number
) {
  const totalActive = active.length;
  const totalInactive = inactive.length;
  const activeOffset = (activePage - 1) * pageSize;
  const inactiveOffset = (inactivePage - 1) * pageSize;
  const paginatedActive = active.slice(activeOffset, activeOffset + pageSize);
  const paginatedInactive = inactive.slice(inactiveOffset, inactiveOffset + pageSize);

  return {
    ok: true,
    data: {
      active: paginatedActive,
      inactive: paginatedInactive,
      totalActive,
      totalInactive,
      hasMoreActive: activeOffset + paginatedActive.length < totalActive,
      hasMoreInactive: inactiveOffset + paginatedInactive.length < totalInactive,
    },
  };
}

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 several features: pagination for session views, empty response detection with provider failover, 404 error handling without circuit breaker impact, and leaderboard improvements. The changes are well-structured overall, with proper error categorization and session management.

PR Size: M

  • Lines changed: 552 (496 additions, 56 deletions)
  • Files changed: 15

Issues Found

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

High Priority Issues (Should Fix)

1. [ERROR-BROAD-CATCH] Empty response JSON parse catch block logs without parse error details

  • File: src/app/v1/_lib/proxy/forwarder.ts (new code around line 286 in diff)
  • Issue: The catch block uses _parseError (unused variable pattern) and logs at debug level without including the actual parse error. If the response is malformed JSON due to truncation or encoding issues, this masks the real problem.
  • Suggested fix:
} catch (parseError) {
  // JSON 解析失败但响应体不为空,不视为空响应错误
  logger.debug("ProxyForwarder: Non-JSON response body, skipping content check", {
    providerId: currentProvider.id,
    contentType,
    parseError: parseError instanceof Error ? parseError.message : String(parseError),
  });
}

Medium Priority Issues (Consider Fixing)

1. [TEST-MISSING-CRITICAL] No tests for new EmptyResponseError class and detection logic

  • File: src/app/v1/_lib/proxy/errors.ts
  • Issue: The new EmptyResponseError class and isEmptyResponseError type guard, plus the RESOURCE_NOT_FOUND error category, are added without corresponding unit tests. This is critical error handling logic that affects provider failover behavior.
  • Suggested action: Add unit tests covering:
    • EmptyResponseError construction with all three reason types
    • isEmptyResponseError type guard
    • categorizeErrorAsync returning correct categories for EmptyResponseError and 404 ProxyError

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - One issue noted
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Gap identified
  • Code clarity - Good

Notes on Implementation Quality

Good patterns observed:

  • Proper use of response.clone() before consuming the body for empty response detection
  • Clear separation of 404 handling from general provider errors
  • SSE/streaming detection to skip empty response checks appropriately
  • Pagination with proper input validation and bounds checking in active-sessions.ts

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.

Code Review Summary

This PR implements several features: pagination for session views, empty response detection with provider failover, 404 error handling without circuit breaker impact, and leaderboard improvements. The changes are well-structured overall, with proper error categorization and session management.

PR Size: M

  • Lines changed: 552 (496 additions, 56 deletions)
  • Files changed: 15

Issues Found

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

High Priority Issues (Should Fix)

1. [ERROR-BROAD-CATCH] Empty response JSON parse catch block logs without parse error details

  • File: src/app/v1/_lib/proxy/forwarder.ts (new code around line 286 in diff)
  • Issue: The catch block uses _parseError (unused variable pattern) and logs at debug level without including the actual parse error. If the response is malformed JSON due to truncation or encoding issues, this masks the real problem.
  • Suggested fix:
} catch (parseError) {
  // JSON 解析失败但响应体不为空,不视为空响应错误
  logger.debug("ProxyForwarder: Non-JSON response body, skipping content check", {
    providerId: currentProvider.id,
    contentType,
    parseError: parseError instanceof Error ? parseError.message : String(parseError),
  });
}

Medium Priority Issues (Consider Fixing)

1. [TEST-MISSING-CRITICAL] No tests for new EmptyResponseError class and detection logic

  • File: src/app/v1/_lib/proxy/errors.ts
  • Issue: The new EmptyResponseError class and isEmptyResponseError type guard, plus the RESOURCE_NOT_FOUND error category, are added without corresponding unit tests. This is critical error handling logic that affects provider failover behavior.
  • Suggested action: Add unit tests covering:
    • EmptyResponseError construction with all three reason types
    • isEmptyResponseError type guard
    • categorizeErrorAsync returning correct categories for EmptyResponseError and 404 ProxyError

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - One issue noted
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Gap identified
  • Code clarity - Good

Notes on Implementation Quality

Good patterns observed:

  • Proper use of response.clone() before consuming the body for empty response detection
  • Clear separation of 404 handling from general provider errors
  • SSE/streaming detection to skip empty response checks appropriately
  • Pagination with proper input validation and bounds checking in active-sessions.ts

Automated review by Claude AI

- Add quick date filter buttons (Today, Yesterday, Last 7 Days, Last 30 Days)
- Add CSV export with formula injection protection
- Use differenceInCalendarDays to avoid DST issues
- Add aria-pressed for accessibility
- Add i18n translations for en, zh-CN, zh-TW, ja, ru

Closes #256
@ding113 ding113 merged commit 3e0ef9e into main Dec 10, 2025
3 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Dec 10, 2025
@ding113 ding113 linked an issue Dec 10, 2025 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

bug: Anthropic 类型供应商输出 token 计数错误 每日用户消费排行榜应该发送过去24H Bug: 状态码为 404 时会熔断

1 participant