| package executor |
|
|
| import ( |
| "context" |
| json "github.com/goccy/go-json" |
| "fmt" |
| "strings" |
| "sync" |
| "time" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" |
| ) |
|
|
| type codexCache struct { |
| ID string |
| Expire time.Time |
| } |
|
|
| |
| |
| var ( |
| codexCacheMap = make(map[string]codexCache) |
| codexCacheMu sync.RWMutex |
| codexKVStore cache.KVStore |
| ) |
|
|
| |
| func SetCodexKVStore(store cache.KVStore) { |
| codexKVStore = store |
| } |
|
|
| |
| const codexCacheCleanupInterval = 15 * time.Minute |
|
|
| |
| var codexCacheCleanupOnce sync.Once |
|
|
| |
| |
| func startCodexCacheCleanup() { |
| go func() { |
| ticker := time.NewTicker(codexCacheCleanupInterval) |
| defer ticker.Stop() |
| for range ticker.C { |
| purgeExpiredCodexCache() |
| } |
| }() |
| } |
|
|
| |
| func purgeExpiredCodexCache() { |
| now := time.Now() |
| codexCacheMu.Lock() |
| defer codexCacheMu.Unlock() |
| for key, cache := range codexCacheMap { |
| if cache.Expire.Before(now) { |
| delete(codexCacheMap, key) |
| } |
| } |
| } |
|
|
| |
| func getCodexCache(key string) (codexCache, bool) { |
| if codexKVStore != nil { |
| ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) |
| payload, err := codexKVStore.Get(ctx, codexRedisKey(key)) |
| cancel() |
| if err == nil && payload != "" { |
| var entry codexCache |
| if json.Unmarshal([]byte(payload), &entry) == nil && !entry.Expire.Before(time.Now()) { |
| return entry, true |
| } |
| } |
| } |
|
|
| codexCacheCleanupOnce.Do(startCodexCacheCleanup) |
| codexCacheMu.RLock() |
| cache, ok := codexCacheMap[key] |
| codexCacheMu.RUnlock() |
| if !ok || cache.Expire.Before(time.Now()) { |
| return codexCache{}, false |
| } |
| return cache, true |
| } |
|
|
| |
| func setCodexCache(key string, cache codexCache) { |
| if codexKVStore != nil { |
| if payload, err := json.Marshal(cache); err == nil { |
| ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) |
| ttl := time.Until(cache.Expire) |
| if ttl <= 0 { |
| ttl = time.Hour |
| } |
| _ = codexKVStore.Set(ctx, codexRedisKey(key), string(payload), ttl) |
| cancel() |
| } |
| } |
|
|
| codexCacheCleanupOnce.Do(startCodexCacheCleanup) |
| codexCacheMu.Lock() |
| codexCacheMap[key] = cache |
| codexCacheMu.Unlock() |
| } |
|
|
| func codexRedisKey(key string) string { |
| trimmed := strings.TrimSpace(key) |
| if trimmed == "" { |
| return "codex" |
| } |
| parts := strings.SplitN(trimmed, ":", 2) |
| if len(parts) == 2 && strings.TrimSpace(parts[0]) != "" && strings.TrimSpace(parts[1]) != "" { |
| return fmt.Sprintf("codex:%s:%s", strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) |
| } |
| return fmt.Sprintf("codex:%s", trimmed) |
| } |
|
|