Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
148
backend/internal/runtime/executor/helps/session_id_cache.go
Normal file
148
backend/internal/runtime/executor/helps/session_id_cache.go
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
package helps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
homekv "github.com/router-for-me/CLIProxyAPI/v7/internal/home"
|
||||
)
|
||||
|
||||
type sessionIDCacheEntry struct {
|
||||
value string
|
||||
expire time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
sessionIDCache = make(map[string]sessionIDCacheEntry)
|
||||
sessionIDCacheMu sync.RWMutex
|
||||
sessionIDCacheCleanupOnce sync.Once
|
||||
)
|
||||
|
||||
type claudeIDKVClient interface {
|
||||
KVGet(ctx context.Context, key string) ([]byte, bool, error)
|
||||
KVSetNX(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)
|
||||
KVExpire(ctx context.Context, key string, ttl time.Duration) (bool, error)
|
||||
}
|
||||
|
||||
var currentClaudeIDKVClient = func() (claudeIDKVClient, bool, error) {
|
||||
return homekv.CurrentKVClient()
|
||||
}
|
||||
|
||||
const (
|
||||
sessionIDTTL = time.Hour
|
||||
sessionIDCacheCleanupPeriod = 15 * time.Minute
|
||||
)
|
||||
|
||||
func startSessionIDCacheCleanup() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(sessionIDCacheCleanupPeriod)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
purgeExpiredSessionIDs()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func purgeExpiredSessionIDs() {
|
||||
now := time.Now()
|
||||
sessionIDCacheMu.Lock()
|
||||
for key, entry := range sessionIDCache {
|
||||
if !entry.expire.After(now) {
|
||||
delete(sessionIDCache, key)
|
||||
}
|
||||
}
|
||||
sessionIDCacheMu.Unlock()
|
||||
}
|
||||
|
||||
func sessionIDCacheKey(apiKey string) string {
|
||||
sum := sha256.Sum256([]byte(apiKey))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// CachedSessionID returns a stable session UUID per apiKey, refreshing the TTL on each access.
|
||||
func CachedSessionID(apiKey string) string {
|
||||
value, errValue := CachedSessionIDRequired(context.Background(), apiKey)
|
||||
if errValue == nil && value != "" {
|
||||
return value
|
||||
}
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
// CachedSessionIDRequired returns a stable session UUID per apiKey for request-time paths.
|
||||
func CachedSessionIDRequired(ctx context.Context, apiKey string) (string, error) {
|
||||
if apiKey == "" {
|
||||
return uuid.New().String(), nil
|
||||
}
|
||||
client, homeMode, errClient := currentClaudeIDKVClient()
|
||||
if homeMode {
|
||||
if errClient != nil {
|
||||
return "", errClient
|
||||
}
|
||||
key := claudeSessionIDKVKey(apiKey)
|
||||
raw, found, errGet := client.KVGet(ctx, key)
|
||||
if errGet != nil {
|
||||
return "", errGet
|
||||
}
|
||||
if found && strings.TrimSpace(string(raw)) != "" {
|
||||
if _, errExpire := client.KVExpire(ctx, key, sessionIDTTL); errExpire != nil {
|
||||
return "", errExpire
|
||||
}
|
||||
return strings.TrimSpace(string(raw)), nil
|
||||
}
|
||||
newID := uuid.New().String()
|
||||
if _, errSet := client.KVSetNX(ctx, key, []byte(newID), sessionIDTTL); errSet != nil {
|
||||
return "", errSet
|
||||
}
|
||||
raw, found, errGet = client.KVGet(ctx, key)
|
||||
if errGet != nil {
|
||||
return "", errGet
|
||||
}
|
||||
if found && strings.TrimSpace(string(raw)) != "" {
|
||||
return strings.TrimSpace(string(raw)), nil
|
||||
}
|
||||
return "", fmt.Errorf("home kv session id missing after set")
|
||||
}
|
||||
|
||||
sessionIDCacheCleanupOnce.Do(startSessionIDCacheCleanup)
|
||||
|
||||
key := sessionIDCacheKey(apiKey)
|
||||
now := time.Now()
|
||||
|
||||
sessionIDCacheMu.RLock()
|
||||
entry, ok := sessionIDCache[key]
|
||||
valid := ok && entry.value != "" && entry.expire.After(now)
|
||||
sessionIDCacheMu.RUnlock()
|
||||
if valid {
|
||||
sessionIDCacheMu.Lock()
|
||||
entry = sessionIDCache[key]
|
||||
if entry.value != "" && entry.expire.After(now) {
|
||||
entry.expire = now.Add(sessionIDTTL)
|
||||
sessionIDCache[key] = entry
|
||||
sessionIDCacheMu.Unlock()
|
||||
return entry.value, nil
|
||||
}
|
||||
sessionIDCacheMu.Unlock()
|
||||
}
|
||||
|
||||
newID := uuid.New().String()
|
||||
|
||||
sessionIDCacheMu.Lock()
|
||||
entry, ok = sessionIDCache[key]
|
||||
if !ok || entry.value == "" || !entry.expire.After(now) {
|
||||
entry.value = newID
|
||||
}
|
||||
entry.expire = now.Add(sessionIDTTL)
|
||||
sessionIDCache[key] = entry
|
||||
sessionIDCacheMu.Unlock()
|
||||
return entry.value, nil
|
||||
}
|
||||
|
||||
func claudeSessionIDKVKey(apiKey string) string {
|
||||
return "cpa:claude:session-id:" + homekv.HashKeyPart(apiKey)
|
||||
}
|
||||
Loading…
Reference in a new issue