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 }