pricing

package
v0.2.0-rc.1 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2026 License: MIT Imports: 23 Imported by: 0

README

pricing

The shared price-list implementation used by the local product and the hosted platform. prices.json is the single compiled baseline. The platform's pricing-sync command updates it and the pricing-publish workflow signs it and publishes it at PublishedURL (static.usebaum.com/pricing/v3/prices.json, signature at .sig). Local refresh/cache handling and backend cost accounting use the same List and active-list state as the core token-cost calculations.

Published lists are verified against PublishedKey, the price-list key, which signs nothing else; a list signed by any other key fails with ErrSignatureNotTrusted. The path version changes whenever older builds could not read the publication: v2 added the full format, and v3 moved to the price-list key. Builds before v3, which includes every appliance released before the change, fetch v2 and trust the release manifest key. While those are supported, pricing-publish also signs the same file with the manifest key and publishes it at v2. When the transition ends, v2 stops changing and keeps its last signed pair, as v1 did.

Accounting prices each attempt using its reported model, or its configured model when the actual model was not reported. A total using any configured model carries estimated_from_configured_model. Agent costs (OutcomeCost, AccountingCost) estimate rather than stay unknown: an attempt with no tokens costs nothing, an incomplete one is priced from what it reported, and a missing speed, cache TTL or prompt tier takes the lowest rate the usage could have been billed at (standard speed, the 5-minute write, the base tier). The reason then names what was missing (usage_incomplete, model_or_prompt_rates_unknown, speed_not_reported, cache_ttl_not_reported) and the cost is a lower bound. An attempt with no model or no rates is left out of the sum; the cost is nil only when no attempt can be priced. Cost itself stays exact: it reports not priced rather than guess, for the hosted ledger and analytics.

oss/internal/pricing is an import facade for local consumers; it owns no price list or mutable state. Hosted fixture-model coverage stays in the platform so the standalone module does not need hosted fixtures.

Documentation

Overview

Package pricing turns the raw model strings adapters record into canonical models with known token rates.

Cost is computed at read time from the price list rather than stored on model_invocations. A stored cost_usd would freeze whatever the list said the day a row was written: a wrong rate, or a fix to the normalisation rules, would then apply only to new rows and need a backfill. Computing at read time means fixing the list fixes history.

The normalisation rules are code and the rates are data (list.go). A model whose name the rules already resolve — any new Claude or GPT name — is priced by adding its rate alone, which is what lets a published list price it without a release. A name that needs a new alias still needs one.

Index

Constants

View Source
const (
	VendorClaudeCode = "claude_code"
	VendorCodex      = "codex"
)

Canonical vendor slugs this package reasons about. They are `agent_session_ingests.vendor` values, which is what analytics groups by; the recorder that delivered the session (upload, Entire checkpoint) never reaches here, because it says nothing about how the tokens were counted.

View Source
const (
	SourceLiteLLM = "litellm"
	SourceManual  = "manual"
)

Entry sources. The sync tool rewrites litellm entries from LiteLLM's price file and never touches manual ones, which record a deliberate choice the source would overwrite (a Cursor rate, a peak-hour rate, a retired model).

View Source
const (
	OriginEmbedded = "embedded"
	OriginFetched  = "fetched"
	OriginCached   = "cached"
)

Origins of the active price list.

View Source
const (
	ReasonUsageNotReported    = "usage_not_reported"
	ReasonUsageIncomplete     = "usage_incomplete"
	ReasonRatesUnknown        = "model_or_prompt_rates_unknown"
	ReasonSpeedNotReported    = "speed_not_reported"
	ReasonCacheTTLNotReported = "cache_ttl_not_reported"
	ReasonConfiguredModel     = "estimated_from_configured_model"
)

Cost reasons qualify an agent run's cost (AccountingCost). Every one but ReasonConfiguredModel makes a cost that is shown a lower bound: the run used at least that much. A cost is nil only with ReasonUsageNotReported or, when no attempt's model has rates, ReasonRatesUnknown.

