Files
sub2api-ht/backend/internal/service/openai_messages_digest_session.go

136 lines
3.8 KiB
Go

package service
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/apicompat"
)
type openAICompatAnthropicDigestBinding struct {
PromptCacheKey string
ExpiresAt time.Time
}
func buildOpenAICompatAnthropicDigestChain(req *apicompat.AnthropicRequest) string {
if req == nil {
return ""
}
parts := make([]string, 0, len(req.Messages)+1)
if len(req.System) > 0 && strings.TrimSpace(string(req.System)) != "" && strings.TrimSpace(string(req.System)) != "null" {
parts = append(parts, "s:"+shortHash(req.System))
}
for _, msg := range req.Messages {
content := msg.Content
if len(content) == 0 || strings.TrimSpace(string(content)) == "" {
continue
}
prefix := "u"
if strings.TrimSpace(msg.Role) == "assistant" {
prefix = "a"
}
parts = append(parts, prefix+":"+shortHash(content))
}
return strings.Join(parts, "-")
}
func openAICompatAnthropicDigestNamespace(account *Account, cAPIKeyID int64) string {
if account == nil || account.ID <= 0 {
return ""
}
return fmt.Sprintf("%d|%d|", account.ID, cAPIKeyID)
}
func (s *OpenAIGatewayService) findOpenAICompatAnthropicDigestPromptCacheKey(account *Account, cAPIKeyID int64, digestChain string) (promptCacheKey string, matchedChain string) {
if s == nil || digestChain == "" {
return "", ""
}
ns := openAICompatAnthropicDigestNamespace(account, cAPIKeyID)
if ns == "" {
return "", ""
}
chain := digestChain
for {
if raw, ok := s.openaiCompatAnthropicDigestSessions.Load(ns + chain); ok {
if binding, ok := raw.(openAICompatAnthropicDigestBinding); ok {
if binding.ExpiresAt.IsZero() || time.Now().Before(binding.ExpiresAt) {
if key := strings.TrimSpace(binding.PromptCacheKey); key != "" {
return key, chain
}
}
}
s.openaiCompatAnthropicDigestSessions.Delete(ns + chain)
}
i := strings.LastIndex(chain, "-")
if i < 0 {
return "", ""
}
chain = chain[:i]
}
}
func (s *OpenAIGatewayService) bindOpenAICompatAnthropicDigestPromptCacheKey(account *Account, cAPIKeyID int64, digestChain, promptCacheKey, oldDigestChain string) {
if s == nil || digestChain == "" || strings.TrimSpace(promptCacheKey) == "" {
return
}
ns := openAICompatAnthropicDigestNamespace(account, cAPIKeyID)
if ns == "" {
return
}
binding := openAICompatAnthropicDigestBinding{
PromptCacheKey: strings.TrimSpace(promptCacheKey),
ExpiresAt: time.Now().Add(s.openAIWSResponseStickyTTL()),
}
s.openaiCompatAnthropicDigestSessions.Store(ns+digestChain, binding)
if oldDigestChain != "" && oldDigestChain != digestChain {
s.openaiCompatAnthropicDigestSessions.Delete(ns + oldDigestChain)
}
}
func promptCacheKeyFromAnthropicDigest(digestChain string) string {
if strings.TrimSpace(digestChain) == "" {
return ""
}
return "anthropic-digest-" + hashSensitiveValueForLog(digestChain)
}
func promptCacheKeyFromAnthropicMetadataSession(req *apicompat.AnthropicRequest) string {
if req == nil || len(req.Metadata) == 0 {
return ""
}
var metadata struct {
UserID string `json:"user_id"`
}
if err := json.Unmarshal(req.Metadata, &metadata); err != nil {
return ""
}
parsed := ParseMetadataUserID(metadata.UserID)
if parsed == nil || strings.TrimSpace(parsed.SessionID) == "" {
return ""
}
seed := strings.Join([]string{
"anthropic-metadata",
strings.TrimSpace(parsed.DeviceID),
strings.TrimSpace(parsed.AccountUUID),
strings.TrimSpace(parsed.SessionID),
}, "|")
return "anthropic-metadata-" + hashSensitiveValueForLog(seed)
}
func cloneAnthropicRequestForDigest(req *apicompat.AnthropicRequest) *apicompat.AnthropicRequest {
if req == nil {
return nil
}
cp := *req
if len(req.System) > 0 {
cp.System = append(json.RawMessage(nil), req.System...)
}
if len(req.Messages) > 0 {
cp.Messages = append([]apicompat.AnthropicMessage(nil), req.Messages...)
}
return &cp
}