128 lines · 3.6 KB · Go
RawDownload
package helps import (	"context"	"sync"	"time" 	homekv "github.com/router-for-me/CLIProxyAPI/v7/internal/home") type CodexCache struct {	ID     string	Expire time.Time} // codexCacheMap stores prompt cache IDs keyed by model+user_id.// Protected by codexCacheMu. Entries expire after 1 hour.var (	codexCacheMap = make(map[string]CodexCache)	codexCacheMu  sync.RWMutex) // codexCacheCleanupInterval controls how often expired entries are purged.const codexCacheCleanupInterval = 15 * time.Minute // codexCacheCleanupOnce ensures the background cleanup goroutine starts only once.var codexCacheCleanupOnce sync.Once // startCodexCacheCleanup launches a background goroutine that periodically// removes expired entries from codexCacheMap to prevent memory leaks.func startCodexCacheCleanup() {	go func() {		ticker := time.NewTicker(codexCacheCleanupInterval)		defer ticker.Stop()		for range ticker.C {			purgeExpiredCodexCache()		}	}()} // purgeExpiredCodexCache removes entries that have expired.func purgeExpiredCodexCache() {	now := time.Now()	codexCacheMu.Lock()	defer codexCacheMu.Unlock()	for key, cache := range codexCacheMap {		if cache.Expire.Before(now) {			delete(codexCacheMap, key)		}	}} // GetCodexCache retrieves a cached entry, returning ok=false if not found or expired.func GetCodexCache(key string) (CodexCache, bool) {	cache, ok, err := GetCodexCacheRequired(context.Background(), key)	if err == nil {		return cache, ok	}	return CodexCache{}, false} // GetCodexCacheRequired retrieves a cached entry for request-time paths.func GetCodexCacheRequired(ctx context.Context, key string) (CodexCache, bool, error) {	var homeCache CodexCache	homeMode, found, errGet := homekv.KVGetJSONRequired(ctx, key, &homeCache)	if homeMode {		if errGet != nil || !found {			return CodexCache{}, false, errGet		}		if homeCache.Expire.Before(time.Now()) {			_, _, _ = homekv.KVDelRequired(ctx, key)			return CodexCache{}, false, nil		}		return homeCache, true, nil	} 	codexCacheCleanupOnce.Do(startCodexCacheCleanup)	codexCacheMu.RLock()	cache, ok := codexCacheMap[key]	codexCacheMu.RUnlock()	if !ok || cache.Expire.Before(time.Now()) {		return CodexCache{}, false, nil	}	return cache, true, nil} // SetCodexCache stores a cache entry.func SetCodexCache(key string, cache CodexCache) {	SetCodexCacheBestEffort(context.Background(), key, cache)} // SetCodexCacheRequired stores a cache entry for request-time paths.func SetCodexCacheRequired(ctx context.Context, key string, cache CodexCache) error {	ttl := time.Until(cache.Expire)	if ttl <= 0 {		return nil	}	if _, homeMode, _ := homekv.CurrentKVClient(); homeMode {		_, errSet := homekv.KVSetJSONRequired(ctx, key, cache, ttl)		return errSet	}	codexCacheCleanupOnce.Do(startCodexCacheCleanup)	codexCacheMu.Lock()	codexCacheMap[key] = cache	codexCacheMu.Unlock()	return nil} // SetCodexCacheBestEffort stores a cache entry without failing completed responses.func SetCodexCacheBestEffort(ctx context.Context, key string, cache CodexCache) bool {	ttl := time.Until(cache.Expire)	if ttl <= 0 {		return false	}	if _, homeMode, _ := homekv.CurrentKVClient(); homeMode {		return homekv.KVSetJSONBestEffort(ctx, key, cache, ttl)	}	codexCacheCleanupOnce.Do(startCodexCacheCleanup)	codexCacheMu.Lock()	codexCacheMap[key] = cache	codexCacheMu.Unlock()	return true} // CodexPromptCacheKey builds the Home KV key for a model/user prompt cache.func CodexPromptCacheKey(modelName string, userScope string) string {	return "cpa:codex:prompt-cache:" + homekv.HashKeyPart(modelName) + ":" + homekv.HashKeyPart(userScope)}