Files
anthropic-proxy/internal/ratelimit/usage.go
T
Alexander fac9578975 feat(ratelimit): track per-window token usage and utilization
Poll /api/oauth/usage every 5 min and extract utilization from
/v1/messages response headers for real-time updates. Track proxy
tokens in/out per rate limit window (5h/7d), resetting on window
change. Expose as OTel observable gauges for Grafana dashboards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:51:31 +02:00

63 lines
1.6 KiB
Go

package ratelimit
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const usageURL = "https://api.anthropic.com/api/oauth/usage"
type RateLimit struct {
Utilization *float64 `json:"utilization"` // 0-100
ResetsAt *string `json:"resets_at"` // ISO 8601
}
type ExtraUsage struct {
IsEnabled bool `json:"is_enabled"`
MonthlyLimit *float64 `json:"monthly_limit"`
UsedCredits *float64 `json:"used_credits"`
Utilization *float64 `json:"utilization"`
}
type UsageResponse struct {
FiveHour *RateLimit `json:"five_hour"`
SevenDay *RateLimit `json:"seven_day"`
SevenDaySonnet *RateLimit `json:"seven_day_sonnet"`
ExtraUsage *ExtraUsage `json:"extra_usage"`
}
func fetchUsage(ctx context.Context, token string) (*UsageResponse, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, usageURL, nil)
if err != nil {
return nil, fmt.Errorf("build request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("anthropic-beta", "oauth-2025-04-20")
req.Header.Set("User-Agent", "claude-cli/2.1.92")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("usage returned %d: %s", resp.StatusCode, string(body))
}
var usage UsageResponse
if err := json.Unmarshal(body, &usage); err != nil {
return nil, fmt.Errorf("decode: %w", err)
}
return &usage, nil
}