fix(opencode): improve free tier limit CTAs

This commit is contained in:
opencode-agent[bot] 2026-06-02 07:16:58 +00:00
commit 767e17fbdc
8 changed files with 147 additions and 15 deletions

View file

@ -12,7 +12,18 @@ class LimitError extends Error {
}
}
export class RateLimitError extends LimitError {}
export class FreeUsageLimitError extends LimitError {}
export type FreeUsageLimitMetadata = {
workspace?: string
subscribedToGo?: boolean
hasCredits?: boolean
}
export class FreeUsageLimitError extends LimitError {
metadata: FreeUsageLimitMetadata
constructor(message: string, retryAfter?: number, metadata: FreeUsageLimitMetadata = {}) {
super(message, retryAfter)
this.metadata = metadata
}
}
export class BlackUsageLimitError extends LimitError {}
type LimitName = "5 hour" | "weekly" | "monthly"

View file

@ -120,8 +120,8 @@ export async function handler(
const rateLimiter = modelInfo.allowAnonymous
? createIpRateLimiter(modelInfo.id, modelInfo.rateLimit, ip, input.request)
: createKeyRateLimiter(modelInfo.id, modelInfo.rateLimit, zenApiKey, input.request)
await rateLimiter?.check()
const authInfo = await authenticate(modelInfo, zenApiKey)
await rateLimiter?.check(freeUsageLimitMetadata(authInfo))
const stickyId = sessionId ? sessionId : (authInfo?.workspaceID ?? ip)
const stickyTracker = createStickyTracker(modelInfo.id, modelInfo.stickyProvider, stickyId)
const stickyProvider = await stickyTracker?.get()
@ -424,6 +424,8 @@ export async function handler(
workspace: error.workspace,
limitName: error.limitName,
}
: error instanceof FreeUsageLimitError
? error.metadata
: {},
}),
{ status: 429, headers },
@ -881,6 +883,15 @@ export async function handler(
return "balance"
}
function freeUsageLimitMetadata(authInfo: AuthInfo) {
if (!authInfo) return {}
return {
workspace: authInfo.workspaceID,
subscribedToGo: !!(authInfo.billing.lite || authInfo.lite),
hasCredits: authInfo.billing.balance > 0,
}
}
function validateModelSettings(billingSource: BillingSource, authInfo: AuthInfo) {
if (billingSource === "lite") return
if (billingSource === "anonymous") return

View file

@ -1,4 +1,4 @@
import { FreeUsageLimitError } from "./error"
import { FreeUsageLimitError, type FreeUsageLimitMetadata } from "./error"
import { logger } from "./logger"
import { buildRateLimitKey, getRedis } from "./redis"
import { i18n } from "~/i18n"
@ -28,7 +28,7 @@ export function createRateLimiter(modelId: string, rateLimit: number | undefined
let isNew = false
return {
check: async () => {
check: async (metadata?: FreeUsageLimitMetadata) => {
const counts = await redis.mget<(string | number | null)[]>(isDefaultModel ? [lifetimeKey, dailyKey] : [dailyKey])
const lifetimeCount = isDefaultModel ? Number(counts[0] ?? 0) : 0
const dailyCount = Number(counts[isDefaultModel ? 1 : 0] ?? 0)
@ -37,7 +37,7 @@ export function createRateLimiter(modelId: string, rateLimit: number | undefined
isNew = isDefaultModel && lifetimeCount < dailyLimit * 7
if ((isNew && dailyCount >= dailyLimit * 2) || (!isNew && dailyCount >= dailyLimit))
throw new FreeUsageLimitError(dict["zen.api.error.rateLimitExceeded"], retryAfter)
throw new FreeUsageLimitError(dict["zen.api.error.rateLimitExceeded"], retryAfter, metadata)
},
track: async () => {
const pipeline = redis.pipeline()

View file

@ -1,4 +1,4 @@
import { RateLimitError } from "./error"
import { RateLimitError, type FreeUsageLimitMetadata } from "./error"
import { buildRateLimitKey, getRedis } from "./redis"
import { i18n } from "~/i18n"
import { localeFromRequest } from "~/lib/language"
@ -22,7 +22,7 @@ export function createRateLimiter(
const key = buildRateLimitKey("key", zenApiKey, interval)
return {
check: async () => {
check: async (_metadata?: FreeUsageLimitMetadata) => {
const count = Number((await redis.mget<(string | number | null)[]>([key]))[0] ?? 0)
if (count >= LIMIT) throw new RateLimitError(dict["zen.api.error.rateLimitExceeded"], 60)