Files
esengine d62e4bc71b fix(compaction): let the provider's last reported prompt size trigger a fold when the local estimate runs low
The fold trigger compared only a local estimate against the threshold. That
estimate is characters times one tokens-per-character ratio, and the ratio is
not learned from requests that carry images or provider-side tool results, so a
session with screenshots can sit well under the real size and defer the fold.

The trigger now takes the larger of the local estimate and the provider's
reported prompt_tokens (cached tokens included) for the last request plus the
local estimate of what the visible view gained since. The anchor is dropped when
a fold changes the projection, when the transcript is swapped, and when the
usage is estimated, aggregated over retries, or padded by provider-run tools.
The hard ceiling and the overflow paths still read the local estimate alone, so
a provider count that no fold can lower never ends a turn.
2026-10-06 07:26:33 -07:00

50 lines
2.1 KiB
Go

package agent
import "reasonix/internal/contract/provider"
// usageAnchor ties one provider-reported prompt size to the visible view that
// request carried. promptTokens stays zero until the usage arrives, so a request
// whose stream ended without one leaves no anchor to read.
type usageAnchor struct {
// promptTokens is the request's prompt_tokens as the provider counted it,
// cached tokens included (every provider adapter normalises to that).
promptTokens int
visible requestCalibrationShape
projectionVersion uint64
}
// anchorProviderUsage completes the anchor of the request in flight. It trusts
// the same usage as calibration does, minus the image rule: pixels the host
// cannot count are exactly what the anchor is for. A turn the provider padded
// with its own tool results counts pages no fold can remove, so it anchors nothing.
func (a *contextWindow) anchorProviderUsage(usage *provider.Usage) {
if a == nil || usage == nil || usage.Estimated || usage.ServerToolRequests > 0 {
return
}
tokens := usage.LatestPromptTokens()
sent := a.sess.output.usageAnchor.Load()
if tokens <= 0 || sent == nil || sent.visible.requestChars <= 0 {
return
}
anchored := *sent
anchored.promptTokens = tokens
a.sess.output.usageAnchor.Store(&anchored)
}
// providerReportedFloor is the provider's last prompt size plus what the visible
// view has gained since that request, both sides of the gain sized by the same
// local estimate so a stale ratio cancels. It answers false after any fold: the
// reported size describes a context that no longer exists, and only the next
// request's usage anchors the new one.
func (a *contextWindow) providerReportedFloor(visible requestCalibrationShape, projectionVersion uint64) (int, bool) {
if a == nil {
return 0, false
}
anchor := a.sess.output.usageAnchor.Load()
if anchor == nil || anchor.promptTokens <= 0 || anchor.projectionVersion != projectionVersion {
return 0, false
}
gain := a.estimatedShapeTokens(visible) - a.estimatedShapeTokens(anchor.visible)
return max(anchor.promptTokens+gain, 0), true
}