summaryrefslogtreecommitdiffhomepage
path: root/packages/console/core/src/subscription.ts
diff options
context:
space:
mode:
authorFrank <[email protected]>2026-02-22 18:41:34 -0500
committerFrank <[email protected]>2026-02-22 22:19:44 -0500
commit5712cff5c453a185ac75a160f76ca06135d6ab2d (patch)
tree6838fedd40aed31421265fdc8b9fe5826d396f86 /packages/console/core/src/subscription.ts
parentee754c46f992dd4024e56e93246421246d16d13f (diff)
downloadopencode-5712cff5c453a185ac75a160f76ca06135d6ab2d.tar.gz
opencode-5712cff5c453a185ac75a160f76ca06135d6ab2d.zip
zen: track session in usage
Diffstat (limited to 'packages/console/core/src/subscription.ts')
-rw-r--r--packages/console/core/src/subscription.ts75
1 files changed, 75 insertions, 0 deletions
diff --git a/packages/console/core/src/subscription.ts b/packages/console/core/src/subscription.ts
new file mode 100644
index 000000000..ca3b17042
--- /dev/null
+++ b/packages/console/core/src/subscription.ts
@@ -0,0 +1,75 @@
+import { z } from "zod"
+import { fn } from "./util/fn"
+import { centsToMicroCents } from "./util/price"
+import { getWeekBounds } from "./util/date"
+
+export namespace Subscription {
+ export const analyzeRollingUsage = fn(
+ z.object({
+ limit: z.number().int(),
+ window: z.number().int(),
+ usage: z.number().int(),
+ timeUpdated: z.date(),
+ }),
+ ({ limit, window, usage, timeUpdated }) => {
+ const now = new Date()
+ const rollingWindowMs = window * 3600 * 1000
+ const rollingLimitInMicroCents = centsToMicroCents(limit * 100)
+ const windowStart = new Date(now.getTime() - rollingWindowMs)
+ if (timeUpdated < windowStart) {
+ return {
+ status: "ok" as const,
+ resetInSec: window * 3600,
+ usagePercent: 0,
+ }
+ }
+
+ const windowEnd = new Date(timeUpdated.getTime() + rollingWindowMs)
+ if (usage < rollingLimitInMicroCents) {
+ return {
+ status: "ok" as const,
+ resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
+ usagePercent: Math.ceil(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
+ }
+ }
+ return {
+ status: "rate-limited" as const,
+ resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
+ usagePercent: 100,
+ }
+ },
+ )
+
+ export const analyzeWeeklyUsage = fn(
+ z.object({
+ limit: z.number().int(),
+ usage: z.number().int(),
+ timeUpdated: z.date(),
+ }),
+ ({ limit, usage, timeUpdated }) => {
+ const now = new Date()
+ const week = getWeekBounds(now)
+ const fixedLimitInMicroCents = centsToMicroCents(limit * 100)
+ if (timeUpdated < week.start) {
+ return {
+ status: "ok" as const,
+ resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
+ usagePercent: 0,
+ }
+ }
+ if (usage < fixedLimitInMicroCents) {
+ return {
+ status: "ok" as const,
+ resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
+ usagePercent: Math.ceil(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
+ }
+ }
+
+ return {
+ status: "rate-limited" as const,
+ resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
+ usagePercent: 100,
+ }
+ },
+ )
+}