feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
+253
-44
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/pkg/billingexpr"
|
||||
"github.com/QuantumNous/new-api/pkg/jsplugin"
|
||||
"github.com/QuantumNous/new-api/setting/billing_setting"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
@@ -31,11 +33,24 @@ type ModelPricingChange struct {
|
||||
}
|
||||
|
||||
type ModelPricingEntry struct {
|
||||
ModelName string `json:"model_name"`
|
||||
Version string `json:"version"`
|
||||
Configured PricingValues `json:"configured"`
|
||||
Effective PricingValues `json:"effective"`
|
||||
UsageSchema map[string]jsplugin.UsageFieldSchema `json:"usage_schema,omitempty"`
|
||||
ModelPricingDescription
|
||||
PluginVariants []ModelPricingPluginVariant `json:"plugin_variants,omitempty"`
|
||||
ModelName string `json:"model_name"`
|
||||
Version string `json:"version"`
|
||||
Configured PricingValues `json:"configured"`
|
||||
UsageSchema map[string]jsplugin.UsageFieldSchema `json:"usage_schema,omitempty"`
|
||||
}
|
||||
|
||||
type ModelPricingPluginVariant struct {
|
||||
PluginKey string `json:"plugin_key"`
|
||||
PluginName string `json:"plugin_name"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
UsageSchema map[string]jsplugin.UsageFieldSchema `json:"usage_schema"`
|
||||
UsageExamples []jsplugin.UsageExample `json:"usage_examples,omitempty"`
|
||||
Configured string `json:"configured"`
|
||||
Effective string `json:"effective"`
|
||||
Compatible bool `json:"compatible"`
|
||||
Stale bool `json:"stale,omitempty"`
|
||||
}
|
||||
|
||||
type ModelPricingSnapshot struct {
|
||||
@@ -51,7 +66,7 @@ var ErrModelPricingConflict = errors.New("model pricing changed; reload before s
|
||||
var modelPricingOptionKeys = []string{
|
||||
"AudioCompletionRatio", "AudioRatio", "CacheRatio", "CompletionRatio",
|
||||
"CreateCacheRatio", "ImageRatio", "ModelPrice", "ModelRatio",
|
||||
"billing_setting.billing_expr", "billing_setting.billing_mode",
|
||||
"billing_setting.billing_expr", "billing_setting.billing_mode", billing_setting.PluginBillingExprOption,
|
||||
}
|
||||
|
||||
var modelPricingMutationMu sync.Mutex
|
||||
@@ -78,28 +93,50 @@ func defaultPricingMaps() map[string]map[string]any {
|
||||
return result
|
||||
}
|
||||
|
||||
func readModelPricingMaps(db *gorm.DB) (map[string]map[string]any, error) {
|
||||
func readModelPricingMaps(db *gorm.DB) (map[string]map[string]any, map[string]bool, []string, error) {
|
||||
var rows []Option
|
||||
if err := db.Where(commonKeyCol+" IN ?", modelPricingOptionKeys).Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
values := defaultPricingMaps()
|
||||
existing := make(map[string]bool)
|
||||
counts := make(map[string]int)
|
||||
for _, row := range rows {
|
||||
var entries map[string]any
|
||||
if err := common.UnmarshalJsonStr(row.Value, &entries); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", row.Key, err)
|
||||
return nil, nil, nil, fmt.Errorf("%s: %w", row.Key, err)
|
||||
}
|
||||
if entries == nil {
|
||||
return nil, fmt.Errorf("%s must be a JSON object", row.Key)
|
||||
return nil, nil, nil, fmt.Errorf("%s must be a JSON object", row.Key)
|
||||
}
|
||||
values[row.Key] = entries
|
||||
existing[row.Key] = true
|
||||
counts[row.Key]++
|
||||
}
|
||||
return values, nil
|
||||
var duplicated []string
|
||||
for _, key := range modelPricingOptionKeys {
|
||||
if counts[key] > 1 {
|
||||
duplicated = append(duplicated, key)
|
||||
}
|
||||
}
|
||||
return values, existing, duplicated, nil
|
||||
}
|
||||
|
||||
func modelPricingValues(values map[string]map[string]any, name string) PricingValues {
|
||||
result := make(PricingValues)
|
||||
for _, key := range modelPricingOptionKeys {
|
||||
if key == billing_setting.PluginBillingExprOption {
|
||||
variants := make(map[string]any)
|
||||
for variant, expression := range values[key] {
|
||||
if plugin, model, ok := billing_setting.SplitPluginBillingExprKey(variant); ok && model == name {
|
||||
variants[plugin] = expression
|
||||
}
|
||||
}
|
||||
if len(variants) > 0 {
|
||||
result[key] = variants
|
||||
}
|
||||
continue
|
||||
}
|
||||
if value, exists := values[key][name]; exists {
|
||||
result[key] = value
|
||||
}
|
||||
@@ -107,11 +144,35 @@ func modelPricingValues(values map[string]map[string]any, name string) PricingVa
|
||||
return result
|
||||
}
|
||||
|
||||
// replaceModelPricing writes one complete model draft into the option maps.
|
||||
// Plugin expressions are grouped in the draft and flattened only in storage.
|
||||
func replaceModelPricing(values map[string]map[string]any, name string, draft PricingValues) {
|
||||
for _, key := range modelPricingOptionKeys {
|
||||
if key == billing_setting.PluginBillingExprOption {
|
||||
for variant := range values[key] {
|
||||
if _, model, ok := billing_setting.SplitPluginBillingExprKey(variant); ok && model == name {
|
||||
delete(values[key], variant)
|
||||
}
|
||||
}
|
||||
variants, _ := draft[key].(map[string]any)
|
||||
for plugin, expr := range variants {
|
||||
values[key][billing_setting.PluginBillingExprKey(plugin, name)] = expr
|
||||
}
|
||||
continue
|
||||
}
|
||||
delete(values[key], name)
|
||||
if value, exists := draft[key]; exists {
|
||||
values[key][name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func effectiveModelPricing(values map[string]map[string]any, name string) PricingValues {
|
||||
result := modelPricingValues(values, name)
|
||||
// Legacy wildcard aliases are resolved by the same normalization as relay.
|
||||
alias := ratio_setting.FormatMatchingModelName(name)
|
||||
for _, key := range modelPricingOptionKeys[:8] {
|
||||
for _, key := range []string{"ModelPrice", "ModelRatio", "CompletionRatio", "AudioRatio", "AudioCompletionRatio"} {
|
||||
delete(result, key)
|
||||
if value, exists := values[key][alias]; exists {
|
||||
result[key] = value
|
||||
}
|
||||
@@ -141,22 +202,56 @@ func effectiveModelPricing(values map[string]map[string]any, name string) Pricin
|
||||
}
|
||||
// Completion ratios include engine-enforced model defaults. Expose their
|
||||
// effective value without persisting them into the editable configuration.
|
||||
completion := ratio_setting.GetCompletionRatioInfo(name)
|
||||
if _, exists := result["CompletionRatio"]; !exists || completion.Locked {
|
||||
result["CompletionRatio"] = completion.Ratio
|
||||
var configuredCompletion *float64
|
||||
if ratio, exists := result["CompletionRatio"].(float64); exists {
|
||||
configuredCompletion = &ratio
|
||||
}
|
||||
result["CompletionRatio"] = ratio_setting.ResolveCompletionRatio(name, configuredCompletion).Ratio
|
||||
for key, fallback := range map[string]float64{
|
||||
"CacheRatio": ratio_setting.DefaultCacheRatio,
|
||||
"CreateCacheRatio": ratio_setting.DefaultCreateCacheRatio,
|
||||
"ImageRatio": ratio_setting.DefaultImageRatio,
|
||||
} {
|
||||
if _, exists := result[key]; !exists {
|
||||
result[key] = fallback
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PreviewModelPricing resolves a complete editable draft using the same defaults
|
||||
// as the saved-price display and conversion. It has no write side effects.
|
||||
func PreviewModelPricing(name string, draft PricingValues) (PricingValues, error) {
|
||||
if draft == nil {
|
||||
return nil, errors.New("pricing draft is required")
|
||||
}
|
||||
values, _, _, err := readModelPricingMaps(DB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateModelPricing(name, draft, modelPricingValues(values, name)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
replaceModelPricing(values, name, draft)
|
||||
return effectiveModelPricing(values, name), nil
|
||||
}
|
||||
|
||||
func GetModelPricingSnapshot(names []string) (*ModelPricingSnapshot, error) {
|
||||
values, err := readModelPricingMaps(DB)
|
||||
values, _, _, err := readModelPricingMaps(DB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(names) == 0 {
|
||||
nameSet := make(map[string]bool)
|
||||
for _, entries := range values {
|
||||
for key, entries := range values {
|
||||
for name := range entries {
|
||||
if key == billing_setting.PluginBillingExprOption {
|
||||
_, model, ok := billing_setting.SplitPluginBillingExprKey(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name = model
|
||||
}
|
||||
nameSet[name] = true
|
||||
}
|
||||
}
|
||||
@@ -172,12 +267,55 @@ func GetModelPricingSnapshot(names []string) (*ModelPricingSnapshot, error) {
|
||||
generation := jsplugin.DefaultRegistry.Generation()
|
||||
for _, name := range names {
|
||||
configured := modelPricingValues(values, name)
|
||||
entry := ModelPricingEntry{ModelName: name, Version: ModelPricingVersion(configured), Configured: configured, Effective: effectiveModelPricing(values, name)}
|
||||
entry := ModelPricingEntry{ModelName: name, Version: ModelPricingVersion(configured), Configured: configured,
|
||||
ModelPricingDescription: ModelPricingDescription{Effective: effectiveModelPricing(values, name)}}
|
||||
entry.CacheWriteMode = ResolveCacheWriteMode(name, configured)
|
||||
entry.BillingDetails = ResolveLegacyBillingDetails(name, entry.Effective, configured)
|
||||
if plugin, ok := generation.GetByModel(name); ok {
|
||||
entry.UsageSchema = plugin.Meta.UsageSchema
|
||||
entry.UsageSchema, _ = plugin.Meta.UsageForModel(name)
|
||||
} else if target, ok := ResolveTaskModelAlias(generation, name); ok {
|
||||
if plugin, ok := generation.Get(target.PluginKey); ok {
|
||||
entry.UsageSchema = plugin.Meta.UsageSchema
|
||||
entry.UsageSchema, _ = plugin.Meta.UsageForModel(target.Declared)
|
||||
}
|
||||
}
|
||||
plugins := generation.PluginsByModel(name)
|
||||
configuredVariants, _ := configured[billing_setting.PluginBillingExprOption].(map[string]any)
|
||||
if len(plugins) >= 2 || len(configuredVariants) > 0 {
|
||||
keys := make(map[string]bool, len(plugins)+len(configuredVariants))
|
||||
for _, plugin := range plugins {
|
||||
keys[plugin.Meta.Key] = true
|
||||
}
|
||||
for key := range configuredVariants {
|
||||
keys[key] = true
|
||||
}
|
||||
for _, key := range slices.Sorted(maps.Keys(keys)) {
|
||||
configuredValue, overridden := configuredVariants[key]
|
||||
configuredExpr, _ := configuredValue.(string)
|
||||
plugin, exists := generation.Get(key)
|
||||
if !exists || !slices.Contains(plugin.Meta.Models, name) {
|
||||
variant := ModelPricingPluginVariant{
|
||||
PluginKey: key, PluginName: key, Configured: configuredExpr,
|
||||
UsageSchema: map[string]jsplugin.UsageFieldSchema{}, Stale: true,
|
||||
}
|
||||
if exists {
|
||||
variant.PluginName, variant.Icon = plugin.Meta.Name, plugin.Meta.Icon
|
||||
}
|
||||
entry.PluginVariants = append(entry.PluginVariants, variant)
|
||||
continue
|
||||
}
|
||||
schema, examples := plugin.Meta.UsageForModel(name)
|
||||
if schema == nil {
|
||||
schema = map[string]jsplugin.UsageFieldSchema{}
|
||||
}
|
||||
expression := configuredExpr
|
||||
if !overridden && entry.Effective["billing_setting.billing_mode"] == billing_setting.BillingModeTieredExpr {
|
||||
expression, _ = entry.Effective["billing_setting.billing_expr"].(string)
|
||||
}
|
||||
entry.PluginVariants = append(entry.PluginVariants, ModelPricingPluginVariant{
|
||||
PluginKey: plugin.Meta.Key, PluginName: plugin.Meta.Name, Icon: plugin.Meta.Icon,
|
||||
UsageSchema: schema, UsageExamples: examples, Configured: configuredExpr, Effective: expression,
|
||||
Compatible: billing_setting.TaskExprCompatible(expression, schema),
|
||||
})
|
||||
}
|
||||
}
|
||||
result.Entries = append(result.Entries, entry)
|
||||
@@ -207,10 +345,56 @@ func GetModelPricingSnapshot(names []string) (*ModelPricingSnapshot, error) {
|
||||
}
|
||||
|
||||
func ValidateModelPricing(name string, values PricingValues) error {
|
||||
variants := make(map[string]any)
|
||||
for key, expression := range billing_setting.GetPluginBillingExprCopy() {
|
||||
if plugin, model, ok := billing_setting.SplitPluginBillingExprKey(key); ok && model == name {
|
||||
variants[plugin] = expression
|
||||
}
|
||||
}
|
||||
previous := PricingValues{billing_setting.PluginBillingExprOption: variants}
|
||||
if expression, ok := billing_setting.GetBillingExpr(name); ok {
|
||||
previous["billing_setting.billing_expr"] = expression
|
||||
}
|
||||
return validateModelPricing(name, values, previous)
|
||||
}
|
||||
|
||||
// Writes pass the locked database snapshot here, so allowing an unchanged stale
|
||||
// override cannot bypass validation through an out-of-date process-local cache.
|
||||
func validateModelPricing(name string, values, previous PricingValues) error {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return errors.New("model name is required")
|
||||
}
|
||||
generation := jsplugin.DefaultRegistry.Generation()
|
||||
previousVariants, _ := previous[billing_setting.PluginBillingExprOption].(map[string]any)
|
||||
variants := map[string]any{}
|
||||
if value, exists := values[billing_setting.PluginBillingExprOption]; exists {
|
||||
var ok bool
|
||||
variants, ok = value.(map[string]any)
|
||||
if !ok || variants == nil {
|
||||
return errors.New("plugin billing expressions must be a plugin-to-expression object")
|
||||
}
|
||||
for key, value := range variants {
|
||||
expression, ok := value.(string)
|
||||
if !ok || strings.TrimSpace(expression) == "" {
|
||||
return fmt.Errorf("model %s: plugin %s: billing expression is required", name, key)
|
||||
}
|
||||
plugin, exists := generation.Get(key)
|
||||
if !exists || !slices.Contains(plugin.Meta.Models, name) {
|
||||
if previousVariants[key] == expression {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("model %s: plugin %s does not declare this model", name, key)
|
||||
}
|
||||
schema, _ := plugin.Meta.UsageForModel(name)
|
||||
if err := billing_setting.SmokeTestTaskExpr(expression, schema); err != nil {
|
||||
return fmt.Errorf("model %s: plugin %s: %w", name, key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for key, value := range values {
|
||||
if key == billing_setting.PluginBillingExprOption {
|
||||
continue
|
||||
}
|
||||
if !IsModelPricingOption(key) {
|
||||
return fmt.Errorf("unsupported pricing field: %s", key)
|
||||
}
|
||||
@@ -225,19 +409,35 @@ func ValidateModelPricing(name string, values PricingValues) error {
|
||||
if !ok || strings.TrimSpace(expression) == "" {
|
||||
return errors.New("billing expression is required")
|
||||
}
|
||||
generation := jsplugin.DefaultRegistry.Generation()
|
||||
// Even a model expression currently shadowed by every provider must
|
||||
// compile; only its schema-specific smoke tests can be skipped.
|
||||
if _, err := billingexpr.CompileFromCache(expression); err != nil {
|
||||
return fmt.Errorf("model %s: %w", name, err)
|
||||
}
|
||||
var err error
|
||||
if plugin, ok := generation.GetByModel(name); ok {
|
||||
err = billing_setting.SmokeTestTaskExpr(expression, plugin.Meta.UsageSchema)
|
||||
if plugins := generation.PluginsByModel(name); len(plugins) > 0 {
|
||||
for _, plugin := range plugins {
|
||||
if _, overridden := variants[plugin.Meta.Key]; overridden {
|
||||
continue
|
||||
}
|
||||
schema, _ := plugin.Meta.UsageForModel(name)
|
||||
if err = billing_setting.SmokeTestTaskExpr(expression, schema); err != nil {
|
||||
return fmt.Errorf("model %s: plugin %s: %w", name, plugin.Meta.Key, err)
|
||||
}
|
||||
}
|
||||
} else if target, resolved := ResolveTaskModelAlias(generation, name); resolved {
|
||||
if plugin, ok := generation.Get(target.PluginKey); ok {
|
||||
err = billing_setting.SmokeTestTaskExpr(expression, plugin.Meta.UsageSchema)
|
||||
schema, _ := plugin.Meta.UsageForModel(target.Declared)
|
||||
err = billing_setting.SmokeTestTaskExpr(expression, schema)
|
||||
} else {
|
||||
err = billing_setting.SmokeTestExpr(expression)
|
||||
}
|
||||
} else {
|
||||
} else if previous[key] != expression || len(billingexpr.UsedUsageKeys(expression)) == 0 {
|
||||
err = billing_setting.SmokeTestExpr(expression)
|
||||
}
|
||||
// With no remaining plugin, an unchanged stored usage expression has
|
||||
// no schema to test. Preserve it so removing stale overrides or saving
|
||||
// other model prices does not become impossible.
|
||||
if err != nil {
|
||||
return fmt.Errorf("model %s: %w", name, err)
|
||||
}
|
||||
@@ -271,26 +471,22 @@ func UpdateModelPricing(changes []ModelPricingChange) error {
|
||||
if change.ExpectedVersion == "" {
|
||||
return ErrModelPricingConflict
|
||||
}
|
||||
if err := ValidateModelPricing(change.ModelName, change.Pricing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return mutateModelPricingOptions(func(_ *gorm.DB, values map[string]map[string]any) error {
|
||||
defaults := defaultPricingMaps()
|
||||
for _, change := range changes {
|
||||
if ModelPricingVersion(modelPricingValues(values, change.ModelName)) != change.ExpectedVersion {
|
||||
previous := modelPricingValues(values, change.ModelName)
|
||||
if ModelPricingVersion(previous) != change.ExpectedVersion {
|
||||
return fmt.Errorf("%w: %s", ErrModelPricingConflict, change.ModelName)
|
||||
}
|
||||
pricing := change.Pricing
|
||||
if change.Reset {
|
||||
pricing = modelPricingValues(defaults, change.ModelName)
|
||||
}
|
||||
for _, key := range modelPricingOptionKeys {
|
||||
delete(values[key], change.ModelName)
|
||||
if value, exists := pricing[key]; exists {
|
||||
values[key][change.ModelName] = value
|
||||
}
|
||||
if err := validateModelPricing(change.ModelName, pricing, previous); err != nil {
|
||||
return err
|
||||
}
|
||||
replaceModelPricing(values, change.ModelName, pricing)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -300,6 +496,7 @@ func UpdateModelPricing(changes []ModelPricingChange) error {
|
||||
// locking, validation and transaction path as the model-level API.
|
||||
func UpdateModelPricingOptions(updates map[string]string) error {
|
||||
return mutateModelPricingOptions(func(_ *gorm.DB, values map[string]map[string]any) error {
|
||||
previous := maps.Clone(values)
|
||||
names := make(map[string]bool)
|
||||
for key, raw := range updates {
|
||||
if !IsModelPricingOption(key) {
|
||||
@@ -312,16 +509,22 @@ func UpdateModelPricingOptions(updates map[string]string) error {
|
||||
if entries == nil {
|
||||
return fmt.Errorf("%s must be a JSON object", key)
|
||||
}
|
||||
for name := range values[key] {
|
||||
names[name] = true
|
||||
for _, entriesForKey := range []map[string]any{values[key], entries} {
|
||||
for name := range entriesForKey {
|
||||
if key == billing_setting.PluginBillingExprOption {
|
||||
_, model, ok := billing_setting.SplitPluginBillingExprKey(name)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid plugin billing expression key: %s", name)
|
||||
}
|
||||
name = model
|
||||
}
|
||||
names[name] = true
|
||||
}
|
||||
}
|
||||
values[key] = entries
|
||||
for name := range entries {
|
||||
names[name] = true
|
||||
}
|
||||
}
|
||||
for name := range names {
|
||||
if err := ValidateModelPricing(name, modelPricingValues(values, name)); err != nil {
|
||||
if err := validateModelPricing(name, modelPricingValues(values, name), modelPricingValues(previous, name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -334,8 +537,18 @@ func mutateModelPricingOptions(mutate func(*gorm.DB, map[string]map[string]any)
|
||||
defer modelPricingMutationMu.Unlock()
|
||||
var committed map[string]map[string]any
|
||||
err := DB.Transaction(func(tx *gorm.DB) error {
|
||||
values, existing, duplicated, err := readModelPricingMaps(lockForUpdate(tx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(duplicated) > 0 {
|
||||
common.SysError("options table has duplicate pricing keys [" + strings.Join(duplicated, ", ") + "]; the table is missing a primary key")
|
||||
}
|
||||
defaults := defaultPricingMaps()
|
||||
for _, key := range modelPricingOptionKeys {
|
||||
if existing[key] {
|
||||
continue
|
||||
}
|
||||
encoded, err := common.Marshal(defaults[key])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -345,10 +558,6 @@ func mutateModelPricingOptions(mutate func(*gorm.DB, map[string]map[string]any)
|
||||
return err
|
||||
}
|
||||
}
|
||||
values, err := readModelPricingMaps(lockForUpdate(tx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mutate(tx, values); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user