mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-04 03:31:21 +00:00
基于 Claude Code v2.1.88 源码分析,修复多个可被 Anthropic 检测的差距: - 实现消息指纹算法(SHA256 盐值 + 字符索引),替代随机 buildHash - billing header cc_version 从设备 profile 动态取版本号,不再硬编码 - billing header cc_entrypoint 从客户端 UA 解析,支持 cli/vscode/local-agent - billing header 新增 cc_workload 支持(通过 X-CPA-Claude-Workload 头传入) - 新增 X-Claude-Code-Session-Id 头(每 apiKey 缓存 UUID,TTL=1h) - 新增 x-client-request-id 头(仅 api.anthropic.com,每请求 UUID) - 补全 4 个缺失的 beta flags(structured-outputs/fast-mode/redact-thinking/token-efficient-tools) - OAuth scope 对齐 Claude Code 2.1.88(移除 org:create_api_key,添加 sessions/mcp/file_upload) - Anthropic-Dangerous-Direct-Browser-Access 仅在 API key 模式发送 - 响应头网关指纹清洗(剥离 litellm/helicone/portkey/cloudflare/kong/braintrust 前缀头)
93 lines
1.9 KiB
Go
93 lines
1.9 KiB
Go
package helps
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type sessionIDCacheEntry struct {
|
|
value string
|
|
expire time.Time
|
|
}
|
|
|
|
var (
|
|
sessionIDCache = make(map[string]sessionIDCacheEntry)
|
|
sessionIDCacheMu sync.RWMutex
|
|
sessionIDCacheCleanupOnce sync.Once
|
|
)
|
|
|
|
const (
|
|
sessionIDTTL = time.Hour
|
|
sessionIDCacheCleanupPeriod = 15 * time.Minute
|
|
)
|
|
|
|
func startSessionIDCacheCleanup() {
|
|
go func() {
|
|
ticker := time.NewTicker(sessionIDCacheCleanupPeriod)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
purgeExpiredSessionIDs()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func purgeExpiredSessionIDs() {
|
|
now := time.Now()
|
|
sessionIDCacheMu.Lock()
|
|
for key, entry := range sessionIDCache {
|
|
if !entry.expire.After(now) {
|
|
delete(sessionIDCache, key)
|
|
}
|
|
}
|
|
sessionIDCacheMu.Unlock()
|
|
}
|
|
|
|
func sessionIDCacheKey(apiKey string) string {
|
|
sum := sha256.Sum256([]byte(apiKey))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
// CachedSessionID returns a stable session UUID per apiKey, refreshing the TTL on each access.
|
|
func CachedSessionID(apiKey string) string {
|
|
if apiKey == "" {
|
|
return uuid.New().String()
|
|
}
|
|
|
|
sessionIDCacheCleanupOnce.Do(startSessionIDCacheCleanup)
|
|
|
|
key := sessionIDCacheKey(apiKey)
|
|
now := time.Now()
|
|
|
|
sessionIDCacheMu.RLock()
|
|
entry, ok := sessionIDCache[key]
|
|
valid := ok && entry.value != "" && entry.expire.After(now)
|
|
sessionIDCacheMu.RUnlock()
|
|
if valid {
|
|
sessionIDCacheMu.Lock()
|
|
entry = sessionIDCache[key]
|
|
if entry.value != "" && entry.expire.After(now) {
|
|
entry.expire = now.Add(sessionIDTTL)
|
|
sessionIDCache[key] = entry
|
|
sessionIDCacheMu.Unlock()
|
|
return entry.value
|
|
}
|
|
sessionIDCacheMu.Unlock()
|
|
}
|
|
|
|
newID := uuid.New().String()
|
|
|
|
sessionIDCacheMu.Lock()
|
|
entry, ok = sessionIDCache[key]
|
|
if !ok || entry.value == "" || !entry.expire.After(now) {
|
|
entry.value = newID
|
|
}
|
|
entry.expire = now.Add(sessionIDTTL)
|
|
sessionIDCache[key] = entry
|
|
sessionIDCacheMu.Unlock()
|
|
return entry.value
|
|
}
|