package llm import "strings" // Price is per-million-token list pricing. type Price struct { InputPerMTok float64 OutputPerMTok float64 } // Cache multipliers relative to the input rate. const ( cacheReadMultiplier = 0.1 cacheWriteMultiplier = 1.25 ) // prices covers the models this harness is likely to be pointed at. A model // that is not listed reports no cost rather than a wrong one. var prices = map[string]Price{ "claude-opus-5": {InputPerMTok: 5, OutputPerMTok: 25}, "claude-opus-4-8": {InputPerMTok: 5, OutputPerMTok: 25}, "claude-opus-4-7": {InputPerMTok: 5, OutputPerMTok: 25}, "claude-sonnet-5": {InputPerMTok: 2, OutputPerMTok: 10}, "claude-haiku-4-5": {InputPerMTok: 1, OutputPerMTok: 5}, "claude-fable-5-1": {InputPerMTok: 10, OutputPerMTok: 50}, "claude-fable-5": {InputPerMTok: 10, OutputPerMTok: 50}, } // Cost prices one usage record. The boolean reports whether the model's // pricing is known; a made-up number in METRICS.md is worse than a blank. func (u Usage) Cost(model string) (float64, bool) { p, ok := prices[basePrice(model)] if !ok { return 0, false } return (float64(u.InputTokens)*p.InputPerMTok + float64(u.OutputTokens)*p.OutputPerMTok + float64(u.CacheReadInputTokens)*p.InputPerMTok*cacheReadMultiplier + float64(u.CacheCreationInputTokens)*p.InputPerMTok*cacheWriteMultiplier) / 1_000_000, true } // PriceOf reports the list pricing for a model, if this harness knows it. // The picker shows what it knows and says nothing about what it does not. func PriceOf(model string) (Price, bool) { p, ok := prices[basePrice(model)] return p, ok } // basePrice strips the decoration the API puts on a model id — a bracketed // variant tag, a -latest or -YYYYMMDD suffix — leaving the name the price is // listed under. A dated id and a context-window variant are the same model // billed at the same rate, and /v1/models hands out both. func basePrice(model string) string { if i := strings.IndexByte(model, '['); i >= 0 { model = model[:i] } model = strings.TrimSuffix(model, "-latest") if i := strings.LastIndexByte(model, '-'); i >= 0 { if suffix := model[i+1:]; len(suffix) == 8 && allDigits(suffix) { model = model[:i] } } return model } func allDigits(s string) bool { for _, r := range s { if r < '0' || r > '9' { return false } } return len(s) > 0 }