View Source
const FormatVersion = 2

FormatVersion marks the full format: a list that may carry cache_write_1h, above, fast and fast_above. Published lists must carry this marker.

View Source
const PublishedURL = "https://static.usebaum.com/pricing/v3/prices.json"

PublishedURL is where main's prices.json is published, with its signature at PublishedURL + ".sig". It carries the full format (tiers, fast mode, 1-hour cache writes). The path version changes whenever older builds could not read the publication: v2 added the full format, and v3 is signed with the price-list key. pricing-publish keeps writing v2, signed with the release manifest key, for appliances built before v3.

View Source
const ReasonBilledByCursor = "billed_by_cursor_subscription"

ReasonBilledByCursor is why a cursor-agent run has no recorded cost: Cursor bills it through the user's own subscription, so no list price describes it, and pricing its tokens at an API rate would mislead.

Variables

View Source
var ErrSignatureNotTrusted = errors.New("price list signature is not trusted")

ErrSignatureNotTrusted is the error for a list that publishedKey (or the key passed to ParsePublished) did not sign.

Functions

func CompareVersions

func CompareVersions(a, b string) int

CompareVersions orders two valid versions: negative when a is older.

func Cost

func Cost(model Model, usage Usage) (float64, bool)

Cost prices one rollup. ok is false when the model has no rate or the usage cannot be matched to one (see List.Cost), in which case the caller must report the tokens without a dollar figure rather than treating the cost as zero.

func EmbeddedDocument

func EmbeddedDocument() []byte

EmbeddedDocument returns the compiled prices.json bytes.

func Install

func Install(list *List)

Install makes list the active list. Callers choose which list is newest; see Newest.

func LoadPersisted

func LoadPersisted(dir string) error

LoadPersisted installs the list a previous Refresh stored in dir if its signature still verifies and it is newer than the active list. A missing file is not an error; any other failure leaves the active list unchanged.

func PublishedKey

func PublishedKey() ed25519.PublicKey

PublishedKey is the key published lists are signed with.

func Refresh

func Refresh(ctx context.Context, dir string) error

Refresh downloads only public signed data, without credentials or redirects. A failure leaves the compiled or previously verified list active. When dir is not empty, a downloaded list that becomes active is stored there for LoadPersisted.

func UnpricedReason

func UnpricedReason(model Model) string

UnpricedReason explains why a recognised model has no rate, or "" if the model is priced or unknown.

func Version

func Version() string

Version is the active list's version. A dollar figure with no version behind it cannot be reproduced later, because the list it came from will have moved.

Types

type Document

type Document struct {
	// Format identifies the full pricing format.
	Format int `json:"format,omitzero"`
	// Version is a date, with a ".N" suffix for a second list on one day. It
	// travels with every figure computed from the list.
	Version string           `json:"version"`
	Models  map[string]Entry `json:"models"`
}

Document is prices.json.

func ParseDocument

func ParseDocument(data []byte) (Document, error)

ParseDocument decodes and validates a price list. Unknown fields are rejected so a document written for a newer format is not half understood.

func (Document) Validate

func (d Document) Validate() error

Validate rejects a list that could not have been meant: no version, no models, or a rate that is negative, not finite or all zero.

type Entry

type Entry struct {
	Input        float64 `json:"input"`
	Output       float64 `json:"output"`
	CacheRead    float64 `json:"cache_read"`
	CacheWrite   float64 `json:"cache_write"`
	CacheWrite1h float64 `json:"cache_write_1h,omitzero"`
	// Above holds the rates for a request whose prompt is over
	// Above.PromptTokens.
	Above Tier `json:"above,omitzero"`
	// Fast holds the rates for a request run in fast mode.
	Fast RateSet `json:"fast,omitzero"`
	// FastAbove holds the fast-mode rates for a request whose prompt is over
	// FastAbove.PromptTokens, which is Above.PromptTokens. It needs both Above
	// and Fast. An entry with Above and Fast but no FastAbove has no published
	// fast rate over the threshold, and a fast request over it is not priced.
	FastAbove Tier   `json:"fast_above,omitzero"`
	Source    string `json:"source"`
	// LiteLLMKey names the source entry when it differs from the canonical id.
	LiteLLMKey string `json:"litellm_key,omitempty"`
	Note       string `json:"note,omitempty"`
}

