| package executor |
|
|
| import ( |
| "fmt" |
| "net/http" |
| "strings" |
| "time" |
|
|
| "github.com/google/uuid" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" |
| "github.com/tidwall/gjson" |
| "github.com/tidwall/sjson" |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| func applyCodexPromptCache(cfg *config.Config, from sdktranslator.Format, reqPayload []byte, model string, rawJSON []byte, skipIfBodyEmpty bool) ([]byte, string, http.Header) { |
| headers := http.Header{} |
| if skipIfBodyEmpty && len(rawJSON) == 0 { |
| return rawJSON, "", headers |
| } |
|
|
| cacheID := resolveCodexPromptCacheID(cfg, from, reqPayload, model) |
| if cacheID == "" { |
| return rawJSON, "", headers |
| } |
|
|
| rawJSON, _ = sjson.SetBytes(rawJSON, "prompt_cache_key", cacheID) |
| headers.Set("Conversation_id", cacheID) |
| headers.Set("Session_id", cacheID) |
| return rawJSON, cacheID, headers |
| } |
|
|
| func resolveCodexPromptCacheID(cfg *config.Config, from sdktranslator.Format, reqPayload []byte, model string) string { |
| var cache codexCache |
| if explicit := explicitCodexPromptCacheKey(reqPayload); explicit != "" { |
| cache.ID = explicit |
| return cache.ID |
| } |
| if cacheDisabledByRetention(reqPayload) { |
| return "" |
| } |
|
|
| if from == sdktranslator.FromString("claude") { |
| if userID := codexPromptCacheUserID(reqPayload); userID != "" { |
| return getOrCreateCodexPromptCacheID(cfg, model, userID, codexPromptCacheEffectiveTTL(cfg, reqPayload)) |
| } |
| return "" |
| } |
|
|
| |
| if !cacheRetentionRequested(reqPayload) { |
| return "" |
| } |
| if userID := codexPromptCacheUserID(reqPayload); userID != "" { |
| return getOrCreateCodexPromptCacheID(cfg, model, userID, codexPromptCacheEffectiveTTL(cfg, reqPayload)) |
| } |
|
|
| return cache.ID |
| } |
|
|
| func explicitCodexPromptCacheKey(reqPayload []byte) string { |
| if len(reqPayload) == 0 { |
| return "" |
| } |
| if key := strings.TrimSpace(gjson.GetBytes(reqPayload, "prompt_cache_key").String()); key != "" { |
| return key |
| } |
| if key := strings.TrimSpace(gjson.GetBytes(reqPayload, "metadata.prompt_cache_key").String()); key != "" { |
| return key |
| } |
| return "" |
| } |
|
|
| func codexPromptCacheUserID(reqPayload []byte) string { |
| if len(reqPayload) == 0 { |
| return "" |
| } |
| paths := []string{ |
| "metadata.user_id", |
| "user", |
| "safety_identifier", |
| "metadata.safety_identifier", |
| } |
| for _, path := range paths { |
| if v := strings.TrimSpace(gjson.GetBytes(reqPayload, path).String()); v != "" { |
| return v |
| } |
| } |
| return "" |
| } |
|
|
| func getOrCreateCodexPromptCacheID(cfg *config.Config, model, userID string, ttl time.Duration) string { |
| model = strings.TrimSpace(model) |
| userID = strings.TrimSpace(userID) |
| if model == "" || userID == "" { |
| return "" |
| } |
| key := fmt.Sprintf("%s-%s", model, userID) |
| if distributedID, ok := getOrCreateCodexPromptCacheRedis(cfg, key, ttl); ok { |
| return distributedID |
| } |
| cache, ok := getCodexCache(key) |
| if ok { |
| return cache.ID |
| } |
| cache = codexCache{ |
| ID: uuid.New().String(), |
| Expire: time.Now().Add(clampCodexPromptCacheTTL(ttl)), |
| } |
| setCodexCache(key, cache) |
| return cache.ID |
| } |
|
|
| func cacheDisabledByRetention(reqPayload []byte) bool { |
| if len(reqPayload) == 0 { |
| return false |
| } |
| ret := gjson.GetBytes(reqPayload, "prompt_cache_retention") |
| if !ret.Exists() { |
| return false |
| } |
| switch ret.Type { |
| case gjson.False: |
| return true |
| case gjson.Number: |
| return ret.Float() <= 0 |
| case gjson.String: |
| s := strings.ToLower(strings.TrimSpace(ret.String())) |
| switch s { |
| case "", "default", "auto": |
| return false |
| case "0", "off", "none", "false", "disabled", "disable", "no": |
| return true |
| } |
| } |
| return false |
| } |
|
|
| func cacheRetentionRequested(reqPayload []byte) bool { |
| if len(reqPayload) == 0 { |
| return false |
| } |
| ret := gjson.GetBytes(reqPayload, "prompt_cache_retention") |
| return ret.Exists() && !cacheDisabledByRetention(reqPayload) |
| } |
|
|
| func codexPromptCacheRetentionTTL(reqPayload []byte) time.Duration { |
| if len(reqPayload) == 0 { |
| return time.Hour |
| } |
| ret := gjson.GetBytes(reqPayload, "prompt_cache_retention") |
| if !ret.Exists() { |
| return time.Hour |
| } |
| switch ret.Type { |
| case gjson.True: |
| return time.Hour |
| case gjson.Number: |
| if ret.Float() > 0 { |
| return time.Duration(ret.Int()) * time.Second |
| } |
| return time.Hour |
| case gjson.String: |
| s := strings.TrimSpace(ret.String()) |
| if s == "" { |
| return time.Hour |
| } |
| if d, err := time.ParseDuration(s); err == nil && d > 0 { |
| return d |
| } |
| |
| return time.Hour |
| default: |
| return time.Hour |
| } |
| } |
|
|
| func codexPromptCacheEffectiveTTL(cfg *config.Config, reqPayload []byte) time.Duration { |
| if len(reqPayload) > 0 { |
| ret := gjson.GetBytes(reqPayload, "prompt_cache_retention") |
| if ret.Exists() { |
| if cacheDisabledByRetention(reqPayload) { |
| return 0 |
| } |
| ttl := codexPromptCacheRetentionTTL(reqPayload) |
| if ttl > 0 { |
| return clampCodexPromptCacheTTL(ttl) |
| } |
| } |
| } |
| if cfg != nil && cfg.CodexPromptCache.TTLSeconds > 0 { |
| return clampCodexPromptCacheTTL(time.Duration(cfg.CodexPromptCache.TTLSeconds) * time.Second) |
| } |
| return clampCodexPromptCacheTTL(time.Duration(config.DefaultCodexPromptCacheTTLSeconds) * time.Second) |
| } |
|
|
| func clampCodexPromptCacheTTL(ttl time.Duration) time.Duration { |
| if ttl <= 0 { |
| return time.Hour |
| } |
| const maxTTL = 7 * 24 * time.Hour |
| if ttl > maxTTL { |
| return maxTTL |
| } |
| return ttl |
| } |
|
|