package config import ( "fmt" "os" "strings" "reasonix/internal/provider" ) func deepSeekV4FlashPrice() *provider.Pricing { return &provider.Pricing{CacheHit: 0.02, Input: 1, Output: 2, Currency: "¥"} } func deepSeekV4ProPrice() *provider.Pricing { return &provider.Pricing{CacheHit: 0.025, Input: 3, Output: 6, Currency: "¥"} } func deepSeekV4Prices() map[string]*provider.Pricing { return map[string]*provider.Pricing{ "deepseek-v4-flash": deepSeekV4FlashPrice(), "deepseek-v4-pro": deepSeekV4ProPrice(), } } func deepSeekV4FlashPriceUSD() *provider.Pricing { return &provider.Pricing{CacheHit: 0.0028, Input: 0.14, Output: 0.28, Currency: "$"} } func deepSeekV4ProPriceUSD() *provider.Pricing { return &provider.Pricing{CacheHit: 0.003625, Input: 0.435, Output: 0.87, Currency: "$"} } func deepSeekV4PricesUSD() map[string]*provider.Pricing { return map[string]*provider.Pricing{ "deepseek-v4-flash": deepSeekV4FlashPriceUSD(), "deepseek-v4-pro": deepSeekV4ProPriceUSD(), } } // DeepSeekV4PricesForLanguage keeps the settings/template call site stable while // official DeepSeek defaults move to RMB. Persisted prices still win; this is // only used for templates and missing-default backfills. func DeepSeekV4PricesForLanguage(lang string) map[string]*provider.Pricing { _ = lang return deepSeekV4Prices() } func deepSeekV4PricesForConfig(c *Config) map[string]*provider.Pricing { _ = c return deepSeekV4Prices() } func deepSeekV4PriceForModel(lang, model string) *provider.Pricing { _ = lang return clonePricing(deepSeekV4Prices()[strings.TrimSpace(model)]) } // DeepSeekOfficialPricingLanguage is retained for settings/template compatibility. // Official DeepSeek providers now seed RMB prices by default; explicit user // prices in config still override these defaults. func (c *Config) DeepSeekOfficialPricingLanguage() string { _ = c return "zh" } // ApplyDeepSeekOfficialDefaultPricing refreshes built-in/official DeepSeek // prices that still match known official defaults. Custom user prices are left // untouched. func (c *Config) ApplyDeepSeekOfficialDefaultPricing() { applyDeepSeekOfficialDefaultPricing(c) } func applyDeepSeekOfficialDefaultPricing(c *Config) { if c == nil { return } lang := c.DeepSeekOfficialPricingLanguage() for i := range c.Providers { p := &c.Providers[i] if officialProviderKind(p) != "deepseek" { continue } if isKnownDeepSeekOfficialPricing(p.Model, p.Price) { p.Price = deepSeekV4PriceForModel(lang, p.Model) } for model, price := range p.Prices { if isKnownDeepSeekOfficialPricing(model, price) { p.Prices[model] = deepSeekV4PriceForModel(lang, model) } } } } func mimoV25ProPrice() *provider.Pricing { return &provider.Pricing{CacheHit: 0.025, Input: 3, Output: 6, Currency: "¥"} } func mimoV25Price() *provider.Pricing { return &provider.Pricing{CacheHit: 0.02, Input: 1, Output: 2, Currency: "¥"} } func mimoV2FlashPrice() *provider.Pricing { return &provider.Pricing{CacheHit: 0.07, Input: 0.70, Output: 2.10, Currency: "¥"} } func mimoDomesticPrices(models []string) map[string]*provider.Pricing { prices := map[string]*provider.Pricing{} for _, model := range models { switch strings.TrimSpace(model) { case "mimo-v2.5-pro", "mimo-v2-pro": prices[model] = mimoV25ProPrice() case "mimo-v2.5", "mimo-v2-omni": prices[model] = mimoV25Price() case "mimo-v2-flash": prices[model] = mimoV2FlashPrice() } } return prices } func longCat20Price() *provider.Pricing { return &provider.Pricing{CacheHit: 0.04, Input: 2, Output: 8, Currency: "¥"} } func longCat20Prices(models []string) map[string]*provider.Pricing { prices := map[string]*provider.Pricing{} for _, model := range models { switch strings.TrimSpace(model) { case "LongCat-2.0": prices[model] = longCat20Price() } } return prices } const ( deepSeekPricingResetConfigVersion = 3 windowsBashSandboxDefaultConfigVersion = 4 ) // ApplyUserConfigUpgradesOnStartup applies one-time startup migrations. It // intentionally runs from the desktop and CLI startup paths, not every config // Load(), so user edits made after the upgrade are preserved. func ApplyUserConfigUpgradesOnStartup(path string) (bool, error) { path = strings.TrimSpace(path) if path == "" { return false, nil } if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { return false, nil } return false, err } var header Config if _, err := decodeTOMLFile(path, &header); err != nil { return false, fmt.Errorf("config %s: %w", path, err) } if header.ConfigVersion >= Default().ConfigVersion { return false, nil } cfg := LoadForEdit(path) changed := false if header.ConfigVersion < deepSeekPricingResetConfigVersion { resetOfficialProviderPricingDefaults(cfg) changed = true } if shouldMarkWindowsBashSandboxDefaultUpgrade(header.ConfigVersion) { resetWindowsBashSandboxDefaultOnUpgrade(cfg) // Mark the Windows v4 migration even when the user was already on off, // so a later manual enforce choice is not treated as the old template default. changed = true } if !changed { return false, nil } cfg.ConfigVersion = Default().ConfigVersion if err := cfg.SaveTo(path); err != nil { return false, err } return true, nil } // ResetOfficialProviderPricingOnUpgrade is retained for older call sites. func ResetOfficialProviderPricingOnUpgrade(path string) (bool, error) { return ApplyUserConfigUpgradesOnStartup(path) } func shouldMarkWindowsBashSandboxDefaultUpgrade(fromVersion int) bool { return runtimeGOOS == "windows" && fromVersion < windowsBashSandboxDefaultConfigVersion } func resetWindowsBashSandboxDefaultOnUpgrade(c *Config) { if c == nil { return } if strings.TrimSpace(c.Sandbox.Bash) != "enforce" { return } c.Sandbox.Bash = "off" } func resetOfficialProviderPricingDefaults(c *Config) { if c == nil { return } for i := range c.Providers { p := &c.Providers[i] switch { case officialProviderKind(p) == "deepseek": resetDeepSeekOfficialPricing(p) } } } func resetDeepSeekOfficialPricing(p *ProviderEntry) { if p == nil { return } defaults := deepSeekV4Prices() p.Price = nil if strings.TrimSpace(p.Model) != "" && len(p.Models) == 0 { if price := defaults[strings.TrimSpace(p.Model)]; price != nil { p.Price = clonePricing(price) p.Prices = nil return } } if p.Prices == nil { p.Prices = map[string]*provider.Pricing{} } for model, price := range defaults { if p.HasModel(model) { p.Prices[model] = clonePricing(price) } } } func isKnownDeepSeekOfficialPricing(model string, price *provider.Pricing) bool { model = strings.TrimSpace(model) if model == "" || price == nil { return false } for _, prices := range []map[string]*provider.Pricing{deepSeekV4Prices(), deepSeekV4PricesUSD()} { if samePricing(price, prices[model]) { return true } } return false } func samePricing(a, b *provider.Pricing) bool { if a == nil || b == nil { return false } return a.CacheHit == b.CacheHit && a.Input == b.Input && a.Output == b.Output && a.Currency == b.Currency }