package ai // TokenTotals is the summed token usage across ledger records. The fields // mirror the unexported per-call accounting (input, cached input, output); // these are real provider counts, never estimates. type TokenTotals struct { Input int CachedInput int Output int } // Total is the sum of the three token classes. func (t TokenTotals) Total() int { return t.Input + t.CachedInput + t.Output } // UsageSummary is the aggregated view of the AI-calls ledger // (/state/ai-calls.json). It is the read-only seam cmd/eeco uses // to surface cumulative AI usage without reaching the unexported ledger types. type UsageSummary struct { TotalCalls int Ran int Parked int Tokens TokenTotals ByProvider map[string]int // provider name -> call count FirstTS string // earliest record ts ("" if no dated records) LastTS string // latest record ts ("" if no dated records) } // Summarize aggregates /ai-calls.json into a UsageSummary. A // missing or corrupt ledger yields the zero summary (loadAICalls already // degrades both to the empty ledger), so callers never need to special-case // an absent workspace. ts values are RFC 3339 UTC, so a lexical compare is // chronological — no time.Parse and no clock seam are needed; the date range // derives from the data itself. func Summarize(stateDir string) UsageSummary { ledger := loadAICalls(stateDir) sum := UsageSummary{ByProvider: map[string]int{}} for _, r := range ledger.Records { sum.TotalCalls++ if r.Ran { sum.Ran++ } if r.Parked { sum.Parked++ } sum.Tokens.Input += r.Tokens.Input sum.Tokens.CachedInput += r.Tokens.CachedInput sum.Tokens.Output += r.Tokens.Output if r.Provider != "" { sum.ByProvider[r.Provider]++ } if r.TS != "" { if sum.FirstTS == "" || r.TS < sum.FirstTS { sum.FirstTS = r.TS } if r.TS > sum.LastTS { sum.LastTS = r.TS } } } return sum }