mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-12 17:24:13 +00:00
Antigravity 的 Claude thinking signature 处理新增 cache/bypass 双模式, 并为 bypass 模式实现按 SIGNATURE-CHANNEL-SPEC.md 的签名校验。 新增 antigravity-signature-cache-enabled 配置项(默认 true): - cache mode(true):使用服务端缓存的签名,行为与原有逻辑完全一致 - bypass mode(false):直接使用客户端提供的签名,经过校验和归一化 支持配置热重载,运行时可切换模式。 校验流程: 1. 剥离历史 cache-mode 的 'modelGroup#' 前缀(如 claude#Exxxx → Exxxx) 2. 首字符必须为 'E'(单层编码)或 'R'(双层编码),否则拒绝 3. R 开头:base64 解码 → 内层必须以 'E' 开头 → 继续单层校验 4. E 开头:base64 解码 → 首字节必须为 0x12(Claude protobuf 标识) 5. 所有合法签名归一化为 R 形式(双层 base64)发往 Antigravity 后端 非法签名处理策略: - 非严格模式(默认):translator 静默丢弃无签名的 thinking block - 严格模式(antigravity-signature-bypass-strict: true): executor 层在请求发往上游前直接返回 HTTP 400 按 SIGNATURE-CHANNEL-SPEC.md 解析 Claude 签名的完整 protobuf 结构: - Top-level Field 2(容器)→ Field 1(渠道块) - 渠道块提取:channel_id (Field 1)、infrastructure (Field 2)、 model_text (Field 6)、field7 (Field 7) - 计算 routing_class、infrastructure_class、schema_features - 使用 google.golang.org/protobuf/encoding/protowire 解析 - resolveThinkingSignature 拆分为 resolveCacheModeSignature / resolveBypassModeSignature - hasResolvedThinkingSignature:mode-aware 签名有效性判断 (cache: len>=50 via HasValidSignature,bypass: non-empty) - validateAntigravityRequestSignatures:executor 预检, 仅在 bypass + strict 模式下拦截非法签名返回 400 - 响应侧签名缓存逻辑与 cache mode 集成 - Cache mode 行为完全保留:无 '#' 前缀的原生签名静默丢弃
235 lines
6.1 KiB
Go
235 lines
6.1 KiB
Go
package cache
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// SignatureEntry holds a cached thinking signature with timestamp
|
|
type SignatureEntry struct {
|
|
Signature string
|
|
Timestamp time.Time
|
|
}
|
|
|
|
const (
|
|
// SignatureCacheTTL is how long signatures are valid
|
|
SignatureCacheTTL = 3 * time.Hour
|
|
|
|
// SignatureTextHashLen is the length of the hash key (16 hex chars = 64-bit key space)
|
|
SignatureTextHashLen = 16
|
|
|
|
// MinValidSignatureLen is the minimum length for a signature to be considered valid
|
|
MinValidSignatureLen = 50
|
|
|
|
// CacheCleanupInterval controls how often stale entries are purged
|
|
CacheCleanupInterval = 10 * time.Minute
|
|
)
|
|
|
|
// signatureCache stores signatures by model group -> textHash -> SignatureEntry
|
|
var signatureCache sync.Map
|
|
|
|
// cacheCleanupOnce ensures the background cleanup goroutine starts only once
|
|
var cacheCleanupOnce sync.Once
|
|
|
|
// groupCache is the inner map type
|
|
type groupCache struct {
|
|
mu sync.RWMutex
|
|
entries map[string]SignatureEntry
|
|
}
|
|
|
|
// hashText creates a stable, Unicode-safe key from text content
|
|
func hashText(text string) string {
|
|
h := sha256.Sum256([]byte(text))
|
|
return hex.EncodeToString(h[:])[:SignatureTextHashLen]
|
|
}
|
|
|
|
// getOrCreateGroupCache gets or creates a cache bucket for a model group
|
|
func getOrCreateGroupCache(groupKey string) *groupCache {
|
|
// Start background cleanup on first access
|
|
cacheCleanupOnce.Do(startCacheCleanup)
|
|
|
|
if val, ok := signatureCache.Load(groupKey); ok {
|
|
return val.(*groupCache)
|
|
}
|
|
sc := &groupCache{entries: make(map[string]SignatureEntry)}
|
|
actual, _ := signatureCache.LoadOrStore(groupKey, sc)
|
|
return actual.(*groupCache)
|
|
}
|
|
|
|
// startCacheCleanup launches a background goroutine that periodically
|
|
// removes caches where all entries have expired.
|
|
func startCacheCleanup() {
|
|
go func() {
|
|
ticker := time.NewTicker(CacheCleanupInterval)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
purgeExpiredCaches()
|
|
}
|
|
}()
|
|
}
|
|
|
|
// purgeExpiredCaches removes caches with no valid (non-expired) entries.
|
|
func purgeExpiredCaches() {
|
|
now := time.Now()
|
|
signatureCache.Range(func(key, value any) bool {
|
|
sc := value.(*groupCache)
|
|
sc.mu.Lock()
|
|
// Remove expired entries
|
|
for k, entry := range sc.entries {
|
|
if now.Sub(entry.Timestamp) > SignatureCacheTTL {
|
|
delete(sc.entries, k)
|
|
}
|
|
}
|
|
isEmpty := len(sc.entries) == 0
|
|
sc.mu.Unlock()
|
|
// Remove cache bucket if empty
|
|
if isEmpty {
|
|
signatureCache.Delete(key)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
// CacheSignature stores a thinking signature for a given model group and text.
|
|
// Used for Claude models that require signed thinking blocks in multi-turn conversations.
|
|
func CacheSignature(modelName, text, signature string) {
|
|
if text == "" || signature == "" {
|
|
return
|
|
}
|
|
if len(signature) < MinValidSignatureLen {
|
|
return
|
|
}
|
|
|
|
groupKey := GetModelGroup(modelName)
|
|
textHash := hashText(text)
|
|
sc := getOrCreateGroupCache(groupKey)
|
|
sc.mu.Lock()
|
|
defer sc.mu.Unlock()
|
|
|
|
sc.entries[textHash] = SignatureEntry{
|
|
Signature: signature,
|
|
Timestamp: time.Now(),
|
|
}
|
|
}
|
|
|
|
// GetCachedSignature retrieves a cached signature for a given model group and text.
|
|
// Returns empty string if not found or expired.
|
|
func GetCachedSignature(modelName, text string) string {
|
|
groupKey := GetModelGroup(modelName)
|
|
|
|
if text == "" {
|
|
if groupKey == "gemini" {
|
|
return "skip_thought_signature_validator"
|
|
}
|
|
return ""
|
|
}
|
|
val, ok := signatureCache.Load(groupKey)
|
|
if !ok {
|
|
if groupKey == "gemini" {
|
|
return "skip_thought_signature_validator"
|
|
}
|
|
return ""
|
|
}
|
|
sc := val.(*groupCache)
|
|
|
|
textHash := hashText(text)
|
|
|
|
now := time.Now()
|
|
|
|
sc.mu.Lock()
|
|
entry, exists := sc.entries[textHash]
|
|
if !exists {
|
|
sc.mu.Unlock()
|
|
if groupKey == "gemini" {
|
|
return "skip_thought_signature_validator"
|
|
}
|
|
return ""
|
|
}
|
|
if now.Sub(entry.Timestamp) > SignatureCacheTTL {
|
|
delete(sc.entries, textHash)
|
|
sc.mu.Unlock()
|
|
if groupKey == "gemini" {
|
|
return "skip_thought_signature_validator"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Refresh TTL on access (sliding expiration).
|
|
entry.Timestamp = now
|
|
sc.entries[textHash] = entry
|
|
sc.mu.Unlock()
|
|
|
|
return entry.Signature
|
|
}
|
|
|
|
// ClearSignatureCache clears signature cache for a specific model group or all groups.
|
|
func ClearSignatureCache(modelName string) {
|
|
if modelName == "" {
|
|
signatureCache.Range(func(key, _ any) bool {
|
|
signatureCache.Delete(key)
|
|
return true
|
|
})
|
|
return
|
|
}
|
|
groupKey := GetModelGroup(modelName)
|
|
signatureCache.Delete(groupKey)
|
|
}
|
|
|
|
// HasValidSignature checks if a signature is valid (non-empty and long enough)
|
|
func HasValidSignature(modelName, signature string) bool {
|
|
return (signature != "" && len(signature) >= MinValidSignatureLen) || (signature == "skip_thought_signature_validator" && GetModelGroup(modelName) == "gemini")
|
|
}
|
|
|
|
func GetModelGroup(modelName string) string {
|
|
if strings.Contains(modelName, "gpt") {
|
|
return "gpt"
|
|
} else if strings.Contains(modelName, "claude") {
|
|
return "claude"
|
|
} else if strings.Contains(modelName, "gemini") {
|
|
return "gemini"
|
|
}
|
|
return modelName
|
|
}
|
|
|
|
var signatureCacheEnabled atomic.Bool
|
|
var signatureBypassStrictMode atomic.Bool
|
|
|
|
func init() {
|
|
signatureCacheEnabled.Store(true)
|
|
signatureBypassStrictMode.Store(false)
|
|
}
|
|
|
|
// SetSignatureCacheEnabled switches Antigravity signature handling between cache mode and bypass mode.
|
|
func SetSignatureCacheEnabled(enabled bool) {
|
|
signatureCacheEnabled.Store(enabled)
|
|
if !enabled {
|
|
log.Warn("antigravity signature cache DISABLED - bypass mode active, cached signatures will not be used for request translation")
|
|
}
|
|
}
|
|
|
|
// SignatureCacheEnabled returns whether signature cache validation is enabled.
|
|
func SignatureCacheEnabled() bool {
|
|
return signatureCacheEnabled.Load()
|
|
}
|
|
|
|
// SetSignatureBypassStrictMode controls whether bypass mode uses strict protobuf-tree validation.
|
|
func SetSignatureBypassStrictMode(strict bool) {
|
|
signatureBypassStrictMode.Store(strict)
|
|
if strict {
|
|
log.Info("antigravity bypass signature validation: strict mode (protobuf tree)")
|
|
} else {
|
|
log.Info("antigravity bypass signature validation: basic mode (R/E + 0x12)")
|
|
}
|
|
}
|
|
|
|
// SignatureBypassStrictMode returns whether bypass mode uses strict protobuf-tree validation.
|
|
func SignatureBypassStrictMode() bool {
|
|
return signatureBypassStrictMode.Load()
|
|
}
|