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
205 lines
5.2 KiB
Go
205 lines
5.2 KiB
Go
package export
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf16"
|
|
)
|
|
|
|
func EffectivePricingDigest(rows []EffectivePricingRow) (string, error) {
|
|
canonical, err := canonicalPricingJSON(canonicalPricingRows(rows))
|
|
if err != nil {
|
|
return "", fmt.Errorf("canonical pricing digest: %w", err)
|
|
}
|
|
sum := sha256.Sum256(canonical)
|
|
return "sha256:" + fmt.Sprintf("%x", sum), nil
|
|
}
|
|
|
|
func canonicalPricingRows(rows []EffectivePricingRow) map[string]any {
|
|
copied := make([]EffectivePricingRow, len(rows))
|
|
copy(copied, rows)
|
|
sort.SliceStable(copied, func(i, j int) bool {
|
|
return canonicalPricingRowLess(copied[i], copied[j])
|
|
})
|
|
out := make([]any, 0, len(copied))
|
|
for _, row := range copied {
|
|
var updatedAt any
|
|
if row.Rates.UpdatedAt != nil {
|
|
updatedAt = row.Rates.UpdatedAt.UTC().Format(jsonTimeLayout)
|
|
}
|
|
out = append(out, map[string]any{
|
|
"cache_read_per_mtok": row.Rates.CacheReadPerMTok,
|
|
"cache_write_per_mtok": row.Rates.CacheWritePerMTok,
|
|
"input_per_mtok": row.Rates.InputPerMTok,
|
|
"model_pattern": row.ModelPattern,
|
|
"output_per_mtok": row.Rates.OutputPerMTok,
|
|
"source": string(row.Rates.Source),
|
|
"updated_at": updatedAt,
|
|
})
|
|
}
|
|
return map[string]any{"rows": out}
|
|
}
|
|
|
|
const jsonTimeLayout = "2006-01-02T15:04:05Z07:00"
|
|
|
|
func canonicalPricingJSON(v any) ([]byte, error) {
|
|
var b bytes.Buffer
|
|
if err := writeCanonicalJSON(&b, reflect.ValueOf(v)); err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func writeCanonicalJSON(b *bytes.Buffer, v reflect.Value) error {
|
|
if !v.IsValid() {
|
|
b.WriteString("null")
|
|
return nil
|
|
}
|
|
if v.Kind() == reflect.Interface || v.Kind() == reflect.Pointer {
|
|
if v.IsNil() {
|
|
b.WriteString("null")
|
|
return nil
|
|
}
|
|
return writeCanonicalJSON(b, v.Elem())
|
|
}
|
|
switch v.Kind() {
|
|
case reflect.Map:
|
|
if v.Type().Key().Kind() != reflect.String {
|
|
return fmt.Errorf("canonical JSON only supports string map keys")
|
|
}
|
|
keys := v.MapKeys()
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
return utf16Less(keys[i].String(), keys[j].String())
|
|
})
|
|
b.WriteByte('{')
|
|
for i, key := range keys {
|
|
if i > 0 {
|
|
b.WriteByte(',')
|
|
}
|
|
writeJSONString(b, key.String())
|
|
b.WriteByte(':')
|
|
if err := writeCanonicalJSON(b, v.MapIndex(key)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
b.WriteByte('}')
|
|
case reflect.Slice, reflect.Array:
|
|
b.WriteByte('[')
|
|
for i := 0; i < v.Len(); i++ {
|
|
if i > 0 {
|
|
b.WriteByte(',')
|
|
}
|
|
if err := writeCanonicalJSON(b, v.Index(i)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
b.WriteByte(']')
|
|
case reflect.String:
|
|
writeJSONString(b, v.String())
|
|
case reflect.Bool:
|
|
if v.Bool() {
|
|
b.WriteString("true")
|
|
} else {
|
|
b.WriteString("false")
|
|
}
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
b.WriteString(strconv.FormatInt(v.Int(), 10))
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
b.WriteString(strconv.FormatUint(v.Uint(), 10))
|
|
case reflect.Float32, reflect.Float64:
|
|
f := v.Float()
|
|
if math.IsNaN(f) || math.IsInf(f, 0) {
|
|
return fmt.Errorf("canonical JSON cannot encode non-finite number")
|
|
}
|
|
b.WriteString(formatCanonicalJSONFloat(f, v.Type().Bits()))
|
|
default:
|
|
return fmt.Errorf("canonical JSON unsupported type %s", v.Type())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeJSONString(b *bytes.Buffer, s string) {
|
|
var encoded bytes.Buffer
|
|
enc := json.NewEncoder(&encoded)
|
|
enc.SetEscapeHTML(false)
|
|
_ = enc.Encode(s)
|
|
b.Write(bytes.TrimSpace(encoded.Bytes()))
|
|
}
|
|
|
|
func canonicalPricingRowLess(a, b EffectivePricingRow) bool {
|
|
aValues := canonicalPricingRowSortValues(a)
|
|
bValues := canonicalPricingRowSortValues(b)
|
|
for i := range aValues {
|
|
if aValues[i] != bValues[i] {
|
|
return aValues[i] < bValues[i]
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func canonicalPricingRowSortValues(row EffectivePricingRow) []string {
|
|
updatedAt := ""
|
|
if row.Rates.UpdatedAt != nil {
|
|
updatedAt = row.Rates.UpdatedAt.UTC().Format(jsonTimeLayout)
|
|
}
|
|
return []string{
|
|
row.ModelPattern,
|
|
string(row.Rates.Source),
|
|
formatCanonicalJSONFloat(row.Rates.InputPerMTok, 64),
|
|
formatCanonicalJSONFloat(row.Rates.OutputPerMTok, 64),
|
|
formatCanonicalJSONFloat(row.Rates.CacheWritePerMTok, 64),
|
|
formatCanonicalJSONFloat(row.Rates.CacheReadPerMTok, 64),
|
|
updatedAt,
|
|
}
|
|
}
|
|
|
|
func formatCanonicalJSONFloat(f float64, bits int) string {
|
|
if f == 0 {
|
|
return "0"
|
|
}
|
|
abs := math.Abs(f)
|
|
if abs >= 1e21 || abs < 1e-6 {
|
|
return normalizeCanonicalExponent(strconv.FormatFloat(f, 'e', -1, bits))
|
|
}
|
|
return strconv.FormatFloat(f, 'f', -1, bits)
|
|
}
|
|
|
|
func normalizeCanonicalExponent(s string) string {
|
|
mantissa, exponent, ok := strings.Cut(s, "e")
|
|
if !ok {
|
|
return s
|
|
}
|
|
sign := ""
|
|
switch {
|
|
case strings.HasPrefix(exponent, "+"):
|
|
sign = "+"
|
|
exponent = strings.TrimPrefix(exponent, "+")
|
|
case strings.HasPrefix(exponent, "-"):
|
|
sign = "-"
|
|
exponent = strings.TrimPrefix(exponent, "-")
|
|
}
|
|
exponent = strings.TrimLeft(exponent, "0")
|
|
if exponent == "" {
|
|
exponent = "0"
|
|
}
|
|
return mantissa + "e" + sign + exponent
|
|
}
|
|
|
|
func utf16Less(a, b string) bool {
|
|
au := utf16.Encode([]rune(a))
|
|
bu := utf16.Encode([]rune(b))
|
|
for i := 0; i < len(au) && i < len(bu); i++ {
|
|
if au[i] != bu[i] {
|
|
return au[i] < bu[i]
|
|
}
|
|
}
|
|
return len(au) < len(bu)
|
|
}
|