package service import ( "encoding/json" "strings" "github.com/Wei-Shaw/sub2api/internal/pkg/apicompat" ) const compatPromptCacheKeyPrefix = "compat_cc_" func shouldAutoInjectPromptCacheKeyForCompat(model string) bool { switch normalizeCodexModel(strings.TrimSpace(model)) { case "gpt-5.4", "gpt-5.3-codex": return true default: return false } } func deriveCompatPromptCacheKey(req *apicompat.ChatCompletionsRequest, mappedModel string) string { if req == nil { return "" } normalizedModel := normalizeCodexModel(strings.TrimSpace(mappedModel)) if normalizedModel == "" { normalizedModel = normalizeCodexModel(strings.TrimSpace(req.Model)) } if normalizedModel == "" { normalizedModel = strings.TrimSpace(req.Model) } seedParts := []string{"model=" + normalizedModel} if req.ReasoningEffort != "" { seedParts = append(seedParts, "reasoning_effort="+strings.TrimSpace(req.ReasoningEffort)) } if len(req.ToolChoice) > 0 { seedParts = append(seedParts, "tool_choice="+normalizeCompatSeedJSON(req.ToolChoice)) } if len(req.Tools) > 0 { if raw, err := json.Marshal(req.Tools); err == nil { seedParts = append(seedParts, "tools="+normalizeCompatSeedJSON(raw)) } } if len(req.Functions) > 0 { if raw, err := json.Marshal(req.Functions); err == nil { seedParts = append(seedParts, "functions="+normalizeCompatSeedJSON(raw)) } } firstUserCaptured := false for _, msg := range req.Messages { switch strings.TrimSpace(msg.Role) { case "system": seedParts = append(seedParts, "system="+normalizeCompatSeedJSON(msg.Content)) case "user": if !firstUserCaptured { seedParts = append(seedParts, "first_user="+normalizeCompatSeedJSON(msg.Content)) firstUserCaptured = true } } } return compatPromptCacheKeyPrefix + hashSensitiveValueForLog(strings.Join(seedParts, "|")) } func normalizeCompatSeedJSON(v json.RawMessage) string { if len(v) == 0 { return "" } var tmp any if err := json.Unmarshal(v, &tmp); err != nil { return string(v) } out, err := json.Marshal(tmp) if err != nil { return string(v) } return string(out) }