0df28e9dd8
- Unify duplicate uTLS transports into shared internal/transport package - Extract shared version constant into internal/version - Move LoadDefaultCredentials from config to auth (remove config→auth import) - Deduplicate handler.go: extract telemetry/error helpers (324→268 lines) - Break up main.go::run() into initCredential/initEmbedded - Eliminate logging.Config duplication (use config.LoggingConfig directly) - Extract logWriter to embedded/log.go, SSE fixtures to consts in sniff.go - Use uTLS client for usage polling (consistent TLS fingerprint) - Handle sjson.SetBytes errors in sanitize.go instead of silently swallowing - Document reverse-engineered magic values in billing.go - Unexport Credential.CooldownUntil (internal state) - Replace hardcoded auth bypass paths with map in server.go
68 lines
1.8 KiB
Go
68 lines
1.8 KiB
Go
package ratelimit
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/fujin/anthropic-proxy/internal/transport"
|
|
"github.com/fujin/anthropic-proxy/internal/version"
|
|
)
|
|
|
|
var usageClient = transport.NewHTTPClient(10 * time.Second)
|
|
|
|
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/"+version.ClaudeCodeFallback)
|
|
|
|
resp, err := usageClient.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
|
|
}
|