usage

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2026 License: AGPL-3.0 Imports: 2 Imported by: 0

Documentation

Overview

Package usage tracks token consumption and dollar cost across API requests.

Pricing and token counts are kept separate so the same RequestUsage value can be evaluated against different pricing tiers, and so the TUI can display raw counts independently of cost.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Pricing

type Pricing struct {
	InputPrice        float64 // cost of non-cached input tokens
	CacheWrite5mPrice float64 // Anthropic: cost to write tokens into the prompt cache with a 5-minute TTL (higher than InputPrice)
	CacheWrite1hPrice float64 // Anthropic: cost to write tokens into the prompt cache with a 1-hour TTL (higher than CacheWrite5mPrice)
	CacheWritePrice   float64 // OpenAI: cost to write tokens into the prompt cache (no TTL split; higher than InputPrice)
	CacheReadPrice    float64 // cost to read tokens from the prompt cache (lower than InputPrice)
	OutputPrice       float64 // cost of tokens generated by the model
}

Pricing holds per-million-token rates for a specific model, within a single context-size tier.

Cache writes cost more than uncached input because the API must store the KV cache entry; cache reads are substantially cheaper, making long multi-turn sessions progressively less expensive. Anthropic distinguishes cache-write TTL (5-minute vs 1-hour); OpenAI publishes a single cache-write rate with no TTL split. A model only ever populates the fields its provider actually bills — the rest stay zero. All prices are in US dollars per one million tokens.

type RequestUsage

type RequestUsage struct {
	InputTokens                int64 // uncached input tokens sent to the model
	OutputTokens               int64 // tokens generated by the model in this response
	CacheCreation5mInputTokens int64 // input tokens written to the 5-minute prompt cache this request (Anthropic)
	CacheCreation1hInputTokens int64 // input tokens written to the 1-hour prompt cache this request (Anthropic)
	CacheWriteInputTokens      int64 // input tokens written to the prompt cache this request, no TTL split (OpenAI)
	CacheReadInputTokens       int64 // input tokens served from the prompt cache this request

	// Sum of input/output/cache tokens consumed by any compaction iterations
	// reported in usage.iterations. Billed by the API but NOT included in the
	// top-level fields above. Compaction is an Anthropic-only feature, so
	// there is no CompactionCacheWriteInputTokens counterpart.
	CompactionInputTokens                int64
	CompactionOutputTokens               int64
	CompactionCacheCreation5mInputTokens int64
	CompactionCacheCreation1hInputTokens int64
	CompactionCacheReadInputTokens       int64
}

RequestUsage captures the token buckets reported in a single API response. The five primary fields (InputTokens, OutputTokens, CacheCreation5mInputTokens, CacheCreation1hInputTokens, CacheReadInputTokens) map directly to the top-level Anthropic usage object and represent the post-compaction "message" iteration only.

The Compaction* fields capture the per-compaction-iteration overhead the API reports separately in `usage.iterations`. Per Anthropic's compaction docs, top-level input_tokens/output_tokens omit compaction iterations, so callers MUST fold these in for accurate billing and total token counts. They are excluded from the context-window-size calculation in SessionUsage.Add, because the next request starts from the small, post-compaction context, not from the size summarized away.

CacheCreation5mInputTokens and CacheCreation1hInputTokens are charged at their respective write rates (both higher than input; 1h costs more than 5m). CacheReadInputTokens are charged at the read rate (lower than input). These fields are zero when prompt caching is not active.

CacheWriteInputTokens is OpenAI's equivalent of a cache-write bucket: it has no TTL split, so it is a single field billed at Pricing.CacheWritePrice. It is always zero for Anthropic requests, which use the CacheCreation5m/1h buckets above instead. OpenAI reports cached input tokens as a subset of its total input token count and cache-write tokens as a separate count; FromOpenAI already derives InputTokens as the uncached remainder, so this field never overlaps with InputTokens or CacheReadInputTokens.

func FromAnthropic

func FromAnthropic(u anthropic.BetaUsage) RequestUsage

FromAnthropic maps the SDK's BetaUsage fields into the harness-internal RequestUsage type, decoupling the rest of the application from the SDK struct.

Top-level input_tokens and output_tokens reflect only the final "message" iteration — Anthropic's compaction docs explicitly state that compaction iterations are excluded from the top-level totals. We walk usage.iterations and sum every entry of type "compaction" into the Compaction* fields so cost/total accounting includes that overhead. A long conversation may compact more than once; all such iterations are summed.

func FromOpenAI

func FromOpenAI(u responses.ResponseUsage) RequestUsage

FromOpenAI maps the SDK's ResponseUsage fields into the harness-internal RequestUsage type, decoupling the rest of the application from the SDK struct.