Entry is one canonical model's rates, per million tokens. It stays comparable with == (pricingsync compares entries), so optional parts are value structs that are omitted when zero rather than pointers.

func (Entry) Base

func (e Entry) Base() RateSet

Base returns the entry's standard rates.

func (Entry) Rate

func (e Entry) Rate() Rate

Rate returns the entry's base four rates.

type Family

type Family string

Family is the model vendor family a raw string resolved to.

const (
	FamilyClaude  Family = "claude"
	FamilyGPT     Family = "gpt"
	FamilyGemini  Family = "gemini"
	FamilyGrok    Family = "grok"
	FamilyUnknown Family = ""
)

type List

type List struct {
	// contains filtered or unexported fields
}

List is a validated price list ready to price usage.

func Active

func Active() *List

Active is the list costs are priced with.

func Embedded

func Embedded() *List

Embedded is the list compiled into this build.

func NewList

func NewList(document Document) *List

NewList builds a List from a validated document.

func Newest

func Newest(a, b *List) *List

Newest returns whichever list has the later version, preferring a when they are equal.

func ParsePublished

func ParsePublished(data, signature []byte, key ed25519.PublicKey) (*List, error)

ParsePublished accepts a published list only when key signed it and it still prices every model the compiled list prices. The second check stops a truncated or mistaken publication from turning known costs into unknowns across every appliance at once.

func (*List) AccountingCost

func (l *List) AccountingCost(a *agent.Accounting) (*float64, string)

AccountingCost prices attempts separately so a repair using a different model cannot reprice the whole run at its final model's rate. It estimates rather than leave the cost unknown, because a rough figure is more use than none: an attempt with no tokens (Claude Code's <synthetic> error message) costs nothing, a call cut off or still running is priced from what it reported so far, and a missing speed, cache TTL or prompt tier takes the lowest rate it could have been billed at. The reason then names what was missing, and the cost is a lower bound. An attempt with no model, or one the list has no rates for, is left out of the sum. The cost is nil only when no attempt could be priced.

func (*List) BackendAccountingCost

func (l *List) BackendAccountingCost(backend string, a *agent.Accounting) (*float64, string)

BackendAccountingCost is AccountingCost for a run of backend: nil with the reason for a backend the list does not price.

func (*List) BackendOutcomeCost

func (l *List) BackendOutcomeCost(backend, model string, outcome agent.Outcome) *float64

BackendOutcomeCost is OutcomeCost for a run of backend: nil for a backend the list does not price.

func (*List) Cost

func (l *List) Cost(model Model, usage Usage) (float64, bool)

Cost prices one rollup with the list's rates. A tier applies to a request whose prompt is over its threshold, so the usage's prompt range decides: at or under the threshold throughout is the base (or fast) rates, over it throughout is the tier's, and a range that straddles it is not priced, because the usage does not say which requests were long. It also reports not priced for fast mode on an entry without fast rates, for fast mode over a threshold the entry has no fast_above rates for, for ultrafast, for 1-hour writes where the chosen rates have no 1-hour price, for a 1-hour count that is negative or larger than the cache writes it is part of, and for an inverted or negative prompt range.

func (*List) Full

func (l *List) Full() bool

Full reports whether the list was read from the full format. An unmarked list prices correctly only for usage without tiers, fast mode or 1-hour writes, so a build that reads the full format must not price with one.

func (*List) Models

func (l *List) Models() []string

