Documentation
¶
Overview ¶
Package contextwindow estimates token usage for a message history. The numbers are approximate — exact tokenization depends on the model's tokenizer and varies enough between providers that "good enough for a watermark" is the operative bar.
Two callers consume this:
- The TUI status bar, which colors the token counter when usage crosses a configurable warn_threshold.
- The auto-summarization trigger, which fires before the next turn when usage crosses auto_threshold.
Both treat the numbers as ratios, not absolute counts, so a 10–15% estimation error is harmless.
The companion question — how big IS the model's window — lives in the catalog package (catalog.ResolveWindow / WindowFor): the window is a per-model/per-deployment fact sourced from the catalog, the user's override, or a live probe, none of which belong in this leaf estimation package (which would otherwise drag the catalog's config/network deps in, and form an import cycle via adapter).
Index ¶
- func EstimateImage(img adapter.ImageBlock) int
- func EstimateMessage(m adapter.Message) int
- func EstimateText(s string) int
- func EstimateTokens(messages []adapter.Message) int
- func EstimateToolSchemas(tools []adapter.Tool) int
- func SplitMessages(msgs []adapter.Message) (systemTokens, conversationTokens int)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EstimateImage ¶ added in v0.4.0
func EstimateImage(img adapter.ImageBlock) int
EstimateImage returns the approximate token cost of a single image.
This deliberately does NOT scale with len(img.Data). A 4MB screenshot and a 200KB one of the same dimensions cost the model the same — byte size reflects compression, not context. Estimating from bytes would have scored one screenshot at a million tokens and triggered compaction constantly; ignoring images (the prior behavior) scored them at zero, so an image-heavy session could never trigger compaction at all. Pixel area is the measure both major providers actually bill on.
DecodeConfig reads only the header, so this stays cheap enough for the per-turn and status-bar call sites.
img.MediaType is honored: only formats with a registered header decoder can be dimension-read, and webp — which the read_file tool actively advertises — has none in the stdlib's image package. Rather than pull in a webp decoder (golang.org/x/image) just to read a width, we skip the sniff for any MediaType we can't decode and return the fallback. That's the same outcome the byte-sniff would have produced for an unregistered format, but explicit and stable rather than accidental.
func EstimateMessage ¶ added in v0.4.0
EstimateMessage returns the approximate token cost of one message — content, tool-call arguments, and any attached images.
Callers that walk long histories (compaction's retain-point search) use this per-message form to avoid building a slice per call.
func EstimateText ¶ added in v0.3.0
EstimateText returns an approximate token count for an arbitrary string under the same heuristic EstimateTokens uses. /context calls this for the system-prompt and memory-file buckets where the input isn't a Message slice.
func EstimateTokens ¶
EstimateTokens returns an approximate token count for the given messages. Tool-call argument JSON and tool-result content are counted; role metadata is not (it's bounded and small).
func EstimateToolSchemas ¶ added in v0.3.0
EstimateToolSchemas counts what the adapter advertises per tool: the tool name, description, and the JSON-serialized parameter schema. Mirrors what's actually sent on the wire so the /context Tools bucket matches the real cost.
Marshal errors fall back to a zero contribution for the offending tool — a malformed schema would have failed at registration time, so in practice this branch only fires for tools that pass nil/non-JSON schemas (which the adapter would reject anyway).
func SplitMessages ¶ added in v0.3.0
SplitMessages partitions a message slice into the system-prompt prefix and the conversation body, returning estimated token counts for each bucket. The split is by role: every RoleSystem message (typically just one) goes into the system bucket, everything else into conversation. /context charges the two to different buckets so the user can see the always-present system overhead separately from the growing chat history.
Types ¶
This section is empty.