diff --git a/cli/src/components/freebuff-model-selector.tsx b/cli/src/components/freebuff-model-selector.tsx
index 3a67ffed8..c3111b277 100644
--- a/cli/src/components/freebuff-model-selector.tsx
+++ b/cli/src/components/freebuff-model-selector.tsx
@@ -7,8 +7,10 @@ import {
DEFAULT_FREEBUFF_MODEL_ID,
FALLBACK_FREEBUFF_MODEL_ID,
FREEBUFF_MODELS,
+ FREEBUFF_PREMIUM_SESSION_LIMIT,
getFreebuffDeploymentAvailabilityLabel,
isFreebuffModelAvailable,
+ isFreebuffPremiumModelId,
} from '@codebuff/common/constants/freebuff-models'
import { joinFreebuffQueue } from '../hooks/use-freebuff-session'
@@ -31,6 +33,10 @@ const FREEBUFF_MODEL_SELECTOR_MODELS: readonly FreebuffModelOption[] = [
...FREEBUFF_MODELS.filter((model) => model.id !== DEFAULT_FREEBUFF_MODEL_ID),
]
+function formatSessionUnits(units: number): string {
+ return Number.isInteger(units) ? String(units) : units.toFixed(1)
+}
+
/**
* Dual-purpose model picker:
* - Pre-chat landing (session 'none'): user hasn't joined any queue. Picking
@@ -45,11 +51,6 @@ const FREEBUFF_MODEL_SELECTOR_MODELS: readonly FreebuffModelOption[] = [
* Always stacked vertically. On narrow terminals where the longest one-line
* label wouldn't fit, the secondary details (warning / deployment hours)
* spill onto an indented second line under the name.
- *
- * No queue-position hint: traffic doesn't reach the threshold where a wait
- * would form, so showing "N in line" everywhere just adds noise (and width).
- * The picker still surfaces "Closed" (outside deployment hours) and "Limit
- * used" (per-user quota) inline since those gate the actual click.
*/
export const FreebuffModelSelector: React.FC = () => {
const theme = useTheme()
@@ -91,15 +92,30 @@ export const FreebuffModelSelector: React.FC = () => {
}
}, [now, selectedModel, session, setSelectedModel])
+ const committedModelId = session?.status === 'queued' ? session.model : null
+ const rateLimitsByModel =
+ session && 'rateLimitsByModel' in session
+ ? session.rateLimitsByModel
+ : undefined
+
+ const getQuotaHint = useCallback(
+ (modelId: string): string => {
+ const rateLimit = rateLimitsByModel?.[modelId]
+ if (rateLimit) {
+ return `${formatSessionUnits(rateLimit.recentCount)}/${rateLimit.limit} used`
+ }
+ return isFreebuffPremiumModelId(modelId)
+ ? `0/${FREEBUFF_PREMIUM_SESSION_LIMIT} used`
+ : 'Unlimited'
+ },
+ [rateLimitsByModel],
+ )
+
const BUTTON_CHROME = 4 // 2 border + 2 padding
// Decide whether secondary details (warning / deployment hours) get their
- // own indented line under the name. Trigger: the widest one-line button
- // wouldn't fit in our content budget. All buttons share a uniform width so
- // the column reads as a clean stack of equal choices. We size to the
- // *label* — Closed / Limit used hints can transiently push the text past
- // this width, but they're rare (deployment hours closing, daily quota hit)
- // and a small one-time grow is fine.
+ // own indented line under the name. All buttons share a uniform width so
+ // the column reads as a clean stack of equal choices.
const { wrapDetails, buttonOuterWidth } = useMemo(() => {
const detailsTextLen = (model: FreebuffModelOption): number => {
const parts: number[] = []
@@ -108,9 +124,14 @@ export const FreebuffModelSelector: React.FC = () => {
}
if (model.warning) parts.push(model.warning.length)
if (parts.length === 0) return 0
- return parts.reduce((a, b) => a + b, 0) + (parts.length - 1) * 3 /* " · " */
+ return (
+ parts.reduce((a, b) => a + b, 0) + (parts.length - 1) * 3
+ ) /* " · " */
}
+ const hintLen = (model: FreebuffModelOption): number =>
+ Math.max(getQuotaHint(model.id).length, 'Closed'.length)
+
const oneLineLen = (model: FreebuffModelOption): number => {
const inlineDetails = detailsTextLen(model)
return (
@@ -118,12 +139,19 @@ export const FreebuffModelSelector: React.FC = () => {
model.displayName.length +
3 /* " · " */ +
model.tagline.length +
- (inlineDetails > 0 ? 3 + inlineDetails : 0)
+ (inlineDetails > 0 ? 3 + inlineDetails : 0) +
+ 1 /* space before hint */ +
+ hintLen(model)
)
}
const labelLineLen = (model: FreebuffModelOption): number =>
- 2 + model.displayName.length + 3 + model.tagline.length
+ 2 +
+ model.displayName.length +
+ 3 +
+ model.tagline.length +
+ 1 +
+ hintLen(model)
const detailsLineLen = (model: FreebuffModelOption): number => {
const len = detailsTextLen(model)
@@ -148,16 +176,8 @@ export const FreebuffModelSelector: React.FC = () => {
contentMaxWidth,
),
}
- }, [contentMaxWidth, deploymentAvailabilityLabel])
+ }, [contentMaxWidth, deploymentAvailabilityLabel, getQuotaHint])
- // "Already committed to this model" — only when the server has us queued
- // on it. On the landing screen (status 'none'), nothing is committed yet,
- // so picking the focused model is always a real action (first join).
- const committedModelId = session?.status === 'queued' ? session.model : null
- const rateLimitsByModel =
- session && 'rateLimitsByModel' in session
- ? session.rateLimitsByModel
- : undefined
const isJoinable = useCallback(
(modelId: string) => {
if (!isFreebuffModelAvailable(modelId, new Date(now))) return false
@@ -230,19 +250,13 @@ export const FreebuffModelSelector: React.FC = () => {
const isHovered = hoveredId === model.id
const isFocused = focusedId === model.id
const isAvailable = isFreebuffModelAvailable(model.id, new Date(now))
- const rateLimit = rateLimitsByModel?.[model.id]
- const isQuotaExhausted =
- rateLimit !== undefined && rateLimit.recentCount >= rateLimit.limit
- const canJoin = isAvailable && !isQuotaExhausted
+ const canJoin = isJoinable(model.id)
// Clickable whenever picking would actually do something — i.e.
// anything except re-picking the queue we're already in.
const interactable =
!pending && canJoin && model.id !== committedModelId
- const hint = !isAvailable
- ? 'Closed'
- : isQuotaExhausted
- ? 'Limit used'
- : ''
+ const quotaHint = getQuotaHint(model.id)
+ const hint = isAvailable ? quotaHint : 'Closed'
// Focused row: green border + arrow indicator + bold name. The name
// itself stays the normal foreground color so it doesn't shout — the
@@ -251,7 +265,7 @@ export const FreebuffModelSelector: React.FC = () => {
const fgColor = canJoin ? theme.foreground : theme.muted
const mutedColor = theme.muted
const warningColor = theme.secondary
- const hintColor = theme.secondary
+ const hintColor = canJoin ? theme.muted : theme.secondary
const borderColor = isFocused
? theme.primary
@@ -303,16 +317,17 @@ export const FreebuffModelSelector: React.FC = () => {
{showInlineWarning && (
· {model.warning}
)}
- {hint && {hint}}
+ {hint}
{showWrappedDetails && (
-
+
{model.availability === 'deployment_hours' && (
{deploymentAvailabilityLabel}
)}
- {model.availability === 'deployment_hours' &&
- model.warning && · }
+ {model.availability === 'deployment_hours' && model.warning && (
+ ·
+ )}
{model.warning && (
{model.warning}
)}
diff --git a/cli/src/components/status-bar.tsx b/cli/src/components/status-bar.tsx
index 4216a1d66..82c2b16d8 100644
--- a/cli/src/components/status-bar.tsx
+++ b/cli/src/components/status-bar.tsx
@@ -66,6 +66,9 @@ const formatSessionRemaining = (ms: number): string => {
return minutes === 0 ? `${hours}h left` : `${hours}h ${minutes}m left`
}
+const formatSessionUnits = (units: number): string =>
+ Number.isInteger(units) ? String(units) : units.toFixed(1)
+
interface StatusBarProps {
timerStartTime: number | null
isAtBottom: boolean
@@ -131,7 +134,8 @@ export const StatusBar = ({
case 'clipboard':
// Use green color for feedback success messages
- const isFeedbackSuccess = statusIndicatorState.message.includes('Feedback sent')
+ const isFeedbackSuccess =
+ statusIndicatorState.message.includes('Feedback sent')
return (
{statusIndicatorState.message}
@@ -142,12 +146,7 @@ export const StatusBar = ({
return Reconnected
case 'retrying':
- return (
-
- )
+ return
case 'connecting':
return
@@ -180,8 +179,17 @@ export const StatusBar = ({
freebuffSession?.status === 'active'
? getFreebuffModel(freebuffSession.model).displayName
: null
+ const quotaText =
+ freebuffSession?.status === 'active' && freebuffSession.rateLimit
+ ? `Premium ${formatSessionUnits(freebuffSession.rateLimit.recentCount)}/${freebuffSession.rateLimit.limit} used · `
+ : freebuffSession?.status === 'active'
+ ? 'Unlimited · '
+ : ''
return (
- {modelName ? `${modelName} · ` : ''}{formatSessionRemaining(sessionProgress.remainingMs)}
+
+ {modelName ? `${modelName} · ` : ''}
+ {quotaText}Free session ·{' '}
+ {formatSessionRemaining(sessionProgress.remainingMs)}
)
}
@@ -258,12 +266,18 @@ export const StatusBar = ({
}}
>
{elapsedTimeContent}
- {onStop && (statusIndicatorState.kind === 'waiting' || statusIndicatorState.kind === 'streaming') && (
- ■ Esc
- )}
- {onEndSession && statusIndicatorState.kind === 'idle' && freebuffSession?.status === 'active' && (
- ✕ End session
- )}
+ {onStop &&
+ (statusIndicatorState.kind === 'waiting' ||
+ statusIndicatorState.kind === 'streaming') && (
+ ■ Esc
+ )}
+ {onEndSession &&
+ statusIndicatorState.kind === 'idle' &&
+ freebuffSession?.status === 'active' && (
+
+ ✕ End session
+
+ )}
{sessionProgress !== null &&
sessionProgress.remainingMs < COUNTDOWN_VISIBLE_MS &&
statusIndicatorState.kind !== 'idle' && (
diff --git a/cli/src/components/waiting-room-screen.tsx b/cli/src/components/waiting-room-screen.tsx
index a87980905..36de9a86d 100644
--- a/cli/src/components/waiting-room-screen.tsx
+++ b/cli/src/components/waiting-room-screen.tsx
@@ -3,10 +3,7 @@ import { useRenderer } from '@opentui/react'
import React, { useMemo, useState } from 'react'
import { Button } from './button'
-import {
- ChoiceAdBanner,
- CHOICE_AD_BANNER_HEIGHT,
-} from './choice-ad-banner'
+import { ChoiceAdBanner, CHOICE_AD_BANNER_HEIGHT } from './choice-ad-banner'
import { FreebuffModelSelector } from './freebuff-model-selector'
import { ShimmerText } from './shimmer-text'
import { useFreebuffCtrlCExit } from '../hooks/use-freebuff-ctrl-c-exit'
@@ -59,6 +56,9 @@ const formatRetryAfter = (ms: number): string => {
return rem === 0 ? `${hours}h` : `${hours}h ${rem}m`
}
+const formatSessionUnits = (units: number): string =>
+ Number.isInteger(units) ? String(units) : units.toFixed(1)
+
const PRIVACY_SIGNAL_LABELS: Partial> =
{
anonymous: 'anonymized network',
@@ -263,17 +263,16 @@ export const WaitingRoomScreen: React.FC = ({
Elapsed
{formatElapsed(elapsedMs)}
- {/* Per-model session quota (e.g. DeepSeek V4 Pro caps at 5/12h).
- Only rendered for rate-limited models so the Minimax queue
- stays clutter-free. */}
+ {/* Premium session quota. Minimax is unlimited, so it has no
+ rateLimit payload and skips this line. */}
{session.rateLimit && (
- Sessions
+ Premium sessions
- {session.rateLimit.recentCount} /{' '}
+ {formatSessionUnits(session.rateLimit.recentCount)} /{' '}
{session.rateLimit.limit}
- used in last {session.rateLimit.windowHours}h
+ used in the last 20 hours
)}
@@ -346,8 +345,8 @@ export const WaitingRoomScreen: React.FC = ({
>
)}
- {/* Per-model session quota exhausted (e.g. 5+ DeepSeek sessions in
- the last 12h). Terminal for this run — the user can exit and come
+ {/* Shared premium-session quota exhausted. Terminal for this run —
+ the user can exit and come
back once the oldest session in the window rolls off. */}
{session?.status === 'rate_limited' && (
<>
@@ -357,10 +356,9 @@ export const WaitingRoomScreen: React.FC = ({
You've used{' '}
- {session.recentCount} of {session.limit}
+ {formatSessionUnits(session.recentCount)} of {session.limit}
{' '}
- hour-long sessions on {session.model} in the last{' '}
- {session.windowHours}h. Try again in{' '}
+ premium sessions in the last 20 hours. Try again in{' '}
{formatRetryAfter(session.retryAfterMs)}
diff --git a/common/src/constants/freebuff-models.ts b/common/src/constants/freebuff-models.ts
index 657d5343d..3f9618328 100644
--- a/common/src/constants/freebuff-models.ts
+++ b/common/src/constants/freebuff-models.ts
@@ -30,6 +30,8 @@ export const FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID = 'deepseek/deepseek-v4-pro'
export const FREEBUFF_GLM_MODEL_ID = 'z-ai/glm-5.1'
export const FREEBUFF_KIMI_MODEL_ID = 'moonshotai/kimi-k2.6'
export const FREEBUFF_MINIMAX_MODEL_ID = 'minimax/minimax-m2.7'
+export const FREEBUFF_PREMIUM_SESSION_LIMIT = 5
+export const FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS = 20
const FREEBUFF_EASTERN_TIMEZONE = 'America/New_York'
const FREEBUFF_PACIFIC_TIMEZONE = 'America/Los_Angeles'
@@ -78,7 +80,7 @@ export const FREEBUFF_MODELS = [
{
id: FREEBUFF_MINIMAX_MODEL_ID,
displayName: 'MiniMax M2.7',
- tagline: 'Fastest',
+ tagline: 'Fastest, unlimited',
availability: 'always',
},
] as const satisfies readonly FreebuffModelOption[]
@@ -92,6 +94,12 @@ export const LEGACY_FREEBUFF_MODELS = [
},
] as const satisfies readonly FreebuffModelOption[]
+export const FREEBUFF_PREMIUM_MODEL_IDS = [
+ FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
+ FREEBUFF_KIMI_MODEL_ID,
+ FREEBUFF_GLM_MODEL_ID,
+] as const
+
export const SUPPORTED_FREEBUFF_MODELS = [
...FREEBUFF_MODELS,
...LEGACY_FREEBUFF_MODELS,
@@ -100,6 +108,7 @@ export const SUPPORTED_FREEBUFF_MODELS = [
export type FreebuffModelId = (typeof FREEBUFF_MODELS)[number]['id']
export type SupportedFreebuffModelId =
(typeof SUPPORTED_FREEBUFF_MODELS)[number]['id']
+export type FreebuffPremiumModelId = (typeof FREEBUFF_PREMIUM_MODEL_IDS)[number]
/** What new freebuff users see selected in the picker. DeepSeek is the
* smartest of the free options; the picker surfaces its data-collection
@@ -136,6 +145,13 @@ export function isSupportedFreebuffModelId(
return SUPPORTED_FREEBUFF_MODELS.some((m) => m.id === id)
}
+export function isFreebuffPremiumModelId(
+ id: string | null | undefined,
+): id is FreebuffPremiumModelId {
+ if (!id) return false
+ return FREEBUFF_PREMIUM_MODEL_IDS.some((modelId) => modelId === id)
+}
+
export function resolveSupportedFreebuffModel(
id: string | null | undefined,
): SupportedFreebuffModelId {
diff --git a/common/src/types/freebuff-session.ts b/common/src/types/freebuff-session.ts
index b80ffed26..6f44d202b 100644
--- a/common/src/types/freebuff-session.ts
+++ b/common/src/types/freebuff-session.ts
@@ -7,11 +7,12 @@
*/
/**
- * Per-model usage counter surfaced to the CLI so the waiting-room UI can
- * render "N of M sessions used" alongside queue/active state. Present when
- * the joined model has a rate limit applied. `recentCount` is the number of
- * admissions inside `windowHours` at the time the response was produced —
- * see also the standalone `rate_limited` status for the reject path.
+ * Usage counter surfaced to the CLI so the waiting-room UI can render
+ * "N of M sessions used" alongside queue/active state. Present when the
+ * joined model consumes premium Freebuff sessions. `recentCount` is the
+ * rounded session units inside `windowHours` at the time the response was
+ * produced — see also the standalone `rate_limited` status for the reject
+ * path.
*/
export interface FreebuffSessionRateLimit {
model: string
@@ -61,9 +62,9 @@ export type FreebuffSessionServerResponse =
* Present on GET responses; not returned from POST (POST never
* produces `none`). */
queueDepthByModel?: Record
- /** Current quota snapshots for rate-limited models, keyed by model id.
- * Lets the picker show exhausted daily/session caps before the user
- * commits to a queue. */
+ /** Current quota snapshots for premium models, keyed by model id. Lets
+ * the picker show rolling premium-session usage before the user commits
+ * to a queue. */
rateLimitsByModel?: FreebuffSessionRateLimitByModel
}
| {
@@ -81,9 +82,7 @@ export type FreebuffSessionServerResponse =
queueDepthByModel: Record
estimatedWaitMs: number
queuedAt: string
- /** Rate-limit quota for rate-limited models. Absent
- * for unlimited models or when the status was produced outside the
- * rate-limit check path (e.g. pure read via GET). */
+ /** Premium-session quota for this model. Absent for unlimited models. */
rateLimit?: FreebuffSessionRateLimit
rateLimitsByModel?: FreebuffSessionRateLimitByModel
}
@@ -95,9 +94,7 @@ export type FreebuffSessionServerResponse =
admittedAt: string
expiresAt: string
remainingMs: number
- /** Rate-limit quota for rate-limited models. Absent
- * for unlimited models or when the status was produced outside the
- * rate-limit check path (e.g. pure read via GET). */
+ /** Premium-session quota for this model. Absent for unlimited models. */
rateLimit?: FreebuffSessionRateLimit
rateLimitsByModel?: FreebuffSessionRateLimitByModel
}
@@ -162,21 +159,20 @@ export type FreebuffSessionServerResponse =
status: 'banned'
}
| {
- /** User has used up their per-model admission quota in the rolling
- * window. Returned from POST
- * /session before the user is placed in the queue. `retryAfterMs` is
- * the time until the oldest admission inside the window falls off
- * and one quota slot opens up — clients should show the user when
- * they can try again. Terminal for the CLI's current poll session;
+ /** User has used up their shared premium-session quota in the rolling
+ * window. Returned from POST /session before the user is placed in the
+ * queue. `retryAfterMs` is the time until enough session units fall out
+ * of the window to open one quota slot — clients should show the user
+ * when they can try again. Terminal for the CLI's current poll session;
* the user can exit and come back later. */
status: 'rate_limited'
/** The freebuff model the user tried to join. */
model: string
- /** Max admissions permitted per window (e.g. 5). */
+ /** Max premium session units permitted per window (e.g. 5). */
limit: number
/** Rolling window size in hours (e.g. 20). */
windowHours: number
- /** Admission count inside the window at check time — will be ≥ limit. */
+ /** Premium session units inside the window at check time — will be ≥ limit. */
recentCount: number
/** Milliseconds from now until the oldest admission in the window
* exits and the user regains one quota slot. */
diff --git a/packages/internal/src/db/migrations/0050_overrated_stellaris.sql b/packages/internal/src/db/migrations/0050_overrated_stellaris.sql
new file mode 100644
index 000000000..9255e390b
--- /dev/null
+++ b/packages/internal/src/db/migrations/0050_overrated_stellaris.sql
@@ -0,0 +1 @@
+ALTER TABLE "free_session_admit" ADD COLUMN "session_units" numeric(3, 1) DEFAULT '1.0' NOT NULL;
\ No newline at end of file
diff --git a/packages/internal/src/db/migrations/meta/0050_snapshot.json b/packages/internal/src/db/migrations/meta/0050_snapshot.json
new file mode 100644
index 000000000..7e56edc6e
--- /dev/null
+++ b/packages/internal/src/db/migrations/meta/0050_snapshot.json
@@ -0,0 +1,3198 @@
+{
+ "id": "4c7aa6ac-8afc-4c2c-b0a4-2bbfcde731b8",
+ "prevId": "927c6e1e-457f-4815-99d1-96701792e9e5",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "name": "account_provider_providerAccountId_pk",
+ "columns": ["provider", "providerAccountId"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ad_impression": {
+ "name": "ad_impression",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'gravity'"
+ },
+ "ad_text": {
+ "name": "ad_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cta": {
+ "name": "cta",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "click_url": {
+ "name": "click_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "imp_url": {
+ "name": "imp_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "extra_pixels": {
+ "name": "extra_pixels",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payout": {
+ "name": "payout",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credits_granted": {
+ "name": "credits_granted",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_operation_id": {
+ "name": "grant_operation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "served_at": {
+ "name": "served_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "impression_fired_at": {
+ "name": "impression_fired_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "clicked_at": {
+ "name": "clicked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_ad_impression_user": {
+ "name": "idx_ad_impression_user",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "served_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_ad_impression_imp_url": {
+ "name": "idx_ad_impression_imp_url",
+ "columns": [
+ {
+ "expression": "imp_url",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ad_impression_user_id_user_id_fk": {
+ "name": "ad_impression_user_id_user_id_fk",
+ "tableFrom": "ad_impression",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "ad_impression_imp_url_unique": {
+ "name": "ad_impression_imp_url_unique",
+ "nullsNotDistinct": false,
+ "columns": ["imp_url"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_config": {
+ "name": "agent_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "publisher_id": {
+ "name": "publisher_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "major": {
+ "name": "major",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CAST(SPLIT_PART(\"agent_config\".\"version\", '.', 1) AS INTEGER)",
+ "type": "stored"
+ }
+ },
+ "minor": {
+ "name": "minor",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CAST(SPLIT_PART(\"agent_config\".\"version\", '.', 2) AS INTEGER)",
+ "type": "stored"
+ }
+ },
+ "patch": {
+ "name": "patch",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CAST(SPLIT_PART(\"agent_config\".\"version\", '.', 3) AS INTEGER)",
+ "type": "stored"
+ }
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_agent_config_publisher": {
+ "name": "idx_agent_config_publisher",
+ "columns": [
+ {
+ "expression": "publisher_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_config_publisher_id_publisher_id_fk": {
+ "name": "agent_config_publisher_id_publisher_id_fk",
+ "tableFrom": "agent_config",
+ "tableTo": "publisher",
+ "columnsFrom": ["publisher_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_config_publisher_id_id_version_pk": {
+ "name": "agent_config_publisher_id_id_version_pk",
+ "columns": ["publisher_id", "id", "version"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_run": {
+ "name": "agent_run",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "publisher_id": {
+ "name": "publisher_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CASE\n WHEN agent_id ~ '^[^/@]+/[^/@]+@[^/@]+$'\n THEN split_part(agent_id, '/', 1)\n ELSE NULL\n END",
+ "type": "stored"
+ }
+ },
+ "agent_name": {
+ "name": "agent_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CASE\n WHEN agent_id ~ '^[^/@]+/[^/@]+@[^/@]+$'\n THEN split_part(split_part(agent_id, '/', 2), '@', 1)\n ELSE agent_id\n END",
+ "type": "stored"
+ }
+ },
+ "agent_version": {
+ "name": "agent_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CASE\n WHEN agent_id ~ '^[^/@]+/[^/@]+@[^/@]+$'\n THEN split_part(agent_id, '@', 2)\n ELSE NULL\n END",
+ "type": "stored"
+ }
+ },
+ "ancestor_run_ids": {
+ "name": "ancestor_run_ids",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "root_run_id": {
+ "name": "root_run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CASE WHEN array_length(ancestor_run_ids, 1) >= 1 THEN ancestor_run_ids[1] ELSE id END",
+ "type": "stored"
+ }
+ },
+ "parent_run_id": {
+ "name": "parent_run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CASE WHEN array_length(ancestor_run_ids, 1) >= 1 THEN ancestor_run_ids[array_length(ancestor_run_ids, 1)] ELSE NULL END",
+ "type": "stored"
+ }
+ },
+ "depth": {
+ "name": "depth",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "COALESCE(array_length(ancestor_run_ids, 1), 1)",
+ "type": "stored"
+ }
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CASE WHEN completed_at IS NOT NULL THEN EXTRACT(EPOCH FROM (completed_at - created_at)) * 1000 ELSE NULL END::integer",
+ "type": "stored"
+ }
+ },
+ "total_steps": {
+ "name": "total_steps",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "direct_credits": {
+ "name": "direct_credits",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0'"
+ },
+ "total_credits": {
+ "name": "total_credits",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0'"
+ },
+ "status": {
+ "name": "status",
+ "type": "agent_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'running'"
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_agent_run_user_id": {
+ "name": "idx_agent_run_user_id",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_parent": {
+ "name": "idx_agent_run_parent",
+ "columns": [
+ {
+ "expression": "parent_run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_root": {
+ "name": "idx_agent_run_root",
+ "columns": [
+ {
+ "expression": "root_run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_agent_id": {
+ "name": "idx_agent_run_agent_id",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_publisher": {
+ "name": "idx_agent_run_publisher",
+ "columns": [
+ {
+ "expression": "publisher_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_status": {
+ "name": "idx_agent_run_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"agent_run\".\"status\" = 'running'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_ancestors_gin": {
+ "name": "idx_agent_run_ancestors_gin",
+ "columns": [
+ {
+ "expression": "ancestor_run_ids",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ },
+ "idx_agent_run_completed_publisher_agent": {
+ "name": "idx_agent_run_completed_publisher_agent",
+ "columns": [
+ {
+ "expression": "publisher_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"agent_run\".\"status\" = 'completed'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_completed_recent": {
+ "name": "idx_agent_run_completed_recent",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "publisher_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"agent_run\".\"status\" = 'completed'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_completed_version": {
+ "name": "idx_agent_run_completed_version",
+ "columns": [
+ {
+ "expression": "publisher_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_version",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"agent_run\".\"status\" = 'completed'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_run_completed_user": {
+ "name": "idx_agent_run_completed_user",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"agent_run\".\"status\" = 'completed'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_run_user_id_user_id_fk": {
+ "name": "agent_run_user_id_user_id_fk",
+ "tableFrom": "agent_run",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_step": {
+ "name": "agent_step",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_run_id": {
+ "name": "agent_run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "step_number": {
+ "name": "step_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "CASE WHEN completed_at IS NOT NULL THEN EXTRACT(EPOCH FROM (completed_at - created_at)) * 1000 ELSE NULL END::integer",
+ "type": "stored"
+ }
+ },
+ "credits": {
+ "name": "credits",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "child_run_ids": {
+ "name": "child_run_ids",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "spawned_count": {
+ "name": "spawned_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "array_length(child_run_ids, 1)",
+ "type": "stored"
+ }
+ },
+ "message_id": {
+ "name": "message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "agent_step_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "unique_step_number_per_run": {
+ "name": "unique_step_number_per_run",
+ "columns": [
+ {
+ "expression": "agent_run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "step_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_step_run_id": {
+ "name": "idx_agent_step_run_id",
+ "columns": [
+ {
+ "expression": "agent_run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_agent_step_children_gin": {
+ "name": "idx_agent_step_children_gin",
+ "columns": [
+ {
+ "expression": "child_run_ids",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_step_agent_run_id_agent_run_id_fk": {
+ "name": "agent_step_agent_run_id_agent_run_id_fk",
+ "tableFrom": "agent_step",
+ "tableTo": "agent_run",
+ "columnsFrom": ["agent_run_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credit_ledger": {
+ "name": "credit_ledger",
+ "schema": "",
+ "columns": {
+ "operation_id": {
+ "name": "operation_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "principal": {
+ "name": "principal",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "balance": {
+ "name": "balance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "grant_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_subscription_id": {
+ "name": "stripe_subscription_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_credit_ledger_active_balance": {
+ "name": "idx_credit_ledger_active_balance",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "balance",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "priority",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"credit_ledger\".\"balance\" != 0 AND \"credit_ledger\".\"expires_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_credit_ledger_org": {
+ "name": "idx_credit_ledger_org",
+ "columns": [
+ {
+ "expression": "org_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_credit_ledger_subscription": {
+ "name": "idx_credit_ledger_subscription",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "credit_ledger_user_id_user_id_fk": {
+ "name": "credit_ledger_user_id_user_id_fk",
+ "tableFrom": "credit_ledger",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credit_ledger_org_id_org_id_fk": {
+ "name": "credit_ledger_org_id_org_id_fk",
+ "tableFrom": "credit_ledger",
+ "tableTo": "org",
+ "columnsFrom": ["org_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.encrypted_api_keys": {
+ "name": "encrypted_api_keys",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "api_key_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "api_key": {
+ "name": "api_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "encrypted_api_keys_user_id_user_id_fk": {
+ "name": "encrypted_api_keys_user_id_user_id_fk",
+ "tableFrom": "encrypted_api_keys",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "encrypted_api_keys_user_id_type_pk": {
+ "name": "encrypted_api_keys_user_id_type_pk",
+ "columns": ["user_id", "type"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fingerprint": {
+ "name": "fingerprint",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "sig_hash": {
+ "name": "sig_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.free_session": {
+ "name": "free_session",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "free_session_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active_instance_id": {
+ "name": "active_instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "country_code": {
+ "name": "country_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cf_country": {
+ "name": "cf_country",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "geoip_country": {
+ "name": "geoip_country",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_block_reason": {
+ "name": "country_block_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ip_privacy_signals": {
+ "name": "ip_privacy_signals",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_ip_hash": {
+ "name": "client_ip_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_checked_at": {
+ "name": "country_checked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "queued_at": {
+ "name": "queued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "admitted_at": {
+ "name": "admitted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_free_session_queue": {
+ "name": "idx_free_session_queue",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "queued_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_free_session_expiry": {
+ "name": "idx_free_session_expiry",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "free_session_user_id_user_id_fk": {
+ "name": "free_session_user_id_user_id_fk",
+ "tableFrom": "free_session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.free_session_admit": {
+ "name": "free_session_admit",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "admitted_at": {
+ "name": "admitted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session_units": {
+ "name": "session_units",
+ "type": "numeric(3, 1)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'1.0'"
+ }
+ },
+ "indexes": {
+ "idx_free_session_admit_user_model_time": {
+ "name": "idx_free_session_admit_user_model_time",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "admitted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "free_session_admit_user_id_user_id_fk": {
+ "name": "free_session_admit_user_id_user_id_fk",
+ "tableFrom": "free_session_admit",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_eval_results": {
+ "name": "git_eval_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "cost_mode": {
+ "name": "cost_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reasoner_model": {
+ "name": "reasoner_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "agent_model": {
+ "name": "agent_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost": {
+ "name": "cost",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.limit_override": {
+ "name": "limit_override",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "credits_per_block": {
+ "name": "credits_per_block",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_duration_hours": {
+ "name": "block_duration_hours",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "weekly_credit_limit": {
+ "name": "weekly_credit_limit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "limit_override_user_id_user_id_fk": {
+ "name": "limit_override_user_id_user_id_fk",
+ "tableFrom": "limit_override",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.message": {
+ "name": "message",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_request_id": {
+ "name": "client_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "request": {
+ "name": "request",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "\"message\".\"request\" -> -1",
+ "type": "stored"
+ }
+ },
+ "reasoning_text": {
+ "name": "reasoning_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "response": {
+ "name": "response",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "input_tokens": {
+ "name": "input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_creation_input_tokens": {
+ "name": "cache_creation_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cache_read_input_tokens": {
+ "name": "cache_read_input_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "reasoning_tokens": {
+ "name": "reasoning_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "output_tokens": {
+ "name": "output_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cost": {
+ "name": "cost",
+ "type": "numeric(100, 20)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credits": {
+ "name": "credits",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "byok": {
+ "name": "byok",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "latency_ms": {
+ "name": "latency_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ttft_ms": {
+ "name": "ttft_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "repo_url": {
+ "name": "repo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "message_user_id_idx": {
+ "name": "message_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "message_finished_at_user_id_idx": {
+ "name": "message_finished_at_user_id_idx",
+ "columns": [
+ {
+ "expression": "finished_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "message_org_id_idx": {
+ "name": "message_org_id_idx",
+ "columns": [
+ {
+ "expression": "org_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "message_org_id_finished_at_idx": {
+ "name": "message_org_id_finished_at_idx",
+ "columns": [
+ {
+ "expression": "org_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "finished_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "message_user_id_user_id_fk": {
+ "name": "message_user_id_user_id_fk",
+ "tableFrom": "message",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "message_org_id_org_id_fk": {
+ "name": "message_org_id_org_id_fk",
+ "tableFrom": "message",
+ "tableTo": "org",
+ "columnsFrom": ["org_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.org": {
+ "name": "org",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_subscription_id": {
+ "name": "stripe_subscription_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "current_period_start": {
+ "name": "current_period_start",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "current_period_end": {
+ "name": "current_period_end",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_topup_enabled": {
+ "name": "auto_topup_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_topup_threshold": {
+ "name": "auto_topup_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auto_topup_amount": {
+ "name": "auto_topup_amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credit_limit": {
+ "name": "credit_limit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_alerts": {
+ "name": "billing_alerts",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "usage_alerts": {
+ "name": "usage_alerts",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "weekly_reports": {
+ "name": "weekly_reports",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "org_owner_id_user_id_fk": {
+ "name": "org_owner_id_user_id_fk",
+ "tableFrom": "org",
+ "tableTo": "user",
+ "columnsFrom": ["owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "org_slug_unique": {
+ "name": "org_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": ["slug"]
+ },
+ "org_stripe_customer_id_unique": {
+ "name": "org_stripe_customer_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["stripe_customer_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.org_feature": {
+ "name": "org_feature",
+ "schema": "",
+ "columns": {
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_org_feature_active": {
+ "name": "idx_org_feature_active",
+ "columns": [
+ {
+ "expression": "org_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "org_feature_org_id_org_id_fk": {
+ "name": "org_feature_org_id_org_id_fk",
+ "tableFrom": "org_feature",
+ "tableTo": "org",
+ "columnsFrom": ["org_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "org_feature_org_id_feature_pk": {
+ "name": "org_feature_org_id_feature_pk",
+ "columns": ["org_id", "feature"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.org_invite": {
+ "name": "org_invite",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "org_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invited_by": {
+ "name": "invited_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "accepted_at": {
+ "name": "accepted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accepted_by": {
+ "name": "accepted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_org_invite_token": {
+ "name": "idx_org_invite_token",
+ "columns": [
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_org_invite_email": {
+ "name": "idx_org_invite_email",
+ "columns": [
+ {
+ "expression": "org_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_org_invite_expires": {
+ "name": "idx_org_invite_expires",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "org_invite_org_id_org_id_fk": {
+ "name": "org_invite_org_id_org_id_fk",
+ "tableFrom": "org_invite",
+ "tableTo": "org",
+ "columnsFrom": ["org_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "org_invite_invited_by_user_id_fk": {
+ "name": "org_invite_invited_by_user_id_fk",
+ "tableFrom": "org_invite",
+ "tableTo": "user",
+ "columnsFrom": ["invited_by"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "org_invite_accepted_by_user_id_fk": {
+ "name": "org_invite_accepted_by_user_id_fk",
+ "tableFrom": "org_invite",
+ "tableTo": "user",
+ "columnsFrom": ["accepted_by"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "org_invite_token_unique": {
+ "name": "org_invite_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.org_member": {
+ "name": "org_member",
+ "schema": "",
+ "columns": {
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "org_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "org_member_org_id_org_id_fk": {
+ "name": "org_member_org_id_org_id_fk",
+ "tableFrom": "org_member",
+ "tableTo": "org",
+ "columnsFrom": ["org_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "org_member_user_id_user_id_fk": {
+ "name": "org_member_user_id_user_id_fk",
+ "tableFrom": "org_member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "org_member_org_id_user_id_pk": {
+ "name": "org_member_org_id_user_id_pk",
+ "columns": ["org_id", "user_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.org_repo": {
+ "name": "org_repo",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repo_url": {
+ "name": "repo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repo_name": {
+ "name": "repo_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repo_owner": {
+ "name": "repo_owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {
+ "idx_org_repo_active": {
+ "name": "idx_org_repo_active",
+ "columns": [
+ {
+ "expression": "org_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_org_repo_unique": {
+ "name": "idx_org_repo_unique",
+ "columns": [
+ {
+ "expression": "org_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "repo_url",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "org_repo_org_id_org_id_fk": {
+ "name": "org_repo_org_id_org_id_fk",
+ "tableFrom": "org_repo",
+ "tableTo": "org",
+ "columnsFrom": ["org_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "org_repo_approved_by_user_id_fk": {
+ "name": "org_repo_approved_by_user_id_fk",
+ "tableFrom": "org_repo",
+ "tableTo": "user",
+ "columnsFrom": ["approved_by"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.publisher": {
+ "name": "publisher",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "bio": {
+ "name": "bio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "publisher_user_id_user_id_fk": {
+ "name": "publisher_user_id_user_id_fk",
+ "tableFrom": "publisher",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "publisher_org_id_org_id_fk": {
+ "name": "publisher_org_id_org_id_fk",
+ "tableFrom": "publisher",
+ "tableTo": "org",
+ "columnsFrom": ["org_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "publisher_created_by_user_id_fk": {
+ "name": "publisher_created_by_user_id_fk",
+ "tableFrom": "publisher",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "publisher_single_owner": {
+ "name": "publisher_single_owner",
+ "value": "(\"publisher\".\"user_id\" IS NOT NULL AND \"publisher\".\"org_id\" IS NULL) OR\n (\"publisher\".\"user_id\" IS NULL AND \"publisher\".\"org_id\" IS NOT NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.referral": {
+ "name": "referral",
+ "schema": "",
+ "columns": {
+ "referrer_id": {
+ "name": "referrer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "referred_id": {
+ "name": "referred_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "referral_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "credits": {
+ "name": "credits",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_legacy": {
+ "name": "is_legacy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "referral_referrer_id_user_id_fk": {
+ "name": "referral_referrer_id_user_id_fk",
+ "tableFrom": "referral",
+ "tableTo": "user",
+ "columnsFrom": ["referrer_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "referral_referred_id_user_id_fk": {
+ "name": "referral_referred_id_user_id_fk",
+ "tableFrom": "referral",
+ "tableTo": "user",
+ "columnsFrom": ["referred_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "referral_referrer_id_referred_id_pk": {
+ "name": "referral_referrer_id_referred_id_pk",
+ "columns": ["referrer_id", "referred_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fingerprint_id": {
+ "name": "fingerprint_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cli_auth_hash": {
+ "name": "cli_auth_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "session_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'web'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "session_cli_auth_code_idx": {
+ "name": "session_cli_auth_code_idx",
+ "columns": [
+ {
+ "expression": "fingerprint_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "cli_auth_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"session\".\"cli_auth_hash\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_fingerprint_id_fingerprint_id_fk": {
+ "name": "session_fingerprint_id_fingerprint_id_fk",
+ "tableFrom": "session",
+ "tableTo": "fingerprint",
+ "columnsFrom": ["fingerprint_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.subscription": {
+ "name": "subscription",
+ "schema": "",
+ "columns": {
+ "stripe_subscription_id": {
+ "name": "stripe_subscription_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_price_id": {
+ "name": "stripe_price_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tier": {
+ "name": "tier",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scheduled_tier": {
+ "name": "scheduled_tier",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "subscription_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "billing_period_start": {
+ "name": "billing_period_start",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "billing_period_end": {
+ "name": "billing_period_end",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cancel_at_period_end": {
+ "name": "cancel_at_period_end",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canceled_at": {
+ "name": "canceled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_subscription_customer": {
+ "name": "idx_subscription_customer",
+ "columns": [
+ {
+ "expression": "stripe_customer_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_subscription_user": {
+ "name": "idx_subscription_user",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_subscription_status": {
+ "name": "idx_subscription_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"subscription\".\"status\" = 'active'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "subscription_user_id_user_id_fk": {
+ "name": "subscription_user_id_user_id_fk",
+ "tableFrom": "subscription",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sync_failure": {
+ "name": "sync_failure",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_attempt_at": {
+ "name": "last_attempt_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "retry_count": {
+ "name": "retry_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "idx_sync_failure_retry": {
+ "name": "idx_sync_failure_retry",
+ "columns": [
+ {
+ "expression": "retry_count",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "last_attempt_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"sync_failure\".\"retry_count\" < 5",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_quota_reset": {
+ "name": "next_quota_reset",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now() + INTERVAL '1 month'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "referral_code": {
+ "name": "referral_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'ref-' || gen_random_uuid()"
+ },
+ "referral_limit": {
+ "name": "referral_limit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 5
+ },
+ "discord_id": {
+ "name": "discord_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_topup_enabled": {
+ "name": "auto_topup_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_topup_threshold": {
+ "name": "auto_topup_threshold",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auto_topup_amount": {
+ "name": "auto_topup_amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "fallback_to_a_la_carte": {
+ "name": "fallback_to_a_la_carte",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ },
+ "user_stripe_customer_id_unique": {
+ "name": "user_stripe_customer_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["stripe_customer_id"]
+ },
+ "user_referral_code_unique": {
+ "name": "user_referral_code_unique",
+ "nullsNotDistinct": false,
+ "columns": ["referral_code"]
+ },
+ "user_discord_id_unique": {
+ "name": "user_discord_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["discord_id"]
+ },
+ "user_handle_unique": {
+ "name": "user_handle_unique",
+ "nullsNotDistinct": false,
+ "columns": ["handle"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verificationToken": {
+ "name": "verificationToken",
+ "schema": "",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "name": "verificationToken_identifier_token_pk",
+ "columns": ["identifier", "token"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.referral_status": {
+ "name": "referral_status",
+ "schema": "public",
+ "values": ["pending", "completed"]
+ },
+ "public.agent_run_status": {
+ "name": "agent_run_status",
+ "schema": "public",
+ "values": ["running", "completed", "failed", "cancelled"]
+ },
+ "public.agent_step_status": {
+ "name": "agent_step_status",
+ "schema": "public",
+ "values": ["running", "completed", "skipped"]
+ },
+ "public.api_key_type": {
+ "name": "api_key_type",
+ "schema": "public",
+ "values": ["anthropic", "gemini", "openai"]
+ },
+ "public.free_session_status": {
+ "name": "free_session_status",
+ "schema": "public",
+ "values": ["queued", "active"]
+ },
+ "public.grant_type": {
+ "name": "grant_type",
+ "schema": "public",
+ "values": [
+ "free",
+ "referral",
+ "referral_legacy",
+ "subscription",
+ "purchase",
+ "admin",
+ "organization",
+ "ad"
+ ]
+ },
+ "public.org_role": {
+ "name": "org_role",
+ "schema": "public",
+ "values": ["owner", "admin", "member"]
+ },
+ "public.session_type": {
+ "name": "session_type",
+ "schema": "public",
+ "values": ["web", "pat", "cli"]
+ },
+ "public.subscription_status": {
+ "name": "subscription_status",
+ "schema": "public",
+ "values": [
+ "incomplete",
+ "incomplete_expired",
+ "trialing",
+ "active",
+ "past_due",
+ "canceled",
+ "unpaid",
+ "paused"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/packages/internal/src/db/migrations/meta/_journal.json b/packages/internal/src/db/migrations/meta/_journal.json
index d93bf8857..6dcc93004 100644
--- a/packages/internal/src/db/migrations/meta/_journal.json
+++ b/packages/internal/src/db/migrations/meta/_journal.json
@@ -351,6 +351,13 @@
"when": 1777929052630,
"tag": "0049_loud_madame_masque",
"breakpoints": true
+ },
+ {
+ "idx": 50,
+ "version": "7",
+ "when": 1777936763321,
+ "tag": "0050_overrated_stellaris",
+ "breakpoints": true
}
]
}
diff --git a/packages/internal/src/db/schema.ts b/packages/internal/src/db/schema.ts
index 28406296d..ee4f32509 100644
--- a/packages/internal/src/db/schema.ts
+++ b/packages/internal/src/db/schema.ts
@@ -911,7 +911,9 @@ export const freeSession = pgTable(
/**
* Audit log of every admission — one row per queued→active transition. Used
- * to rate-limit heavy users (e.g. no more than 5 DeepSeek sessions per 12h).
+ * to track shared premium-session usage for Freebuff's 5 sessions / 20h
+ * allowance. `session_units` starts at 1.0 and may be reduced when users end
+ * active sessions early.
*
* Separate from `free_session` because that table is one-row-per-user (state,
* not history); the UPSERT path there would otherwise destroy prior admissions.
@@ -932,6 +934,12 @@ export const freeSessionAdmit = pgTable(
})
.notNull()
.defaultNow(),
+ session_units: numeric('session_units', {
+ precision: 3,
+ scale: 1,
+ })
+ .notNull()
+ .default('1.0'),
},
(table) => [
// Rate-limit lookup: WHERE user_id=$1 AND model=$2 AND admitted_at > $cutoff
diff --git a/web/src/app/api/v1/freebuff/session/__tests__/session.test.ts b/web/src/app/api/v1/freebuff/session/__tests__/session.test.ts
index 6f630e4d2..af77ac8f5 100644
--- a/web/src/app/api/v1/freebuff/session/__tests__/session.test.ts
+++ b/web/src/app/api/v1/freebuff/session/__tests__/session.test.ts
@@ -112,7 +112,7 @@ function makeSessionDeps(overrides: Partial = {}): SessionDeps & {
promoteQueuedUser: async () => null,
// No admits in handler tests — the rate-limit check reads empty and
// every request falls through to the queue.
- listRecentAdmits: async () => [],
+ listRecentPremiumAdmits: async () => [],
now: () => now,
getSessionRow: async (userId) => rows.get(userId) ?? null,
queueDepthsByModel: async () => {
@@ -124,7 +124,7 @@ function makeSessionDeps(overrides: Partial = {}): SessionDeps & {
return out
},
queuePositionFor: async () => 1,
- endSession: async (userId) => {
+ endSession: async ({ userId }) => {
rows.delete(userId)
},
joinOrTakeOver: async ({ userId, model, now, countryAccess }) => {
diff --git a/web/src/server/free-session/__tests__/public-api.test.ts b/web/src/server/free-session/__tests__/public-api.test.ts
index 153021d8e..d29c2cb1f 100644
--- a/web/src/server/free-session/__tests__/public-api.test.ts
+++ b/web/src/server/free-session/__tests__/public-api.test.ts
@@ -5,6 +5,8 @@ import {
FREEBUFF_GEMINI_PRO_MODEL_ID,
FREEBUFF_GLM_MODEL_ID,
FREEBUFF_KIMI_MODEL_ID,
+ FREEBUFF_PREMIUM_SESSION_LIMIT,
+ FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS,
} from '@codebuff/common/constants/freebuff-models'
import {
@@ -26,6 +28,7 @@ interface AdmitRecord {
user_id: string
model: string
admitted_at: Date
+ session_units?: number
}
function makeDeps(overrides: Partial = {}): SessionDeps & {
@@ -67,17 +70,20 @@ function makeDeps(overrides: Partial = {}): SessionDeps & {
}
return n
},
- listRecentAdmits: async ({ userId, model, since, limit }) => {
+ listRecentPremiumAdmits: async ({ userId, models, since }) => {
return admits
.filter(
(a) =>
a.user_id === userId &&
- a.model === model &&
+ models.includes(a.model) &&
a.admitted_at.getTime() >= since.getTime(),
)
.sort((a, b) => a.admitted_at.getTime() - b.admitted_at.getTime())
- .slice(0, limit)
- .map((a) => a.admitted_at)
+ .map((a) => ({
+ admittedAt: a.admitted_at,
+ model: a.model,
+ sessionUnits: a.session_units ?? 1,
+ }))
},
promoteQueuedUser: async ({ userId, model, sessionLengthMs, now }) => {
const row = rows.get(userId)
@@ -86,12 +92,38 @@ function makeDeps(overrides: Partial = {}): SessionDeps & {
row.admitted_at = now
row.expires_at = new Date(now.getTime() + sessionLengthMs)
row.updated_at = now
- admits.push({ user_id: userId, model, admitted_at: now })
+ admits.push({
+ user_id: userId,
+ model,
+ admitted_at: now,
+ session_units: 1,
+ })
return row
},
now: () => currentNow,
getSessionRow: async (userId) => rows.get(userId) ?? null,
- endSession: async (userId) => {
+ endSession: async ({ userId, now, sessionLengthMs }) => {
+ const row = rows.get(userId)
+ if (
+ row?.status === 'active' &&
+ row.admitted_at &&
+ row.expires_at &&
+ row.expires_at.getTime() > now.getTime()
+ ) {
+ const latest = admits
+ .filter((a) => a.user_id === userId && a.model === row.model)
+ .sort((a, b) => b.admitted_at.getTime() - a.admitted_at.getTime())[0]
+ if (latest) {
+ const usedMs = Math.max(
+ 0,
+ Math.min(
+ sessionLengthMs,
+ now.getTime() - row.admitted_at.getTime(),
+ ),
+ )
+ latest.session_units = Math.ceil((usedMs / sessionLengthMs) * 10) / 10
+ }
+ }
rows.delete(userId)
},
queueDepthsByModel: async () => {
@@ -239,8 +271,8 @@ describe('requestSession', () => {
expect(deps.rows.get('u1')?.model).toBe(FREEBUFF_GLM_MODEL_ID)
expect(state.rateLimit).toEqual({
model: FREEBUFF_GLM_MODEL_ID,
- limit: 5,
- windowHours: 12,
+ limit: FREEBUFF_PREMIUM_SESSION_LIMIT,
+ windowHours: FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS,
recentCount: 0,
})
})
@@ -269,8 +301,8 @@ describe('requestSession', () => {
expect(state.instanceId).not.toBe('inst-pre')
expect(state.rateLimit).toEqual({
model: FREEBUFF_GLM_MODEL_ID,
- limit: 5,
- windowHours: 12,
+ limit: FREEBUFF_PREMIUM_SESSION_LIMIT,
+ windowHours: FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS,
recentCount: 0,
})
})
@@ -282,7 +314,11 @@ describe('requestSession', () => {
deps._tick(new Date(deps._now().getTime() + 1000))
await requestSession({ userId: 'u2', model: DEFAULT_MODEL, deps })
deps._tick(new Date(deps._now().getTime() + 1000))
- await requestSession({ userId: 'u3', model: 'deepseek/deepseek-v4-pro', deps })
+ await requestSession({
+ userId: 'u3',
+ model: 'deepseek/deepseek-v4-pro',
+ deps,
+ })
const state = await getSessionState({ userId: 'u1', deps })
if (state.status !== 'queued') throw new Error('unreachable')
@@ -396,51 +432,101 @@ describe('requestSession', () => {
expect(s3.status).toBe('active')
})
- // Per-user rate limit (5 DeepSeek admissions per 18h) — the wire limit is
+ // Per-user premium session limit (5 units per 20h) — the wire limit is
// hard-coded in public-api.ts, so tests seed the fake admit log directly
- // rather than configuring it. DeepSeek runs 24/7, so the open-time anchor
- // here just keeps these scenarios deterministic against the test clock.
- const DEEPSEEK_MODEL = FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID
- const DEEPSEEK_LIMIT = 5
- const DEEPSEEK_WINDOW_HOURS = 18
- const DEEPSEEK_OPEN_TIME = new Date('2026-04-17T16:00:00Z')
-
- test('rate_limited: 5th DeepSeek admit in window blocks the 6th attempt', async () => {
- deps._tick(DEEPSEEK_OPEN_TIME)
- // Seed 5 admits inside the 18h window, spaced so we can verify retryAfter
+ // rather than configuring it.
+ const PREMIUM_MODEL = FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID
+ const KIMI_MODEL = FREEBUFF_KIMI_MODEL_ID
+ const PREMIUM_LIMIT = FREEBUFF_PREMIUM_SESSION_LIMIT
+ const PREMIUM_WINDOW_HOURS = FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS
+ const PREMIUM_OPEN_TIME = new Date('2026-04-17T16:00:00Z')
+
+ test('rate_limited: shared premium pool blocks the next premium session at 5 units', async () => {
+ deps._tick(PREMIUM_OPEN_TIME)
+ const now = deps._now()
+ for (let i = 0; i < PREMIUM_LIMIT; i++) {
+ deps.admits.push({
+ user_id: 'u1',
+ model: i === 0 ? KIMI_MODEL : PREMIUM_MODEL,
+ admitted_at: new Date(now.getTime() - (19 - i) * 60 * 60 * 1000),
+ })
+ }
+
+ const state = await requestSession({
+ userId: 'u1',
+ model: PREMIUM_MODEL,
+ deps,
+ })
+ expect(state.status).toBe('rate_limited')
+ if (state.status !== 'rate_limited') throw new Error('unreachable')
+ expect(state.model).toBe(PREMIUM_MODEL)
+ expect(state.limit).toBe(PREMIUM_LIMIT)
+ expect(state.windowHours).toBe(PREMIUM_WINDOW_HOURS)
+ expect(state.recentCount).toBe(PREMIUM_LIMIT)
+ expect(state.retryAfterMs).toBe(60 * 60 * 1000)
+ expect(deps.rows.has('u1')).toBe(false)
+ })
+
+ test('rate_limited: DeepSeek admit outside 20h window does not count', async () => {
+ deps._tick(PREMIUM_OPEN_TIME)
+ const now = deps._now()
+ deps.admits.push({
+ user_id: 'u1',
+ model: PREMIUM_MODEL,
+ admitted_at: new Date(now.getTime() - 21 * 60 * 60 * 1000),
+ })
+
+ const state = await requestSession({
+ userId: 'u1',
+ model: PREMIUM_MODEL,
+ deps,
+ })
+ expect(state.status).toBe('queued')
+ if (state.status !== 'queued') throw new Error('unreachable')
+ expect(state.rateLimit).toEqual({
+ model: PREMIUM_MODEL,
+ limit: PREMIUM_LIMIT,
+ windowHours: PREMIUM_WINDOW_HOURS,
+ recentCount: 0,
+ })
+ })
+
+ test('rate_limited: 5th Kimi admit in window blocks the 6th attempt', async () => {
+ deps._tick(PREMIUM_OPEN_TIME)
+ // Seed 5 admits inside the 20h window, spaced so we can verify retryAfter
// points at the oldest one sliding off.
const now = deps._now()
- // Oldest: 17h ago (still in window). Next 4: 1h, 2h, 3h, 4h ago.
- const ages = [17, 4, 3, 2, 1]
+ // Oldest: 19h ago (still in window). Next 4: 1h, 2h, 3h, 4h ago.
+ const ages = [19, 4, 3, 2, 1]
for (const hoursAgo of ages) {
deps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: KIMI_MODEL,
admitted_at: new Date(now.getTime() - hoursAgo * 60 * 60 * 1000),
})
}
const state = await requestSession({
userId: 'u1',
- model: DEEPSEEK_MODEL,
+ model: KIMI_MODEL,
deps,
})
expect(state.status).toBe('rate_limited')
if (state.status !== 'rate_limited') throw new Error('unreachable')
- expect(state.model).toBe(DEEPSEEK_MODEL)
- expect(state.limit).toBe(DEEPSEEK_LIMIT)
- expect(state.windowHours).toBe(DEEPSEEK_WINDOW_HOURS)
- expect(state.recentCount).toBe(DEEPSEEK_LIMIT)
- // Oldest admit is 17h ago; slot opens when it hits 18h, i.e. in 1h.
+ expect(state.model).toBe(KIMI_MODEL)
+ expect(state.limit).toBe(PREMIUM_LIMIT)
+ expect(state.windowHours).toBe(PREMIUM_WINDOW_HOURS)
+ expect(state.recentCount).toBe(PREMIUM_LIMIT)
+ // Oldest admit is 19h ago; slot opens when it hits 20h, i.e. in 1h.
expect(state.retryAfterMs).toBe(60 * 60 * 1000)
// Blocked before any row is written — the user doesn't take a queue slot.
expect(deps.rows.has('u1')).toBe(false)
})
- test('rate_limited: legacy GLM 5.1 keeps the deployment-hours quota', async () => {
- deps._tick(DEEPSEEK_OPEN_TIME)
+ test('rate_limited: legacy GLM 5.1 uses the shared premium quota', async () => {
+ deps._tick(PREMIUM_OPEN_TIME)
const now = deps._now()
- for (let i = 0; i < DEEPSEEK_LIMIT; i++) {
+ for (let i = 0; i < PREMIUM_LIMIT; i++) {
deps.admits.push({
user_id: 'u1',
model: FREEBUFF_GLM_MODEL_ID,
@@ -456,26 +542,26 @@ describe('requestSession', () => {
expect(state.status).toBe('rate_limited')
if (state.status !== 'rate_limited') throw new Error('unreachable')
expect(state.model).toBe(FREEBUFF_GLM_MODEL_ID)
- expect(state.limit).toBe(DEEPSEEK_LIMIT)
- expect(state.windowHours).toBe(12)
+ expect(state.limit).toBe(PREMIUM_LIMIT)
+ expect(state.windowHours).toBe(PREMIUM_WINDOW_HOURS)
})
- test('rate_limited: admits outside the 18h window do not count', async () => {
- deps._tick(DEEPSEEK_OPEN_TIME)
- // 5 admits, each just over 18h old → all fall off the window.
+ test('rate_limited: admits outside the 20h window do not count', async () => {
+ deps._tick(PREMIUM_OPEN_TIME)
+ // 5 admits, each just over 20h old → all fall off the window.
const now = deps._now()
for (let i = 0; i < 5; i++) {
deps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
admitted_at: new Date(
- now.getTime() - (DEEPSEEK_WINDOW_HOURS * 60 * 60 * 1000 + 60_000 + i),
+ now.getTime() - (PREMIUM_WINDOW_HOURS * 60 * 60 * 1000 + 60_000 + i),
),
})
}
const state = await requestSession({
userId: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
deps,
})
expect(state.status).toBe('queued')
@@ -504,48 +590,76 @@ describe('requestSession', () => {
})
test('queued DeepSeek response carries the current admit count', async () => {
- deps._tick(DEEPSEEK_OPEN_TIME)
+ deps._tick(PREMIUM_OPEN_TIME)
const now = deps._now()
// 2 admits in the window — under the limit so the user still queues.
deps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
admitted_at: new Date(now.getTime() - 60 * 60 * 1000),
})
deps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
admitted_at: new Date(now.getTime() - 30 * 60 * 1000),
})
const state = await requestSession({
userId: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
deps,
})
if (state.status !== 'queued') throw new Error('unreachable')
expect(state.rateLimit).toEqual({
- model: DEEPSEEK_MODEL,
- limit: DEEPSEEK_LIMIT,
- windowHours: DEEPSEEK_WINDOW_HOURS,
+ model: PREMIUM_MODEL,
+ limit: PREMIUM_LIMIT,
+ windowHours: PREMIUM_WINDOW_HOURS,
recentCount: 2,
})
})
- test('rate_limited: takeover of an active DeepSeek row is allowed even when at cap', async () => {
- // Reclaim path: user has an active+unexpired DeepSeek session and restarts
+ test('rate_limited: fractional premium usage under the cap can start another session', async () => {
+ deps._tick(PREMIUM_OPEN_TIME)
+ const now = deps._now()
+ deps.admits.push({
+ user_id: 'u1',
+ model: KIMI_MODEL,
+ admitted_at: new Date(now.getTime() - 19 * 60 * 60 * 1000),
+ session_units: 0.9,
+ })
+ for (let i = 0; i < 4; i++) {
+ deps.admits.push({
+ user_id: 'u1',
+ model: KIMI_MODEL,
+ admitted_at: new Date(now.getTime() - (i + 1) * 60 * 60 * 1000),
+ })
+ }
+
+ const state = await requestSession({
+ userId: 'u1',
+ model: KIMI_MODEL,
+ deps,
+ })
+
+ expect(state.status).toBe('queued')
+ if (state.status !== 'queued') throw new Error('unreachable')
+ expect(state.rateLimit?.recentCount).toBe(4.9)
+ })
+
+ test('rate_limited: takeover of an active premium row is allowed even when at cap', async () => {
+ // Reclaim path: user has an active+unexpired premium session and restarts
// the CLI. POST must rotate their instance id (takeover) and NOT reject
// with rate_limited — otherwise they'd be stranded with a live session
// they can't reconnect to. The 5th admission is already in the log, so
// this also exercises "at the cap" rather than "over the cap".
- deps._tick(DEEPSEEK_OPEN_TIME)
+ deps._tick(PREMIUM_OPEN_TIME)
const now = deps._now()
// Seed 5 prior admits (the cap), with the latest one matching the
// active row we're about to install.
- const ages = [11, 4, 3, 2, 0]
+ const ages = [19, 4, 3, 2, 0]
for (const hoursAgo of ages) {
deps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
admitted_at: new Date(now.getTime() - hoursAgo * 60 * 60 * 1000),
})
}
@@ -556,7 +670,7 @@ describe('requestSession', () => {
user_id: 'u1',
status: 'active',
active_instance_id: 'inst-pre',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
queued_at: admittedAt,
admitted_at: admittedAt,
expires_at: new Date(admittedAt.getTime() + SESSION_LEN),
@@ -566,27 +680,27 @@ describe('requestSession', () => {
const state = await requestSession({
userId: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
deps,
})
expect(state.status).toBe('active')
if (state.status !== 'active') throw new Error('unreachable')
// Instance id rotated; quota snapshot still reflects the full window.
expect(state.instanceId).not.toBe('inst-pre')
- expect(state.rateLimit?.recentCount).toBe(DEEPSEEK_LIMIT)
+ expect(state.rateLimit?.recentCount).toBe(PREMIUM_LIMIT)
})
- test('rate_limited: reclaim of a queued DeepSeek row is allowed even when at cap', async () => {
+ test('rate_limited: reclaim of a queued premium row is allowed even when at cap', async () => {
// Same reclaim exception for queued rows: if a user has already queued
// (say they slipped in just before their 5th admit landed), a subsequent
// POST from the same CLI must preserve their queue position instead of
// flipping to rate_limited.
- deps._tick(DEEPSEEK_OPEN_TIME)
+ deps._tick(PREMIUM_OPEN_TIME)
const now = deps._now()
- for (let i = 0; i < DEEPSEEK_LIMIT; i++) {
+ for (let i = 0; i < PREMIUM_LIMIT; i++) {
deps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
admitted_at: new Date(now.getTime() - (i + 1) * 60 * 60 * 1000),
})
}
@@ -595,7 +709,7 @@ describe('requestSession', () => {
user_id: 'u1',
status: 'queued',
active_instance_id: 'inst-pre',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
queued_at: queuedAt,
admitted_at: null,
expires_at: null,
@@ -605,7 +719,7 @@ describe('requestSession', () => {
const state = await requestSession({
userId: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
deps,
})
expect(state.status).toBe('queued')
@@ -613,20 +727,20 @@ describe('requestSession', () => {
// Same position (1) since we preserved queued_at and nobody else is
// ahead; the instance id rotated so any prior CLI is superseded.
expect(state.instanceId).not.toBe('inst-pre')
- expect(state.rateLimit?.recentCount).toBe(DEEPSEEK_LIMIT)
+ expect(state.rateLimit?.recentCount).toBe(PREMIUM_LIMIT)
})
- test('rate_limited: expired DeepSeek row is not a reclaim — quota still applies', async () => {
+ test('rate_limited: expired premium row is not a reclaim — quota still applies', async () => {
// The stored row's expires_at is in the past, so it doesn't represent
// an in-flight session. This POST is effectively a fresh request and
// must be blocked by the quota.
- deps._tick(DEEPSEEK_OPEN_TIME)
+ deps._tick(PREMIUM_OPEN_TIME)
const now = deps._now()
- const ages = [11, 4, 3, 2, 1]
+ const ages = [19, 4, 3, 2, 1]
for (const hoursAgo of ages) {
deps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
admitted_at: new Date(now.getTime() - hoursAgo * 60 * 60 * 1000),
})
}
@@ -635,7 +749,7 @@ describe('requestSession', () => {
user_id: 'u1',
status: 'active',
active_instance_id: 'inst-pre',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
queued_at: admittedAt,
admitted_at: admittedAt,
expires_at: new Date(admittedAt.getTime() + SESSION_LEN),
@@ -644,7 +758,7 @@ describe('requestSession', () => {
})
const state = await requestSession({
userId: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
deps,
})
expect(state.status).toBe('rate_limited')
@@ -652,18 +766,18 @@ describe('requestSession', () => {
test('instant-admit bumps the quota count for the freshly-written admit row', async () => {
const admitDeps = makeDeps({ getInstantAdmitCapacity: () => 3 })
- admitDeps._tick(DEEPSEEK_OPEN_TIME)
+ admitDeps._tick(PREMIUM_OPEN_TIME)
// 1 existing admit in the window; this new call should instant-admit and
// write a second row, so the response's recentCount reflects 2.
const now = admitDeps._now()
admitDeps.admits.push({
user_id: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
admitted_at: new Date(now.getTime() - 30 * 60 * 1000),
})
const state = await requestSession({
userId: 'u1',
- model: DEEPSEEK_MODEL,
+ model: PREMIUM_MODEL,
deps: admitDeps,
})
if (state.status !== 'active') throw new Error('unreachable')
@@ -697,6 +811,27 @@ describe('getSessionState', () => {
expect(state).toEqual({ status: 'none', queueDepthByModel: {} })
})
+ test('no row surfaces used premium quota before joining', async () => {
+ const now = deps._now()
+ deps.admits.push({
+ user_id: 'u1',
+ model: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
+ admitted_at: new Date(now.getTime() - 19 * 60 * 60 * 1000),
+ })
+
+ const state = await getSessionState({ userId: 'u1', deps })
+ expect(state.status).toBe('none')
+ if (state.status !== 'none') throw new Error('unreachable')
+ expect(
+ state.rateLimitsByModel?.[FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID],
+ ).toEqual({
+ model: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
+ limit: FREEBUFF_PREMIUM_SESSION_LIMIT,
+ windowHours: FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS,
+ recentCount: 1,
+ })
+ })
+
test('active session with matching instance id returns active', async () => {
await requestSession({ userId: 'u1', model: DEFAULT_MODEL, deps })
const row = deps.rows.get('u1')!
@@ -740,7 +875,11 @@ describe('getSessionState', () => {
model: 'deepseek/deepseek-v4-pro',
admitted_at: new Date(now.getTime() - 60 * 60 * 1000),
})
- await requestSession({ userId: 'u1', model: 'deepseek/deepseek-v4-pro', deps })
+ await requestSession({
+ userId: 'u1',
+ model: 'deepseek/deepseek-v4-pro',
+ deps,
+ })
const row = deps.rows.get('u1')!
row.status = 'active'
row.admitted_at = now
@@ -753,23 +892,27 @@ describe('getSessionState', () => {
})
if (state.status !== 'active') throw new Error('unreachable')
expect(state.rateLimit).toEqual({
- model: 'deepseek/deepseek-v4-pro',
- limit: 5,
- windowHours: 18,
+ model: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
+ limit: FREEBUFF_PREMIUM_SESSION_LIMIT,
+ windowHours: FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS,
recentCount: 1,
})
})
- test('active session only fetches quota for its own model', async () => {
+ test('active session only fetches one shared premium quota snapshot', async () => {
deps._tick(new Date('2026-04-17T16:00:00Z'))
let listRecentAdmitsCalls = 0
- const originalListRecentAdmits = deps.listRecentAdmits
- deps.listRecentAdmits = async (params) => {
+ const originalListRecentAdmits = deps.listRecentPremiumAdmits
+ deps.listRecentPremiumAdmits = async (params) => {
listRecentAdmitsCalls++
return originalListRecentAdmits(params)
}
- await requestSession({ userId: 'u1', model: 'deepseek/deepseek-v4-pro', deps })
+ await requestSession({
+ userId: 'u1',
+ model: 'deepseek/deepseek-v4-pro',
+ deps,
+ })
const row = deps.rows.get('u1')!
row.status = 'active'
row.admitted_at = deps._now()
@@ -1117,6 +1260,23 @@ describe('endUserSession', () => {
expect(deps.rows.has('u1')).toBe(false)
})
+ test('rounds active premium session usage up to nearest tenth on early end', async () => {
+ const deps = makeDeps({ getInstantAdmitCapacity: () => 3 })
+ deps._tick(new Date('2026-04-17T16:00:00Z'))
+ const state = await requestSession({
+ userId: 'u1',
+ model: FREEBUFF_KIMI_MODEL_ID,
+ deps,
+ })
+ expect(state.status).toBe('active')
+ deps._tick(new Date(deps._now().getTime() + 14 * 60 * 1000))
+
+ await endUserSession({ userId: 'u1', deps })
+
+ expect(deps.rows.has('u1')).toBe(false)
+ expect(deps.admits[0]?.session_units).toBe(0.3)
+ })
+
test('is no-op when disabled', async () => {
const deps = makeDeps({ isWaitingRoomEnabled: () => false })
deps.rows.set('u1', {
diff --git a/web/src/server/free-session/public-api.ts b/web/src/server/free-session/public-api.ts
index 52d5d442b..a1a065abe 100644
--- a/web/src/server/free-session/public-api.ts
+++ b/web/src/server/free-session/public-api.ts
@@ -3,9 +3,11 @@ import {
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
FREEBUFF_DEPLOYMENT_HOURS_LABEL,
FREEBUFF_GEMINI_PRO_MODEL_ID,
- FREEBUFF_GLM_MODEL_ID,
- FREEBUFF_KIMI_MODEL_ID,
+ FREEBUFF_PREMIUM_MODEL_IDS,
+ FREEBUFF_PREMIUM_SESSION_LIMIT,
+ FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS,
isFreebuffModelAvailable,
+ isFreebuffPremiumModelId,
isSupportedFreebuffModelId,
resolveSupportedFreebuffModel,
} from '@codebuff/common/constants/freebuff-models'
@@ -23,7 +25,7 @@ import {
FreeSessionModelLockedError,
getSessionRow,
joinOrTakeOver,
- listRecentAdmits,
+ listRecentPremiumAdmits,
promoteQueuedUser,
queueDepthsByModel,
queuePositionFor,
@@ -40,72 +42,106 @@ import type {
SessionStateResponse,
} from './types'
-/**
- * Per-model admission rate limits. Keyed by freebuff model id; a model not
- * in the map has no rate limit applied. Minimax is cheap enough to leave
- * unlimited.
- *
- * Hard-coded rather than env-driven: the values need to be observable in the
- * code review, and the CLI already renders the numbers via `rateLimit` on
- * queued/active responses — changing them is a deliberate, typed edit.
- */
-const RATE_LIMITS: Record = {
- [FREEBUFF_GLM_MODEL_ID]: { limit: 5, windowHours: 12 },
- [FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID]: { limit: 5, windowHours: 18 },
- [FREEBUFF_KIMI_MODEL_ID]: { limit: 5, windowHours: 18 },
+function roundSessionUnits(units: number): number {
+ return Math.round(units * 10) / 10
}
-/** Fetch the caller's current quota snapshot for `model`, or undefined if the
- * model isn't rate-limited. Used by both POST (after admit) and GET polls so
- * the CLI's "N of M sessions used" line stays live instead of disappearing
- * after the first poll. Also returns the oldest admit in-window and the
- * window duration so callers that need `retryAfterMs` don't have to re-query
- * or duplicate the window math. */
-async function fetchRateLimitSnapshot(
+function getRetryAfterMsForPremiumLimit(params: {
+ admits: Awaited>
+ totalUnits: number
+ targetUnits: number
+ windowMs: number
+ now: Date
+}): number {
+ let remainingUnits = params.totalUnits
+ for (const admit of params.admits) {
+ remainingUnits = roundSessionUnits(remainingUnits - admit.sessionUnits)
+ if (remainingUnits <= params.targetUnits) {
+ return Math.max(
+ 0,
+ admit.admittedAt.getTime() + params.windowMs - params.now.getTime(),
+ )
+ }
+ }
+ return 0
+}
+
+function canStartPremiumSession(snapshot: FreebuffSessionRateLimit): boolean {
+ return snapshot.recentCount < snapshot.limit
+}
+
+interface PremiumQuotaSnapshot {
+ recentCount: number
+ admits: Awaited>
+ windowMs: number
+}
+
+async function fetchPremiumQuotaSnapshot(
userId: string,
- model: string,
deps: SessionDeps,
-): Promise<
- | { info: FreebuffSessionRateLimit; oldest: Date | null; windowMs: number }
- | undefined
-> {
- const cfg = RATE_LIMITS[model]
- if (!cfg) return undefined
+): Promise {
const now = nowOf(deps)
- const windowMs = cfg.windowHours * 60 * 60 * 1000
+ const windowMs = FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS * 60 * 60 * 1000
const since = new Date(now.getTime() - windowMs)
- const admits = await deps.listRecentAdmits({
+ const admits = await deps.listRecentPremiumAdmits({
userId,
- model,
since,
- limit: cfg.limit,
+ models: FREEBUFF_PREMIUM_MODEL_IDS,
})
return {
- info: {
- model,
- limit: cfg.limit,
- windowHours: cfg.windowHours,
- recentCount: admits.length,
- },
- oldest: admits[0] ?? null,
+ recentCount: roundSessionUnits(
+ admits.reduce((sum, admit) => sum + admit.sessionUnits, 0),
+ ),
+ admits,
windowMs,
}
}
+function toRateLimitInfo(
+ model: string,
+ snapshot: PremiumQuotaSnapshot,
+): FreebuffSessionRateLimit {
+ return {
+ model,
+ limit: FREEBUFF_PREMIUM_SESSION_LIMIT,
+ windowHours: FREEBUFF_PREMIUM_SESSION_WINDOW_HOURS,
+ recentCount: snapshot.recentCount,
+ }
+}
+
+/** Fetch the caller's current shared premium-session quota snapshot for
+ * `model`, or undefined if the model is unlimited. Used by both POST (after
+ * admit) and GET polls so the CLI's "N of M sessions used" line stays live
+ * instead of disappearing after the first poll. */
+async function fetchRateLimitSnapshot(
+ userId: string,
+ model: string,
+ deps: SessionDeps,
+): Promise<
+ | {
+ info: FreebuffSessionRateLimit
+ admits: Awaited>
+ windowMs: number
+ }
+ | undefined
+> {
+ if (!isFreebuffPremiumModelId(model)) return undefined
+ const snapshot = await fetchPremiumQuotaSnapshot(userId, deps)
+ return {
+ info: toRateLimitInfo(model, snapshot),
+ admits: snapshot.admits,
+ windowMs: snapshot.windowMs,
+ }
+}
+
async function fetchRateLimitsByModel(
userId: string,
deps: SessionDeps,
): Promise> {
- const entries = await Promise.all(
- Object.keys(RATE_LIMITS).map(async (model) => {
- const snapshot = await fetchRateLimitSnapshot(userId, model, deps)
- return snapshot ? ([model, snapshot.info] as const) : null
- }),
- )
+ const snapshot = await fetchPremiumQuotaSnapshot(userId, deps)
return Object.fromEntries(
- entries.filter(
- (entry): entry is readonly [string, FreebuffSessionRateLimit] =>
- entry !== null,
+ FREEBUFF_PREMIUM_MODEL_IDS.map(
+ (model) => [model, toRateLimitInfo(model, snapshot)] as const,
),
)
}
@@ -134,7 +170,11 @@ export interface SessionDeps {
now: Date
countryAccess?: FreeSessionCountryAccessMetadata
}) => Promise
- endSession: (userId: string) => Promise
+ endSession: (params: {
+ userId: string
+ now: Date
+ sessionLengthMs: number
+ }) => Promise
queueDepthsByModel: () => Promise>
queuePositionFor: (params: {
userId: string
@@ -145,15 +185,12 @@ export interface SessionDeps {
* bound to a given model. Compared against the model's configured
* `instantAdmitCapacity` to decide whether a new joiner skips the queue. */
activeCountForModel: (model: string) => Promise
- /** Rate-limit helper: oldest-first admission timestamps for (userId, model)
- * inside the window. The caller uses `rows.length` as the count (capped
- * at `limit`) and `rows[0]` as the oldest for `retryAfterMs`. */
- listRecentAdmits: (params: {
+ /** Rate-limit helper: oldest-first premium admissions inside the window. */
+ listRecentPremiumAdmits: (params: {
userId: string
- model: string
+ models: readonly string[]
since: Date
- limit: number
- }) => Promise
+ }) => Promise<{ admittedAt: Date; model: string; sessionUnits: number }[]>
/** Instant-admit promotion: flips a specific queued row to active. Returns
* the updated row or null if the row wasn't in a queued state. */
promoteQueuedUser: (params: {
@@ -182,7 +219,7 @@ const defaultDeps: SessionDeps = {
queueDepthsByModel,
queuePositionFor,
activeCountForModel,
- listRecentAdmits,
+ listRecentPremiumAdmits,
promoteQueuedUser,
getInstantAdmitCapacity,
isWaitingRoomEnabled,
@@ -291,8 +328,8 @@ export async function requestSession(params: {
}
// Rate-limit check runs before joinOrTakeOver so heavy users never even
- // create a queued row. Only models listed in RATE_LIMITS are gated; others
- // (Minimax today) fall through unchanged.
+ // create a queued row. Premium models share one 20h session-unit pool;
+ // Minimax falls through unchanged as unlimited.
//
// Takeover/reclaim exception: a user who already holds a queued or
// active+unexpired row on this same model is re-anchoring (CLI restart,
@@ -319,13 +356,14 @@ export async function requestSession(params: {
if (!isReclaim) {
const snapshot = await fetchRateLimitSnapshot(params.userId, model, deps)
- if (snapshot && snapshot.info.recentCount >= snapshot.info.limit) {
- // Oldest admit's window-anniversary is when one slot opens back up.
- // Clamped at 0 so a clock skew can't surface a negative retry-after.
- const retryAfterMs = Math.max(
- 0,
- (snapshot.oldest?.getTime() ?? 0) + snapshot.windowMs - now.getTime(),
- )
+ if (snapshot && !canStartPremiumSession(snapshot.info)) {
+ const retryAfterMs = getRetryAfterMsForPremiumLimit({
+ admits: snapshot.admits,
+ totalUnits: snapshot.info.recentCount,
+ targetUnits: snapshot.info.limit,
+ windowMs: snapshot.windowMs,
+ now,
+ })
return {
status: 'rate_limited',
model,
@@ -493,7 +531,11 @@ export async function endUserSession(params: {
) {
return
}
- await deps.endSession(params.userId)
+ await deps.endSession({
+ userId: params.userId,
+ now: nowOf(deps),
+ sessionLengthMs: deps.sessionLengthMs,
+ })
}
export type SessionGateResult =
diff --git a/web/src/server/free-session/store.ts b/web/src/server/free-session/store.ts
index 1a8d2dba0..660f7a34a 100644
--- a/web/src/server/free-session/store.ts
+++ b/web/src/server/free-session/store.ts
@@ -1,7 +1,7 @@
import { db } from '@codebuff/internal/db'
import { coerceBool } from '@codebuff/internal/db/advisory-lock'
import * as schema from '@codebuff/internal/db/schema'
-import { and, asc, count, eq, gte, lt, sql } from 'drizzle-orm'
+import { and, asc, count, desc, eq, gte, inArray, lt, sql } from 'drizzle-orm'
import { FREEBUFF_ADMISSION_LOCK_ID } from './config'
@@ -161,10 +161,70 @@ export async function joinOrTakeOver(params: {
return row as InternalSessionRow
}
-export async function endSession(userId: string): Promise {
- await db
- .delete(schema.freeSession)
- .where(eq(schema.freeSession.user_id, userId))
+export function getRoundedSessionUnits(params: {
+ admittedAt: Date | null
+ now: Date
+ sessionLengthMs: number
+}): number {
+ const { admittedAt, now, sessionLengthMs } = params
+ if (!admittedAt || sessionLengthMs <= 0) return 0
+ const usedMs = Math.max(
+ 0,
+ Math.min(sessionLengthMs, now.getTime() - admittedAt.getTime()),
+ )
+ return Math.ceil((usedMs / sessionLengthMs) * 10) / 10
+}
+
+export async function endSession(params: {
+ userId: string
+ now: Date
+ sessionLengthMs: number
+}): Promise {
+ const { userId, now, sessionLengthMs } = params
+ await db.transaction(async (tx) => {
+ const [row] = await tx
+ .select()
+ .from(schema.freeSession)
+ .where(eq(schema.freeSession.user_id, userId))
+ .for('update')
+ .limit(1)
+
+ if (
+ row?.status === 'active' &&
+ row.admitted_at &&
+ row.expires_at &&
+ row.expires_at.getTime() > now.getTime()
+ ) {
+ const sessionUnits = getRoundedSessionUnits({
+ admittedAt: row.admitted_at,
+ now,
+ sessionLengthMs,
+ }).toFixed(1)
+
+ const [latestAdmit] = await tx
+ .select({ id: schema.freeSessionAdmit.id })
+ .from(schema.freeSessionAdmit)
+ .where(
+ and(
+ eq(schema.freeSessionAdmit.user_id, userId),
+ eq(schema.freeSessionAdmit.model, row.model),
+ ),
+ )
+ .orderBy(desc(schema.freeSessionAdmit.admitted_at))
+ .limit(1)
+
+ if (latestAdmit) {
+ await tx
+ .update(schema.freeSessionAdmit)
+ .set({ session_units: sessionUnits })
+ .where(eq(schema.freeSessionAdmit.id, latestAdmit.id))
+ }
+ }
+
+ await tx
+ .delete(schema.freeSession)
+ .where(eq(schema.freeSession.user_id, userId))
+ })
}
export async function queueDepth(params: { model: string }): Promise {
@@ -459,36 +519,44 @@ export async function promoteQueuedUser(params: {
})
}
+export interface RecentSessionAdmit {
+ admittedAt: Date
+ model: string
+ sessionUnits: number
+}
+
/**
- * List admissions for `userId` on `model` whose `admitted_at` is within the
- * window `[since, ∞)`, ordered oldest-first. Caller gets both the count
- * (array length, capped at `limit`) and the oldest timestamp (`rows[0]`) —
- * the oldest is needed to compute `retryAfterMs` when the window is full,
- * so one query covers both the check and the reject path.
- *
- * Drives the per-user, per-model rate limit (e.g. at most 5 DeepSeek sessions
- * in the last 12h) enforced before `joinOrTakeOver`.
+ * List premium-model admissions for `userId` inside `[since, ∞)`, ordered
+ * oldest-first. Each row carries charged session units; manual early end can
+ * revise a freshly written 1.0-unit admit down to a fractional value.
*/
-export async function listRecentAdmits(params: {
+export async function listRecentPremiumAdmits(params: {
userId: string
- model: string
+ models: readonly string[]
since: Date
- limit: number
-}): Promise {
- const { userId, model, since, limit } = params
+}): Promise {
+ const { userId, models, since } = params
+ if (models.length === 0) return []
const rows = await db
- .select({ admitted_at: schema.freeSessionAdmit.admitted_at })
+ .select({
+ admitted_at: schema.freeSessionAdmit.admitted_at,
+ model: schema.freeSessionAdmit.model,
+ session_units: schema.freeSessionAdmit.session_units,
+ })
.from(schema.freeSessionAdmit)
.where(
and(
eq(schema.freeSessionAdmit.user_id, userId),
- eq(schema.freeSessionAdmit.model, model),
+ inArray(schema.freeSessionAdmit.model, [...models]),
gte(schema.freeSessionAdmit.admitted_at, since),
),
)
.orderBy(asc(schema.freeSessionAdmit.admitted_at))
- .limit(limit)
- return rows.map((r) => r.admitted_at)
+ return rows.map((r) => ({
+ admittedAt: r.admitted_at,
+ model: r.model,
+ sessionUnits: Number(r.session_units),
+ }))
}
/** Stable 31-bit hash so model-keyed advisory lock ids don't overflow int4. */