ajhahn.de
← eeco
Go 62 lines
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
// (<workspace>/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 <stateDir>/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
}