Models lists the canonical ids the list prices, sorted.

func (*List) Normalize

func (l *List) Normalize(raw, vendor string) Model

Normalize resolves a raw model string to a canonical model.

The hard case is Claude, where two orderings are both in the wild: Anthropic writes `claude-sonnet-4-5` and Cursor writes `claude-4.5-sonnet` — family and size transposed. Prefix matching fails on that pair, so the decomposition below finds the size word and the version numbers wherever they sit rather than assuming an order.

vendor is the last resort, and only disambiguates a bare version string. The session model stores no provider column: which API served a model is not a fact any mapper reliably recorded, and the vendor implies it wherever it mattered.

func (*List) OutcomeCost

func (l *List) OutcomeCost(model string, outcome agent.Outcome) *float64

OutcomeCost is what an outcome's usage costs at API prices, estimated as AccountingCost estimates; nil only when it reported no tokens or its model has no rates. Agent usage is already disjoint, including Codex: it must not apply Codex's raw-session subtraction.

func (*List) PromptBands

func (l *List) PromptBands() []int64

PromptBands lists, ascending and without repeats, the prompt sizes at which any entry's rates change: the thresholds of its above and fast_above tiers. A query that sums usage groups requests by band, which keeps every request of a group on one side of every threshold, so the group can be priced exactly.

func (*List) Rates

func (l *List) Rates(model Model) (Rate, bool)

Rates returns the list's per-million-token prices for a model.

func (*List) RatesFor

func (l *List) RatesFor(model Model, usage Usage) (RateSet, bool)

RatesFor is the rate set Cost applies to the usage: the base, fast or long-prompt tier rates its speed and prompt range select. ok is false where Cost reports not priced for the model, speed or prompt range (an unknown model, ultrafast, fast with no fast rates, a range straddling a threshold, an inverted range), so a caller estimating before a request can tell "priced at these rates" from "would not be priced" and never falls back to base rates.

func (*List) Version

func (l *List) Version() string

type Model

type Model struct {
	// Canonical is the id the price table is keyed on, e.g. "claude-sonnet-4-5".
	// Empty when the raw string could not be resolved.
	Canonical string
	Family    Family
	// Size is the Claude tier (opus, sonnet, haiku, fable, mythos). Empty for
	// other families.
	Size string
	// Raw is the string the adapter recorded, kept so an unknown model can be
	// surfaced by name rather than as a silent zero.
	Raw string
}

Model is a normalised model identity.

func Normalize

func Normalize(raw, vendor string) Model

Normalize resolves a raw model string to a canonical model under the active list. A calculation that normalises or prices more than once should take one list with Active and use its methods, so a list installed mid-way does not split it across two versions.

func (Model) Known

func (m Model) Known() bool

Known reports whether normalisation resolved the raw string to a canonical model. An unknown model never fails a request; it costs zero and is surfaced in the response's coverage block.

type Rate

type Rate struct {
	Input      float64
	Output     float64
	CacheRead  float64
	CacheWrite float64
}

Rate is a per-million-token price in USD.

func Rates

func Rates(model Model) (Rate, bool)

Rates returns the per-million-token prices for a model. The second return is false for a model with no published rate we can cite — including every unknown model. Callers must treat that as "not priced", never as free.

type RateSet

type RateSet struct {
	Input      float64 `json:"input"`
	Output     float64 `json:"output"`
	CacheRead  float64 `json:"cache_read"`
	CacheWrite float64 `json:"cache_write"`
	// CacheWrite1h prices cache writes with the 1-hour TTL; zero means the
	// set has no such rate.
	CacheWrite1h float64 `json:"cache_write_1h,omitzero"`
}

RateSet is one set of per-million-token rates in USD.

func (RateSet) IsZero

func (r RateSet) IsZero() bool

