package acp import ( "sync" "time" ) // Message is one entry in the conversation history. type Message struct { Role string `json:"role"` Text string `json:"text"` Time time.Time `json:"time"` MessageID string `json:"messageId,omitempty"` } // History accumulates prompt/response messages in memory for a session. type History struct { mu sync.RWMutex messages []Message indexByID map[string]int } // NewHistory creates an empty History. func NewHistory() *History { return &History{indexByID: make(map[string]int)} } // Add appends a message with the current time. It is used for messages that // have no messageId (e.g. the user prompt path). func (h *History) Add(role, text string) { h.AppendOrCreate(role, "", text) } // AppendOrCreate finds the existing entry with the same messageId and appends // text to it; otherwise creates a new entry. When messageId is empty this // behaves like Add(role, text). It returns the time of the created or existing // entry. func (h *History) AppendOrCreate(role, messageId, text string) time.Time { h.mu.Lock() defer h.mu.Unlock() if messageId == "" { h.messages = append(h.messages, Message{Role: role, Text: text, Time: time.Now()}) return h.messages[len(h.messages)-1].Time } if idx, ok := h.indexByID[messageId]; ok { h.messages[idx].Text += text return h.messages[idx].Time } h.messages = append(h.messages, Message{Role: role, Text: text, Time: time.Now(), MessageID: messageId}) h.indexByID[messageId] = len(h.messages) - 1 return h.messages[len(h.messages)-1].Time } // List returns a copy of all messages, oldest first. func (h *History) List() []Message { h.mu.RLock() defer h.mu.RUnlock() out := make([]Message, len(h.messages)) copy(out, h.messages) return out } // Len returns the number of stored messages. func (h *History) Len() int { h.mu.RLock() defer h.mu.RUnlock() return len(h.messages) } // Since returns messages starting at index idx. func (h *History) Since(idx int) []Message { h.mu.RLock() defer h.mu.RUnlock() if idx < 0 { idx = 0 } if idx > len(h.messages) { idx = len(h.messages) } out := make([]Message, len(h.messages)-idx) copy(out, h.messages[idx:]) return out } // Clear removes all stored messages. func (h *History) Clear() { h.mu.Lock() defer h.mu.Unlock() h.messages = h.messages[:0] h.indexByID = make(map[string]int) }