InputTokens is derived as the uncached remainder of the SDK's total input_tokens: OpenAI reports CachedTokens as a subset of InputTokens, and CacheWriteTokens as a distinct, separately-billed count that is never itself part of InputTokens. Subtracting only CachedTokens (not CacheWriteTokens) here keeps that separation intact so cache-write tokens are billed once, at Pricing.CacheWritePrice, rather than being folded into uncached input.

func (RequestUsage) ContextTokens

func (r RequestUsage) ContextTokens() int64

ContextTokens returns the total input-side tokens this request consumed: uncached input plus every cache bucket (write and read), excluding compaction-iteration overhead. This is the figure used to select a context-tiered Pricing from a Schedule, and matches the definition used for SessionUsage.LastContextTokens.

func (RequestUsage) Cost

func (r RequestUsage) Cost(p Pricing) float64

Cost returns the dollar cost of this request given the provided pricing tier.

Each token bucket is billed at its own rate. The division by 1_000_000 converts per-million prices to per-token before multiplying. Compaction iteration tokens are billed identically to their non-compaction counterparts (same model, same rates).

func (RequestUsage) CostWithSchedule

func (r RequestUsage) CostWithSchedule(s Schedule) float64

CostWithSchedule returns the dollar cost of this request, selecting the applicable Pricing tier from s based on the request's total input-side context (see ContextTokens). This is how callers should compute cost for any model whose Schedule may have a long-context tier; calling Cost directly with a single Pricing skips tier selection.

type Schedule

type Schedule struct {
	Short     Pricing
	Long      *Pricing
	Threshold int64
}

Schedule is a provider-neutral pricing schedule for one model. Some providers (OpenAI) charge different per-token rates once a request's total input-side context reaches a published threshold; others (Anthropic, and OpenAI's smaller models) charge a single flat rate regardless of context size.

Long is nil for single-tier models. When non-nil, a request is billed at Long instead of Short once its total input-side context tokens are >= Threshold (the boundary is inclusive of the long tier).

func (Schedule) Select

func (s Schedule) Select(contextTokens int64) Pricing

Select returns the Pricing tier that applies to a request whose total input-side context is contextTokens. It returns Long when the schedule has a long-context tier and contextTokens is at or above Threshold; otherwise it returns Short.

type SessionUsage

type SessionUsage struct {
	TotalInputTokens              int64   // sum of uncached input tokens across all requests
	TotalOutputTokens             int64   // sum of output tokens across all requests
	TotalCacheCreationInputTokens int64   // sum of cache-write tokens across all requests
	TotalCacheReadInputTokens     int64   // sum of cache-read tokens across all requests
	LastContextTokens             int64   // total input-side tokens from the most recent request; used for context-fill display
	TotalCost                     float64 // cumulative dollar cost of all requests
	RequestCount                  int     // number of completed API requests
	CompactionCount               int     // number of times the context was compacted; incremented by the TUI layer
}

SessionUsage accumulates token counts and cost across all requests in a session, and tracks how full the model's context window currently is.

LastContextTokens is not a running total; it is overwritten on each call to Add and reflects only the most recent request. The TUI uses this to display a context-window fill indicator. CompactionCount is incremented by the caller (the TUI layer) rather than here, because compaction detection lives in the client layer, not in usage accounting.

func (*SessionUsage) Add

func (s *SessionUsage) Add(r RequestUsage, sched Schedule)

Add folds one request's token counts into the running session totals and updates the current context window size.

s's tier is selected from r's total input-side context (see RequestUsage.ContextTokens), so long-context requests are billed at the long tier both here and in per-request API logging.

Compaction-iteration tokens are added to the running totals so that the session-level numbers and TotalCost reflect the true bill. They are deliberately NOT added to LastContextTokens — that field tracks the context window state going INTO the next request, which after compaction is the small post-summary state (the API drops pre-compaction messages automatically). Including the compaction overhead here would inflate the context-fill indicator past 100% on the response that triggered the summarisation.

func (*SessionUsage) Merge

func (s *SessionUsage) Merge(other SessionUsage)

Merge folds another SessionUsage's running totals into the receiver. It is used to fold a subagent's fully-accumulated usage (e.g. from research_codebase or review_ticket, each with its own model schedule and pre-computed TotalCost) into the parent session's totals.

LastContextTokens is deliberately left untouched: a merged subagent runs its own, separate context window, and folding its context-fill snapshot into the parent's would inflate the parent's context-fill indicator with a number that has nothing to do with the parent's own context state. The parent's LastContextTokens must only ever reflect the parent's own most recent request, exactly as documented on Add.

Source Files

  • anthropic_adapter.go
  • openai_adapter.go
  • usage.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL