Files
anthropic-proxy/internal/proxy/billing.go
T
Alexander 0df28e9dd8 refactor: modularize codebase — deduplicate, extract, clean up
- 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
2026-04-15 11:01:29 +02:00

95 lines
2.6 KiB
Go

package proxy
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"unicode/utf16"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// fingerprintSalt is the fixed salt used by Claude Code for billing header
// fingerprint computation. Extracted from the Claude Code CLI source.
const fingerprintSalt = "59cf53e54c78"
func computeFingerprint(firstUserMessage string, version string) string {
// UTF-16 character indices sampled from the first user message, matching
// the Claude Code CLI's fingerprinting algorithm.
indices := []int{4, 7, 20}
runes := utf16.Encode([]rune(firstUserMessage))
var chars string
for _, i := range indices {
if i < len(runes) {
chars += string(rune(runes[i]))
} else {
chars += "0"
}
}
input := fingerprintSalt + chars + version
hash := sha256.Sum256([]byte(input))
return hex.EncodeToString(hash[:])[:3]
}
func extractFirstUserMessage(body []byte) string {
messages := gjson.GetBytes(body, "messages")
if !messages.Exists() || !messages.IsArray() {
return ""
}
for _, msg := range messages.Array() {
if msg.Get("role").String() != "user" {
continue
}
content := msg.Get("content")
if content.Type == gjson.String {
return content.String()
}
if content.IsArray() {
for _, block := range content.Array() {
if block.Get("type").String() == "text" {
return block.Get("text").String()
}
}
}
break
}
return ""
}
func buildBillingHeader(body []byte, version string) string {
userMsg := extractFirstUserMessage(body)
fp := computeFingerprint(userMsg, version)
return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=cli; cch=00000;", version, fp)
}
func injectBillingHeader(body []byte, version string) []byte {
header := buildBillingHeader(body, version)
billingBlock := map[string]interface{}{"type": "text", "text": header}
billingJSON, _ := json.Marshal(billingBlock)
existing := gjson.GetBytes(body, "system")
if !existing.Exists() {
body, _ = sjson.SetRawBytes(body, "system", []byte("["+string(billingJSON)+"]"))
return body
}
if existing.IsArray() {
items := make([]json.RawMessage, 0, len(existing.Array())+1)
items = append(items, billingJSON)
for _, item := range existing.Array() {
items = append(items, json.RawMessage(item.Raw))
}
systemJSON, _ := json.Marshal(items)
body, _ = sjson.SetRawBytes(body, "system", systemJSON)
return body
}
origText := existing.String()
origBlock := map[string]string{"type": "text", "text": origText}
origJSON, _ := json.Marshal(origBlock)
body, _ = sjson.SetRawBytes(body, "system", []byte("["+string(billingJSON)+","+string(origJSON)+"]"))
return body
}