Files
BlackBean/pigo/internal/cli/telemetry.go
T
2026-08-14 23:41:57 +08:00

136 lines
4.8 KiB
Go

// This file implements the telemetry holder that retains per-run telemetry
// events (US-001, #291) and folds them into a cumulative accumulator for the
// /status command to display. It was moved verbatim from cmd/pigo
// (telemetry_state.go, US-013) into the shared cli layer so the status and repl
// subpackages share one holder type.
package cli
import (
"github.com/smallnest/pigo/internal/agentcore"
)
// TelemetryHolder holds the most recent run's telemetry event and a cumulative
// accumulator that sums metrics across all runs in the session.
type TelemetryHolder struct {
// last is the telemetry event from the most recent completed run, or nil if
// no run has completed yet.
last *agentcore.TelemetryEvent
// cumulative is the accumulator that sums metrics across all runs.
cumulative cumulativeTelemetry
}
// cumulativeTelemetry holds the accumulated metrics from all runs in the session.
type cumulativeTelemetry struct {
// turns is the total number of turns across all runs.
turns int
// toolDurationsMs maps tool name to aggregated count and total milliseconds
// across all runs.
toolDurationsMs map[string]agentcore.ToolTiming
// truncationCount is the total number of truncations across all runs.
truncationCount int
// compactionCount is the total number of compactions across all runs.
compactionCount int
// contextTokens is the most recent observed context token count.
contextTokens int
// contextWindow is the most recent observed context window size.
contextWindow int
}
// NewTelemetryHolder creates a new, empty TelemetryHolder with no telemetry yet.
func NewTelemetryHolder() *TelemetryHolder {
return &TelemetryHolder{
last: nil,
cumulative: cumulativeTelemetry{
toolDurationsMs: make(map[string]agentcore.ToolTiming),
},
}
}
// Reset clears the holder to "no telemetry yet", as if the session had just
// started. It is called when switching sessions (/fork, /clone, /import).
func (h *TelemetryHolder) Reset() {
h.last = nil
h.cumulative = cumulativeTelemetry{
toolDurationsMs: make(map[string]agentcore.ToolTiming),
}
}
// Fold incorporates a new TelemetryEvent into the holder: it becomes the new
// "last run" event, and its metrics are added to the cumulative accumulator.
func (h *TelemetryHolder) Fold(ev agentcore.TelemetryEvent) {
// Store the new event as the last run.
h.last = &ev
// Add its metrics to the cumulative accumulator.
h.cumulative.turns += ev.Turns
h.cumulative.truncationCount += ev.TruncationCount
h.cumulative.compactionCount += ev.CompactionCount
// Update the latest context tokens and window (always use the most recent).
h.cumulative.contextTokens = ev.ContextTokens
h.cumulative.contextWindow = ev.ContextWindow
// Sum the per-tool timings.
for name, timing := range ev.ToolDurationsMs {
agg := h.cumulative.toolDurationsMs[name]
agg.Count += timing.Count
agg.TotalMs += timing.TotalMs
h.cumulative.toolDurationsMs[name] = agg
}
}
// HasTelemetry returns true if at least one run has completed and telemetry is
// available.
func (h *TelemetryHolder) HasTelemetry() bool {
return h.last != nil
}
// Last returns the most recent TelemetryEvent, or nil if no run has completed.
func (h *TelemetryHolder) Last() *agentcore.TelemetryEvent {
return h.last
}
// CumulativeTurns returns the total number of turns across all runs.
func (h *TelemetryHolder) CumulativeTurns() int {
return h.cumulative.turns
}
// CumulativeTruncationCount returns the total number of truncations across all runs.
func (h *TelemetryHolder) CumulativeTruncationCount() int {
return h.cumulative.truncationCount
}
// CumulativeCompactionCount returns the total number of compactions across all runs.
func (h *TelemetryHolder) CumulativeCompactionCount() int {
return h.cumulative.compactionCount
}
// CumulativeToolDurations returns the aggregated tool timings across all runs.
func (h *TelemetryHolder) CumulativeToolDurations() map[string]agentcore.ToolTiming {
// Return a copy to prevent mutation of the internal state.
result := make(map[string]agentcore.ToolTiming, len(h.cumulative.toolDurationsMs))
for name, timing := range h.cumulative.toolDurationsMs {
result[name] = timing
}
return result
}
// LatestContextTokens returns the most recent observed context token count.
func (h *TelemetryHolder) LatestContextTokens() int {
return h.cumulative.contextTokens
}
// LatestContextWindow returns the most recent observed context window size.
func (h *TelemetryHolder) LatestContextWindow() int {
return h.cumulative.contextWindow
}
// CumulativeContextUtilization returns the latest utilization ratio, or 0 if no
// window is known.
func (h *TelemetryHolder) CumulativeContextUtilization() float64 {
if h.cumulative.contextWindow <= 0 {
return 0
}
return float64(h.cumulative.contextTokens) / float64(h.cumulative.contextWindow)
}