IsZero reports whether the set is absent (encoding/json's omitzero).

type Source

type Source struct {
	// Origin is "embedded", "fetched" or "cached".
	Origin  string
	Version string
	// FetchedAt is when Refresh installed the active list; zero for the
	// embedded and cached lists.
	FetchedAt time.Time
	// RefreshError is why the last Refresh failed; empty after a success or
	// before any attempt.
	RefreshError string
}

Source says where the active price list came from: compiled in, fetched by Refresh in this process, or loaded by LoadPersisted from the list an earlier daemon run stored.

func CurrentSource

func CurrentSource() Source

CurrentSource describes the active price list and the last Refresh. It is safe to call while Refresh runs.

type Speed

type Speed string

Speed is the service speed a request was billed at.

const (
	SpeedUnknown   Speed = ""
	SpeedStandard  Speed = "standard"
	SpeedFast      Speed = "fast"
	SpeedUltrafast Speed = "ultrafast"
)

Speeds. Unreported speed is priced as standard, which is how a caller that does not record speed has always been priced; fast mode has its own rates; ultrafast is a separate, pricier tier that the price list has no rates for.

type Tier

type Tier struct {
	PromptTokens int64   `json:"prompt_tokens"`
	Input        float64 `json:"input"`
	Output       float64 `json:"output"`
	CacheRead    float64 `json:"cache_read"`
	CacheWrite   float64 `json:"cache_write"`
	CacheWrite1h float64 `json:"cache_write_1h,omitzero"`
}

Tier is the rate set a vendor charges for a request whose prompt is over PromptTokens. It replaces every rate, output included.

func (Tier) IsZero

func (t Tier) IsZero() bool

IsZero reports whether the tier is absent (encoding/json's omitzero).

func (Tier) RateSet

func (t Tier) RateSet() RateSet

RateSet returns the tier's rates.

type Usage

type Usage struct {
	Vendor           string
	InputTokens      int64
	OutputTokens     int64
	CacheReadTokens  int64
	CacheWriteTokens int64
	// CacheWrite1hTokens is the part of CacheWriteTokens written with the
	// 1-hour TTL; 0 when unknown or none. The rest are 5-minute writes.
	CacheWrite1hTokens int64
	// Speed is the service speed the request was billed at; SpeedUnknown (the
	// zero value) when the vendor did not report it.
	Speed Speed
	// SingleRequest is true when the usage is exactly one API request, so its
	// prompt is PromptTokens() and the prompt range below is not needed.
	SingleRequest bool
	// PromptRangeKnown says MinPromptTokens and MaxPromptTokens are set: the
	// smallest and largest prompt (by the PromptTokens rule) among the single
	// requests the usage covers. A caller that sums rows can know them without
	// knowing each request, for instance from a SQL group whose rows all sit on
	// one side of every tier threshold. Without it, and without SingleRequest,
	// the range is [0, PromptTokens()]: each request's prompt is at most the
	// combined prompt, and nothing bounds it from below.
	PromptRangeKnown bool
	MinPromptTokens  int64
	MaxPromptTokens  int64
}

Usage is one rollup's raw token columns, exactly as agent_session_model_invocations and agent_session_token_usage store them.

func (Usage) PromptTokens

func (u Usage) PromptTokens() int64

PromptTokens is the size of the prompt a request was billed for, which decides whether a long-prompt tier applies. Codex's input_tokens already includes its cached input, so it is the prompt; for every other vendor the buckets are disjoint and the prompt is their sum. This is the single place to change if a vendor documents a different rule.

func (Usage) UncachedInput

func (u Usage) UncachedInput() int64

UncachedInput applies the vendor-dependent cache-subsumption rule.

Codex's input_tokens already includes its cached input, reads and writes alike; Anthropic's buckets are disjoint. The token columns are stored raw, so any rollup that skips this branch charges a codex session for its cache reads (and writes) twice, inflating its cost by the cache-hit rate, which on a long session is 3-5x and still a believable dollar figure.

Jump to

Keyboard shortcuts

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