mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-22 05:32:52 +08:00
- Updated Antigravity Credits fallback to handle KV store unavailability as a service error. - Enhanced signature caching mechanisms with request-time KV access and sliding expiration. - Added and improved tests for KV client interactions, including error handling and expiration behaviors. - Introduced `CacheSignatureBestEffort` for non-critical signature caching and clarified function flows with required context. - Ensured consistent error reporting for missing or unavailable KV stores in various scenarios. - Replaced direct `homekv` calls with injectable KV client interfaces for `antigravity` and `codex_reasoning_replay` modules. - Improved error reporting and handling for KV operations, including `KVGet`, `KVSet`, `KVDel`, and `KVExpire`. - Introduced dedicated fake KV clients for expanded and granular test coverage. - Added new unit tests to validate KV client behaviors and error scenarios, ensuring robustness and sliding expiration functionality.
129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
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)
|
|
}
|