Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

275 lines
6.6 KiB
Go

package export
import (
"sort"
"strings"
"time"
pricingpkg "go.kenn.io/agentsview/internal/pricing"
)
type PricingRowSource string
const (
PricingRowSourceCustom PricingRowSource = "custom"
PricingRowSourceFetched PricingRowSource = "fetched"
PricingRowSourceEmbedded PricingRowSource = "embedded"
)
type ModelRates struct {
InputPerMTok float64
OutputPerMTok float64
CacheWritePerMTok float64
CacheReadPerMTok float64
UpdatedAt *time.Time
Source PricingRowSource
}
func (r ModelRates) CostForTokens(
inputTokens, outputTokens, reasoningTokens, cacheWriteTokens, cacheReadTokens int,
) float64 {
// reasoningTokens is a breakdown of outputTokens for current sources, not
// additional billable output. Reasoning-only rows still bill at output rate.
billableOutputTokens := outputTokens
if billableOutputTokens == 0 {
billableOutputTokens = reasoningTokens
}
return (float64(inputTokens)*r.InputPerMTok +
float64(billableOutputTokens)*r.OutputPerMTok +
float64(cacheWriteTokens)*r.CacheWritePerMTok +
float64(cacheReadTokens)*r.CacheReadPerMTok) / 1_000_000
}
type EffectivePricingRow struct {
ModelPattern string
Rates ModelRates
}
type PricingLookup struct {
Rates ModelRates
Pattern string
OK bool
}
type PricingResolver struct {
rows []EffectivePricingRow
byModel map[string]ModelRates
lookupCache map[string]PricingLookup
recorded map[string]*pricingRecord
}
type pricingRecord struct {
lookup PricingLookup
computed bool
reported bool
}
func NewPricingResolver(rows []EffectivePricingRow) *PricingResolver {
copied := make([]EffectivePricingRow, len(rows))
copy(copied, rows)
byModel := make(map[string]ModelRates, len(rows))
for _, row := range rows {
if row.ModelPattern == "" {
continue
}
byModel[row.ModelPattern] = row.Rates
}
return &PricingResolver{
rows: copied,
byModel: byModel,
lookupCache: make(map[string]PricingLookup),
recorded: make(map[string]*pricingRecord),
}
}
func (r *PricingResolver) Lookup(model string) PricingLookup {
if r == nil {
return PricingLookup{}
}
if lookup, ok := r.lookupCache[model]; ok {
return lookup
}
match := pricingpkg.ResolveMatch(model, r.byModel)
lookup := PricingLookup{
Rates: match.Value,
Pattern: match.Pattern,
OK: match.OK,
}
r.lookupCache[model] = lookup
return lookup
}
func (r *PricingResolver) RecordComputed(model string, lookup PricingLookup) {
if r == nil || model == "" {
return
}
rec := r.record(model, lookup)
rec.computed = true
}
func (r *PricingResolver) RecordReported(model string, lookup PricingLookup) {
if r == nil || model == "" {
return
}
rec := r.record(model, lookup)
rec.reported = true
}
func (r *PricingResolver) record(model string, lookup PricingLookup) *pricingRecord {
rec := r.recorded[model]
if rec == nil {
rec = &pricingRecord{}
r.recorded[model] = rec
}
rec.lookup = lookup
return rec
}
func (r *PricingResolver) BuildBlock() (PricingBlock, error) {
if r == nil {
return PricingBlock{}, nil
}
models := make(map[string]EffectiveModelRate, len(r.recorded))
fallbackSet := make(map[string]struct{})
var hasComputed, hasReported bool
modelNames := make([]string, 0, len(r.recorded))
for model := range r.recorded {
modelNames = append(modelNames, model)
}
sort.Strings(modelNames)
for _, model := range modelNames {
rec := r.recorded[model]
if rec == nil {
continue
}
source := recordCostSource(rec)
hasComputed = hasComputed || rec.computed
hasReported = hasReported || rec.reported
rate := EffectiveModelRate{
InputCostPerMTok: rec.lookup.Rates.InputPerMTok,
OutputCostPerMTok: rec.lookup.Rates.OutputPerMTok,
CacheWriteCostPerMTok: rec.lookup.Rates.CacheWritePerMTok,
CacheReadCostPerMTok: rec.lookup.Rates.CacheReadPerMTok,
CostSource: source,
}
if rec.lookup.OK {
pattern := rec.lookup.Pattern
rate.MatchedPattern = &pattern
if rec.lookup.Rates.Source == PricingRowSourceEmbedded {
fallbackSet[model] = struct{}{}
}
}
models[model] = rate
}
fallbackModels := make([]string, 0, len(fallbackSet))
for model := range fallbackSet {
fallbackModels = append(fallbackModels, model)
}
sort.Strings(fallbackModels)
digest, err := EffectivePricingDigest(r.rows)
if err != nil {
return PricingBlock{}, err
}
return PricingBlock{
Source: pricingSource(r.rows),
TableVersion: pricingTableVersion(r.rows),
LatestRowUpdatedAt: latestPricingRowUpdate(r.rows),
CustomOverrideCount: customPricingRowCount(r.rows),
EffectiveRowCount: len(r.rows),
Digest: digest,
CostSource: combinedCostSource(hasComputed, hasReported),
Fallback: PricingFallback{
Used: len(fallbackModels) > 0,
Models: fallbackModels,
},
Models: models,
}, nil
}
func pricingTableVersion(rows []EffectivePricingRow) string {
source := pricingSource(rows)
if strings.Contains(source, string(PricingRowSourceFetched)) {
if latest := latestPricingRowUpdate(rows); latest != nil {
return latest.UTC().Format(jsonTimeLayout)
}
return string(PricingRowSourceFetched)
}
if strings.Contains(source, string(PricingRowSourceEmbedded)) {
return pricingpkg.FallbackVersion
}
if source == string(PricingRowSourceCustom) {
return string(PricingRowSourceCustom)
}
return ""
}
func recordCostSource(rec *pricingRecord) CostSource {
return combinedCostSource(rec.computed, rec.reported)
}
func combinedCostSource(computed, reported bool) CostSource {
switch {
case computed && reported:
return CostSourceMixed
case reported:
return CostSourceReported
default:
return CostSourceComputed
}
}
func pricingSource(rows []EffectivePricingRow) string {
var custom, fetched, embedded bool
for _, row := range rows {
switch row.Rates.Source {
case PricingRowSourceCustom:
custom = true
case PricingRowSourceFetched:
fetched = true
case PricingRowSourceEmbedded:
embedded = true
}
}
var base string
switch {
case fetched:
base = string(PricingRowSourceFetched)
case embedded:
base = string(PricingRowSourceEmbedded)
}
if custom {
if base == "" {
return string(PricingRowSourceCustom)
}
return string(PricingRowSourceCustom) + "+" + base
}
return base
}
func customPricingRowCount(rows []EffectivePricingRow) int {
var count int
for _, row := range rows {
if row.Rates.Source == PricingRowSourceCustom {
count++
}
}
return count
}
func latestPricingRowUpdate(rows []EffectivePricingRow) *time.Time {
var latest *time.Time
for _, row := range rows {
if row.Rates.UpdatedAt == nil {
continue
}
t := row.Rates.UpdatedAt.UTC()
if latest == nil || t.After(*latest) {
latest = &t
}
}
return latest
}