package compaction import ( "git.gobha.me/xcaliber/chat-switchboard/treepath" ) // ── Token Estimation ──────────────────────── // // Rough heuristic matching the frontend Tokens.estimate() (~4 chars/token). // Good enough for threshold checks. Swap in a real tokenizer later if // over/under-triggering becomes a problem. // EstimateTokens returns a rough token count for a string. // Uses the ~4 chars/token heuristic for English (GPT/Claude average). func EstimateTokens(text string) int { return (len(text) + 3) / 4 } // EstimatePath returns total estimated tokens for a message path. // Adds +4 per message for role/delimiter overhead, matching the frontend. func EstimatePath(path []treepath.PathMessage) int { total := 0 for i := range path { total += EstimateTokens(path[i].Content) + 4 } return total } // EstimatePathFromSummary returns estimated tokens for messages after // the most recent summary boundary. Returns 0 and false if a summary // exists but fewer than minMessages follow it. func EstimatePathFromSummary(path []treepath.PathMessage, minMessages int) (tokens int, msgCount int, ok bool) { startIdx := 0 for i := range path { if treepath.IsSummaryMessage(&path[i]) { startIdx = i + 1 } } post := path[startIdx:] // Count non-system, non-summary messages count := 0 est := 0 for i := range post { if post[i].Role == "system" || treepath.IsSummaryMessage(&post[i]) { continue } count++ est += EstimateTokens(post[i].Content) + 4 } if count < minMessages { return 0, count, false } return est, count, true }