Files
thotam 503909d3b6 fix(sessions): store last-call context tokens instead of run-cumulative total
SetLastPromptTokens received TotalUsage.PromptTokens — the sum of prompt
tokens across every think→act→observe iteration of a run — instead of the
final iteration's own usage. A tool-heavy run inflated the stored value by
the iteration count, so the sessions list showed e.g. 901K "context used"
against a 258K window, and maybeSummarize over-triggered compaction once
the inflation exceeded the 40% overhead clamp (observed: 83 compactions
on a 4-message session).

Track the final iteration's usage separately (ThinkState.LastUsage) and
persist its actual context size: prompt tokens + cached segments (Anthropic
reports input_tokens excluding cache read/creation) + output tokens (the
final reply joins history, so it occupies the next request's prompt). The
run-cumulative total still feeds AccumulateTokens for billing.
2026-07-23 10:37:25 +07:00

56 lines
2.2 KiB
Go

package providers
// CallUsage attributes one LLM call (or a tool's internal LLM call) to its
// provider/model with the tokens it consumed and its cost. Usage is embedded
// so its token fields serialize flat (prompt_tokens, cache_read_input_tokens, …)
// alongside type/name/provider/model — this same value is both the in-memory
// accumulation record and the webhook `calls[]` element.
type CallUsage struct {
Type string `json:"type"` // "llm_call" | "tool_call"
Name string `json:"name"` // e.g. "9router/cx/gpt-5.6 #3" or "read_image"
Provider string `json:"provider"`
Model string `json:"model"`
Usage // embedded, NO json tag → promotes token fields to flat CallUsage JSON
CostUSD float64 `json:"cost_usd,omitempty"`
}
// SumCallUsage folds per-call usage into one aggregate: summed tokens/cache,
// OR-ed PromptTokensIncludeCachedSegments. Zero value for an empty slice.
func SumCallUsage(calls []CallUsage) Usage {
var total Usage
for _, c := range calls {
total.PromptTokens += c.PromptTokens
total.CompletionTokens += c.CompletionTokens
total.TotalTokens += c.TotalTokens
total.CacheReadTokens += c.CacheReadTokens
total.CacheCreationTokens += c.CacheCreationTokens
total.ThinkingTokens += c.ThinkingTokens
if c.PromptTokensIncludeCachedSegments {
total.PromptTokensIncludeCachedSegments = true
}
}
return total
}
// ContextTokens returns the full prompt-side size of a single LLM call —
// the actual context the model saw — normalizing provider cache accounting.
// Anthropic-style usage reports input_tokens EXCLUDING cached segments, so
// cache read/creation tokens must be added back; OpenAI-style usage
// (PromptTokensIncludeCachedSegments=true) already includes them.
// Only meaningful for per-call usage, not run-cumulative totals.
func (u Usage) ContextTokens() int {
if u.PromptTokensIncludeCachedSegments {
return u.PromptTokens
}
return u.PromptTokens + u.CacheReadTokens + u.CacheCreationTokens
}
// SumCallCost totals the per-call USD cost (0 when pricing was unavailable).
func SumCallCost(calls []CallUsage) float64 {
var total float64
for _, c := range calls {
total += c.CostUSD
}
return total
}