black.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { z } from "zod"
  2. import { fn } from "./util/fn"
  3. import { Resource } from "@opencode-ai/console-resource"
  4. import { centsToMicroCents } from "./util/price"
  5. import { getWeekBounds } from "./util/date"
  6. export namespace BlackData {
  7. const Schema = z.object({
  8. fixedLimit: z.number().int(),
  9. rollingLimit: z.number().int(),
  10. rollingWindow: z.number().int(),
  11. })
  12. export const validate = fn(Schema, (input) => {
  13. return input
  14. })
  15. export const get = fn(z.void(), () => {
  16. const json = JSON.parse(Resource.ZEN_BLACK.value)
  17. return Schema.parse(json)
  18. })
  19. }
  20. export namespace Black {
  21. export const analyzeRollingUsage = fn(
  22. z.object({
  23. usage: z.number().int(),
  24. timeUpdated: z.date(),
  25. }),
  26. ({ usage, timeUpdated }) => {
  27. const now = new Date()
  28. const black = BlackData.get()
  29. const rollingWindowMs = black.rollingWindow * 3600 * 1000
  30. const rollingLimitInMicroCents = centsToMicroCents(black.rollingLimit * 100)
  31. const windowStart = new Date(now.getTime() - rollingWindowMs)
  32. if (timeUpdated < windowStart) {
  33. return {
  34. status: "ok" as const,
  35. resetInSec: black.rollingWindow * 3600,
  36. usagePercent: 0,
  37. }
  38. }
  39. const windowEnd = new Date(timeUpdated.getTime() + rollingWindowMs)
  40. if (usage < rollingLimitInMicroCents) {
  41. return {
  42. status: "ok" as const,
  43. resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
  44. usagePercent: Math.ceil(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
  45. }
  46. }
  47. return {
  48. status: "rate-limited" as const,
  49. resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
  50. usagePercent: 100,
  51. }
  52. },
  53. )
  54. export const analyzeWeeklyUsage = fn(
  55. z.object({
  56. usage: z.number().int(),
  57. timeUpdated: z.date(),
  58. }),
  59. ({ usage, timeUpdated }) => {
  60. const black = BlackData.get()
  61. const now = new Date()
  62. const week = getWeekBounds(now)
  63. const fixedLimitInMicroCents = centsToMicroCents(black.fixedLimit * 100)
  64. if (timeUpdated < week.start) {
  65. return {
  66. status: "ok" as const,
  67. resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
  68. usagePercent: 0,
  69. }
  70. }
  71. if (usage < fixedLimitInMicroCents) {
  72. return {
  73. status: "ok" as const,
  74. resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
  75. usagePercent: Math.ceil(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
  76. }
  77. }
  78. return {
  79. status: "rate-limited" as const,
  80. resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
  81. usagePercent: 100,
  82. }
  83. },
  84. )
  85. }