api_key_auth_cache_impl.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. package service
  2. import (
  3. "context"
  4. "crypto/sha256"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "log/slog"
  9. "math/rand/v2"
  10. "time"
  11. "github.com/Wei-Shaw/sub2api/internal/config"
  12. "github.com/dgraph-io/ristretto"
  13. )
  14. const apiKeyAuthSnapshotVersion = 25 // v24: group model_allowlist field (renamed from models_list_config, enforcing semantics)
  15. type apiKeyAuthCacheConfig struct {
  16. l1Size int
  17. l1TTL time.Duration
  18. l2TTL time.Duration
  19. negativeTTL time.Duration
  20. jitterPercent int
  21. singleflight bool
  22. }
  23. func newAPIKeyAuthCacheConfig(cfg *config.Config) apiKeyAuthCacheConfig {
  24. if cfg == nil {
  25. return apiKeyAuthCacheConfig{}
  26. }
  27. auth := cfg.APIKeyAuth
  28. return apiKeyAuthCacheConfig{
  29. l1Size: auth.L1Size,
  30. l1TTL: time.Duration(auth.L1TTLSeconds) * time.Second,
  31. l2TTL: time.Duration(auth.L2TTLSeconds) * time.Second,
  32. negativeTTL: time.Duration(auth.NegativeTTLSeconds) * time.Second,
  33. jitterPercent: auth.JitterPercent,
  34. singleflight: auth.Singleflight,
  35. }
  36. }
  37. func (c apiKeyAuthCacheConfig) l1Enabled() bool {
  38. return c.l1Size > 0 && c.l1TTL > 0
  39. }
  40. func (c apiKeyAuthCacheConfig) l2Enabled() bool {
  41. return c.l2TTL > 0
  42. }
  43. func (c apiKeyAuthCacheConfig) negativeEnabled() bool {
  44. return c.negativeTTL > 0
  45. }
  46. // jitterTTL 为缓存 TTL 添加抖动,避免多个请求在同一时刻同时过期触发集中回源。
  47. // 这里直接使用 rand/v2 的顶层函数:并发安全,无需全局互斥锁。
  48. func (c apiKeyAuthCacheConfig) jitterTTL(ttl time.Duration) time.Duration {
  49. if ttl <= 0 {
  50. return ttl
  51. }
  52. if c.jitterPercent <= 0 {
  53. return ttl
  54. }
  55. percent := c.jitterPercent
  56. if percent > 100 {
  57. percent = 100
  58. }
  59. delta := float64(percent) / 100
  60. randVal := rand.Float64()
  61. factor := 1 - delta + randVal*(2*delta)
  62. if factor <= 0 {
  63. return ttl
  64. }
  65. return time.Duration(float64(ttl) * factor)
  66. }
  67. func (s *APIKeyService) initAuthCache(cfg *config.Config) {
  68. s.authCfg = newAPIKeyAuthCacheConfig(cfg)
  69. if s.authCfg.negativeEnabled() {
  70. negativeSize := defaultNegativeAuthCacheSize
  71. if s.authCfg.l1Size > 0 && s.authCfg.l1Size < negativeSize {
  72. negativeSize = s.authCfg.l1Size
  73. }
  74. cache, err := ristretto.NewCache(&ristretto.Config{
  75. NumCounters: int64(negativeSize) * 10,
  76. MaxCost: int64(negativeSize),
  77. BufferItems: 64,
  78. })
  79. if err == nil {
  80. s.authNegativeCacheL1 = cache
  81. }
  82. }
  83. if s.authCfg.l1Enabled() {
  84. cache, err := ristretto.NewCache(&ristretto.Config{
  85. NumCounters: int64(s.authCfg.l1Size) * 10,
  86. MaxCost: int64(s.authCfg.l1Size),
  87. BufferItems: 64,
  88. })
  89. if err == nil {
  90. s.authCacheL1 = cache
  91. }
  92. }
  93. }
  94. // StartAuthCacheInvalidationSubscriber starts the Pub/Sub subscriber for L1 cache invalidation.
  95. // This should be called after the service is fully initialized.
  96. func (s *APIKeyService) StartAuthCacheInvalidationSubscriber(ctx context.Context) {
  97. if s.cache == nil || (s.authCacheL1 == nil && s.authNegativeCacheL1 == nil) {
  98. return
  99. }
  100. s.authInvalidationStart.Do(func() {
  101. subscriberCtx, cancel := context.WithCancel(ctx)
  102. subscriberCtx = withAuthCacheSubscriptionReady(subscriberCtx, func() {
  103. s.authInvalidationConnected.Store(true)
  104. })
  105. s.authInvalidationCancel = cancel
  106. s.authInvalidationWG.Add(1)
  107. go func() {
  108. defer s.authInvalidationWG.Done()
  109. backoff := time.Second
  110. for {
  111. err := s.cache.SubscribeAuthCacheInvalidation(subscriberCtx, func(cacheKey string) {
  112. s.invalidateLocalAuthCache(cacheKey)
  113. })
  114. wasConnected := s.authInvalidationConnected.Swap(false)
  115. if subscriberCtx.Err() != nil {
  116. return
  117. }
  118. if wasConnected {
  119. backoff = time.Second
  120. }
  121. s.authInvalidationFailures.Add(1)
  122. if err == nil {
  123. err = errors.New("auth cache invalidation subscription closed")
  124. }
  125. slog.Warn("failed to start auth cache invalidation subscriber; retrying", "error", err, "retry_in", backoff)
  126. timer := time.NewTimer(backoff)
  127. select {
  128. case <-subscriberCtx.Done():
  129. timer.Stop()
  130. return
  131. case <-timer.C:
  132. }
  133. if backoff < 30*time.Second {
  134. backoff *= 2
  135. if backoff > 30*time.Second {
  136. backoff = 30 * time.Second
  137. }
  138. }
  139. }
  140. }()
  141. })
  142. }
  143. func (s *APIKeyService) invalidateLocalAuthCache(cacheKey string) {
  144. if s == nil {
  145. return
  146. }
  147. if s.authCacheL1 != nil {
  148. s.authCacheL1.Del(cacheKey)
  149. }
  150. if s.authNegativeCacheL1 != nil {
  151. s.authNegativeCacheL1.Del(cacheKey)
  152. }
  153. }
  154. type AuthCacheInvalidationSubscriberHealth struct {
  155. Connected bool `json:"connected"`
  156. Failures uint64 `json:"failures"`
  157. }
  158. func (s *APIKeyService) AuthCacheInvalidationSubscriberHealth() AuthCacheInvalidationSubscriberHealth {
  159. if s == nil {
  160. return AuthCacheInvalidationSubscriberHealth{}
  161. }
  162. return AuthCacheInvalidationSubscriberHealth{
  163. Connected: s.authInvalidationConnected.Load(),
  164. Failures: s.authInvalidationFailures.Load(),
  165. }
  166. }
  167. func (s *APIKeyService) StopAuthCacheInvalidationSubscriber() {
  168. if s == nil {
  169. return
  170. }
  171. s.authInvalidationStop.Do(func() {
  172. if s.authInvalidationCancel != nil {
  173. s.authInvalidationCancel()
  174. }
  175. s.authInvalidationWG.Wait()
  176. })
  177. }
  178. func (s *APIKeyService) authCacheKey(key string) string {
  179. sum := sha256.Sum256([]byte(key))
  180. return hex.EncodeToString(sum[:])
  181. }
  182. func (s *APIKeyService) getAuthCacheEntry(ctx context.Context, cacheKey string) (*APIKeyAuthCacheEntry, bool) {
  183. if s.authCacheL1 != nil {
  184. if val, ok := s.authCacheL1.Get(cacheKey); ok {
  185. if entry, ok := val.(*APIKeyAuthCacheEntry); ok {
  186. return entry, true
  187. }
  188. }
  189. }
  190. if s.authNegativeCacheL1 != nil {
  191. if val, ok := s.authNegativeCacheL1.Get(cacheKey); ok {
  192. if entry, ok := val.(*APIKeyAuthCacheEntry); ok && entry.NotFound {
  193. return entry, true
  194. }
  195. }
  196. }
  197. if s.cache == nil || !s.authCfg.l2Enabled() {
  198. return nil, false
  199. }
  200. entry, err := s.cache.GetAuthCache(ctx, cacheKey)
  201. if err != nil {
  202. return nil, false
  203. }
  204. s.setAuthCacheL1(cacheKey, entry)
  205. return entry, true
  206. }
  207. func (s *APIKeyService) setAuthCacheL1(cacheKey string, entry *APIKeyAuthCacheEntry) {
  208. if entry == nil {
  209. return
  210. }
  211. if entry.NotFound {
  212. if s.authNegativeCacheL1 != nil && s.authCfg.negativeTTL > 0 {
  213. _ = s.authNegativeCacheL1.SetWithTTL(cacheKey, entry, 1, s.authCfg.jitterTTL(s.authCfg.negativeTTL))
  214. }
  215. return
  216. }
  217. if s.authCacheL1 == nil {
  218. return
  219. }
  220. ttl := s.authCfg.l1TTL
  221. ttl = s.authCfg.jitterTTL(ttl)
  222. _ = s.authCacheL1.SetWithTTL(cacheKey, entry, 1, ttl)
  223. }
  224. func (s *APIKeyService) setAuthCacheEntry(ctx context.Context, cacheKey string, entry *APIKeyAuthCacheEntry, ttl time.Duration) {
  225. if entry == nil {
  226. return
  227. }
  228. s.setAuthCacheL1(cacheKey, entry)
  229. if s.cache == nil || !s.authCfg.l2Enabled() {
  230. return
  231. }
  232. _ = s.cache.SetAuthCache(ctx, cacheKey, entry, s.authCfg.jitterTTL(ttl))
  233. }
  234. func (s *APIKeyService) deleteAuthCache(ctx context.Context, cacheKey string) {
  235. if s.authCacheL1 != nil {
  236. s.authCacheL1.Del(cacheKey)
  237. }
  238. if s.authNegativeCacheL1 != nil {
  239. s.authNegativeCacheL1.Del(cacheKey)
  240. }
  241. if s.cache == nil {
  242. return
  243. }
  244. _ = s.cache.DeleteAuthCache(ctx, cacheKey)
  245. // Publish invalidation message to other instances
  246. _ = s.cache.PublishAuthCacheInvalidation(ctx, cacheKey)
  247. }
  248. func (s *APIKeyService) loadAuthCacheEntry(ctx context.Context, key, cacheKey string) (*APIKeyAuthCacheEntry, error) {
  249. apiKey, err := s.lookupAPIKeyForAuth(ctx, key)
  250. if err != nil {
  251. if errors.Is(err, ErrAPIKeyNotFound) {
  252. entry := &APIKeyAuthCacheEntry{NotFound: true}
  253. if s.authCfg.negativeEnabled() {
  254. // Invalid keys are attacker-controlled and high-cardinality. Keep their
  255. // negative entries in the bounded process-local cache; do not amplify
  256. // random-key scans into Redis writes on every instance.
  257. s.setAuthCacheL1(cacheKey, entry)
  258. }
  259. return entry, nil
  260. }
  261. return nil, fmt.Errorf("get api key: %w", err)
  262. }
  263. apiKey.Key = key
  264. snapshot := s.snapshotFromAPIKey(ctx, apiKey)
  265. if snapshot == nil {
  266. return nil, fmt.Errorf("get api key: %w", ErrAPIKeyNotFound)
  267. }
  268. entry := &APIKeyAuthCacheEntry{Snapshot: snapshot}
  269. s.setAuthCacheEntry(ctx, cacheKey, entry, s.authCfg.l2TTL)
  270. return entry, nil
  271. }
  272. func (s *APIKeyService) lookupAPIKeyForAuth(ctx context.Context, key string) (*APIKey, error) {
  273. if s == nil || s.apiKeyRepo == nil {
  274. return nil, ErrAPIKeyNotFound
  275. }
  276. if s.authLookupSlots == nil {
  277. return s.apiKeyRepo.GetByKeyForAuth(ctx, key)
  278. }
  279. s.authLookupTotal.Add(1)
  280. select {
  281. case s.authLookupSlots <- struct{}{}:
  282. s.authLookupInFlight.Add(1)
  283. defer func() {
  284. s.authLookupInFlight.Add(-1)
  285. <-s.authLookupSlots
  286. }()
  287. case <-ctx.Done():
  288. return nil, ctx.Err()
  289. default:
  290. s.authLookupRejected.Add(1)
  291. return nil, ErrAPIKeyAuthOverloaded
  292. }
  293. return s.apiKeyRepo.GetByKeyForAuth(ctx, key)
  294. }
  295. func (s *APIKeyService) applyAuthCacheEntry(key string, entry *APIKeyAuthCacheEntry) (*APIKey, bool, error) {
  296. if entry == nil {
  297. return nil, false, nil
  298. }
  299. if entry.NotFound {
  300. return nil, true, ErrAPIKeyNotFound
  301. }
  302. if entry.Snapshot == nil {
  303. return nil, false, nil
  304. }
  305. if entry.Snapshot.Version != apiKeyAuthSnapshotVersion {
  306. return nil, false, nil
  307. }
  308. return s.snapshotToAPIKey(key, entry.Snapshot), true, nil
  309. }
  310. func (s *APIKeyService) snapshotFromAPIKey(ctx context.Context, apiKey *APIKey) *APIKeyAuthSnapshot {
  311. if apiKey == nil || apiKey.User == nil {
  312. return nil
  313. }
  314. snapshot := &APIKeyAuthSnapshot{
  315. Version: apiKeyAuthSnapshotVersion,
  316. APIKeyID: apiKey.ID,
  317. UserID: apiKey.UserID,
  318. GroupID: apiKey.GroupID,
  319. Name: apiKey.Name,
  320. Status: apiKey.Status,
  321. IPWhitelist: apiKey.IPWhitelist,
  322. IPBlacklist: apiKey.IPBlacklist,
  323. Quota: apiKey.Quota,
  324. QuotaUsed: apiKey.QuotaUsed,
  325. ExpiresAt: apiKey.ExpiresAt,
  326. RateLimit5h: apiKey.RateLimit5h,
  327. RateLimit1d: apiKey.RateLimit1d,
  328. RateLimit7d: apiKey.RateLimit7d,
  329. User: APIKeyAuthUserSnapshot{
  330. ID: apiKey.User.ID,
  331. Status: apiKey.User.Status,
  332. Role: apiKey.User.Role,
  333. Balance: apiKey.User.Balance,
  334. Concurrency: apiKey.User.Concurrency,
  335. AllowedGroups: apiKey.User.AllowedGroups,
  336. Email: apiKey.User.Email,
  337. Username: apiKey.User.Username,
  338. BalanceNotifyEnabled: apiKey.User.BalanceNotifyEnabled,
  339. RestrictPublicGroups: apiKey.User.RestrictPublicGroups,
  340. BalanceNotifyThresholdType: apiKey.User.BalanceNotifyThresholdType,
  341. BalanceNotifyThreshold: apiKey.User.BalanceNotifyThreshold,
  342. BalanceNotifyExtraEmails: apiKey.User.BalanceNotifyExtraEmails,
  343. TotalRecharged: apiKey.User.TotalRecharged,
  344. RPMLimit: apiKey.User.RPMLimit,
  345. },
  346. }
  347. // 填充 (user, group) RPM override —— snapshot 构建时查一次 DB,后续请求零 DB 往返。
  348. if apiKey.GroupID != nil && *apiKey.GroupID > 0 && s.userGroupRateRepo != nil {
  349. override, err := s.userGroupRateRepo.GetRPMOverrideByUserAndGroup(ctx, apiKey.UserID, *apiKey.GroupID)
  350. if err == nil && override != nil {
  351. snapshot.User.UserGroupRPMOverride = override
  352. }
  353. // 查询失败或无 override 时留 nil,checkRPM 会回退到 DB 查询
  354. }
  355. if apiKey.Group != nil {
  356. snapshot.Group = &APIKeyAuthGroupSnapshot{
  357. ID: apiKey.Group.ID,
  358. Name: apiKey.Group.Name,
  359. Platform: apiKey.Group.Platform,
  360. IsExclusive: apiKey.Group.IsExclusive,
  361. SecurityPolicyEnabled: apiKey.Group.SecurityPolicyEnabled,
  362. SecurityPolicyMode: apiKey.Group.SecurityPolicyMode,
  363. SecurityPolicyEmailEnabled: apiKey.Group.SecurityPolicyEmailEnabled,
  364. Status: apiKey.Group.Status,
  365. SubscriptionType: apiKey.Group.SubscriptionType,
  366. RateMultiplier: apiKey.Group.RateMultiplier,
  367. DailyLimitUSD: apiKey.Group.DailyLimitUSD,
  368. WeeklyLimitUSD: apiKey.Group.WeeklyLimitUSD,
  369. MonthlyLimitUSD: apiKey.Group.MonthlyLimitUSD,
  370. AllowImageGeneration: apiKey.Group.AllowImageGeneration,
  371. AllowBatchImageGeneration: apiKey.Group.AllowBatchImageGeneration,
  372. ImageRateIndependent: apiKey.Group.ImageRateIndependent,
  373. ImageRateMultiplier: apiKey.Group.ImageRateMultiplier,
  374. ImagePrice1K: apiKey.Group.ImagePrice1K,
  375. ImagePrice2K: apiKey.Group.ImagePrice2K,
  376. ImagePrice4K: apiKey.Group.ImagePrice4K,
  377. VideoRateIndependent: apiKey.Group.VideoRateIndependent,
  378. VideoRateMultiplier: apiKey.Group.VideoRateMultiplier,
  379. VideoPrice480P: apiKey.Group.VideoPrice480P,
  380. VideoPrice720P: apiKey.Group.VideoPrice720P,
  381. VideoPrice1080P: apiKey.Group.VideoPrice1080P,
  382. VideoModelPrices: NormalizeVideoModelPrices(apiKey.Group.VideoModelPrices),
  383. WebSearchPricePerCall: apiKey.Group.WebSearchPricePerCall,
  384. SearchPricePer1k: apiKey.Group.SearchPricePer1k,
  385. AudioRealtimePricePerMin: apiKey.Group.AudioRealtimePricePerMin,
  386. AudioTTSPricePerMillionChars: apiKey.Group.AudioTTSPricePerMillionChars,
  387. AudioSTTPricePerHour: apiKey.Group.AudioSTTPricePerHour,
  388. LongContextPricingEnabled: apiKey.Group.LongContextPricingEnabled,
  389. ModelPricing: apiKey.Group.ModelPricing,
  390. ClaudeCodeOnly: apiKey.Group.ClaudeCodeOnly,
  391. FallbackGroupID: apiKey.Group.FallbackGroupID,
  392. FallbackGroupIDOnInvalidRequest: apiKey.Group.FallbackGroupIDOnInvalidRequest,
  393. ModelRouting: apiKey.Group.ModelRouting,
  394. ModelRoutingEnabled: apiKey.Group.ModelRoutingEnabled,
  395. MCPXMLInject: apiKey.Group.MCPXMLInject,
  396. SupportedModelScopes: apiKey.Group.SupportedModelScopes,
  397. AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch,
  398. AllowLive: apiKey.Group.AllowLive,
  399. ForceOpenAIFast: apiKey.Group.ForceOpenAIFast,
  400. FreeOpenAIFast: apiKey.Group.FreeOpenAIFast,
  401. DefaultMappedModel: apiKey.Group.DefaultMappedModel,
  402. MessagesDispatchModelConfig: apiKey.Group.MessagesDispatchModelConfig,
  403. ModelAllowlist: apiKey.Group.ModelAllowlist,
  404. CodexModelsManifestConfig: apiKey.Group.CodexModelsManifestConfig,
  405. RPMLimit: apiKey.Group.RPMLimit,
  406. MaxReasoningEffort: apiKey.Group.MaxReasoningEffort,
  407. MaxReasoningEffortOverLimit: apiKey.Group.MaxReasoningEffortOverLimit,
  408. ReasoningEffortMappings: apiKey.Group.ReasoningEffortMappings,
  409. PeakRateEnabled: apiKey.Group.PeakRateEnabled,
  410. PeakStart: apiKey.Group.PeakStart,
  411. PeakEnd: apiKey.Group.PeakEnd,
  412. PeakRateMultiplier: apiKey.Group.PeakRateMultiplier,
  413. ProfitControlEnabled: apiKey.Group.ProfitControlEnabled,
  414. ProfitMinMargin: apiKey.Group.ProfitMinMargin,
  415. ProfitSafetyBuffer: apiKey.Group.ProfitSafetyBuffer,
  416. }
  417. }
  418. return snapshot
  419. }
  420. func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapshot) *APIKey {
  421. if snapshot == nil {
  422. return nil
  423. }
  424. apiKey := &APIKey{
  425. ID: snapshot.APIKeyID,
  426. UserID: snapshot.UserID,
  427. GroupID: snapshot.GroupID,
  428. Key: key,
  429. Name: snapshot.Name,
  430. Status: snapshot.Status,
  431. IPWhitelist: snapshot.IPWhitelist,
  432. IPBlacklist: snapshot.IPBlacklist,
  433. Quota: snapshot.Quota,
  434. QuotaUsed: snapshot.QuotaUsed,
  435. ExpiresAt: snapshot.ExpiresAt,
  436. RateLimit5h: snapshot.RateLimit5h,
  437. RateLimit1d: snapshot.RateLimit1d,
  438. RateLimit7d: snapshot.RateLimit7d,
  439. User: &User{
  440. ID: snapshot.User.ID,
  441. Status: snapshot.User.Status,
  442. Role: snapshot.User.Role,
  443. Balance: snapshot.User.Balance,
  444. Concurrency: snapshot.User.Concurrency,
  445. AllowedGroups: snapshot.User.AllowedGroups,
  446. Email: snapshot.User.Email,
  447. Username: snapshot.User.Username,
  448. BalanceNotifyEnabled: snapshot.User.BalanceNotifyEnabled,
  449. RestrictPublicGroups: snapshot.User.RestrictPublicGroups,
  450. BalanceNotifyThresholdType: snapshot.User.BalanceNotifyThresholdType,
  451. BalanceNotifyThreshold: snapshot.User.BalanceNotifyThreshold,
  452. BalanceNotifyExtraEmails: snapshot.User.BalanceNotifyExtraEmails,
  453. TotalRecharged: snapshot.User.TotalRecharged,
  454. RPMLimit: snapshot.User.RPMLimit,
  455. UserGroupRPMOverride: snapshot.User.UserGroupRPMOverride,
  456. },
  457. }
  458. if snapshot.Group != nil {
  459. apiKey.Group = &Group{
  460. ID: snapshot.Group.ID,
  461. Name: snapshot.Group.Name,
  462. Platform: snapshot.Group.Platform,
  463. IsExclusive: snapshot.Group.IsExclusive,
  464. SecurityPolicyEnabled: snapshot.Group.SecurityPolicyEnabled,
  465. SecurityPolicyMode: snapshot.Group.SecurityPolicyMode,
  466. SecurityPolicyEmailEnabled: snapshot.Group.SecurityPolicyEmailEnabled,
  467. Status: snapshot.Group.Status,
  468. Hydrated: true,
  469. SubscriptionType: snapshot.Group.SubscriptionType,
  470. RateMultiplier: snapshot.Group.RateMultiplier,
  471. DailyLimitUSD: snapshot.Group.DailyLimitUSD,
  472. WeeklyLimitUSD: snapshot.Group.WeeklyLimitUSD,
  473. MonthlyLimitUSD: snapshot.Group.MonthlyLimitUSD,
  474. AllowImageGeneration: snapshot.Group.AllowImageGeneration,
  475. AllowBatchImageGeneration: snapshot.Group.AllowBatchImageGeneration,
  476. ImageRateIndependent: snapshot.Group.ImageRateIndependent,
  477. ImageRateMultiplier: snapshot.Group.ImageRateMultiplier,
  478. ImagePrice1K: snapshot.Group.ImagePrice1K,
  479. ImagePrice2K: snapshot.Group.ImagePrice2K,
  480. ImagePrice4K: snapshot.Group.ImagePrice4K,
  481. VideoRateIndependent: snapshot.Group.VideoRateIndependent,
  482. VideoRateMultiplier: snapshot.Group.VideoRateMultiplier,
  483. VideoPrice480P: snapshot.Group.VideoPrice480P,
  484. VideoPrice720P: snapshot.Group.VideoPrice720P,
  485. VideoPrice1080P: snapshot.Group.VideoPrice1080P,
  486. VideoModelPrices: NormalizeVideoModelPrices(snapshot.Group.VideoModelPrices),
  487. WebSearchPricePerCall: snapshot.Group.WebSearchPricePerCall,
  488. SearchPricePer1k: snapshot.Group.SearchPricePer1k,
  489. AudioRealtimePricePerMin: snapshot.Group.AudioRealtimePricePerMin,
  490. AudioTTSPricePerMillionChars: snapshot.Group.AudioTTSPricePerMillionChars,
  491. AudioSTTPricePerHour: snapshot.Group.AudioSTTPricePerHour,
  492. LongContextPricingEnabled: snapshot.Group.LongContextPricingEnabled,
  493. ModelPricing: snapshot.Group.ModelPricing,
  494. ClaudeCodeOnly: snapshot.Group.ClaudeCodeOnly,
  495. FallbackGroupID: snapshot.Group.FallbackGroupID,
  496. FallbackGroupIDOnInvalidRequest: snapshot.Group.FallbackGroupIDOnInvalidRequest,
  497. ModelRouting: snapshot.Group.ModelRouting,
  498. ModelRoutingEnabled: snapshot.Group.ModelRoutingEnabled,
  499. MCPXMLInject: snapshot.Group.MCPXMLInject,
  500. SupportedModelScopes: snapshot.Group.SupportedModelScopes,
  501. AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch,
  502. AllowLive: snapshot.Group.AllowLive,
  503. ForceOpenAIFast: snapshot.Group.ForceOpenAIFast,
  504. FreeOpenAIFast: snapshot.Group.FreeOpenAIFast,
  505. DefaultMappedModel: snapshot.Group.DefaultMappedModel,
  506. MessagesDispatchModelConfig: snapshot.Group.MessagesDispatchModelConfig,
  507. ModelAllowlist: snapshot.Group.ModelAllowlist,
  508. CodexModelsManifestConfig: snapshot.Group.CodexModelsManifestConfig,
  509. RPMLimit: snapshot.Group.RPMLimit,
  510. MaxReasoningEffort: snapshot.Group.MaxReasoningEffort,
  511. MaxReasoningEffortOverLimit: snapshot.Group.MaxReasoningEffortOverLimit,
  512. ReasoningEffortMappings: snapshot.Group.ReasoningEffortMappings,
  513. PeakRateEnabled: snapshot.Group.PeakRateEnabled,
  514. PeakStart: snapshot.Group.PeakStart,
  515. PeakEnd: snapshot.Group.PeakEnd,
  516. PeakRateMultiplier: snapshot.Group.PeakRateMultiplier,
  517. ProfitControlEnabled: snapshot.Group.ProfitControlEnabled,
  518. ProfitMinMargin: snapshot.Group.ProfitMinMargin,
  519. ProfitSafetyBuffer: snapshot.Group.ProfitSafetyBuffer,
  520. }
  521. }
  522. s.compileAPIKeyIPRules(apiKey)
  523. return apiKey
  524. }