feat: 可选拦截 Anthropic Warmup 请求#526
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughWalkthrough本PR引入Anthropic预热请求拦截功能。添加数据库列、系统配置、国际化文本、业务逻辑处理和统计查询过滤,使CCH能检测并本地响应Claude/Anthropic预热请求。 Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 分钟 Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Summary of ChangesHello @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! 此拉取请求旨在通过引入一个可选的 Anthropic Warmup 请求拦截机制来优化资源使用。该机制允许系统在检测到特定的 Warmup 请求时,不将其转发给上游供应商,而是直接在本地生成并返回一个响应。这不仅减少了不必要的 API 调用和潜在的费用支出,还确保了相关的日志记录和统计数据能够准确反映实际的计费请求,从而提升了系统的效率和成本控制能力。 Highlights
Using Gemini Code AssistThe 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
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 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
|
🧪 测试结果
总体结果: ✅ 所有测试通过 |
| if ( | ||
| session.authState?.success && | ||
| session.authState.user && | ||
| session.authState.apiKey && | ||
| session.sessionId | ||
| ) { | ||
| await db.insert(messageRequest).values({ | ||
| providerId: 0, // 特殊值:表示未选择供应商(本地抢答) | ||
| userId: session.authState.user.id, | ||
| key: session.authState.apiKey, |
There was a problem hiding this comment.
在记录已拦截的 Warmup 请求日志时,存在一个关键错误。if 条件和 db.insert 调用中使用了 session.authState.apiKey,但 session.authState 对象上并不存在 apiKey 属性。authState 的 key 属性是一个 Key 对象,真正的密钥字符串应该是 session.authState.key.key。
这个错误会导致 if 条件始终为假,从而无法执行 db.insert 操作,所有被拦截的 Warmup 请求都不会被记入日志。这违背了此 PR 的一个主要目标(“写入请求日志但不计费...便于审计”)。
此外,if 条件中的 session.authState.user 检查是多余的,因为 session.authState.success 为 true 时,user 和 key 对象必然存在。
建议进行如下修改以修复此问题并简化代码:
if (session.authState?.success && session.sessionId) {
await db.insert(messageRequest).values({
providerId: 0, // 特殊值:表示未选择供应商(本地抢答)
userId: session.authState.user.id,
key: session.authState.key.key,There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
src/repository/message.ts (1)
299-316: 统计过滤逻辑正确,建议使用常量提升可维护性。使用
IS DISTINCT FROM 'warmup'正确处理了 NULL 值情况。requestCountAll保留了 session 存在性判断,而requestCount则排除 warmup 用于统计。建议将硬编码的
'warmup'字符串替换为WARMUP_INTERCEPT_BLOCKED_BY常量(已在src/lib/utils/performance-formatter.ts中定义),确保全局一致性。🔎 建议的改进
import { WARMUP_INTERCEPT_BLOCKED_BY } from "@/lib/utils/performance-formatter"; // 在 SQL 中使用参数化方式 sql`${messageRequest.blockedBy} IS DISTINCT FROM ${WARMUP_INTERCEPT_BLOCKED_BY}`src/repository/client-versions.ts (1)
45-50: 过滤逻辑正确,与其他仓库查询保持一致。
IS DISTINCT FROM 'warmup'确保 warmup 请求不计入客户端版本统计,符合 PR 中「统计口径排除 warmup」的设计目标。同样建议引用
WARMUP_INTERCEPT_BLOCKED_BY常量以提升可维护性:🔎 建议的改进
+import { WARMUP_INTERCEPT_BLOCKED_BY } from "@/lib/utils/performance-formatter"; .where( and( isNull(messageRequest.deletedAt), - sql`${messageRequest.blockedBy} IS DISTINCT FROM 'warmup'`, + sql`${messageRequest.blockedBy} IS DISTINCT FROM ${WARMUP_INTERCEPT_BLOCKED_BY}`, gte(messageRequest.createdAt, cutoffDate), sql`${messageRequest.userAgent} IS NOT NULL` ) )src/repository/statistics.ts (1)
49-50: 统计查询对 warmup 记录的排除方式一致且语义正确
- 在用户/Key/混合统计的原生 SQL 中,将
mr.deleted_at IS NULL旁边统一增加mr.blocked_by IS DISTINCT FROM 'warmup',只排除被 warmup 拦截的记录,同时保留正常与blocked_by IS NULL的请求,确保COUNT(mr.id)与SUM(mr.cost_usd)都不受 warmup 干扰。sumUserCostToday同样排除blocked_by='warmup',即便该函数标记为 deprecated,也避免历史调用把 warmup 次数纳入日用量。- 其它基于
SUM(cost_usd)的 Drizzle 查询在 warmup 场景下因cost_usd=0自然不会增加额度,与这里的显式过滤逻辑保持整体一致性。后续如新增统计 SQL,建议复用同样的 blocked_by 过滤条件。Based on learnings, ...Also applies to: 86-87, 123-124, 160-161, 237-238, 279-280, 321-322, 363-364, 447-448, 477-478, 521-522, 551-552, 595-596, 625-626, 668-669, 699-700, 747-748
src/app/v1/_lib/proxy/warmup-intercept.ts (2)
10-31: 建议为类型属性添加 readonly 修饰符这些类型表示不可变的 API 响应数据结构。根据编码规范("Use readonly or const assertions for immutable data structures"),建议为所有属性添加
readonly修饰符以增强类型安全性,防止意外修改。🔎 建议的类型定义改进
type ClaudeTextContentBlock = { - type: "text"; - text: string; + readonly type: "text"; + readonly text: string; }; type ClaudeMessageUsage = { - input_tokens: number; - output_tokens: number; - cache_creation_input_tokens: number; - cache_read_input_tokens: number; + readonly input_tokens: number; + readonly output_tokens: number; + readonly cache_creation_input_tokens: number; + readonly cache_read_input_tokens: number; }; type ClaudeMessageResponse = { - model: string; - id: string; - type: "message"; - role: "assistant"; - content: ClaudeTextContentBlock[]; - stop_reason: "end_turn"; - stop_sequence: null; - usage: ClaudeMessageUsage; + readonly model: string; + readonly id: string; + readonly type: "message"; + readonly role: "assistant"; + readonly content: readonly ClaudeTextContentBlock[]; + readonly stop_reason: "end_turn"; + readonly stop_sequence: null; + readonly usage: ClaudeMessageUsage; };基于编码规范要求。
80-154: 响应构建逻辑实现正确
buildClaudeWarmupMessageResponse正确生成零 token 消耗的合成响应,符合 PR 目标(不计费、不消耗 token)buildClaudeWarmupSse准确构建 SSE 事件序列(message_start → content_block_start → content_block_delta → content_block_stop → message_delta → message_stop),格式符合 Claude 流式 API 规范- Line 127 的可选链操作符使用是防御性编程,虽然 content[0] 始终存在,但这增强了代码健壮性
可选改进:考虑在
buildClaudeWarmupMessageResponse中验证model参数非空,但鉴于该参数可能在上游 guard 已验证,当前实现可接受。
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
📒 Files selected for processing (37)
drizzle/0044_add_anthropic_warmup_intercept.sqldrizzle/meta/_journal.jsonmessages/en/dashboard.jsonmessages/en/settings.jsonmessages/ja/dashboard.jsonmessages/ja/settings.jsonmessages/ru/dashboard.jsonmessages/ru/settings.jsonmessages/zh-CN/dashboard.jsonmessages/zh-CN/settings.jsonmessages/zh-TW/dashboard.jsonmessages/zh-TW/settings.jsonsrc/actions/my-usage.tssrc/actions/system-config.tssrc/app/[locale]/dashboard/logs/_components/error-details-dialog.tsxsrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxsrc/app/[locale]/settings/config/_components/system-settings-form.tsxsrc/app/[locale]/settings/config/page.tsxsrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/warmup-intercept.tssrc/drizzle/schema.tssrc/lib/config/system-settings-cache.tssrc/lib/utils/performance-formatter.tssrc/lib/validation/schemas.tssrc/repository/_shared/transformers.tssrc/repository/client-versions.tssrc/repository/key.tssrc/repository/leaderboard.tssrc/repository/message.tssrc/repository/overview.tssrc/repository/statistics.tssrc/repository/system-config.tssrc/types/system-config.tstests/unit/proxy/session.test.tstests/unit/proxy/warmup-intercept.test.ts
🧰 Additional context used
📓 Path-based instructions (21)
**/*.{ts,tsx,js,jsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Use 2-space indentation in all code files
Files:
src/lib/validation/schemas.tssrc/repository/client-versions.tsdrizzle/meta/_journal.jsonmessages/ja/dashboard.jsonmessages/zh-TW/settings.jsonmessages/zh-CN/settings.jsonsrc/repository/leaderboard.tssrc/repository/key.tsmessages/en/dashboard.jsonmessages/ja/settings.jsonsrc/lib/utils/performance-formatter.tssrc/repository/statistics.tsmessages/ru/settings.jsonsrc/app/[locale]/settings/config/page.tsxsrc/actions/system-config.tssrc/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxsrc/repository/system-config.tssrc/app/v1/_lib/proxy/warmup-guard.tsmessages/zh-TW/dashboard.jsonmessages/en/settings.jsonsrc/drizzle/schema.tstests/unit/proxy/warmup-intercept.test.tssrc/types/system-config.tssrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/error-details-dialog.tsxmessages/ru/dashboard.jsonsrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/[locale]/settings/config/_components/system-settings-form.tsxsrc/repository/message.tssrc/actions/my-usage.tssrc/repository/_shared/transformers.tssrc/app/v1/_lib/proxy/warmup-intercept.tssrc/repository/overview.tssrc/lib/config/system-settings-cache.tstests/unit/proxy/session.test.tsmessages/zh-CN/dashboard.json
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias@/*to reference files from./src/*directory
**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias@/*to reference files in./src/*directory
Files:
src/lib/validation/schemas.tssrc/repository/client-versions.tssrc/repository/leaderboard.tssrc/repository/key.tssrc/lib/utils/performance-formatter.tssrc/repository/statistics.tssrc/app/[locale]/settings/config/page.tsxsrc/actions/system-config.tssrc/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxsrc/repository/system-config.tssrc/app/v1/_lib/proxy/warmup-guard.tssrc/drizzle/schema.tstests/unit/proxy/warmup-intercept.test.tssrc/types/system-config.tssrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/error-details-dialog.tsxsrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/[locale]/settings/config/_components/system-settings-form.tsxsrc/repository/message.tssrc/actions/my-usage.tssrc/repository/_shared/transformers.tssrc/app/v1/_lib/proxy/warmup-intercept.tssrc/repository/overview.tssrc/lib/config/system-settings-cache.tstests/unit/proxy/session.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures
Files:
src/lib/validation/schemas.tssrc/repository/client-versions.tssrc/repository/leaderboard.tssrc/repository/key.tssrc/lib/utils/performance-formatter.tssrc/repository/statistics.tssrc/app/[locale]/settings/config/page.tsxsrc/actions/system-config.tssrc/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxsrc/repository/system-config.tssrc/app/v1/_lib/proxy/warmup-guard.tssrc/drizzle/schema.tstests/unit/proxy/warmup-intercept.test.tssrc/types/system-config.tssrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/error-details-dialog.tsxsrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/[locale]/settings/config/_components/system-settings-form.tsxsrc/repository/message.tssrc/actions/my-usage.tssrc/repository/_shared/transformers.tssrc/app/v1/_lib/proxy/warmup-intercept.tssrc/repository/overview.tssrc/lib/config/system-settings-cache.tstests/unit/proxy/session.test.ts
src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages
Files:
src/lib/validation/schemas.tssrc/repository/client-versions.tssrc/repository/leaderboard.tssrc/repository/key.tssrc/lib/utils/performance-formatter.tssrc/repository/statistics.tssrc/actions/system-config.tssrc/repository/system-config.tssrc/app/v1/_lib/proxy/warmup-guard.tssrc/drizzle/schema.tssrc/types/system-config.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/repository/message.tssrc/actions/my-usage.tssrc/repository/_shared/transformers.tssrc/app/v1/_lib/proxy/warmup-intercept.tssrc/repository/overview.tssrc/lib/config/system-settings-cache.ts
src/lib/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Use connection pooling for database and Redis connections
Files:
src/lib/validation/schemas.tssrc/lib/utils/performance-formatter.tssrc/lib/config/system-settings-cache.ts
src/repository/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use Repository pattern in
src/repository/to wrap Drizzle queries
Files:
src/repository/client-versions.tssrc/repository/leaderboard.tssrc/repository/key.tssrc/repository/statistics.tssrc/repository/system-config.tssrc/repository/message.tssrc/repository/_shared/transformers.tssrc/repository/overview.ts
src/{repository,actions}/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Avoid N+1 queries by using eager loading and batch queries for statistics
Files:
src/repository/client-versions.tssrc/repository/leaderboard.tssrc/repository/key.tssrc/repository/statistics.tssrc/actions/system-config.tssrc/repository/system-config.tssrc/repository/message.tssrc/actions/my-usage.tssrc/repository/_shared/transformers.tssrc/repository/overview.ts
**/*.{tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW
Files:
drizzle/meta/_journal.jsonmessages/ja/dashboard.jsonmessages/zh-TW/settings.jsonmessages/zh-CN/settings.jsonmessages/en/dashboard.jsonmessages/ja/settings.jsonmessages/ru/settings.jsonsrc/app/[locale]/settings/config/page.tsxsrc/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxmessages/zh-TW/dashboard.jsonmessages/en/settings.jsonsrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/error-details-dialog.tsxmessages/ru/dashboard.jsonsrc/app/[locale]/settings/config/_components/system-settings-form.tsxmessages/zh-CN/dashboard.json
messages/**/*.json
📄 CodeRabbit inference engine (CLAUDE.md)
Support 5 locales via next-intl: en, ja, ru, zh-CN, zh-TW with messages in
messages/{locale}/*.jsonStore message translations in
messages/{locale}/*.jsonfiles
Files:
messages/ja/dashboard.jsonmessages/zh-TW/settings.jsonmessages/zh-CN/settings.jsonmessages/en/dashboard.jsonmessages/ja/settings.jsonmessages/ru/settings.jsonmessages/zh-TW/dashboard.jsonmessages/en/settings.jsonmessages/ru/dashboard.jsonmessages/zh-CN/dashboard.json
src/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{tsx,jsx}: Uselucide-reactfor icons, no custom SVGs
Use React's automatic escaping to prevent XSS vulnerabilities
Files:
src/app/[locale]/settings/config/page.tsxsrc/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/error-details-dialog.tsxsrc/app/[locale]/settings/config/_components/system-settings-form.tsx
src/actions/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
src/actions/**/*.ts: Validate all user inputs with Zod schemas before processing
Use Server Actions innext-safe-actionwith OpenAPI generation for admin API endpoints
Use Next.js API Routes and Server Actions for admin operations and REST endpoints
Files:
src/actions/system-config.tssrc/actions/my-usage.ts
src/app/v1/_lib/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Guard pipeline must execute in order: ProxyAuthenticator, SensitiveWordGuard, VersionGuard, ProxySessionGuard, ProxyRateLimitGuard, ProxyProviderResolver
Files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
src/app/v1/_lib/proxy/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
src/app/v1/_lib/proxy/**/*.ts: Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)
Use undici library for HTTP requests instead of node-fetch for better performance
Files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
src/**/*{guard,auth}*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Use constant-time comparison for API key validation to prevent timing attacks
Files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.ts
src/app/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Implement Content-Security-Policy headers for XSS prevention
Files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
src/app/v1/_lib/proxy/**/*guard*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Implement fail-open strategy: allow requests when Redis is unavailable
Files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.ts
src/app/v1/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Use Hono router for ultrafast, lightweight routing in proxy endpoints
Files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
src/drizzle/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use Drizzle ORM with PostgreSQL for database operations
src/drizzle/**/*.ts: Use Drizzle ORM with parameterized queries to prevent SQL injection
Use soft delete pattern withdeletedAtcolumn instead of hard deletes
Use JSON columns in PostgreSQL for flexible data structures (modelRedirects, providerChain, etc.)
Implement proper indexing strategy for common queries and foreign keys
Files:
src/drizzle/schema.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches
Files:
tests/unit/proxy/warmup-intercept.test.tstests/unit/proxy/session.test.ts
**/*.test.ts
📄 CodeRabbit inference engine (AGENTS.md)
Ensure test database names contain 'test' keyword for safety validation
Files:
tests/unit/proxy/warmup-intercept.test.tstests/unit/proxy/session.test.ts
src/**/*{message,response,log}*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Log request duration, token usage, and cost to message_request table for analytics
Files:
src/repository/message.ts
🧠 Learnings (12)
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to messages/**/*.json : Support 5 locales via next-intl: en, ja, ru, zh-CN, zh-TW with messages in `messages/{locale}/*.json`
Applied to files:
messages/ja/dashboard.jsonmessages/zh-TW/settings.jsonmessages/zh-CN/settings.jsonmessages/ja/settings.jsonmessages/ru/settings.jsonmessages/en/settings.json
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/{repository,actions}/**/*.ts : Avoid N+1 queries by using eager loading and batch queries for statistics
Applied to files:
src/repository/key.tssrc/repository/statistics.tssrc/repository/message.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*{message,response,log}*.ts : Log request duration, token usage, and cost to message_request table for analytics
Applied to files:
src/lib/utils/performance-formatter.tssrc/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/repository/message.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/lib/rate-limit/**/*.ts : Rate Limiting must track dimensions: RPM, cost (5h/week/month), concurrent sessions at User, Key, and Provider levels using Redis Lua scripts
Applied to files:
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsxsrc/app/[locale]/dashboard/logs/_components/usage-logs-table.tsxsrc/repository/message.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)
Applied to files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/v1/_lib/**/*.ts : Guard pipeline must execute in order: ProxyAuthenticator, SensitiveWordGuard, VersionGuard, ProxySessionGuard, ProxyRateLimitGuard, ProxyProviderResolver
Applied to files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/v1/_lib/proxy-handler.ts : Structure request flow in proxy handler through: ProxySession.fromContext() -> detectFormat() -> GuardPipelineBuilder.run() -> ProxyForwarder.send() -> ProxyResponseHandler.dispatch()
Applied to files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*guard*.ts : Implement fail-open strategy: allow requests when Redis is unavailable
Applied to files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.ts
📚 Learning: 2026-01-03T09:10:02.182Z
Learnt from: NieiR
Repo: ding113/claude-code-hub PR: 517
File: src/app/v1/_lib/proxy/auth-guard.ts:205-232
Timestamp: 2026-01-03T09:10:02.182Z
Learning: In `src/app/v1/_lib/proxy/auth-guard.ts`, the `extractApiKeyFromHeaders` function is intentionally a standalone simplified version for non-Guard flows. It deliberately does not check for multi-source conflicts (unlike `ProxyAuthenticator.validate`), and the code duplication is intentional to avoid coupling between Guard and non-Guard authentication flows.
Applied to files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/guard-pipeline.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/*handler*.ts : Stream responses with proper backpressure handling and chunked transfer encoding
Applied to files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Use undici library for HTTP requests instead of node-fetch for better performance
Applied to files:
src/app/v1/_lib/proxy/warmup-guard.tssrc/app/v1/_lib/proxy/warmup-intercept.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/drizzle/**/*.ts : Use soft delete pattern with `deletedAt` column instead of hard deletes
Applied to files:
src/drizzle/schema.ts
🧬 Code graph analysis (13)
src/repository/client-versions.ts (1)
src/drizzle/schema.ts (1)
messageRequest(264-345)
src/repository/leaderboard.ts (1)
src/drizzle/schema.ts (1)
messageRequest(264-345)
src/repository/key.ts (1)
src/drizzle/schema.ts (1)
messageRequest(264-345)
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)
src/lib/utils/performance-formatter.ts (2)
WARMUP_INTERCEPT_BLOCKED_BY(2-2)NON_BILLING_ENDPOINT(1-1)
src/repository/system-config.ts (1)
src/drizzle/schema.ts (1)
systemSettings(439-472)
src/app/v1/_lib/proxy/warmup-guard.ts (4)
src/lib/config/system-settings-cache.ts (1)
getCachedSystemSettings(40-94)src/app/v1/_lib/proxy/warmup-intercept.ts (4)
isClaudeWarmupRequestBody(37-64)getClaudeStreamFlag(66-73)buildClaudeWarmupInterceptResponse(156-197)WARMUP_BLOCKED_BY(8-8)src/lib/session-manager.ts (1)
SessionManager(79-1955)src/drizzle/schema.ts (1)
messageRequest(264-345)
src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (1)
src/lib/utils/performance-formatter.ts (2)
WARMUP_INTERCEPT_BLOCKED_BY(2-2)NON_BILLING_ENDPOINT(1-1)
src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx (1)
src/lib/utils/performance-formatter.ts (1)
WARMUP_INTERCEPT_BLOCKED_BY(2-2)
src/app/v1/_lib/proxy/guard-pipeline.ts (1)
src/app/v1/_lib/proxy/warmup-guard.ts (1)
ProxyWarmupGuard(14-102)
src/repository/message.ts (1)
src/drizzle/schema.ts (1)
messageRequest(264-345)
src/actions/my-usage.ts (1)
src/drizzle/schema.ts (1)
messageRequest(264-345)
src/repository/overview.ts (1)
src/drizzle/schema.ts (1)
messageRequest(264-345)
src/lib/config/system-settings-cache.ts (1)
src/types/system-config.ts (1)
SystemSettings(6-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Greptile Review
- GitHub Check: pr-description
- GitHub Check: 🌐 API Tests
- GitHub Check: pr-label
- GitHub Check: pr-review
- GitHub Check: Docker Build Test
- GitHub Check: check-codex-status
- GitHub Check: dev-build-deploy
🔇 Additional comments (54)
drizzle/meta/_journal.json (2)
313-319: ✅ 迁移日志条目格式正确新增的迁移记录(idx 44)遵循了既有的结构规范:
- 字段完整性:
idx、version、when、tag、breakpoints均已填充- 索引序号递增正确(43 → 44)
- 标签名称遵循命名约定:
0044_add_anthropic_warmup_intercept- JSON 格式语法正确,缩进保持 2 空格一致性
312-320: 此迁移文件已正确创建。对应的 SQL 文件drizzle/0044_add_anthropic_warmup_intercept.sql存在,且包含有效的数据库模式变更逻辑,为system_settings表添加了enable_anthropic_warmup_intercept布尔列。messages/zh-TW/dashboard.json (2)
123-131: 表格列skipped翻译合理
"skipped": "已跳過"用词自然、含义清晰,和英文含义一致,也与同区域其他状态文案(已攔截 / 非計費)风格统一,可保留。
156-161: Warmup 拦截详情文案清晰
"warmupIntercept": "Warmup 搶答(CCH)"能准确表达由 CCH 本地抢答的含义,括号说明来源也方便审计查看;与其他 locale 的含义保持一致,无需调整。messages/ru/dashboard.json (2)
123-131:table.skipped俄文翻译准确
"Пропущено"语气和其他状态列一致,能直观表达“已跳过”的含义,适合作为表格标签。
156-161: Warmup 拦截说明在俄文中表达自然
"Warmup перехвачен (CCH)"清楚说明请求被 CCH 在 warmup 阶段拦截,本地响应;与英文版语义一致,便于运维排查。messages/en/dashboard.json (2)
123-131:table.skipped标签命名合适使用
"Skipped"作为状态列文本,语义明确,和blocked/nonBilling并列时也容易理解,无歧义。
156-161: Warmup Intercept 详情文案与功能一致
"Warmup Intercept (CCH)"准确描述由 CCH 本地拦截 Warmup 请求的场景,和其他 blocked 类型(如 Sensitive Word Blocking)风格统一,便于用户区分拦截原因。messages/en/settings.json (1)
99-107: Anthropic Warmup 开关文案与命名都很合理
- key 命名
enableAnthropicWarmupIntercept与现有布尔配置命名风格一致,易于前后端联动。- 描述清楚交代了“检测 + 短路 + 本地短响应 + 避免上游浪费”的行为,和 PR 说明吻合。
从多语言支持的角度,请确保其它 4 个 locale 的 settings.json 也已添加对应 key 并保持语义一致(当前 PR 描述看起来已覆盖)。Based on learnings, 多语言 messages 需保持 5 个 locale 同步。
messages/ja/dashboard.json (2)
123-131:table.skipped日文翻译自然
"スキップ"是常见 UI 用语,搭配其他状态列(ブロック済み / 非課金)阅读体验一致,能准确表达“已跳过”的含义。
156-161: Warmup 拦截说明在日文里表达准确
"Warmup 先行応答(CCH)"把 Warmup 本地抢答解释成“先行応答”,语义贴合功能(本地优先响应避免上游调用),同时保留 CCH 标注,方便审计;整体表达顺畅,无需修改。messages/zh-TW/settings.json (1)
103-104: LGTM!本地化文案清晰准确。新增的繁体中文翻译与功能描述一致,格式符合现有文件规范。
src/drizzle/schema.ts (1)
465-468: LGTM!Schema 定义正确。新增的
enableAnthropicWarmupIntercept列:
- 使用
notNull().default(false)确保安全的默认行为(需主动开启)- 列命名遵循 snake_case 惯例
- 注释清晰说明功能用途
src/repository/message.ts (3)
332-347: 供应商和模型子查询过滤逻辑正确。
providerId > 0条件与 warmup 过滤配合使用,确保只统计真实的供应商调用记录(warmup 请求设置 provider_id=0)。
458-468: 批量聚合查询与单 session 查询保持一致。多 session 聚合路径的过滤逻辑与单 session 路径完全对齐,避免了统计口径不一致的问题。
607-609: 跳过条件更新正确。使用
requestCountAll === 0判断 session 是否存在,而非requestCount,确保即使只有 warmup 请求的 session 也不会被错误地包含在结果中。drizzle/0044_add_anthropic_warmup_intercept.sql (1)
1-2: LGTM!迁移脚本安全且幂等。
IF NOT EXISTS确保重复执行不会报错NOT NULL DEFAULT false与 schema 定义一致- 现有记录会自动获得默认值
falsesrc/lib/utils/performance-formatter.ts (1)
1-2: LGTM!常量定义有助于维护一致性。
WARMUP_INTERCEPT_BLOCKED_BY常量将blockedBy字段的魔法字符串集中管理。建议在仓库层查询(如src/repository/message.ts)中也引用此常量,确保前后端使用相同的值。tests/unit/proxy/session.test.ts (1)
34-34: LGTM!测试夹具已正确更新。新增的
enableAnthropicWarmupIntercept: false与生产环境默认值一致,确保测试用例继续有效。src/app/[locale]/dashboard/logs/_components/error-details-dialog.tsx (2)
29-29: LGTM!导入语句正确,符合项目路径别名约定。
276-278: LGTM!Warmup 拦截的条件逻辑实现正确,与现有
sensitive_word处理模式保持一致,使用了适当的国际化翻译键。src/app/[locale]/settings/config/page.tsx (1)
43-43: LGTM!新字段
enableAnthropicWarmupIntercept的初始化逻辑正确,与其他设置字段的模式保持一致。src/actions/my-usage.ts (1)
326-326: LGTM!在明细查询中一致地应用了相同的过滤条件,确保聚合数据和明细数据的一致性。
src/repository/overview.ts (1)
42-42: LGTM! 过滤逻辑在统计查询中一致应用。概览统计查询中正确应用了 Warmup 请求过滤条件,与
my-usage.ts中的实现保持一致。这确保了今日请求数、费用、响应时间和错误率等指标排除了 Warmup 拦截的请求。与
src/actions/my-usage.ts中的过滤条件相同,同样需要关注blocked_by列在大数据量场景下的查询性能。src/types/system-config.ts (2)
65-66: LGTM!
UpdateSystemSettingsInput接口中的字段定义正确,作为可选字段支持部分更新操作,与接口中其他设置字段的模式保持一致。
32-34: 数据库默认值配置已验证正确。字段类型定义正确(非可选的
boolean),与其他系统设置字段保持一致。验证结果显示:
- Schema (drizzle/schema.ts, line 466-468):
.notNull().default(false)正确配置- Transformer (repository/_shared/transformers.ts, line 154):使用
?? false确保安全的默认值处理- Repository (system-config.ts):初始化(line 89)和所有更新操作(lines 114, 147, 172, 275)中均正确处理,默认值设为
false默认值在数据库、数据转换和数据库访问层都正确配置为
false,符合 PR 描述要求。实现完整无误。src/repository/_shared/transformers.ts (1)
154-154: SystemSettings 新增开关映射合理且兼容旧数据
enableAnthropicWarmupIntercept从dbSettings映射并默认false,符合“默认关闭”需求,且不会影响旧记录加载,逻辑可以接受。src/app/[locale]/dashboard/logs/_components/usage-logs-table.tsx (1)
24-25: Warmup 拦截日志被正确标记为非计费并以“已跳过”展示
- 通过
log.blockedBy === WARMUP_INTERCEPT_BLOCKED_BY识别 warmup 行,并纳入isNonBilling,保证整行样式与非计费一致,符合“统计口径不计费”的需求。- 费用列对 warmup 单独显示橙色
t("logs.table.skipped"),对其它非计费请求仍用"-",区分度清晰,用户一眼能看出是 CCH 抢答跳过。aria-label仍基于isNonBilling,无额外无障碍回归风险。请同步确认 5 个 locale 都已提供logs.table.skipped翻译键以避免运行时缺失。As per coding guidelines, ...Also applies to: 93-95, 100-103, 307-311
messages/ru/settings.json (1)
103-104: 俄文配置文案与功能含义匹配
enableAnthropicWarmupIntercept及其描述准确表达了“检测 Claude/Anthropic Warmup 请求、本地抢答并跳过上游”的含义,语气与周围系统设置项保持一致,无需调整。src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx (1)
20-21: 虚拟化日志表对 warmup 拦截的处理与普通表保持一致
- 通过
log.blockedBy === WARMUP_INTERCEPT_BLOCKED_BY识别 warmup 行,并将其纳入isNonBilling,行样式统一走“非计费”分支,满足 warmup 不参与统计/费用的需求。- 费用列对 warmup 明确显示橙色
t("logs.table.skipped"),其余非计费仍显示"-",与UsageLogsTable的行为完全一致,避免两种视图表现不一。- loader 行在计算
isWarmupIntercept之前提前返回,不会出现空值访问问题。As per coding guidelines, ...Also applies to: 254-256, 270-271, 472-476
messages/ja/settings.json (1)
103-104: 日文 Warmup 拦截配置文案自然且与功能一致新增的日文标题与描述清晰说明了 Anthropic Warmup 请求会被 CCH 本地截获并跳过上游调用,措辞与周围配置项风格统一,无需进一步修改。
messages/zh-CN/dashboard.json (1)
130-130: 翻译准确,表达清晰。新增的两个翻译键值对表达准确:
- "已跳过" 准确对应被拦截的 warmup 请求
- "Warmup 抢答(CCH)" 形象地描述了拦截机制
Also applies to: 160-160
src/lib/validation/schemas.ts (1)
701-702: Schema 定义正确。新增字段
enableAnthropicWarmupIntercept的定义符合 Zod 最佳实践:
- 使用
.optional()支持部分更新- 类型为 boolean,语义明确
messages/zh-CN/settings.json (1)
86-87: 翻译质量良好。新增的设置项翻译清晰准确,描述详细说明了拦截功能的作用和效果。
src/actions/system-config.ts (1)
40-40: 数据流处理正确。新增字段正确地通过验证层传递到 repository 层:
- formData 类型定义完整
- 通过
UpdateSystemSettingsSchema.parse验证- 正确传递给
updateSystemSettingsAlso applies to: 61-61
src/repository/key.ts (1)
580-586: SQL 条件包装正确。在
and()子句中正确添加了 warmup 过滤条件,查询逻辑清晰。Also applies to: 711-717
src/repository/leaderboard.ts (1)
173-179: LGTM! 一致地排除了 warmup 请求。使用
IS DISTINCT FROM 'warmup'是正确的选择,它能正确处理blockedBy为NULL的情况(正常请求会被包含),同时排除 warmup 拦截的记录。四个排行榜查询函数都应用了相同的过滤逻辑,保持了一致性。Also applies to: 315-315, 415-415, 558-564
src/lib/config/system-settings-cache.ts (1)
27-30: LGTM! 新设置字段正确集成到缓存层。
enableAnthropicWarmupIntercept遵循与enableHttp2相同的模式:默认关闭、包含在调试日志中、在回退设置中提供安全默认值。这符合 fail-open 策略的要求。Also applies to: 58-58, 89-89
tests/unit/proxy/warmup-intercept.test.ts (2)
12-87: LGTM! 测试覆盖全面。测试用例覆盖了
isClaudeWarmupRequestBody的主要分支:有效的 warmup 请求、多消息、错误的 role、缺失/错误的 cache_control、以及错误的文本内容。
89-153: 流式与非流式响应测试完整。测试验证了
getClaudeStreamFlag的流判断逻辑(body.stream、Accept header、默认行为),以及buildClaudeWarmupInterceptResponse对两种响应模式的正确处理,包括响应头和拦截标记。src/app/[locale]/settings/config/_components/system-settings-form.tsx (2)
32-32: LGTM! 状态管理和表单提交逻辑正确。新设置字段的状态初始化、表单提交载荷、以及服务器响应后的状态刷新都遵循了现有模式。
Also applies to: 54-56, 75-75, 90-90
201-216: UI 组件实现规范。新的开关组件遵循了现有设置项的布局模式,正确使用了
htmlFor和id关联,并通过next-intl实现了国际化。src/app/v1/_lib/proxy/warmup-guard.ts (4)
14-28: Guard 逻辑正确,符合 pipeline 模式。三层早期返回条件清晰:仅针对 Claude 格式、需要启用系统设置、请求体必须匹配 warmup 特征。这遵循了 guard pipeline 的 fail-open 策略。
39-64: 非阻塞存储模式正确。使用
void ... .catch()模式确保 Session 存储失败不会阻塞 warmup 响应,符合 fail-open 策略。每个存储操作都有独立的错误处理和日志记录。
67-94: 日志记录条件较严格但合理。仅当认证成功、用户信息完整且存在 sessionId 时才写入日志。这意味着无 session 的 warmup 请求不会被记录,但考虑到 warmup 请求通常在正常会话中发生,这是可接受的设计。
74-75: 验证providerId: 0的外键约束兼容性。
messageRequest表中providerId字段的 Drizzle 关系定义使用references([providers.id]),但未包含显式的外键约束参数(如onDelete、onUpdate)。在不显式指定约束的情况下,Drizzle 不会在数据库层级强制创建外键约束,只为 ORM 提供类型信息。代码在四处使用
providerId: 0作为特殊值(本地抢答、敏感词拦截、测试、webhook),且在查询时明确过滤providerId > 0,说明设计已考虑此场景。然而,建议显式验证:
- 如果数据库已通过迁移添加了外键约束,需删除或修改该约束以允许
providerId: 0- 如果希望强制外键关系,需重新设计
providerId: 0的语义(如添加可空字段或分离日志表)src/app/v1/_lib/proxy/guard-pipeline.ts (2)
13-13: LGTM! Warmup guard 正确集成到 pipeline。
warmupstep 定义遵循了现有 guard 的模式,通过静态方法ProxyWarmupGuard.ensure(session)返回Response | null。Also applies to: 35-35, 96-101
181-181: Warmup 步骤位置合理。
warmup放在session之后、requestFilter和sensitive之前是正确的设计:
- 需要先建立 session 以获取 sessionId 用于日志记录
- Warmup 请求无需经过敏感词检查和速率限制
- Warmup 请求不需要选择 provider,直接本地响应
注意:
COUNT_TOKENS_PIPELINE不包含 warmup 步骤,这是正确的,因为 token 计数请求不应被拦截。src/repository/system-config.ts (2)
89-89: LGTM! 数据库操作全面覆盖新字段。
enableAnthropicWarmupIntercept字段在所有数据库操作中保持一致:fallback 设置、读取投影、插入返回、以及并发回退查询。Also applies to: 114-114, 147-147, 171-171
253-256: 更新逻辑正确处理可选字段。使用
!== undefined检查确保只有明确提供的字段才会被更新,符合部分更新(partial update)模式。返回投影也正确包含了新字段。Also applies to: 275-275
src/app/v1/_lib/proxy/warmup-intercept.ts (4)
1-8: 常量和导入定义合理使用
node:crypto生成随机 ID 是合适的选择,常量命名清晰且作用域正确。
33-64: Warmup 请求检测逻辑严谨类型守卫和检测逻辑实现正确:
- 严格验证单条 user 消息
- 准确匹配 "Warmup" 文本和 ephemeral cache_control
- 类型安全的属性访问避免了运行时错误
156-197: 拦截响应构建实现完整且正确该函数正确处理流式和非流式两种响应模式:
流式响应(lines 163-184):
- SSE 头部设置完整(Content-Type、Cache-Control、Connection)
- ReadableStream 实现适合小型固定响应(立即入队并关闭),无需复杂的背压处理
- 正确添加
x-cch-intercepted: warmup标记非流式响应(lines 186-196):
- JSON 格式响应配置正确
- 同样添加拦截标记头部
返回结构设计合理,同时提供客户端响应、存储用的响应体和响应头,便于日志记录和审计,完全符合 PR 目标。
66-78: 流式标志检测和消息 ID 生成逻辑正确
getClaudeStreamFlag正确优先检查显式 stream 标志,然后回退到 Accept 头检查buildWarmupMessageId使用加密安全的随机字节生成唯一 ID,格式为msg_[24位十六进制字符]Claude 官方文档未规定消息 ID 的固定格式或长度要求,并明确说明格式和长度可能会随时间变化。当前实现生成的 ID 格式与 Claude 实际 ID 模式(
msg_前缀)相符,无需调整。
| and( | ||
| eq(messageRequest.key, session.key.key), | ||
| isNull(messageRequest.deletedAt), | ||
| sql`${messageRequest.blockedBy} IS DISTINCT FROM 'warmup'`, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the code at line 306 in src/actions/my-usage.ts
head -320 src/actions/my-usage.ts | tail -30Repository: ding113/claude-code-hub
Length of output: 1314
🏁 Script executed:
# Let's also check the broader context around line 306
sed -n '290,320p' src/actions/my-usage.tsRepository: ding113/claude-code-hub
Length of output: 1359
🏁 Script executed:
# Check if the file exists and get its structure
wc -l src/actions/my-usage.tsRepository: ding113/claude-code-hub
Length of output: 94
🏁 Script executed:
# Search for the messageRequest table and blockedBy field in schema
rg "messageRequest|blockedBy|blocked_by" src/drizzle/schema.ts -A 3 -B 3Repository: ding113/claude-code-hub
Length of output: 2680
🏁 Script executed:
# Look for index definitions related to blocked_by
rg "index\(|\.index\(" src/drizzle/schema.ts -A 2 -B 2Repository: ding113/claude-code-hub
Length of output: 4394
🏁 Script executed:
# Check for any index() or uniqueIndex() definitions on the messageRequest table
fd "schema" src/drizzle --type fRepository: ding113/claude-code-hub
Length of output: 88
添加 blockedBy 列的索引以优化性能。
过滤逻辑正确使用了 IS DISTINCT FROM 操作符,能够正确排除 blockedBy = 'warmup' 的记录同时保留 NULL 值。但 blockedBy 列当前没有索引,在大数据量下会导致全表扫描。建议添加独立索引或将其包含在复合索引中,特别是对于频繁的统计查询。
🤖 Prompt for AI Agents
In src/actions/my-usage.ts around line 306, the query filters on blockedBy using
IS DISTINCT FROM 'warmup' but blockedBy lacks an index causing full table scans
on large datasets; add a database index for the blockedBy column (either a
standalone index or include blockedBy in the relevant composite index used by
these stats queries), implement the change via a migration script (or schema
change) so it’s applied in all environments, and ensure the migration is tested
on representative data; optionally consider a partial index that excludes the
'warmup' value if that better matches query patterns.
| and( | ||
| eq(messageRequest.key, key.key), | ||
| isNull(messageRequest.deletedAt), | ||
| sql`${messageRequest.blockedBy} IS DISTINCT FROM 'warmup'`, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's explore the repository structure and locate the key.ts file
find . -name "key.ts" -type f | head -20Repository: ding113/claude-code-hub
Length of output: 109
🏁 Script executed:
# Get the total lines in the file to understand its size
wc -l src/repository/key.tsRepository: ding113/claude-code-hub
Length of output: 92
🏁 Script executed:
# Let's examine the specific lines mentioned in the comment (566, 602, 688, 743)
sed -n '560,570p; 596,606p; 682,692p; 737,747p' src/repository/key.tsRepository: ding113/claude-code-hub
Length of output: 1496
🏁 Script executed:
# Now let's look at the findKeyUsageToday function around line 279
sed -n '270,310p' src/repository/key.tsRepository: ding113/claude-code-hub
Length of output: 1160
🏁 Script executed:
# And the findKeyUsageTodayBatch function around line 318
sed -n '310,360p' src/repository/key.tsRepository: ding113/claude-code-hub
Length of output: 1506
🏁 Script executed:
# Search for usages of findKeyUsageToday and findKeyUsageTodayBatch
rg -nP --type=ts 'findKeyUsageToday(Batch)?\(' --glob '!src/repository/key.ts' -C3Repository: ding113/claude-code-hub
Length of output: 891
🏁 Script executed:
# Look for PR-related files or comments about warmup handling
rg -nP --type=ts -i 'warmup' src/repository/key.ts | head -30Repository: ding113/claude-code-hub
Length of output: 502
🏁 Script executed:
# Get more context around the functions that do have warmup filtering to understand the pattern
sed -n '550,610p' src/repository/key.tsRepository: ding113/claude-code-hub
Length of output: 1885
🏁 Script executed:
# Check if there are any comments or documentation about warmup cost being zero
rg -nP --type=ts -C2 'cost.*warmup|warmup.*cost|costUsd.*0'Repository: ding113/claude-code-hub
Length of output: 19581
🏁 Script executed:
# Check for any TODO or FIXME comments related to warmup in key.ts
rg -nP --type=ts '(TODO|FIXME).*warmup|warmup.*(TODO|FIXME)' src/repository/key.tsRepository: ding113/claude-code-hub
Length of output: 49
🏁 Script executed:
# Look at the complete context of findKeyUsageToday and findKeyUsageTodayBatch for any comments
sed -n '275,365p' src/repository/key.tsRepository: ding113/claude-code-hub
Length of output: 2567
🏁 Script executed:
# Check if there are any test files that might clarify the intended behavior
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "findKeyUsageToday" 2>/dev/nullRepository: ding113/claude-code-hub
Length of output: 51
在 findKeyUsageToday 和 findKeyUsageTodayBatch 中添加 warmup 过滤条件,确保统计口径一致。
findKeyUsageToday(第 279 行)和 findKeyUsageTodayBatch(第 318 行)的 leftJoin 条件中缺少对 warmup 请求的过滤。虽然 warmup 请求的 costUsd=0,但为了与其他统计函数保持一致(见 message.ts 中的明确说明"统计口径:排除 warmup"),应添加相同的过滤条件。
在 findKeyUsageToday 的 leftJoin 条件中(第 290-296 行)添加:
.leftJoin(
messageRequest,
and(
eq(messageRequest.key, keys.key),
isNull(messageRequest.deletedAt),
+ sql`${messageRequest.blockedBy} IS DISTINCT FROM 'warmup'`,
gte(messageRequest.createdAt, today),
lt(messageRequest.createdAt, tomorrow)
)
)在 findKeyUsageTodayBatch 的 leftJoin 条件中(第 330-336 行)添加相同的过滤。
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/repository/key.ts around lines 279-296 (findKeyUsageToday) and 318-336
(findKeyUsageTodayBatch), the leftJoin conditions currently omit filtering out
warmup requests; add the clause sql`${messageRequest.blockedBy} IS DISTINCT FROM
'warmup'` to the leftJoin predicates in both functions so warmup requests are
excluded (matching the statistical rules described in message.ts). Ensure the
same exact filter is applied in both leftJoin conditions.
There was a problem hiding this comment.
Code Review Summary
This PR implements optional interception of Anthropic warmup requests. After comprehensive review through 6 specialized perspectives (Comments, Tests, Error Handling, Types, General Code, and Simplification), no significant issues were identified.
PR Size: XL
- Lines changed: 699 (665 additions + 34 deletions)
- Files changed: 37
Note: This is a large PR that touches multiple layers (proxy guards, database queries, UI, i18n). Consider breaking similar features into smaller PRs in the future (e.g., core logic → statistics filtering → UI updates).
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean
- Error handling - Clean (intentional fail-open design for logging)
- Type safety - Clean
- Documentation accuracy - Clean
- Test coverage - Adequate (11 test cases covering detection, response building, streaming/non-streaming)
- Code clarity - Good
Key Strengths
- Comprehensive Filtering: Warmup requests are consistently excluded from all statistics queries using
blocked_by IS DISTINCT FROM 'warmup'(17 instances in statistics.ts, plus leaderboard.ts, overview.ts, message.ts, my-usage.ts, key.ts) - Well-Tested: Covers edge cases for warmup detection (wrong role, missing cache_control, multiple messages, etc.)
- Fail-Safe Design: Feature is opt-in (default disabled), reducing deployment risk
- Complete i18n: All 5 locales updated with proper labels
- Audit Trail: Warmup requests are logged with
provider_id=0,cost_usd=0, andblocked_by=warmupfor visibility
Architecture Notes
- Guard is correctly positioned after session initialization but before request filtering in the pipeline
- Both streaming (SSE) and non-streaming (JSON) responses are properly formatted according to Claude API spec
- Session storage failures are intentionally non-blocking (logged but don't fail the request)
Automated review by Claude AI - Examined 37 files across proxy guards, database repositories, UI components, and tests
| "version", | ||
| "probe", | ||
| "session", | ||
| "warmup", |
There was a problem hiding this comment.
[HIGH] [SECURITY-VULNERABILITY] Warmup intercept bypasses rate limiting on the chat pipeline
Evidence (src/app/v1/_lib/proxy/guard-pipeline.ts:181):
steps: [
// ...
"session",
"warmup", // returns early before rate limiting
"requestFilter",
"sensitive",
"rateLimit",
// ...
]Why this is a problem: ProxyWarmupGuard.ensure() can return a 200 Response before ProxyRateLimitGuard.ensure() runs, so an authenticated client can repeatedly send a warmup-shaped Claude request to bypass concurrent-session / RPM protection and generate unbounded local work (Redis session writes + DB inserts). This creates an easy DoS / quota-bypass vector on the main chat endpoint.
Suggested fix:
// Keep `/v1/messages` requests rate-limited: run warmup after `rateLimit`
steps: [
// ...
"session",
"requestFilter",
"sensitive",
"rateLimit",
"warmup",
// ...
]There was a problem hiding this comment.
Code Review Summary
This PR introduces an optional Claude/Anthropic warmup intercept path plus broad analytics/UI updates to exclude warmup entries from billing/aggregations. The main risk is that the new intercept currently short-circuits before rate limiting in the chat pipeline, creating an unbounded request path on /v1/messages.
PR Size: XL
- Lines changed: 699
- Files changed: 37
- Split suggestions: (1) proxy warmup intercept + tests, (2) DB/schema + repository aggregation changes, (3) dashboard/UI + i18n strings.
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 0 | 0 |
| Security | 0 | 1 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Critical Issues (Must Fix)
None.
High Priority Issues (Should Fix)
src/app/v1/_lib/proxy/guard-pipeline.ts:181[HIGH] [SECURITY-VULNERABILITY] Warmup intercept bypassesProxyRateLimitGuardon the chat pipeline (Confidence: 92).
Review Coverage
- Logic and correctness
- Security (OWASP Top 10)
- Error handling
- Type safety
- Documentation accuracy
- Test coverage
- Code clarity
Automated review by Codex AI
There was a problem hiding this comment.
- Reviewed PR
#526and applied labelsize/XL. - Posted 1 inline review comment: warmup intercept can short-circuit before rate limiting in
src/app/v1/_lib/proxy/guard-pipeline.ts:181, enabling an authenticated DoS / quota-bypass path on/v1/messages(suggested movingwarmupafterrateLimitinCHAT_PIPELINE). - Submitted the required summary review on the PR, including split suggestions for this XL change set.
概述 / Summary
可选拦截 Anthropic Warmup 请求,避免不必要的上游调用与 token 浪费。
Related PRs:
背景:Claude/Anthropic 请求偶发 Warmup(user.content 含 Warmup + ephemeral),会产生不必要的上游请求与 token 浪费。
改动:
验证:bun run lint/typecheck/test/build/test --coverage
Greptile Summary
This PR implements optional interception of Anthropic warmup requests to prevent unnecessary upstream API calls and token waste. When enabled via the
enableAnthropicWarmupInterceptsystem setting, the proxy detects Claude warmup requests (identified byuser.contentcontaining "Warmup" withcache_control.type="ephemeral") and returns a local mock response instead of forwarding to upstream providers.Key Changes:
warmup-intercept.tswith support for both streaming (SSE) and non-streaming (JSON) formatsProxyWarmupGuardinto the guard pipeline after session initialization, before request filteringprovider_id=0,cost_usd=0, andblocked_by=warmupfor audit purposesblocked_by IS DISTINCT FROM 'warmup'filtersFILTERclauses to exclude warmup from statistics while maintaining session existence checks withrequestCountAllIF NOT EXISTSclause and default false valueConfidence Score: 5/5
Important Files Changed
Sequence Diagram
sequenceDiagram participant Client participant GuardPipeline participant WarmupGuard participant SystemSettings participant WarmupIntercept participant SessionManager participant Database participant ProviderResolver Client->>GuardPipeline: POST /v1/messages (Claude format) GuardPipeline->>GuardPipeline: Execute auth, client, model, version, probe, session steps GuardPipeline->>WarmupGuard: ensure(session) WarmupGuard->>WarmupGuard: Check if originalFormat === "claude" alt originalFormat is not claude WarmupGuard-->>GuardPipeline: return null (skip) else originalFormat is claude WarmupGuard->>SystemSettings: getCachedSystemSettings() SystemSettings-->>WarmupGuard: settings alt enableAnthropicWarmupIntercept is false WarmupGuard-->>GuardPipeline: return null (skip) else enableAnthropicWarmupIntercept is true WarmupGuard->>WarmupIntercept: isClaudeWarmupRequestBody(request.message) WarmupIntercept-->>WarmupGuard: boolean alt Not a warmup request WarmupGuard-->>GuardPipeline: return null (continue) else Is warmup request WarmupGuard->>WarmupIntercept: getClaudeStreamFlag(message, accept header) WarmupIntercept-->>WarmupGuard: stream flag WarmupGuard->>WarmupIntercept: buildClaudeWarmupInterceptResponse({model, stream}) WarmupIntercept->>WarmupIntercept: buildWarmupMessageResponse() alt stream is true WarmupIntercept->>WarmupIntercept: buildClaudeWarmupSse() WarmupIntercept-->>WarmupGuard: {response (SSE), responseBodyForStore, responseHeaders} else stream is false WarmupIntercept-->>WarmupGuard: {response (JSON), responseBodyForStore, responseHeaders} end WarmupGuard->>SessionManager: storeSessionResponse(sessionId, body, sequence) WarmupGuard->>SessionManager: storeSessionResponseHeaders(sessionId, headers, sequence) WarmupGuard->>SessionManager: storeSessionRequestHeaders(sessionId, headers, sequence) WarmupGuard->>Database: insert messageRequest Note right of Database: providerId=0<br/>costUsd=0<br/>blocked_by=warmup<br/>statusCode=200 WarmupGuard-->>GuardPipeline: return response (short-circuit) end end end GuardPipeline-->>Client: Return response (if intercepted) opt If not intercepted GuardPipeline->>GuardPipeline: Continue with requestFilter, sensitive, rateLimit steps GuardPipeline->>ProviderResolver: Select provider and forward request ProviderResolver-->>Client: Upstream response endDescription enhanced by Claude AI