feat: improve OpenAI messages compatibility for Claude Code
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user