package llm import ( "sync" "time" ) type CallLogger struct { mu sync.RWMutex records []CallRecord max int index int total int64 } func NewCallLogger(max int) *CallLogger { return &CallLogger{records: make([]CallRecord, max), max: max} } func (l *CallLogger) Record(r CallRecord) { l.mu.Lock() defer l.mu.Unlock() l.records[l.index%l.max] = r l.index++ l.total++ } func (l *CallLogger) Recent(n int) []CallRecord { l.mu.RLock(); defer l.mu.RUnlock() if n > l.max { n = l.max } count := l.index; if count > l.max { count = l.max }; if n > count { n = count } result := make([]CallRecord, n) for i := 0; i < n; i++ { idx := (l.index - 1 - i + l.max) % l.max if idx < l.max { result[n-1-i] = l.records[idx] } } return result } func (l *CallLogger) RecentMaps(n int) []map[string]any { records := l.Recent(n); result := make([]map[string]any, len(records)) for i, r := range records { result[i] = map[string]any{ "time": r.Time.Format(time.RFC3339), "model": r.Model, "purpose": r.Purpose, "prompt_tokens": r.PromptTokens, "completion_tokens": r.CompletionTokens, "duration_ms": r.DurationMs, "success": r.Success, "error": r.Error, } } return result } func (l *CallLogger) Stats() map[string]any { l.mu.RLock(); defer l.mu.RUnlock() var calls, succ int64; var dur, tok int64 count := l.index; if count > l.max { count = l.max } for i := 0; i < count; i++ { r := l.records[(l.index-1-i+l.max)%l.max] calls++; if r.Success { succ++ } dur += r.DurationMs; tok += int64(r.PromptTokens + r.CompletionTokens) } avg := 0; if calls > 0 { avg = int(dur / calls) } return map[string]any{"total_calls": calls, "success_calls": succ, "avg_duration_ms": avg, "total_tokens": tok} }