Skip to content

feat(api-keys): add all-model weekly cost limit - #1186

Open
sxin0 wants to merge 4 commits into
Wei-Shaw:mainfrom
sxin0:feat/weekly-cost-limit-all-models
Open

feat(api-keys): add all-model weekly cost limit#1186
sxin0 wants to merge 4 commits into
Wei-Shaw:mainfrom
sxin0:feat/weekly-cost-limit-all-models

Conversation

@sxin0

@sxin0 sxin0 commented May 19, 2026

Copy link
Copy Markdown

Summary

  • 新增 weeklyCostLimit 字段,对 API Key 的所有模型请求(Claude/GPT/Gemini/Bedrock/Droid 等)统一按周限额
  • 原有 weeklyOpusCostLimit(仅 Claude 生效)行为保持不变,两个限额独立校验
  • 共享现有 weeklyResetDay / weeklyResetHour 重置周期,前端无需新增重置选择器
  • 启动回填:服务启动时根据历史日费用统计自动回填当前周期,重启后限额不归零(与 Opus 周限额行为一致)

Motivation

当前 weeklyOpusCostLimit 只对 Claude 系列模型 + claude-official / claude-console / ccr 三种账户类型生效(apiKeyService.recordOpusCost 双重过滤)。GPT、Gemini、Bedrock 等模型既不累加也不被校验,无法对 API Key 设置跨模型的统一周预算。

Changes

Backend

  • src/models/redis.js: 新增 getWeeklyCost / incrementWeeklyCost / setWeeklyCost,独立 Redis key usage:weekly:{keyId}:{period} + usage:real:weekly:...,14 天 TTL;batchGetApiKeyStats pipeline 扩展第 15 个字段
  • src/services/apiKeyService.js: 新增 recordWeeklyCost(无模型/账户过滤),在 recordUsage / recordUsageWithDetails 中追加调用;createApiKey/validateApiKey/字段白名单/3 个 enrichment 路径全部镜像
  • src/services/weeklyClaudeCostInitService.js: 单次 SCAN 双写两个 bucket
    • Opus bucket 保留原有 isClaudeFamilyModel + OPUS_ACCOUNT_TYPES 过滤,行为完全不变
    • 全模型 bucket 新增 inferAnyAccountType 覆盖 openai/gemini/bedrock/azure-openai/droid;推断不到时退回 realCost(serviceRatesService.getService 按 model fallback)
    • backfillSingleKey 同样双写(reset day/hour 变更时两个 bucket 一起回填)
    • done 标记 key 从 init:weekly_opus_cost:<date>:done 升级为 init:weekly_cost:<date>:done,确保升级后第一次启动会重新回填把全模型 bucket 也填上
    • 调用入口(app.jsadmin/apiKeys.js)函数名保持不变
  • src/middleware/auth.js: Claude 周限制检查之后追加全模型检查,超限返回 402 weekly_cost_limit_exceeded
  • src/routes/admin/apiKeys.js: 8 处 create/update/batchUpdate/getKeyById 全部支持 weeklyCostLimit,校验非负数
  • src/routes/apiStats.js: limits payload 加 weeklyCostLimit / weeklyCost

Frontend

  • Create/Edit/BatchEdit API Key Modal: 新增「全模型周费用限制」输入框(与 Claude 限额共享重置日/时)
  • UsageDetailModal / LimitConfig / ApiKeysView: 新增独立进度条

Design Notes

  • Claude 请求同时计入两个 bucket(Opus + 全模型),触发任一上限均拦截
  • weeklyOpusCostLimit 字段语义和存储不变,完全向后兼容
  • 启动回填扫描的是同一批 usage:*:model:daily:* daily usage key,单次 SCAN 同时填两个 bucket,扫描开销不增

Test Plan

  • `npm run lint` 通过
  • `npm test` 通过
  • 端到端:创建 Key 设置 `weeklyCostLimit=$0.01`,发 GPT 请求第一次成功累加,第二次返回 402 `weekly_cost_limit_exceeded`
  • 端到端:发 Claude 请求验证 `usage:opus:weekly:` 和 `usage:weekly:` 两个 Redis key 同时累加
  • 回填:手动删除 `usage:weekly:` 和 `init:weekly_cost::done` 标记后重启,验证 `usage:weekly:*` 被根据 daily 使用数据正确填回
  • 回填:admin 接口改 `weeklyResetDay/Hour` 后验证 `usage:opus:weekly:` 和 `usage:weekly:` 两个 key 都被刷新
  • UI:Create/Edit Modal 看到新输入框,保存后重新打开值还在;UsageDetailModal/LimitConfig 看到两个独立进度条

新增 weeklyCostLimit 字段,对 API Key 的所有模型请求(含 Claude/GPT/Gemini/Bedrock 等)
统一计费并按周限额。原有 weeklyOpusCostLimit 仅对 Claude 模型生效的行为保持不变,
两个限额独立校验,共享 weeklyResetDay/Hour 重置周期。

- redis: 新增 getWeeklyCost / incrementWeeklyCost / setWeeklyCost (usage:weekly:<id>:<period>)
- apiKeyService: recordWeeklyCost 无模型/账户过滤,在 recordUsage* 中追加调用
- auth: Claude 周限制检查后追加全模型检查,超限返回 402 weekly_cost_limit_exceeded
- admin/apiKeys: create/update/batchUpdate/getKeyById 全部支持 weeklyCostLimit
- 前端 6 个组件镜像 weeklyOpusCostLimit UI,新增独立进度条和输入框
weeklyCost (全模型周费用) 同样需要在重启后立即可用,否则 weeklyCostLimit 会被
当前周已经发生的请求绕过。改造 weeklyClaudeCostInitService 在单次 SCAN 中
同时回填两个 bucket:

- 拿掉准备阶段的 isClaudeFamilyModel 过滤,全模型条目都进入处理
- Opus bucket 保留原有 isClaudeFamilyModel + OPUS_ACCOUNT_TYPES 过滤,行为不变
- 全模型 bucket 不做过滤,新增 inferAnyAccountType 覆盖 openai/gemini/bedrock/droid
  等账户类型;推断不到 account 时退回 realCost(serviceRatesService.getService
  会按 model fallback)
- backfillSingleKey 同样双写 setWeeklyOpusCost + setWeeklyCost
- done 标记 key 从 init:weekly_opus_cost 升级到 init:weekly_cost,确保升级后
  第一次启动会重新回填,把两个 bucket 都填上
- app.js / admin/apiKeys.js 调用入口(函数名 backfillCurrentWeekClaudeCosts /
  backfillSingleKey)保持不变
@sxin0

sxin0 commented May 27, 2026

Copy link
Copy Markdown
Author

@Wei-Shaw

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants