{"repo": "chathurangabw/vrax", "files": {"packages/console/app/src/routes/zen/util/handler.ts": {"0934a11c5d041107dfa310b705adde52a0ca255d": {"t": 1785596387.752402, "n": 44899, "h": "0875ca67fcd02bf5", "c": "import type { APIEvent } from \"@solidjs/start/server\"\nimport { and, Database, eq, isNull, lt, or, sql } from \"@vrax/console-core/drizzle/index.js\"\nimport { KeyTable } from \"@vrax/console-core/schema/key.sql.js\"\nimport { BillingTable, LiteTable, SubscriptionTable, UsageTable } from \"@vrax/console-core/schema/billing.sql.js\"\nimport { centsToMicroCents } from \"@vrax/console-core/util/price.js\"\nimport { getMonthlyBounds, getWeekBounds } from \"@vrax/console-core/util/date.js\"\nimport { Identifier } from \"@vrax/console-core/identifier.js\"\nimport { Billing } from \"@vrax/console-core/billing.js\"\nimport { Actor } from \"@vrax/console-core/actor.js\"\nimport { WorkspaceTable } from \"@vrax/console-core/schema/workspace.sql.js\"\nimport { ZenData } from \"@vrax/console-core/model.js\"\nimport { Subscription } from \"@vrax/console-core/subscription.js\"\nimport { BlackData } from \"@vrax/console-core/black.js\"\nimport { UserTable } from \"@vrax/console-core/schema/user.sql.js\"\nimport { ModelTable } from \"@vrax/console-core/schema/model.sql.js\"\nimport { ProviderTable } from \"@vrax/console-core/schema/provider.sql.js\"\nimport { logger } from \"./logger\"\nimport {\n AuthError,\n CreditsError,\n MonthlyLimitError,\n UserLimitError,\n ModelError,\n RateLimitError,\n FreeUsageLimitError,\n GoUsageLimitError,\n BlackUsageLimitError,\n} from \"./error\"\nimport {\n buildCostChunk,\n createBodyConverter,\n createStreamPartConverter,\n createResponseConverter,\n UsageInfo,\n} from \"./provider/provider\"\nimport { anthropicHelper } from \"./provider/anthropic\"\nimport { googleHelper } from \"./provider/google\"\nimport { openaiHelper } from \"./provider/openai\"\nimport { oaCompatHelper } from \"./provider/openai-compatible\"\nimport { createRateLimiter as createIpRateLimiter } from \"./ipRateLimiter\"\nimport { createRateLimiter as createKeyRateLimiter } from \"./keyRateLimiter\"\nimport { createTrialLimiter } from \"./trialLimiter\"\nimport { createStickyTracker } from \"./stickyProviderTracker\"\nimport { LiteData } from \"@vrax/console-core/lite.js\"\nimport { Resource } from \"@vrax/console-resource\"\nimport { i18n, type Key } from \"~/i18n\"\nimport { localeFromRequest } from \"~/lib/language\"\nimport { createModelTpmLimiter } from \"./modelTpmLimiter\"\nimport { createModelTpsLimiter } from \"./modelTpsLimiter\"\nimport { accumulateUsage, HOT_WORKSPACES } from \"./usageBatcher\"\n\ntype ZenData = Awaited>\ntype RetryOptions = {\n excludeProviders: string[]\n retryCount: number\n}\ntype BillingSource = \"anonymous\" | \"free\" | \"byok\" | \"subscription\" | \"lite\" | \"balance\"\n\nfunction resolve(text: string, params?: Record) {\n if (!params) return text\n return text.replace(/\\{\\{(\\w+)\\}\\}/g, (raw, key) => {\n const value = params[key]\n if (value === undefined || value === null) return raw\n return String(value)\n })\n}\n\nexport async function handler(\n input: APIEvent,\n opts: {\n format: ZenData.Format\n modelList: \"lite\" | \"full\"\n parseApiKey: (headers: Headers) => string | undefined\n parseModel: (url: string, body: any) => string\n parseVariant: (url: string, body: any) => string | undefined\n parseIsStream: (url: string, body: any) => boolean\n },\n) {\n type AuthInfo = Awaited>\n type ModelInfo = Awaited>\n type ProviderInfo = Awaited>\n type CostInfo = ReturnType\n\n const MAX_FAILOVER_RETRIES = 3\n const MAX_429_RETRIES = 3\n const dict = i18n(localeFromRequest(input.request))\n const t = (key: Key, params?: Record) => resolve(dict[key], params)\n const ADMIN_WORKSPACES = [\n \"wrk_01K46JDFR0E75SG2Q8K172KF3Y\", // anomaly\n \"wrk_01K6W1A3VE0KMNVSCQT43BG2SX\", // benchmark\n \"wrk_01KKZDKDWCS1VTJF8QTX62DD50\", // contributors\n ]\n\n try {\n const url = input.request.url\n const body = await input.request.json()\n const model = opts.parseModel(url, body)\n const variant = opts.parseVariant(url, body)\n const isStream = opts.parseIsStream(url, body)\n const rawIp = input.request.headers.get(\"x-real-ip\") ?? \"\"\n const ip = rawIp.includes(\":\") ? rawIp.split(\":\").slice(0, 4).join(\":\") : rawIp\n const rawZenApiKey = opts.parseApiKey(input.request.headers)\n const zenApiKey = rawZenApiKey === \"public\" ? undefined : rawZenApiKey\n const sessionId = input.request.headers.get(\"x-opencode-session\") ?? \"\"\n const requestId = input.request.headers.get(\"x-opencode-request\") ?? \"\"\n const ocClient = input.request.headers.get(\"x-opencode-client\") ?? \"\"\n const userAgent = input.request.headers.get(\"user-agent\") ?? \"\"\n logger.metric({\n is_stream: isStream,\n session: sessionId,\n request: requestId,\n client: ocClient,\n user_agent: userAgent,\n \"model.variant\": variant,\n \"model.tier\": opts.modelList === \"full\" ? \"zen\" : \"go\",\n })\n const zenData = ZenData.list(opts.modelList)\n const modelInfo = validateModel(zenData, model)\n const trialLimiter = createTrialLimiter(modelInfo.trialProvider, ip)\n const trialProviders = await trialLimiter?.check()\n const rateLimiter = modelInfo.allowAnonymous\n ? createIpRateLimiter(modelInfo.id, modelInfo.rateLimit, ip, input.request)\n : createKeyRateLimiter(modelInfo.id, modelInfo.rateLimit, zenApiKey, input.request)\n await rateLimiter?.check()\n const authInfo = await authenticate(modelInfo, zenApiKey)\n const stickyId = sessionId ? sessionId : (authInfo?.workspaceID ?? ip)\n const stickyTracker = createStickyTracker(modelInfo.id, modelInfo.stickyProvider, stickyId)\n const stickyProvider = await stickyTracker?.get()\n const billingSource = validateBilling(authInfo, modelInfo)\n logger.metric({ source: billingSource })\n const modelTpmLimiter = createModelTpmLimiter(modelInfo.providers)\n const modelTpmLimits = await modelTpmLimiter?.check()\n const modelTpsLimiter = createModelTpsLimiter(modelInfo.providers)\n const modelTpsLimits = await modelTpsLimiter?.check()\n\n const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {\n const providerInfo = selectProvider(\n model,\n zenData,\n authInfo,\n modelInfo,\n stickyId,\n trialProviders,\n retry,\n stickyProvider,\n modelTpmLimits,\n modelTpsLimits,\n )\n validateModelSettings(billingSource, authInfo)\n updateProviderKey(authInfo, providerInfo)\n logger.metric({\n provider: providerInfo.id,\n \"provider.model\": providerInfo.model,\n })\n\n const startTimestamp = Date.now()\n const reqUrl = providerInfo.modifyUrl(providerInfo.api, isStream)\n const reqBody = JSON.stringify(\n providerInfo.modifyBody({\n ...createBodyConverter(opts.format, providerInfo.format)(body),\n model: providerInfo.model,\n ...(() => {\n const replacer = (obj: Record): Record =>\n Object.fromEntries(\n Object.entries(obj).flatMap(([k, v]) => {\n if (Array.isArray(v)) return [[k, v]]\n if (typeof v === \"object\") return [[k, replacer(v)]]\n if (typeof v === \"string\") {\n if (v === \"$workspace\") return authInfo?.workspaceID ? [[k, authInfo?.workspaceID]] : []\n if (v === \"$user\") return stickyId ? [[k, stickyId]] : []\n if (v.startsWith(\"$header.\")) {\n const headerValue = input.request.headers.get(v.slice(8))\n return headerValue ? [[k, headerValue]] : []\n }\n }\n return [[k, v]]\n }),\n )\n return replacer(providerInfo.payloadModifier ?? {})\n })(),\n }),\n )\n logger.debug(\"REQUEST URL: \" + reqUrl)\n logger.debug(\"REQUEST: \" + reqBody.substring(0, 300) + \"...\")\n const res = await fetchWith429Retry(reqUrl, {\n method: \"POST\",\n headers: (() => {\n const headers = new Headers(input.request.headers)\n providerInfo.modifyHeaders(headers, providerInfo.apiKey, stickyId)\n Object.entries(providerInfo.headerMappings ?? {}).forEach(([k, v]) => {\n headers.set(k, headers.get(v)!)\n })\n headers.delete(\"host\")\n headers.delete(\"content-length\")\n headers.delete(\"x-opencode-request\")\n headers.delete(\"x-opencode-session\")\n headers.delete(\"x-opencode-project\")\n headers.delete(\"x-opencode-client\")\n return headers\n })(),\n body: reqBody,\n })\n\n if (res.status !== 200) {\n logger.metric({\n \"llm.error.code\": res.status,\n \"llm.error.message\": res.statusText,\n })\n }\n\n // Try another provider => stop retrying if using fallback provider\n if (\n res.status !== 200 &&\n // ie. 400 error is usually provider error like malformed request\n res.status !== 400 &&\n // ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.\n !(modelInfo.id.startsWith(\"gpt-\") && res.status === 404) &&\n // ie. cannot change codex model providers mid-session\n modelInfo.stickyProvider !== \"strict\" &&\n modelInfo.fallbackProvider &&\n providerInfo.id !== modelInfo.fallbackProvider\n ) {\n return retriableRequest({\n excludeProviders: [...retry.excludeProviders, providerInfo.id],\n retryCount: retry.retryCount + 1,\n })\n }\n\n return { providerInfo, reqBody, res, startTimestamp }\n }\n\n const { providerInfo, reqBody, res, startTimestamp } = await retriableRequest()\n\n // Store sticky provider\n if (res.status === 200) await stickyTracker?.set(providerInfo.id)\n\n // Temporarily change 404 to 400 status code b/c solid start automatically override 404 response\n const resStatus = res.status === 404 ? 400 : res.status\n\n // Scrub response headers\n const resHeaders = new Headers()\n const keepHeaders = [\"content-type\", \"cache-control\"]\n for (const [k, v] of res.headers.entries()) {\n if (keepHeaders.includes(k.toLowerCase())) {\n resHeaders.set(k, v)\n }\n }\n logger.debug(\"STATUS: \" + res.status + \" \" + res.statusText)\n\n // Handle non-streaming response\n if (!isStream || [400, 404, 429].includes(res.status)) {\n const json = await res.json()\n await rateLimiter?.track()\n const usage = providerInfo.extractUsage(json)\n if (usage) {\n const usageInfo = providerInfo.normalizeUsage(usage)\n const costInfo = calculateCost(modelInfo, usageInfo)\n await trialLimiter?.track(usageInfo)\n await modelTpmLimiter?.track(providerInfo.id, providerInfo.model, usageInfo)\n await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)\n await reload(billingSource, authInfo, costInfo)\n json.cost = calculateOccurredCost(billingSource, costInfo)\n }\n if (res.status === 400) {\n logger.metric({ \"error.response\": JSON.stringify(json) })\n }\n if (json.error?.message) {\n json.error.message = `Error from provider${providerInfo.displayName ? ` (${providerInfo.displayName})` : \"\"}: ${json.error.message}`\n }\n\n const responseConverter = createResponseConverter(providerInfo.format, opts.format)\n const body = JSON.stringify(responseConverter(json))\n logger.metric({ response_length: body.length })\n logger.debug(\"RESPONSE: \" + body)\n return new Response(body, {\n status: resStatus,\n statusText: res.statusText,\n headers: resHeaders,\n })\n }\n\n // Handle streaming response\n const streamConverter = createStreamPartConverter(providerInfo.format, opts.format)\n const usageParser = providerInfo.createUsageParser()\n const binaryDecoder = providerInfo.createBinaryStreamDecoder()\n const stream = new ReadableStream({\n start(c) {\n const reader = res.body?.getReader()\n const decoder = new TextDecoder()\n const encoder = new TextEncoder()\n\n let buffer = \"\"\n let responseLength = 0\n let timestampFirstByte = 0\n\n function pump(): Promise {\n return (\n reader?.read().then(async ({ done, value: rawValue }) => {\n if (done) {\n const timestampLastByte = Date.now()\n logger.metric({\n response_length: responseLength,\n \"timestamp.last_byte\": timestampLastByte,\n })\n await rateLimiter?.track()\n const usage = usageParser.retrieve()\n if (usage) {\n const usageInfo = providerInfo.normalizeUsage(usage)\n const costInfo = calculateCost(modelInfo, usageInfo)\n await trialLimiter?.track(usageInfo)\n await modelTpmLimiter?.track(providerInfo.id, providerInfo.model, usageInfo)\n await modelTpsLimiter?.track(\n providerInfo.id,\n providerInfo.model,\n providerInfo.tpsGoal,\n timestampFirstByte,\n timestampLastByte,\n usageInfo,\n )\n await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)\n await reload(billingSource, authInfo, costInfo)\n const cost = calculateOccurredCost(billingSource, costInfo)\n c.enqueue(encoder.encode(buildCostChunk(opts.format, cost)))\n }\n c.close()\n return\n }\n\n if (responseLength === 0) {\n timestampFirstByte = Date.now()\n logger.metric({\n time_to_first_byte: timestampFirstByte - startTimestamp,\n \"timestamp.first_byte\": timestampFirstByte,\n })\n }\n\n const value = binaryDecoder ? binaryDecoder(rawValue) : rawValue\n if (!value) return\n\n responseLength += value.length\n buffer += decoder.decode(value, { stream: true })\n\n const parts = buffer.split(providerInfo.streamSeparator)\n buffer = parts.pop() ?? \"\"\n\n for (let part of parts) {\n logger.debug(\"PART: \" + part)\n\n part = part.trim()\n usageParser.parse(part)\n\n if (providerInfo.format !== opts.format) {\n part = streamConverter(part)\n c.enqueue(encoder.encode(part + \"\\n\\n\"))\n }\n }\n\n if (providerInfo.format === opts.format) {\n c.enqueue(value)\n }\n\n return pump()\n }) || Promise.resolve()\n )\n }\n\n return pump()\n },\n })\n return new Response(stream, {\n status: resStatus,\n statusText: res.statusText,\n headers: resHeaders,\n })\n } catch (error: any) {\n logger.metric({\n \"error.type\": error.constructor.name,\n \"error.message\": error.message,\n \"error.cause\": error.cause?.toString(),\n })\n if (error.message.startsWith(\"Failed query\")) {\n try {\n logger.metric({\n \"error.cause2\": JSON.stringify(error.cause),\n })\n } catch {}\n }\n\n // Note: both top level \"type\" and \"error.type\" fields are used by the @ai-sdk/anthropic client to render the error message.\n if (\n error instanceof AuthError ||\n error instanceof CreditsError ||\n error instanceof MonthlyLimitError ||\n error instanceof UserLimitError ||\n error instanceof ModelError\n )\n return new Response(\n JSON.stringify({\n type: \"error\",\n error: { type: error.constructor.name, message: error.message },\n }),\n { status: 401 },\n )\n\n if (\n error instanceof RateLimitError ||\n error instanceof FreeUsageLimitError ||\n error instanceof GoUsageLimitError ||\n error instanceof BlackUsageLimitError\n ) {\n const headers = new Headers()\n if (error.retryAfter) {\n headers.set(\"retry-after\", String(error.retryAfter))\n }\n return new Response(\n JSON.stringify({\n type: \"error\",\n error: {\n type: error.constructor.name,\n message: error.message,\n },\n metadata:\n error instanceof GoUsageLimitError\n ? {\n workspace: error.workspace,\n limitName: error.limitName,\n }\n : {},\n }),\n { status: 429, headers },\n )\n }\n\n return new Response(\n JSON.stringify({\n type: \"error\",\n error: {\n type: \"error\",\n message: \"Internal server error\",\n },\n }),\n { status: 500 },\n )\n }\n\n function validateModel(zenData: ZenData, reqModel: string) {\n if (!(reqModel in zenData.models)) throw new ModelError(t(\"zen.api.error.modelNotSupported\", { model: reqModel }))\n\n const modelId = reqModel\n const modelData = Array.isArray(zenData.models[modelId])\n ? zenData.models[modelId].find((model) => opts.format === model.formatFilter)\n : zenData.models[modelId]\n\n if (!modelData)\n throw new ModelError(\n t(\"zen.api.error.modelFormatNotSupported\", {\n model: reqModel,\n format: opts.format,\n }),\n )\n\n if (modelData.trialEnded)\n throw new ModelError(\n `${t(\"zen.api.error.trialEnded\", {\n model: modelData.name,\n link: \"https://opencode.ai/go\",\n })}`,\n )\n\n logger.metric({ model: modelId })\n\n return { id: modelId, ...modelData }\n }\n\n function selectProvider(\n reqModel: string,\n zenData: ZenData,\n authInfo: AuthInfo,\n modelInfo: ModelInfo,\n stickyId: string,\n trialProviders: string[] | undefined,\n retry: RetryOptions,\n stickyProviderId: string | undefined,\n modelTpmLimits: Record | undefined,\n modelTpsLimits: Record | undefined,\n ) {\n const modelProvider = (() => {\n // Byok is top priority b/c if user set their own API key, we should use it\n // instead of using the sticky provider for the same session\n if (authInfo?.provider?.credentials) {\n return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)\n }\n\n // Prioritize trial providers\n let allProviders = modelInfo.providers.filter((provider) => !provider.disabled)\n if (trialProviders) {\n allProviders = allProviders.map((provider) => ({\n ...provider,\n priority: trialProviders.includes(provider.id) ? 0 : provider.priority,\n }))\n }\n\n if (retry.retryCount !== MAX_FAILOVER_RETRIES) {\n let topPriority = Infinity\n const providers = allProviders\n .filter((provider) => provider.weight !== 0)\n .filter((provider) => !retry.excludeProviders.includes(provider.id))\n .filter((provider) => {\n if (!provider.tpmLimit) return true\n const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0\n return usage < provider.tpmLimit * 1_000_000\n })\n .filter((provider) => {\n if (!provider.tpsGoal) return true\n const tps = modelTpsLimits?.[`${provider.id}/${provider.model}/${provider.tpsGoal}`] ?? {\n qualify: 0,\n unqualify: 0,\n }\n const isLowTps = tps.qualify + tps.unqualify > 10 && tps.qualify < tps.unqualify\n return !isLowTps\n })\n .map((provider) => {\n topPriority = Math.min(topPriority, provider.priority)\n return provider\n })\n .filter((p) => p.priority <= topPriority)\n .flatMap((provider) => Array(provider.weight).fill(provider))\n\n // Use the last 4 characters of session ID to select a provider\n let h = 0\n const l = stickyId.length\n for (let i = l - 4; i < l; i++) {\n h = (h * 31 + stickyId.charCodeAt(i)) | 0 // 32-bit int\n }\n const index = (h >>> 0) % providers.length // make unsigned + range 0..length-1\n const provider = providers[index || 0]\n\n // sticky provider does not exist => use selected provider\n if (!stickyProviderId) return provider\n const stickProvider = allProviders.find((provider) => provider.id === stickyProviderId)\n if (!stickProvider) return provider\n\n // stick provider exists + selected provider is API type => use sticky provider\n if (!provider.tpsGoal) return stickProvider\n\n // stick provier exists + selected provider is GPU type + GPU not idle => use selected provider\n const tps = modelTpsLimits?.[`${provider.id}/${provider.model}/${provider.tpsGoal}`] ?? {\n qualify: 0,\n unqualify: 0,\n }\n if (tps.qualify <= tps.unqualify * 3) return stickProvider\n\n return provider\n }\n\n // fallback provider\n return allProviders.find((provider) => provider.id === modelInfo.fallbackProvider)\n })()\n\n if (!modelProvider) throw new ModelError(t(\"zen.api.error.noProviderAvailable\"))\n if (!(modelProvider.id in zenData.providers))\n throw new ModelError(t(\"zen.api.error.providerNotSupported\", { provider: modelProvider.id }))\n\n return {\n ...modelProvider,\n ...zenData.providers[modelProvider.id],\n ...(() => {\n const providerProps = zenData.providers[modelProvider.id]\n const format = providerProps.format\n const opts = {\n reqModel,\n providerModel: modelProvider.model,\n adjustCacheUsage: providerProps.adjustCacheUsage,\n workspaceID: authInfo?.workspaceID,\n }\n if (format === \"anthropic\") return anthropicHelper(opts)\n if (format === \"google\") return googleHelper(opts)\n if (format === \"openai\") return openaiHelper(opts)\n return oaCompatHelper(opts)\n })(),\n }\n }\n\n async function authenticate(modelInfo: ModelInfo, zenApiKey?: string) {\n if (!zenApiKey) {\n if (modelInfo.allowAnonymous) return\n throw new AuthError(t(\"zen.api.error.missingApiKey\"))\n }\n\n const data = await Database.use((tx) =>\n tx\n .select({\n apiKey: KeyTable.id,\n workspaceID: KeyTable.workspaceID,\n billing: {\n balance: BillingTable.balance,\n paymentMethodID: BillingTable.paymentMethodID,\n monthlyLimit: BillingTable.monthlyLimit,\n monthlyUsage: BillingTable.monthlyUsage,\n timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,\n reloadTrigger: BillingTable.reloadTrigger,\n timeReloadLockedTill: BillingTable.timeReloadLockedTill,\n subscription: BillingTable.subscription,\n lite: BillingTable.lite,\n },\n user: {\n id: UserTable.id,\n monthlyLimit: UserTable.monthlyLimit,\n monthlyUsage: UserTable.monthlyUsage,\n timeMonthlyUsageUpdated: UserTable.timeMonthlyUsageUpdated,\n },\n black: {\n id: SubscriptionTable.id,\n rollingUsage: SubscriptionTable.rollingUsage,\n fixedUsage: SubscriptionTable.fixedUsage,\n timeRollingUpdated: SubscriptionTable.timeRollingUpdated,\n timeFixedUpdated: SubscriptionTable.timeFixedUpdated,\n },\n lite: {\n id: LiteTable.id,\n timeCreated: LiteTable.timeCreated,\n rollingUsage: LiteTable.rollingUsage,\n weeklyUsage: LiteTable.weeklyUsage,\n monthlyUsage: LiteTable.monthlyUsage,\n timeRollingUpdated: LiteTable.timeRollingUpdated,\n timeWeeklyUpdated: LiteTable.timeWeeklyUpdated,\n timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,\n },\n provider: {\n credentials: ProviderTable.credentials,\n },\n timeDisabled: ModelTable.timeCreated,\n })\n .from(KeyTable)\n .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))\n .innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID))\n .innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)))\n .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id)))\n .leftJoin(\n ProviderTable,\n modelInfo.byokProvider\n ? and(\n eq(ProviderTable.workspaceID, KeyTable.workspaceID),\n eq(ProviderTable.provider, modelInfo.byokProvider),\n )\n : sql`false`,\n )\n .leftJoin(\n SubscriptionTable,\n and(\n eq(SubscriptionTable.workspaceID, KeyTable.workspaceID),\n eq(SubscriptionTable.userID, KeyTable.userID),\n isNull(SubscriptionTable.timeDeleted),\n ),\n )\n .leftJoin(\n LiteTable,\n and(\n eq(LiteTable.workspaceID, KeyTable.workspaceID),\n eq(LiteTable.userID, KeyTable.userID),\n isNull(LiteTable.timeDeleted),\n ),\n )\n .where(and(eq(KeyTable.key, zenApiKey), isNull(KeyTable.timeDeleted)))\n .then((rows) => rows[0]),\n )\n\n if (!data) throw new AuthError(t(\"zen.api.error.invalidApiKey\"))\n if (\n modelInfo.id.startsWith(\"alpha-\") &&\n Resource.App.stage === \"production\" &&\n !ADMIN_WORKSPACES.includes(data.workspaceID)\n )\n throw new AuthError(t(\"zen.api.error.modelNotSupported\", { model: modelInfo.id }))\n\n logger.metric({\n api_key: data.apiKey,\n workspace: data.workspaceID,\n ...(() => {\n if (data.billing.subscription)\n return {\n subscription: data.billing.subscription.plan,\n }\n if (data.billing.lite)\n return {\n subscription: \"lite\",\n }\n return {}\n })(),\n })\n\n return {\n apiKeyId: data.apiKey,\n workspaceID: data.workspaceID,\n billing: data.billing,\n user: data.user,\n black: data.black,\n lite: data.lite,\n provider: data.provider,\n isFree: ADMIN_WORKSPACES.includes(data.workspaceID),\n isDisabled: !!data.timeDisabled,\n }\n }\n\n function validateBilling(authInfo: AuthInfo, modelInfo: ModelInfo): BillingSource {\n if (!authInfo) return \"anonymous\"\n if (authInfo.provider?.credentials) return \"byok\"\n if (authInfo.isFree) return \"free\"\n if (modelInfo.allowAnonymous) return \"free\"\n\n const formatRetryTime = (seconds: number) => {\n const days = Math.floor(seconds / 86400)\n if (days >= 1) return `${days} day${days > 1 ? \"s\" : \"\"}`\n const hours = Math.floor(seconds / 3600)\n const minutes = Math.ceil((seconds % 3600) / 60)\n if (hours >= 1) return `${hours}hr ${minutes}min`\n return `${minutes}min`\n }\n\n // Validate black subscription billing\n if (authInfo.billing.subscription && authInfo.black) {\n try {\n const sub = authInfo.black\n const plan = authInfo.billing.subscription.plan\n\n // Check weekly limit\n if (sub.fixedUsage && sub.timeFixedUpdated) {\n const blackData = BlackData.getLimits({ plan })\n const result = Subscription.analyzeWeeklyUsage({\n limit: blackData.fixedLimit,\n usage: sub.fixedUsage,\n timeUpdated: sub.timeFixedUpdated,\n })\n if (result.status === \"rate-limited\")\n throw new BlackUsageLimitError(\n t(\"zen.api.error.subscriptionQuotaExceeded\", {\n retryIn: formatRetryTime(result.resetInSec),\n }),\n result.resetInSec,\n )\n }\n\n // Check rolling limit\n if (sub.rollingUsage && sub.timeRollingUpdated) {\n const blackData = BlackData.getLimits({ plan })\n const result = Subscription.analyzeRollingUsage({\n limit: blackData.rollingLimit,\n window: blackData.rollingWindow,\n usage: sub.rollingUsage,\n timeUpdated: sub.timeRollingUpdated,\n })\n if (result.status === \"rate-limited\")\n throw new BlackUsageLimitError(\n t(\"zen.api.error.subscriptionQuotaExceeded\", {\n retryIn: formatRetryTime(result.resetInSec),\n }),\n result.resetInSec,\n )\n }\n\n return \"subscription\"\n } catch (e) {\n if (!authInfo.billing.subscription.useBalance) throw e\n }\n }\n\n // Validate lite subscription billing\n if (opts.modelList === \"lite\" && authInfo.billing.lite && authInfo.lite) {\n try {\n const consoleGoUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/go`\n const sub = authInfo.lite\n const liteData = LiteData.getLimits()\n\n // Check weekly limit\n if (sub.weeklyUsage && sub.timeWeeklyUpdated) {\n const result = Subscription.analyzeWeeklyUsage({\n limit: liteData.weeklyLimit,\n usage: sub.weeklyUsage,\n timeUpdated: sub.timeWeeklyUpdated,\n })\n if (result.status === \"rate-limited\")\n throw new GoUsageLimitError(\n t(\"zen.api.error.goSubscriptionWeeklyLimitExceeded\", {\n retryIn: formatRetryTime(result.resetInSec),\n consoleGoUrl,\n }),\n authInfo.workspaceID,\n \"weekly\",\n result.resetInSec,\n )\n }\n\n // Check monthly limit\n if (sub.monthlyUsage && sub.timeMonthlyUpdated) {\n const result = Subscription.analyzeMonthlyUsage({\n limit: liteData.monthlyLimit,\n usage: sub.monthlyUsage,\n timeUpdated: sub.timeMonthlyUpdated,\n timeSubscribed: sub.timeCreated,\n })\n if (result.status === \"rate-limited\")\n throw new GoUsageLimitError(\n t(\"zen.api.error.goSubscriptionMonthlyLimitExceeded\", {\n retryIn: formatRetryTime(result.resetInSec),\n consoleGoUrl,\n }),\n authInfo.workspaceID,\n \"monthly\",\n result.resetInSec,\n )\n }\n\n // Check rolling limit\n if (sub.rollingUsage && sub.timeRollingUpdated) {\n const result = Subscription.analyzeRollingUsage({\n limit: liteData.rollingLimit,\n window: liteData.rollingWindow,\n usage: sub.rollingUsage,\n timeUpdated: sub.timeRollingUpdated,\n })\n if (result.status === \"rate-limited\")\n throw new GoUsageLimitError(\n t(\"zen.api.error.goSubscriptionRollingLimitExceeded\", {\n retryIn: formatRetryTime(result.resetInSec),\n consoleGoUrl,\n }),\n authInfo.workspaceID,\n \"5 hour\",\n result.resetInSec,\n )\n }\n\n return \"lite\"\n } catch (e) {\n if (!authInfo.billing.lite.useBalance) throw e\n }\n }\n\n // Validate pay as you go billing\n const billing = authInfo.billing\n const billingUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/billing`\n const membersUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/members`\n if (!billing.paymentMethodID && billing.balance <= 0)\n throw new CreditsError(t(\"zen.api.error.noPaymentMethod\", { billingUrl }))\n if (billing.balance <= 0) throw new CreditsError(t(\"zen.api.error.insufficientBalance\", { billingUrl }))\n\n const now = new Date()\n const currentYear = now.getUTCFullYear()\n const currentMonth = now.getUTCMonth()\n if (\n billing.monthlyLimit &&\n billing.monthlyUsage &&\n billing.timeMonthlyUsageUpdated &&\n billing.monthlyUsage >= centsToMicroCents(billing.monthlyLimit * 100) &&\n currentYear === billing.timeMonthlyUsageUpdated.getUTCFullYear() &&\n currentMonth === billing.timeMonthlyUsageUpdated.getUTCMonth()\n )\n throw new MonthlyLimitError(\n t(\"zen.api.error.workspaceMonthlyLimitReached\", {\n amount: billing.monthlyLimit,\n billingUrl,\n }),\n )\n\n if (\n authInfo.user.monthlyLimit &&\n authInfo.user.monthlyUsage &&\n authInfo.user.timeMonthlyUsageUpdated &&\n authInfo.user.monthlyUsage >= centsToMicroCents(authInfo.user.monthlyLimit * 100) &&\n currentYear === authInfo.user.timeMonthlyUsageUpdated.getUTCFullYear() &&\n currentMonth === authInfo.user.timeMonthlyUsageUpdated.getUTCMonth()\n )\n throw new UserLimitError(\n t(\"zen.api.error.userMonthlyLimitReached\", {\n amount: authInfo.user.monthlyLimit,\n membersUrl,\n }),\n )\n\n return \"balance\"\n }\n\n function validateModelSettings(billingSource: BillingSource, authInfo: AuthInfo) {\n if (billingSource === \"lite\") return\n if (billingSource === \"anonymous\") return\n if (authInfo!.isDisabled) throw new ModelError(t(\"zen.api.error.modelDisabled\"))\n }\n\n function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) {\n if (!authInfo?.provider?.credentials) return\n providerInfo.apiKey = authInfo.provider.credentials\n }\n\n async function fetchWith429Retry(url: string, options: RequestInit, retry = { count: 0 }) {\n const res = await fetch(url, options)\n if (res.status === 429 && retry.count < MAX_429_RETRIES) {\n await new Promise((resolve) => setTimeout(resolve, Math.pow(2, retry.count) * 500))\n return fetchWith429Retry(url, options, { count: retry.count + 1 })\n }\n return res\n }\n\n function calculateCost(modelInfo: ModelInfo, usageInfo: UsageInfo) {\n const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =\n usageInfo\n\n const modelCost =\n modelInfo.cost200K &&\n inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) > 200_000\n ? modelInfo.cost200K\n : modelInfo.cost\n\n const inputCost = modelCost.input * inputTokens * 100\n const outputCost = modelCost.output * outputTokens * 100\n const cacheReadCost = (() => {\n if (!cacheReadTokens) return undefined\n if (!modelCost.cacheRead) return undefined\n return modelCost.cacheRead * cacheReadTokens * 100\n })()\n const cacheWrite5mCost = (() => {\n if (!cacheWrite5mTokens) return undefined\n if (!modelCost.cacheWrite5m) return undefined\n return modelCost.cacheWrite5m * cacheWrite5mTokens * 100\n })()\n const cacheWrite1hCost = (() => {\n if (!cacheWrite1hTokens) return undefined\n if (!modelCost.cacheWrite1h) return undefined\n return modelCost.cacheWrite1h * cacheWrite1hTokens * 100\n })()\n const totalCostInCent =\n inputCost + outputCost + (cacheReadCost ?? 0) + (cacheWrite5mCost ?? 0) + (cacheWrite1hCost ?? 0)\n return {\n totalCostInCent,\n inputCost,\n outputCost,\n cacheReadCost,\n cacheWrite5mCost,\n cacheWrite1hCost,\n }\n }\n\n function calculateOccurredCost(billingSource: BillingSource, costInfo: CostInfo) {\n return billingSource === \"balance\" ? (costInfo.totalCostInCent / 100).toFixed(8) : \"0\"\n }\n\n async function trackUsage(\n sessionId: string,\n billingSource: BillingSource,\n authInfo: AuthInfo,\n modelInfo: ModelInfo,\n providerInfo: ProviderInfo,\n usageInfo: UsageInfo,\n costInfo: CostInfo,\n ) {\n const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =\n usageInfo\n const { totalCostInCent, inputCost, outputCost, cacheReadCost, cacheWrite5mCost, cacheWrite1hCost } = costInfo\n\n logger.metric({\n \"tokens.input\": inputTokens,\n \"tokens.output\": outputTokens,\n \"tokens.reasoning\": reasoningTokens,\n \"tokens.cache_read\": cacheReadTokens,\n \"tokens.cache_write_5m\": cacheWrite5mTokens,\n \"tokens.cache_write_1h\": cacheWrite1hTokens,\n \"cost.input.microcents\": centsToMicroCents(inputCost),\n \"cost.output.microcents\": centsToMicroCents(outputCost),\n \"cost.cache_read.microcents\": cacheReadCost ? centsToMicroCents(cacheReadCost) : undefined,\n \"cost.cache_write.microcents\": cacheWrite5mCost ? centsToMicroCents(cacheWrite5mCost) : undefined,\n \"cost.total.microcents\": centsToMicroCents(totalCostInCent),\n // deprecated - remove after May 20, 2026\n \"cost.input\": Math.round(inputCost),\n \"cost.output\": Math.round(outputCost),\n \"cost.cache_read\": cacheReadCost ? Math.round(cacheReadCost) : undefined,\n \"cost.cache_write_5m\": cacheWrite5mCost ? Math.round(cacheWrite5mCost) : undefined,\n \"cost.cache_write_1h\": cacheWrite1hCost ? Math.round(cacheWrite1hCost) : undefined,\n \"cost.total\": Math.round(totalCostInCent),\n })\n\n if (billingSource === \"anonymous\") return\n authInfo = authInfo!\n\n const cost = centsToMicroCents(totalCostInCent)\n\n // For hot workspaces, batch balance/usage updates through Redis to avoid\n // row-level lock contention on BillingTable/UserTable. Returns the amount\n // to flush this request, or null to skip the DB writes entirely.\n const balanceFlush = await (async () => {\n if (billingSource !== \"subscription\" && billingSource !== \"lite\" && HOT_WORKSPACES.has(authInfo.workspaceID)) {\n const workspaceCost = billingSource === \"free\" || billingSource === \"byok\" ? 0 : cost\n const flush = await accumulateUsage(authInfo.workspaceID, authInfo.user.id, workspaceCost, cost)\n return { batched: true as const, flush }\n }\n return { batched: false as const, flush: null }\n })()\n\n await Database.use((db) =>\n Promise.all([\n db.insert(UsageTable).values({\n workspaceID: authInfo.workspaceID,\n id: Identifier.create(\"usage\"),\n model: modelInfo.id,\n provider: providerInfo.id,\n inputTokens,\n outputTokens,\n reasoningTokens,\n cacheReadTokens,\n cacheWrite5mTokens,\n cacheWrite1hTokens,\n cost,\n keyID: authInfo.apiKeyId,\n sessionID: sessionId.substring(0, 30),\n enrichment: (() => {\n if (billingSource === \"subscription\") return { plan: \"sub\" }\n if (billingSource === \"byok\") return { plan: \"byok\" }\n if (billingSource === \"lite\") return { plan: \"lite\" }\n return undefined\n })(),\n }),\n ...(() => {\n if (billingSource === \"subscription\") {\n const plan = authInfo.billing.subscription!.plan\n const black = BlackData.getLimits({ plan })\n const week = getWeekBounds(new Date())\n const rollingWindowSeconds = black.rollingWindow * 3600\n return [\n db\n .update(SubscriptionTable)\n .set({\n fixedUsage: sql`\n CASE\n WHEN ${SubscriptionTable.timeFixedUpdated} >= ${week.start} THEN ${SubscriptionTable.fixedUsage} + ${cost}\n ELSE ${cost}\n END\n `,\n timeFixedUpdated: sql`now()`,\n rollingUsage: sql`\n CASE\n WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.rollingUsage} + ${cost}\n ELSE ${cost}\n END\n `,\n timeRollingUpdated: sql`\n CASE\n WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.timeRollingUpdated}\n ELSE now()\n END\n `,\n })\n .where(\n and(\n eq(SubscriptionTable.workspaceID, authInfo.workspaceID),\n eq(SubscriptionTable.userID, authInfo.user.id),\n ),\n ),\n ]\n }\n if (billingSource === \"lite\") {\n const lite = LiteData.getLimits()\n const week = getWeekBounds(new Date())\n const month = getMonthlyBounds(new Date(), authInfo.lite!.timeCreated)\n const rollingWindowSeconds = lite.rollingWindow * 3600\n return [\n db\n .update(LiteTable)\n .set({\n monthlyUsage: sql`\n CASE\n WHEN ${LiteTable.timeMonthlyUpdated} >= ${month.start} THEN ${LiteTable.monthlyUsage} + ${cost}\n ELSE ${cost}\n END\n `,\n timeMonthlyUpdated: sql`now()`,\n weeklyUsage: sql`\n CASE\n WHEN ${LiteTable.timeWeeklyUpdated} >= ${week.start} THEN ${LiteTable.weeklyUsage} + ${cost}\n ELSE ${cost}\n END\n `,\n timeWeeklyUpdated: sql`now()`,\n rollingUsage: sql`\n CASE\n WHEN UNIX_TIMESTAMP(${LiteTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${LiteTable.rollingUsage} + ${cost}\n ELSE ${cost}\n END\n `,\n timeRollingUpdated: sql`\n CASE\n WHEN UNIX_TIMESTAMP(${LiteTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${LiteTable.timeRollingUpdated}\n ELSE now()\n END\n `,\n })\n .where(and(eq(LiteTable.workspaceID, authInfo.workspaceID), eq(LiteTable.userID, authInfo.user.id))),\n ]\n }\n\n // Batched hot workspace: skip DB writes unless this request is the flush.\n if (balanceFlush.batched && !balanceFlush.flush) return []\n\n const workspaceDelta = balanceFlush.flush?.workspaceCost ?? cost\n const userDelta = balanceFlush.flush?.userCost ?? cost\n const balanceDelta = billingSource === \"free\" || billingSource === \"byok\" ? 0 : workspaceDelta\n\n return [\n db\n .update(BillingTable)\n .set({\n balance: sql`${BillingTable.balance} - ${balanceDelta}`,\n monthlyUsage: sql`\n CASE\n WHEN MONTH(${BillingTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${BillingTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${BillingTable.monthlyUsage} + ${workspaceDelta}\n ELSE ${workspaceDelta}\n END\n `,\n timeMonthlyUsageUpdated: sql`now()`,\n })\n .where(eq(BillingTable.workspaceID, authInfo.workspaceID)),\n db\n .update(UserTable)\n .set({\n monthlyUsage: sql`\n CASE\n WHEN MONTH(${UserTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${UserTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${UserTable.monthlyUsage} + ${userDelta}\n ELSE ${userDelta}\n END\n `,\n timeMonthlyUsageUpdated: sql`now()`,\n })\n .where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id))),\n ]\n })(),\n ]),\n )\n\n return { costInMicroCents: cost }\n }\n\n async function reload(billingSource: BillingSource, authInfo: AuthInfo, costInfo: CostInfo) {\n if (billingSource !== \"balance\") return\n authInfo = authInfo!\n\n const reloadTrigger = centsToMicroCents((authInfo.billing.reloadTrigger ?? Billing.RELOAD_TRIGGER) * 100)\n if (authInfo.billing.balance - costInfo.totalCostInCent >= reloadTrigger) return\n if (authInfo.billing.timeReloadLockedTill && authInfo.billing.timeReloadLockedTill > new Date()) return\n\n const lock = await Database.use((tx) =>\n tx\n .update(BillingTable)\n .set({\n timeReloadLockedTill: sql`now() + interval 1 minute`,\n })\n .where(\n and(\n eq(BillingTable.workspaceID, authInfo.workspaceID),\n eq(BillingTable.reload, true),\n lt(BillingTable.balance, reloadTrigger),\n or(isNull(BillingTable.timeReloadLockedTill), lt(BillingTable.timeReloadLockedTill, sql`now()`)),\n ),\n ),\n )\n if (lock.rowsAffected === 0) return\n\n await Actor.provide(\"system\", { workspaceID: authInfo.workspaceID }, async () => {\n await Billing.reload()\n })\n }\n}\n"}}, "packages/opencode/src/session/retry.ts": {"0934a11c5d041107dfa310b705adde52a0ca255d": {"t": 1785596398.2511404, "n": 7139, "h": "bf61e6e1f31d8314", "c": "import type { NamedError } from \"@vrax/core/util/error\"\nimport { SessionV1 } from \"@vrax/core/v1/session\"\nimport { Cause, Clock, Duration, Effect, Schedule } from \"effect\"\nimport { MessageV2 } from \"./message-v2\"\nimport { iife } from \"@/util/iife\"\nimport { isRecord } from \"@/util/record\"\n\nexport type Err = ReturnType\n\nexport const GO_UPSELL_MESSAGE = \"Free usage exceeded, subscribe to Go\"\nexport const GO_UPSELL_URL = \"https://opencode.ai/go\"\nexport type RetryReason = \"free_tier_limit\" | \"account_rate_limit\" | (string & {})\n\nexport type Retryable = {\n message: string\n action?: {\n reason: RetryReason\n provider: string\n title: string\n message: string\n label: string\n link?: string\n }\n}\n\nexport const RETRY_INITIAL_DELAY = 2000\nexport const RETRY_BACKOFF_FACTOR = 2\nexport const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds\nexport const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout\n\nfunction cap(ms: number) {\n return Math.min(ms, RETRY_MAX_DELAY)\n}\n\nexport function delay(attempt: number, error?: SessionV1.APIError) {\n if (error) {\n const headers = error.data.responseHeaders\n if (headers) {\n const retryAfterMs = headers[\"retry-after-ms\"]\n if (retryAfterMs) {\n const parsedMs = Number.parseFloat(retryAfterMs)\n if (!Number.isNaN(parsedMs)) {\n return cap(parsedMs)\n }\n }\n\n const retryAfter = headers[\"retry-after\"]\n if (retryAfter) {\n const parsedSeconds = Number.parseFloat(retryAfter)\n if (!Number.isNaN(parsedSeconds)) {\n // convert seconds to milliseconds\n return cap(Math.ceil(parsedSeconds * 1000))\n }\n // Try parsing as HTTP date format\n const parsed = Date.parse(retryAfter) - Date.now()\n if (!Number.isNaN(parsed) && parsed > 0) {\n return cap(Math.ceil(parsed))\n }\n }\n\n return cap(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1))\n }\n }\n\n return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS))\n}\n\nexport function retryable(error: Err, provider: string) {\n // context overflow errors should not be retried\n if (SessionV1.ContextOverflowError.isInstance(error)) return undefined\n if (SessionV1.APIError.isInstance(error)) {\n const status = error.data.statusCode\n // 5xx errors are transient server failures and should always be retried,\n // even when the provider SDK doesn't explicitly mark them as retryable.\n if (!error.data.isRetryable && !(status !== undefined && status >= 500)) return undefined\n if (error.data.responseBody?.includes(\"FreeUsageLimitError\")) {\n return {\n message: GO_UPSELL_MESSAGE,\n action: {\n reason: \"free_tier_limit\",\n provider,\n title: \"Free limit reached\",\n message: \"Subscribe to OpenCode Go for reliable access to the best open-source models, starting at $5/month.\",\n label: \"subscribe\",\n link: GO_UPSELL_URL,\n },\n }\n }\n if (error.data.responseBody?.includes(\"GoUsageLimitError\")) {\n const body = parseJSON(error.data.responseBody)\n const workspace = str(body?.metadata?.workspace)\n const limitName = str(body?.metadata?.limitName)\n const retryAfter = num(error.data.responseHeaders?.[\"retry-after\"])\n const resetIn = iife(() => {\n if (retryAfter === undefined) return \"\"\n const seconds = Math.max(0, Math.ceil(retryAfter))\n const days = Math.floor(seconds / 86_400)\n const hours = Math.floor((seconds % 86_400) / 3_600)\n const minutes = Math.ceil((seconds % 3_600) / 60)\n const unit = (value: number, name: string) => `${value} ${name}${value === 1 ? \"\" : \"s\"}`\n\n if (days > 0) return hours > 0 ? `${unit(days, \"day\")} ${unit(hours, \"hour\")}` : unit(days, \"day\")\n if (hours > 0) return minutes > 0 ? `${unit(hours, \"hour\")} ${unit(minutes, \"minute\")}` : unit(hours, \"hour\")\n return minutes > 0 ? unit(minutes, \"minute\") : \"less than a minute\"\n })\n\n const message = `${limitName ? `${limitName} usage limit` : \"Usage limit\"} reached. It will reset in ${resetIn}. To continue using this model now, enable usage from your available balance`\n\n const link = `https://opencode.ai/workspace/${workspace}/go`\n return {\n message: `${message} - ${link}`,\n action: {\n reason: \"account_rate_limit\",\n provider,\n title: \"Go limit reached\",\n message,\n label: \"open settings\",\n link,\n },\n }\n }\n return { message: error.data.message.includes(\"Overloaded\") ? \"Provider is overloaded\" : error.data.message }\n }\n\n // Check for rate limit patterns in plain text error messages\n const msg = isRecord(error.data) ? error.data.message : undefined\n if (typeof msg === \"string\") {\n const lower = msg.toLowerCase()\n if (\n lower.includes(\"rate increased too quickly\") ||\n lower.includes(\"rate limit\") ||\n lower.includes(\"too many requests\")\n ) {\n return { message: msg }\n }\n }\n\n const json = parseJSON(msg)\n if (!json || typeof json !== \"object\") return undefined\n const code = typeof json.code === \"string\" ? json.code : \"\"\n\n if (json.type === \"error\" && json.error?.type === \"too_many_requests\") {\n return { message: \"Too Many Requests\" }\n }\n if (code.includes(\"exhausted\") || code.includes(\"unavailable\")) {\n return { message: \"Provider is overloaded\" }\n }\n if (json.type === \"error\" && typeof json.error?.code === \"string\" && json.error.code.includes(\"rate_limit\")) {\n return { message: \"Rate Limited\" }\n }\n return undefined\n}\n\nfunction str(value: unknown) {\n if (value === undefined || value === null) return \"\"\n return String(value)\n}\n\nfunction num(value: unknown) {\n const parsed = Number.parseFloat(str(value))\n if (Number.isNaN(parsed)) return undefined\n return parsed\n}\n\nfunction parseJSON(value: unknown) {\n return iife(() => {\n try {\n if (typeof value !== \"string\") return undefined\n return JSON.parse(value)\n } catch {\n return undefined\n }\n })\n}\n\nexport function policy(opts: {\n provider: string\n parse: (error: unknown) => Err\n set: (input: { attempt: number; message: string; action?: Retryable[\"action\"]; next: number }) => Effect.Effect\n}) {\n return Schedule.fromStepWithMetadata(\n Effect.succeed((meta: Schedule.InputMetadata) => {\n const error = opts.parse(meta.input)\n const retry = retryable(error, opts.provider)\n if (!retry) return Cause.done(meta.attempt)\n return Effect.gen(function* () {\n const wait = delay(meta.attempt, SessionV1.APIError.isInstance(error) ? error : undefined)\n const now = yield* Clock.currentTimeMillis\n yield* opts.set({\n attempt: meta.attempt,\n message: retry.message,\n action: retry.action,\n next: now + wait,\n })\n return [meta.attempt, Duration.millis(wait)] as [number, Duration.Duration]\n })\n }),\n )\n}\n\nexport * as SessionRetry from \"./retry\"\n"}}}}