Files
anthropic-proxy/internal/auth/selector.go
T
Alexander c4c1d4daa4 Anthropic API proxy with OAuth credential rotation and Claude Code fingerprinting
Sniffs a real Claude Code request on startup to capture exact HTTP headers,
then replays them for all proxied requests. Injects the billing header with
per-request SHA256 fingerprint into the system prompt. Uses utls with Chrome
TLS fingerprint to pass Cloudflare's bot detection on api.anthropic.com.

Supports both streaming (SSE) and non-streaming modes, round-robin credential
selection with automatic failover, and loading OAuth tokens from both
cli-proxy-api auth files and native ~/.claude/.credentials.json.
2026-04-09 21:05:32 +02:00

80 lines
1.6 KiB
Go

package auth
import (
"context"
"fmt"
"log"
"sync"
"time"
)
type Pool struct {
creds []*Credential
cursor int
mu sync.Mutex
}
func NewPool(creds []*Credential) *Pool {
return &Pool{creds: creds}
}
func (p *Pool) Pick() (*Credential, error) {
p.mu.Lock()
defer p.mu.Unlock()
n := len(p.creds)
if n == 0 {
return nil, fmt.Errorf("no credentials available")
}
for i := 0; i < n; i++ {
idx := (p.cursor + i) % n
cred := p.creds[idx]
if !cred.IsOnCooldown() {
p.cursor = (idx + 1) % n
return cred, nil
}
}
return nil, fmt.Errorf("all %d credentials are on cooldown", n)
}
func (p *Pool) MarkFailure(cred *Credential, statusCode int) {
switch {
case statusCode == 429:
cred.SetCooldown(30 * time.Second)
case statusCode >= 500:
cred.SetCooldown(5 * time.Second)
}
}
func (p *Pool) MarkSuccess(cred *Credential) {
cred.mu.Lock()
defer cred.mu.Unlock()
cred.CooldownUntil = time.Time{}
}
func (p *Pool) RefreshExpiring(ctx context.Context) {
p.mu.Lock()
creds := make([]*Credential, len(p.creds))
copy(creds, p.creds)
p.mu.Unlock()
threshold := time.Now().Add(5 * time.Minute)
for _, cred := range creds {
cred.mu.Lock()
needsRefresh := cred.ExpiresAt.Before(threshold)
email := cred.Email
cred.mu.Unlock()
if needsRefresh {
log.Printf("refreshing token for %s (expires %s)", email, cred.ExpiresAt.Format(time.RFC3339))
if err := RefreshToken(ctx, cred); err != nil {
log.Printf("failed to refresh token for %s: %v", email, err)
} else {
log.Printf("refreshed token for %s, new expiry %s", email, cred.ExpiresAt.Format(time.RFC3339))
}
}
}
}