File size: 2,539 Bytes
1766992 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | package services
import (
"github.com/libaxuan/cursor2api-go/models"
"sync"
"time"
)
const (
defaultResponseStoreTTL = 30 * time.Minute
defaultResponseStoreMaxEntries = 1000
)
type responseStoreEntry struct {
messages []models.Message
createdAt time.Time
}
// ResponseStore keeps recent Responses API conversation state in memory.
// It is best-effort and resets on process restart.
type ResponseStore struct {
mu sync.RWMutex
ttl time.Duration
maxEntries int
entries map[string]responseStoreEntry
order []string
}
func NewResponseStore(ttl time.Duration, maxEntries int) *ResponseStore {
if ttl <= 0 {
ttl = defaultResponseStoreTTL
}
if maxEntries <= 0 {
maxEntries = defaultResponseStoreMaxEntries
}
return &ResponseStore{
ttl: ttl,
maxEntries: maxEntries,
entries: make(map[string]responseStoreEntry),
order: make([]string, 0, maxEntries),
}
}
// Get returns stored messages for a response id if present and not expired.
func (s *ResponseStore) Get(id string) ([]models.Message, bool) {
if id == "" {
return nil, false
}
now := time.Now()
s.mu.Lock()
defer s.mu.Unlock()
s.cleanupLocked(now)
entry, ok := s.entries[id]
if !ok {
return nil, false
}
return cloneMessages(entry.messages), true
}
// Put stores messages for a response id, evicting older entries if needed.
func (s *ResponseStore) Put(id string, messages []models.Message) {
if id == "" || len(messages) == 0 {
return
}
now := time.Now()
s.mu.Lock()
defer s.mu.Unlock()
s.cleanupLocked(now)
if _, exists := s.entries[id]; !exists {
s.order = append(s.order, id)
}
s.entries[id] = responseStoreEntry{
messages: cloneMessages(messages),
createdAt: now,
}
for len(s.entries) > s.maxEntries {
s.evictOldestLocked()
}
}
func (s *ResponseStore) cleanupLocked(now time.Time) {
if s.ttl <= 0 {
return
}
for id, entry := range s.entries {
if now.Sub(entry.createdAt) > s.ttl {
delete(s.entries, id)
s.removeFromOrderLocked(id)
}
}
}
func (s *ResponseStore) evictOldestLocked() {
if len(s.order) == 0 {
return
}
oldest := s.order[0]
s.order = s.order[1:]
delete(s.entries, oldest)
}
func (s *ResponseStore) removeFromOrderLocked(id string) {
for i, key := range s.order {
if key == id {
s.order = append(s.order[:i], s.order[i+1:]...)
return
}
}
}
func cloneMessages(src []models.Message) []models.Message {
if len(src) == 0 {
return nil
}
dst := make([]models.Message, len(src))
copy(dst, src)
return dst
}
|