mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
feat(translator): improve signature handling by associating with model name in cache functions
This commit is contained in:
9
internal/cache/signature_cache.go
vendored
9
internal/cache/signature_cache.go
vendored
@@ -3,6 +3,7 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -94,7 +95,7 @@ func purgeExpiredSessions() {
|
|||||||
|
|
||||||
// CacheSignature stores a thinking signature for a given session and text.
|
// CacheSignature stores a thinking signature for a given session and text.
|
||||||
// Used for Claude models that require signed thinking blocks in multi-turn conversations.
|
// Used for Claude models that require signed thinking blocks in multi-turn conversations.
|
||||||
func CacheSignature(sessionID, text, signature string) {
|
func CacheSignature(modelName, sessionID, text, signature string) {
|
||||||
if sessionID == "" || text == "" || signature == "" {
|
if sessionID == "" || text == "" || signature == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -102,7 +103,7 @@ func CacheSignature(sessionID, text, signature string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sc := getOrCreateSession(sessionID)
|
sc := getOrCreateSession(fmt.Sprintf("%s#%s", modelName, sessionID))
|
||||||
textHash := hashText(text)
|
textHash := hashText(text)
|
||||||
|
|
||||||
sc.mu.Lock()
|
sc.mu.Lock()
|
||||||
@@ -116,12 +117,12 @@ func CacheSignature(sessionID, text, signature string) {
|
|||||||
|
|
||||||
// GetCachedSignature retrieves a cached signature for a given session and text.
|
// GetCachedSignature retrieves a cached signature for a given session and text.
|
||||||
// Returns empty string if not found or expired.
|
// Returns empty string if not found or expired.
|
||||||
func GetCachedSignature(sessionID, text string) string {
|
func GetCachedSignature(modelName, sessionID, text string) string {
|
||||||
if sessionID == "" || text == "" {
|
if sessionID == "" || text == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val, ok := signatureCache.Load(sessionID)
|
val, ok := signatureCache.Load(fmt.Sprintf("%s#%s", modelName, sessionID))
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ func deriveSessionID(rawJSON []byte) string {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - []byte: The transformed request data in Gemini CLI API format
|
// - []byte: The transformed request data in Gemini CLI API format
|
||||||
func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _ bool) []byte {
|
func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _ bool) []byte {
|
||||||
|
enableThoughtTranslate := true
|
||||||
rawJSON := bytes.Clone(inputRawJSON)
|
rawJSON := bytes.Clone(inputRawJSON)
|
||||||
|
|
||||||
// Derive session ID for signature caching
|
// Derive session ID for signature caching
|
||||||
@@ -132,27 +133,34 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "thinking" {
|
if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "thinking" {
|
||||||
// Use GetThinkingText to handle wrapped thinking objects
|
// Use GetThinkingText to handle wrapped thinking objects
|
||||||
thinkingText := thinking.GetThinkingText(contentResult)
|
thinkingText := thinking.GetThinkingText(contentResult)
|
||||||
// signatureResult := contentResult.Get("signature")
|
|
||||||
// clientSignature := ""
|
|
||||||
// if signatureResult.Exists() && signatureResult.String() != "" {
|
|
||||||
// clientSignature = signatureResult.String()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Always try cached signature first (more reliable than client-provided)
|
// Always try cached signature first (more reliable than client-provided)
|
||||||
// Client may send stale or invalid signatures from different sessions
|
// Client may send stale or invalid signatures from different sessions
|
||||||
signature := ""
|
signature := ""
|
||||||
if sessionID != "" && thinkingText != "" {
|
if sessionID != "" && thinkingText != "" {
|
||||||
if cachedSig := cache.GetCachedSignature(sessionID, thinkingText); cachedSig != "" {
|
if cachedSig := cache.GetCachedSignature(modelName, sessionID, thinkingText); cachedSig != "" {
|
||||||
signature = cachedSig
|
signature = cachedSig
|
||||||
// log.Debugf("Using cached signature for thinking block")
|
// log.Debugf("Using cached signature for thinking block")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We do NOT fallback to client signature anymore.
|
// Fallback to client signature only if cache miss and client signature is valid
|
||||||
// Client signatures from Claude models are incompatible with Antigravity/Gemini API.
|
if signature == "" {
|
||||||
// When switching between models (e.g., Claude Opus -> Gemini Flash), the Claude
|
signatureResult := contentResult.Get("signature")
|
||||||
// signatures will cause "Corrupted thought signature" errors.
|
clientSignature := ""
|
||||||
// If we have no cached signature, the thinking block will be skipped below.
|
if signatureResult.Exists() && signatureResult.String() != "" {
|
||||||
|
arrayClientSignatures := strings.SplitN(signatureResult.String(), "#", 2)
|
||||||
|
if len(arrayClientSignatures) == 2 {
|
||||||
|
if modelName == arrayClientSignatures[0] {
|
||||||
|
clientSignature = arrayClientSignatures[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cache.HasValidSignature(clientSignature) {
|
||||||
|
signature = clientSignature
|
||||||
|
}
|
||||||
|
// log.Debugf("Using client-provided signature for thinking block")
|
||||||
|
}
|
||||||
|
|
||||||
// Store for subsequent tool_use in the same message
|
// Store for subsequent tool_use in the same message
|
||||||
if cache.HasValidSignature(signature) {
|
if cache.HasValidSignature(signature) {
|
||||||
@@ -167,6 +175,7 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
// Converting to text would break this requirement
|
// Converting to text would break this requirement
|
||||||
if isUnsigned {
|
if isUnsigned {
|
||||||
// log.Debugf("Dropping unsigned thinking block (no valid signature)")
|
// log.Debugf("Dropping unsigned thinking block (no valid signature)")
|
||||||
|
enableThoughtTranslate = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +403,7 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled
|
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled
|
||||||
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
|
if t := gjson.GetBytes(rawJSON, "thinking"); enableThoughtTranslate && t.Exists() && t.IsObject() {
|
||||||
if t.Get("type").String() == "enabled" {
|
if t.Get("type").String() == "enabled" {
|
||||||
if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number {
|
if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number {
|
||||||
budget := int(b.Int())
|
budget := int(b.Int())
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq
|
|||||||
SessionID: deriveSessionID(originalRequestRawJSON),
|
SessionID: deriveSessionID(originalRequestRawJSON),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
modelName := gjson.GetBytes(requestRawJSON, "model").String()
|
||||||
|
|
||||||
params := (*param).(*Params)
|
params := (*param).(*Params)
|
||||||
|
|
||||||
@@ -139,13 +140,13 @@ func ConvertAntigravityResponseToClaude(_ context.Context, _ string, originalReq
|
|||||||
// log.Debug("Branch: signature_delta")
|
// log.Debug("Branch: signature_delta")
|
||||||
|
|
||||||
if params.SessionID != "" && params.CurrentThinkingText.Len() > 0 {
|
if params.SessionID != "" && params.CurrentThinkingText.Len() > 0 {
|
||||||
cache.CacheSignature(params.SessionID, params.CurrentThinkingText.String(), thoughtSignature.String())
|
cache.CacheSignature(modelName, params.SessionID, params.CurrentThinkingText.String(), thoughtSignature.String())
|
||||||
// log.Debugf("Cached signature for thinking block (sessionID=%s, textLen=%d)", params.SessionID, params.CurrentThinkingText.Len())
|
// log.Debugf("Cached signature for thinking block (sessionID=%s, textLen=%d)", params.SessionID, params.CurrentThinkingText.Len())
|
||||||
params.CurrentThinkingText.Reset()
|
params.CurrentThinkingText.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
output = output + "event: content_block_delta\n"
|
output = output + "event: content_block_delta\n"
|
||||||
data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"signature_delta","signature":""}}`, params.ResponseIndex), "delta.signature", thoughtSignature.String())
|
data, _ := sjson.Set(fmt.Sprintf(`{"type":"content_block_delta","index":%d,"delta":{"type":"signature_delta","signature":""}}`, params.ResponseIndex), "delta.signature", fmt.Sprintf("%s#%s", modelName, thoughtSignature.String()))
|
||||||
output = output + fmt.Sprintf("data: %s\n\n\n", data)
|
output = output + fmt.Sprintf("data: %s\n\n\n", data)
|
||||||
params.HasContent = true
|
params.HasContent = true
|
||||||
} else if params.ResponseType == 2 { // Continue existing thinking block if already in thinking state
|
} else if params.ResponseType == 2 { // Continue existing thinking block if already in thinking state
|
||||||
@@ -372,7 +373,7 @@ func resolveStopReason(params *Params) string {
|
|||||||
// - string: A Claude-compatible JSON response.
|
// - string: A Claude-compatible JSON response.
|
||||||
func ConvertAntigravityResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
|
func ConvertAntigravityResponseToClaudeNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
|
||||||
_ = originalRequestRawJSON
|
_ = originalRequestRawJSON
|
||||||
_ = requestRawJSON
|
modelName := gjson.GetBytes(requestRawJSON, "model").String()
|
||||||
|
|
||||||
root := gjson.ParseBytes(rawJSON)
|
root := gjson.ParseBytes(rawJSON)
|
||||||
promptTokens := root.Get("response.usageMetadata.promptTokenCount").Int()
|
promptTokens := root.Get("response.usageMetadata.promptTokenCount").Int()
|
||||||
@@ -437,7 +438,7 @@ func ConvertAntigravityResponseToClaudeNonStream(_ context.Context, _ string, or
|
|||||||
block := `{"type":"thinking","thinking":""}`
|
block := `{"type":"thinking","thinking":""}`
|
||||||
block, _ = sjson.Set(block, "thinking", thinkingBuilder.String())
|
block, _ = sjson.Set(block, "thinking", thinkingBuilder.String())
|
||||||
if thinkingSignature != "" {
|
if thinkingSignature != "" {
|
||||||
block, _ = sjson.Set(block, "signature", thinkingSignature)
|
block, _ = sjson.Set(block, "signature", fmt.Sprintf("%s#%s", modelName, thinkingSignature))
|
||||||
}
|
}
|
||||||
responseJSON, _ = sjson.SetRaw(responseJSON, "content.-1", block)
|
responseJSON, _ = sjson.SetRaw(responseJSON, "content.-1", block)
|
||||||
thinkingBuilder.Reset()
|
thinkingBuilder.Reset()
|
||||||
|
|||||||
Reference in New Issue
Block a user