subscription.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { z } from "zod"
  2. import { fn } from "./util/fn"
  3. import { centsToMicroCents } from "./util/price"
  4. import { getWeekBounds, getMonthlyBounds } from "./util/date"
  5. import { Resource } from "@opencode-ai/console-resource"
  6. export namespace Subscription {
  7. const LimitsSchema = z.object({
  8. free: z.object({
  9. promoTokens: z.number().int(),
  10. dailyRequests: z.number().int(),
  11. checkHeader: z.string(),
  12. fallbackValue: z.number().int(),
  13. }),
  14. lite: z.object({
  15. rollingLimit: z.number().int(),
  16. rollingWindow: z.number().int(),
  17. weeklyLimit: z.number().int(),
  18. monthlyLimit: z.number().int(),
  19. }),
  20. black: z.object({
  21. "20": z.object({
  22. fixedLimit: z.number().int(),
  23. rollingLimit: z.number().int(),
  24. rollingWindow: z.number().int(),
  25. }),
  26. "100": z.object({
  27. fixedLimit: z.number().int(),
  28. rollingLimit: z.number().int(),
  29. rollingWindow: z.number().int(),
  30. }),
  31. "200": z.object({
  32. fixedLimit: z.number().int(),
  33. rollingLimit: z.number().int(),
  34. rollingWindow: z.number().int(),
  35. }),
  36. }),
  37. })
  38. export const validate = fn(LimitsSchema, (input) => {
  39. return input
  40. })
  41. export const getLimits = fn(z.void(), () => {
  42. const json = JSON.parse(Resource.ZEN_LIMITS.value)
  43. return LimitsSchema.parse(json)
  44. })
  45. export const getFreeLimits = fn(z.void(), () => {
  46. return getLimits()["free"]
  47. })
  48. export const analyzeRollingUsage = fn(
  49. z.object({
  50. limit: z.number().int(),
  51. window: z.number().int(),
  52. usage: z.number().int(),
  53. timeUpdated: z.date(),
  54. }),
  55. ({ limit, window, usage, timeUpdated }) => {
  56. const now = new Date()
  57. const rollingWindowMs = window * 3600 * 1000
  58. const rollingLimitInMicroCents = centsToMicroCents(limit * 100)
  59. const windowStart = new Date(now.getTime() - rollingWindowMs)
  60. if (timeUpdated < windowStart) {
  61. return {
  62. status: "ok" as const,
  63. resetInSec: window * 3600,
  64. usagePercent: 0,
  65. }
  66. }
  67. const windowEnd = new Date(timeUpdated.getTime() + rollingWindowMs)
  68. if (usage < rollingLimitInMicroCents) {
  69. return {
  70. status: "ok" as const,
  71. resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
  72. usagePercent: Math.floor(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
  73. }
  74. }
  75. return {
  76. status: "rate-limited" as const,
  77. resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
  78. usagePercent: 100,
  79. }
  80. },
  81. )
  82. export const analyzeWeeklyUsage = fn(
  83. z.object({
  84. limit: z.number().int(),
  85. usage: z.number().int(),
  86. timeUpdated: z.date(),
  87. }),
  88. ({ limit, usage, timeUpdated }) => {
  89. const now = new Date()
  90. const week = getWeekBounds(now)
  91. const fixedLimitInMicroCents = centsToMicroCents(limit * 100)
  92. if (timeUpdated < week.start) {
  93. return {
  94. status: "ok" as const,
  95. resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
  96. usagePercent: 0,
  97. }
  98. }
  99. if (usage < fixedLimitInMicroCents) {
  100. return {
  101. status: "ok" as const,
  102. resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
  103. usagePercent: Math.floor(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
  104. }
  105. }
  106. return {
  107. status: "rate-limited" as const,
  108. resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
  109. usagePercent: 100,
  110. }
  111. },
  112. )
  113. export const analyzeMonthlyUsage = fn(
  114. z.object({
  115. limit: z.number().int(),
  116. usage: z.number().int(),
  117. timeUpdated: z.date(),
  118. timeSubscribed: z.date(),
  119. }),
  120. ({ limit, usage, timeUpdated, timeSubscribed }) => {
  121. const now = new Date()
  122. const month = getMonthlyBounds(now, timeSubscribed)
  123. const fixedLimitInMicroCents = centsToMicroCents(limit * 100)
  124. if (timeUpdated < month.start) {
  125. return {
  126. status: "ok" as const,
  127. resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
  128. usagePercent: 0,
  129. }
  130. }
  131. if (usage < fixedLimitInMicroCents) {
  132. return {
  133. status: "ok" as const,
  134. resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
  135. usagePercent: Math.floor(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
  136. }
  137. }
  138. return {
  139. status: "rate-limited" as const,
  140. resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
  141. usagePercent: 100,
  142. }
  143. },
  144. )
  145. }