58 lines
1.6 KiB
Go
58 lines
1.6 KiB
Go
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
|
|
}
|