feat: Anthropic 账号被动用量采样,页面默认展示被动数据

从上游 /v1/messages 响应头被动采集 5h/7d utilization 并存储到
Account.Extra,页面加载时直接读取本地数据而非调用外部 Usage API。
用户可点击"查询"按钮主动拉取最新数据,主动查询结果自动回写被动缓存。

后端:
- UpdateSessionWindow 合并采集 5h + 7d headers 为单次 DB 写入
- 新增 GetPassiveUsage 从 Extra 构建 UsageInfo (复用 estimateSetupTokenUsage)
- GetUsage 主动查询后 syncActiveToPassive 回写被动缓存
- passive_usage_ 前缀注册为 scheduler-neutral

前端:
- Anthropic 账号 mount/refresh 默认 source=passive
- 新增"被动采样"标签和"查询"按钮 (带 loading 动画)
This commit is contained in:
shaw
2026-03-19 17:29:21 +08:00
parent a6764e82f2
commit 525cdb8830
9 changed files with 183 additions and 17 deletions

View File

@@ -1110,10 +1110,13 @@ func (s *RateLimitService) UpdateSessionWindow(ctx context.Context, account *Acc
slog.Info("account_session_window_initialized", "account_id", account.ID, "window_start", start, "window_end", end, "status", status)
}
// 窗口重置时清除旧的 utilization避免残留上个窗口的数据
// 窗口重置时清除旧的 utilization 和被动采样数据,避免残留上个窗口的数据
if windowEnd != nil && needInitWindow {
_ = s.accountRepo.UpdateExtra(ctx, account.ID, map[string]any{
"session_window_utilization": nil,
"session_window_utilization": nil,
"passive_usage_7d_utilization": nil,
"passive_usage_7d_reset": nil,
"passive_usage_sampled_at": nil,
})
}
@@ -1121,14 +1124,33 @@ func (s *RateLimitService) UpdateSessionWindow(ctx context.Context, account *Acc
slog.Warn("session_window_update_failed", "account_id", account.ID, "error", err)
}
// 存储真实的 utilization 值0-1 小数),供 estimateSetupTokenUsage 使用
// 被动采样:从响应头收集 5h + 7d utilization合并为一次 DB 写入
extraUpdates := make(map[string]any, 4)
// 5h utilization0-1 小数),供 estimateSetupTokenUsage 使用
if utilStr := headers.Get("anthropic-ratelimit-unified-5h-utilization"); utilStr != "" {
if util, err := strconv.ParseFloat(utilStr, 64); err == nil {
if err := s.accountRepo.UpdateExtra(ctx, account.ID, map[string]any{
"session_window_utilization": util,
}); err != nil {
slog.Warn("session_window_utilization_update_failed", "account_id", account.ID, "error", err)
extraUpdates["session_window_utilization"] = util
}
}
// 7d utilization0-1 小数)
if utilStr := headers.Get("anthropic-ratelimit-unified-7d-utilization"); utilStr != "" {
if util, err := strconv.ParseFloat(utilStr, 64); err == nil {
extraUpdates["passive_usage_7d_utilization"] = util
}
}
// 7d reset timestamp
if resetStr := headers.Get("anthropic-ratelimit-unified-7d-reset"); resetStr != "" {
if ts, err := strconv.ParseInt(resetStr, 10, 64); err == nil {
if ts > 1e11 {
ts = ts / 1000
}
extraUpdates["passive_usage_7d_reset"] = ts
}
}
if len(extraUpdates) > 0 {
extraUpdates["passive_usage_sampled_at"] = time.Now().UTC().Format(time.RFC3339)
if err := s.accountRepo.UpdateExtra(ctx, account.ID, extraUpdates); err != nil {
slog.Warn("passive_usage_update_failed", "account_id", account.ID, "error", err)
}
}