package llm import ( "sync" "time" ) type CallRecord struct { ID string `json:"id"` Time time.Time `json:"time"` Model string `json:"model"` Purpose string `json:"purpose"` PromptTokens int `json:"prompt_tokens"` CompTokens int `json:"completion_tokens"` DurationMs int64 `json:"duration_ms"` Success bool `json:"success"` Error string `json:"error,omitempty"` } type Logger struct { mu sync.RWMutex records []CallRecord max int index int total int64 } func NewLogger(max int) *Logger { return &Logger{records: make([]CallRecord, max), max: max} } func (l *Logger) Record(r CallRecord) { l.mu.Lock() defer l.mu.Unlock() r.ID = time.Now().Format("150405") + "-" + r.Purpose l.records[l.index%l.max] = r l.index++ l.total++ } func (l *Logger) 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 *Logger) 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{ "id": r.ID, "time": r.Time.Format(time.RFC3339), "model": r.Model, "purpose": r.Purpose, "prompt_tokens": r.PromptTokens, "completion_tokens": r.CompTokens, "duration_ms": r.DurationMs, "success": r.Success, "error": r.Error, } } return result } func (l *Logger) 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.CompTokens) } 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} }