subscription.ts 4.5 KB

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