diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts
index 5520df87f..36c86ef10 100644
--- a/packages/console/app/src/i18n/ar.ts
+++ b/packages/console/app/src/i18n/ar.ts
@@ -345,6 +345,7 @@ export const dict = {
"workspace.usage.breakdown.output": "الخرج",
"workspace.usage.breakdown.reasoning": "المنطق",
"workspace.usage.subscription": "الاشتراك (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "التكلفة",
@@ -354,6 +355,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(محذوف)",
"workspace.cost.empty": "لا توجد بيانات استخدام متاحة للفترة المحددة.",
"workspace.cost.subscriptionShort": "اشتراك",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "مفاتيح API",
"workspace.keys.subtitle": "إدارة مفاتيح API الخاصة بك للوصول إلى خدمات opencode.",
@@ -481,6 +483,31 @@ export const dict = {
"workspace.black.waitlist.enrolled": "مسجل",
"workspace.black.waitlist.enrollNote": 'عند النقر فوق "تسجيل"، يبدأ اشتراكك على الفور وسيتم خصم الرسوم من بطاقتك.',
+ "workspace.lite.loading": "جارٍ التحميل...",
+ "workspace.lite.time.day": "يوم",
+ "workspace.lite.time.days": "أيام",
+ "workspace.lite.time.hour": "ساعة",
+ "workspace.lite.time.hours": "ساعات",
+ "workspace.lite.time.minute": "دقيقة",
+ "workspace.lite.time.minutes": "دقائق",
+ "workspace.lite.time.fewSeconds": "بضع ثوان",
+ "workspace.lite.subscription.title": "اشتراك Lite",
+ "workspace.lite.subscription.message": "أنت مشترك في OpenCode Lite.",
+ "workspace.lite.subscription.manage": "إدارة الاشتراك",
+ "workspace.lite.subscription.rollingUsage": "الاستخدام المتجدد",
+ "workspace.lite.subscription.weeklyUsage": "الاستخدام الأسبوعي",
+ "workspace.lite.subscription.monthlyUsage": "الاستخدام الشهري",
+ "workspace.lite.subscription.resetsIn": "إعادة تعيين في",
+ "workspace.lite.subscription.useBalance": "استخدم رصيدك المتوفر بعد الوصول إلى حدود الاستخدام",
+ "workspace.lite.other.title": "اشتراك Lite",
+ "workspace.lite.other.message":
+ "عضو آخر في مساحة العمل هذه مشترك بالفعل في OpenCode Lite. يمكن لعضو واحد فقط لكل مساحة عمل الاشتراك.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "احصل على وصول إلى أفضل النماذج المفتوحة — Kimi K2.5، و GLM-5، و MiniMax M2.5 — مع حدود استخدام سخية مقابل $10 شهريًا.",
+ "workspace.lite.promo.subscribe": "الاشتراك في Lite",
+ "workspace.lite.promo.subscribing": "جارٍ إعادة التوجيه...",
+
"download.title": "OpenCode | تنزيل",
"download.meta.description": "نزّل OpenCode لـ macOS، Windows، وLinux",
"download.hero.title": "تنزيل OpenCode",
diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts
index d03522683..5367a748b 100644
--- a/packages/console/app/src/i18n/br.ts
+++ b/packages/console/app/src/i18n/br.ts
@@ -350,6 +350,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Saída",
"workspace.usage.breakdown.reasoning": "Raciocínio",
"workspace.usage.subscription": "assinatura (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Custo",
@@ -359,6 +360,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(excluído)",
"workspace.cost.empty": "Nenhum dado de uso disponível para o período selecionado.",
"workspace.cost.subscriptionShort": "ass",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "Chaves de API",
"workspace.keys.subtitle": "Gerencie suas chaves de API para acessar os serviços opencode.",
@@ -487,6 +489,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Ao clicar em Inscrever-se, sua assinatura começará imediatamente e seu cartão será cobrado.",
+ "workspace.lite.loading": "Carregando...",
+ "workspace.lite.time.day": "dia",
+ "workspace.lite.time.days": "dias",
+ "workspace.lite.time.hour": "hora",
+ "workspace.lite.time.hours": "horas",
+ "workspace.lite.time.minute": "minuto",
+ "workspace.lite.time.minutes": "minutos",
+ "workspace.lite.time.fewSeconds": "alguns segundos",
+ "workspace.lite.subscription.title": "Assinatura Lite",
+ "workspace.lite.subscription.message": "Você assina o OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Gerenciar Assinatura",
+ "workspace.lite.subscription.rollingUsage": "Uso Contínuo",
+ "workspace.lite.subscription.weeklyUsage": "Uso Semanal",
+ "workspace.lite.subscription.monthlyUsage": "Uso Mensal",
+ "workspace.lite.subscription.resetsIn": "Reinicia em",
+ "workspace.lite.subscription.useBalance": "Use seu saldo disponível após atingir os limites de uso",
+ "workspace.lite.other.title": "Assinatura Lite",
+ "workspace.lite.other.message":
+ "Outro membro neste workspace já assina o OpenCode Lite. Apenas um membro por workspace pode assinar.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Tenha acesso aos melhores modelos abertos — Kimi K2.5, GLM-5 e MiniMax M2.5 — com limites de uso generosos por $10 por mês.",
+ "workspace.lite.promo.subscribe": "Assinar Lite",
+ "workspace.lite.promo.subscribing": "Redirecionando...",
+
"download.title": "OpenCode | Baixar",
"download.meta.description": "Baixe o OpenCode para macOS, Windows e Linux",
"download.hero.title": "Baixar OpenCode",
diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts
index 94826c438..2f1be69ca 100644
--- a/packages/console/app/src/i18n/da.ts
+++ b/packages/console/app/src/i18n/da.ts
@@ -348,6 +348,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Output",
"workspace.usage.breakdown.reasoning": "Ræsonnement",
"workspace.usage.subscription": "abonnement (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Omkostninger",
@@ -357,6 +358,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(slettet)",
"workspace.cost.empty": "Ingen brugsdata tilgængelige for den valgte periode.",
"workspace.cost.subscriptionShort": "sub",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API-nøgler",
"workspace.keys.subtitle": "Administrer dine API-nøgler for at få adgang til opencode-tjenester.",
@@ -485,6 +487,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Når du klikker på Tilmeld, starter dit abonnement med det samme, og dit kort vil blive debiteret.",
+ "workspace.lite.loading": "Indlæser...",
+ "workspace.lite.time.day": "dag",
+ "workspace.lite.time.days": "dage",
+ "workspace.lite.time.hour": "time",
+ "workspace.lite.time.hours": "timer",
+ "workspace.lite.time.minute": "minut",
+ "workspace.lite.time.minutes": "minutter",
+ "workspace.lite.time.fewSeconds": "et par sekunder",
+ "workspace.lite.subscription.title": "Lite-abonnement",
+ "workspace.lite.subscription.message": "Du abonnerer på OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Administrer abonnement",
+ "workspace.lite.subscription.rollingUsage": "Løbende forbrug",
+ "workspace.lite.subscription.weeklyUsage": "Ugentligt forbrug",
+ "workspace.lite.subscription.monthlyUsage": "Månedligt forbrug",
+ "workspace.lite.subscription.resetsIn": "Nulstiller i",
+ "workspace.lite.subscription.useBalance": "Brug din tilgængelige saldo, når du har nået forbrugsgrænserne",
+ "workspace.lite.other.title": "Lite-abonnement",
+ "workspace.lite.other.message":
+ "Et andet medlem i dette workspace abonnerer allerede på OpenCode Lite. Kun ét medlem pr. workspace kan abonnere.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Få adgang til de bedste åbne modeller — Kimi K2.5, GLM-5 og MiniMax M2.5 — med generøse forbrugsgrænser for $10 om måneden.",
+ "workspace.lite.promo.subscribe": "Abonner på Lite",
+ "workspace.lite.promo.subscribing": "Omdirigerer...",
+
"download.title": "OpenCode | Download",
"download.meta.description": "Download OpenCode til macOS, Windows og Linux",
"download.hero.title": "Download OpenCode",
diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts
index cfa207066..49df65f8d 100644
--- a/packages/console/app/src/i18n/de.ts
+++ b/packages/console/app/src/i18n/de.ts
@@ -350,6 +350,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Output",
"workspace.usage.breakdown.reasoning": "Reasoning",
"workspace.usage.subscription": "Abonnement (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Kosten",
@@ -359,6 +360,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(gelöscht)",
"workspace.cost.empty": "Keine Nutzungsdaten für den gewählten Zeitraum verfügbar.",
"workspace.cost.subscriptionShort": "Abo",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API Keys",
"workspace.keys.subtitle": "Verwalte deine API Keys für den Zugriff auf OpenCode-Dienste.",
@@ -487,6 +489,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Wenn du auf Einschreiben klickst, startet dein Abo sofort und deine Karte wird belastet.",
+ "workspace.lite.loading": "Lade...",
+ "workspace.lite.time.day": "Tag",
+ "workspace.lite.time.days": "Tage",
+ "workspace.lite.time.hour": "Stunde",
+ "workspace.lite.time.hours": "Stunden",
+ "workspace.lite.time.minute": "Minute",
+ "workspace.lite.time.minutes": "Minuten",
+ "workspace.lite.time.fewSeconds": "einige Sekunden",
+ "workspace.lite.subscription.title": "Lite-Abonnement",
+ "workspace.lite.subscription.message": "Du hast OpenCode Lite abonniert.",
+ "workspace.lite.subscription.manage": "Abo verwalten",
+ "workspace.lite.subscription.rollingUsage": "Fortlaufende Nutzung",
+ "workspace.lite.subscription.weeklyUsage": "Wöchentliche Nutzung",
+ "workspace.lite.subscription.monthlyUsage": "Monatliche Nutzung",
+ "workspace.lite.subscription.resetsIn": "Setzt zurück in",
+ "workspace.lite.subscription.useBalance": "Nutze dein verfügbares Guthaben, nachdem die Nutzungslimits erreicht sind",
+ "workspace.lite.other.title": "Lite-Abonnement",
+ "workspace.lite.other.message":
+ "Ein anderes Mitglied in diesem Workspace hat OpenCode Lite bereits abonniert. Nur ein Mitglied pro Workspace kann abonnieren.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Erhalte Zugriff auf die besten offenen Modelle — Kimi K2.5, GLM-5 und MiniMax M2.5 — mit großzügigen Nutzungslimits für $10 pro Monat.",
+ "workspace.lite.promo.subscribe": "Lite abonnieren",
+ "workspace.lite.promo.subscribing": "Leite weiter...",
+
"download.title": "OpenCode | Download",
"download.meta.description": "Lade OpenCode für macOS, Windows und Linux herunter",
"download.hero.title": "OpenCode herunterladen",
diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts
index 8e096dd5c..42b88dd16 100644
--- a/packages/console/app/src/i18n/en.ts
+++ b/packages/console/app/src/i18n/en.ts
@@ -342,6 +342,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Output",
"workspace.usage.breakdown.reasoning": "Reasoning",
"workspace.usage.subscription": "subscription (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Cost",
@@ -351,6 +352,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(deleted)",
"workspace.cost.empty": "No usage data available for the selected period.",
"workspace.cost.subscriptionShort": "sub",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API Keys",
"workspace.keys.subtitle": "Manage your API keys for accessing opencode services.",
@@ -479,6 +481,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"When you click Enroll, your subscription starts immediately and your card will be charged.",
+ "workspace.lite.loading": "Loading...",
+ "workspace.lite.time.day": "day",
+ "workspace.lite.time.days": "days",
+ "workspace.lite.time.hour": "hour",
+ "workspace.lite.time.hours": "hours",
+ "workspace.lite.time.minute": "minute",
+ "workspace.lite.time.minutes": "minutes",
+ "workspace.lite.time.fewSeconds": "a few seconds",
+ "workspace.lite.subscription.title": "Lite Subscription",
+ "workspace.lite.subscription.message": "You are subscribed to OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Manage Subscription",
+ "workspace.lite.subscription.rollingUsage": "Rolling Usage",
+ "workspace.lite.subscription.weeklyUsage": "Weekly Usage",
+ "workspace.lite.subscription.monthlyUsage": "Monthly Usage",
+ "workspace.lite.subscription.resetsIn": "Resets in",
+ "workspace.lite.subscription.useBalance": "Use your available balance after reaching the usage limits",
+ "workspace.lite.other.title": "Lite Subscription",
+ "workspace.lite.other.message":
+ "Another member in this workspace is already subscribed to OpenCode Lite. Only one member per workspace can subscribe.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Get access to the best open models — Kimi K2.5, GLM-5, and MiniMax M2.5 — with generous usage limits for $10 per month.",
+ "workspace.lite.promo.subscribe": "Subscribe to Lite",
+ "workspace.lite.promo.subscribing": "Redirecting...",
+
"download.title": "OpenCode | Download",
"download.meta.description": "Download OpenCode for macOS, Windows, and Linux",
"download.hero.title": "Download OpenCode",
diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts
index c8579462e..f4ac1cc63 100644
--- a/packages/console/app/src/i18n/es.ts
+++ b/packages/console/app/src/i18n/es.ts
@@ -351,6 +351,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Salida",
"workspace.usage.breakdown.reasoning": "Razonamiento",
"workspace.usage.subscription": "suscripción (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Costo",
@@ -360,6 +361,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(eliminado)",
"workspace.cost.empty": "No hay datos de uso disponibles para el periodo seleccionado.",
"workspace.cost.subscriptionShort": "sub",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "Claves API",
"workspace.keys.subtitle": "Gestiona tus claves API para acceder a los servicios de opencode.",
@@ -488,6 +490,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Cuando haces clic en Inscribirse, tu suscripción comienza inmediatamente y se cargará a tu tarjeta.",
+ "workspace.lite.loading": "Cargando...",
+ "workspace.lite.time.day": "día",
+ "workspace.lite.time.days": "días",
+ "workspace.lite.time.hour": "hora",
+ "workspace.lite.time.hours": "horas",
+ "workspace.lite.time.minute": "minuto",
+ "workspace.lite.time.minutes": "minutos",
+ "workspace.lite.time.fewSeconds": "unos pocos segundos",
+ "workspace.lite.subscription.title": "Suscripción Lite",
+ "workspace.lite.subscription.message": "Estás suscrito a OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Gestionar Suscripción",
+ "workspace.lite.subscription.rollingUsage": "Uso Continuo",
+ "workspace.lite.subscription.weeklyUsage": "Uso Semanal",
+ "workspace.lite.subscription.monthlyUsage": "Uso Mensual",
+ "workspace.lite.subscription.resetsIn": "Se reinicia en",
+ "workspace.lite.subscription.useBalance": "Usa tu saldo disponible después de alcanzar los límites de uso",
+ "workspace.lite.other.title": "Suscripción Lite",
+ "workspace.lite.other.message":
+ "Otro miembro de este espacio de trabajo ya está suscrito a OpenCode Lite. Solo un miembro por espacio de trabajo puede suscribirse.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Obtén acceso a los mejores modelos abiertos — Kimi K2.5, GLM-5 y MiniMax M2.5 — con generosos límites de uso por $10 al mes.",
+ "workspace.lite.promo.subscribe": "Suscribirse a Lite",
+ "workspace.lite.promo.subscribing": "Redirigiendo...",
+
"download.title": "OpenCode | Descargar",
"download.meta.description": "Descarga OpenCode para macOS, Windows y Linux",
"download.hero.title": "Descargar OpenCode",
diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts
index ccb0a8cc6..05ee4e843 100644
--- a/packages/console/app/src/i18n/fr.ts
+++ b/packages/console/app/src/i18n/fr.ts
@@ -356,6 +356,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Sortie",
"workspace.usage.breakdown.reasoning": "Raisonnement",
"workspace.usage.subscription": "abonnement ({{amount}} $)",
+ "workspace.usage.lite": "lite ({{amount}} $)",
"workspace.usage.byok": "BYOK ({{amount}} $)",
"workspace.cost.title": "Coût",
@@ -365,6 +366,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(supprimé)",
"workspace.cost.empty": "Aucune donnée d'utilisation disponible pour la période sélectionnée.",
"workspace.cost.subscriptionShort": "abo",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "Clés API",
"workspace.keys.subtitle": "Gérez vos clés API pour accéder aux services OpenCode.",
@@ -496,6 +498,32 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Lorsque vous cliquez sur S'inscrire, votre abonnement démarre immédiatement et votre carte sera débitée.",
+ "workspace.lite.loading": "Chargement...",
+ "workspace.lite.time.day": "jour",
+ "workspace.lite.time.days": "jours",
+ "workspace.lite.time.hour": "heure",
+ "workspace.lite.time.hours": "heures",
+ "workspace.lite.time.minute": "minute",
+ "workspace.lite.time.minutes": "minutes",
+ "workspace.lite.time.fewSeconds": "quelques secondes",
+ "workspace.lite.subscription.title": "Abonnement Lite",
+ "workspace.lite.subscription.message": "Vous êtes abonné à OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Gérer l'abonnement",
+ "workspace.lite.subscription.rollingUsage": "Utilisation glissante",
+ "workspace.lite.subscription.weeklyUsage": "Utilisation hebdomadaire",
+ "workspace.lite.subscription.monthlyUsage": "Utilisation mensuelle",
+ "workspace.lite.subscription.resetsIn": "Réinitialisation dans",
+ "workspace.lite.subscription.useBalance":
+ "Utilisez votre solde disponible après avoir atteint les limites d'utilisation",
+ "workspace.lite.other.title": "Abonnement Lite",
+ "workspace.lite.other.message":
+ "Un autre membre de cet espace de travail est déjà abonné à OpenCode Lite. Un seul membre par espace de travail peut s'abonner.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Accédez aux meilleurs modèles ouverts — Kimi K2.5, GLM-5 et MiniMax M2.5 — avec des limites d'utilisation généreuses pour 10 $ par mois.",
+ "workspace.lite.promo.subscribe": "S'abonner à Lite",
+ "workspace.lite.promo.subscribing": "Redirection...",
+
"download.title": "OpenCode | Téléchargement",
"download.meta.description": "Téléchargez OpenCode pour macOS, Windows et Linux",
"download.hero.title": "Télécharger OpenCode",
diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts
index 21162f699..08b795504 100644
--- a/packages/console/app/src/i18n/it.ts
+++ b/packages/console/app/src/i18n/it.ts
@@ -350,6 +350,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Output",
"workspace.usage.breakdown.reasoning": "Reasoning",
"workspace.usage.subscription": "abbonamento (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Costo",
@@ -359,6 +360,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(eliminato)",
"workspace.cost.empty": "Nessun dato di utilizzo disponibile per il periodo selezionato.",
"workspace.cost.subscriptionShort": "sub",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "Chiavi API",
"workspace.keys.subtitle": "Gestisci le tue chiavi API per accedere ai servizi opencode.",
@@ -487,6 +489,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Quando clicchi su Iscriviti, il tuo abbonamento inizia immediatamente e la tua carta verrà addebitata.",
+ "workspace.lite.loading": "Caricamento...",
+ "workspace.lite.time.day": "giorno",
+ "workspace.lite.time.days": "giorni",
+ "workspace.lite.time.hour": "ora",
+ "workspace.lite.time.hours": "ore",
+ "workspace.lite.time.minute": "minuto",
+ "workspace.lite.time.minutes": "minuti",
+ "workspace.lite.time.fewSeconds": "pochi secondi",
+ "workspace.lite.subscription.title": "Abbonamento Lite",
+ "workspace.lite.subscription.message": "Sei abbonato a OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Gestisci Abbonamento",
+ "workspace.lite.subscription.rollingUsage": "Utilizzo Continuativo",
+ "workspace.lite.subscription.weeklyUsage": "Utilizzo Settimanale",
+ "workspace.lite.subscription.monthlyUsage": "Utilizzo Mensile",
+ "workspace.lite.subscription.resetsIn": "Si resetta tra",
+ "workspace.lite.subscription.useBalance": "Usa il tuo saldo disponibile dopo aver raggiunto i limiti di utilizzo",
+ "workspace.lite.other.title": "Abbonamento Lite",
+ "workspace.lite.other.message":
+ "Un altro membro in questo workspace è già abbonato a OpenCode Lite. Solo un membro per workspace può abbonarsi.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Ottieni l'accesso ai migliori modelli aperti — Kimi K2.5, GLM-5 e MiniMax M2.5 — con limiti di utilizzo generosi per $10 al mese.",
+ "workspace.lite.promo.subscribe": "Abbonati a Lite",
+ "workspace.lite.promo.subscribing": "Reindirizzamento...",
+
"download.title": "OpenCode | Download",
"download.meta.description": "Scarica OpenCode per macOS, Windows e Linux",
"download.hero.title": "Scarica OpenCode",
diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts
index 1f2746f2b..2c8e9d6b4 100644
--- a/packages/console/app/src/i18n/ja.ts
+++ b/packages/console/app/src/i18n/ja.ts
@@ -347,6 +347,7 @@ export const dict = {
"workspace.usage.breakdown.output": "出力",
"workspace.usage.breakdown.reasoning": "推論",
"workspace.usage.subscription": "サブスクリプション (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "コスト",
@@ -356,6 +357,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(削除済み)",
"workspace.cost.empty": "選択した期間の使用状況データはありません。",
"workspace.cost.subscriptionShort": "サブ",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "APIキー",
"workspace.keys.subtitle": "OpenCodeサービスにアクセスするためのAPIキーを管理します。",
@@ -485,6 +487,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"「登録する」をクリックすると、サブスクリプションがすぐに開始され、カードに請求されます。",
+ "workspace.lite.loading": "読み込み中...",
+ "workspace.lite.time.day": "日",
+ "workspace.lite.time.days": "日",
+ "workspace.lite.time.hour": "時間",
+ "workspace.lite.time.hours": "時間",
+ "workspace.lite.time.minute": "分",
+ "workspace.lite.time.minutes": "分",
+ "workspace.lite.time.fewSeconds": "数秒",
+ "workspace.lite.subscription.title": "Liteサブスクリプション",
+ "workspace.lite.subscription.message": "あなたは OpenCode Lite を購読しています。",
+ "workspace.lite.subscription.manage": "サブスクリプションの管理",
+ "workspace.lite.subscription.rollingUsage": "ローリング利用量",
+ "workspace.lite.subscription.weeklyUsage": "週間利用量",
+ "workspace.lite.subscription.monthlyUsage": "月間利用量",
+ "workspace.lite.subscription.resetsIn": "リセットまで",
+ "workspace.lite.subscription.useBalance": "利用限度額に達したら利用可能な残高を使用する",
+ "workspace.lite.other.title": "Liteサブスクリプション",
+ "workspace.lite.other.message":
+ "このワークスペースの別のメンバーが既に OpenCode Lite を購読しています。ワークスペースにつき1人のメンバーのみが購読できます。",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "月額$10で、十分な利用枠が設けられた最高のオープンモデル — Kimi K2.5、GLM-5、および MiniMax M2.5 — にアクセスできます。",
+ "workspace.lite.promo.subscribe": "Liteを購読する",
+ "workspace.lite.promo.subscribing": "リダイレクト中...",
+
"download.title": "OpenCode | ダウンロード",
"download.meta.description": "OpenCode を macOS、Windows、Linux 向けにダウンロード",
"download.hero.title": "OpenCode をダウンロード",
diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts
index 5a5f9bc71..8f4e58e7d 100644
--- a/packages/console/app/src/i18n/ko.ts
+++ b/packages/console/app/src/i18n/ko.ts
@@ -344,6 +344,7 @@ export const dict = {
"workspace.usage.breakdown.output": "출력",
"workspace.usage.breakdown.reasoning": "추론",
"workspace.usage.subscription": "구독 (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "비용",
@@ -353,6 +354,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(삭제됨)",
"workspace.cost.empty": "선택한 기간에 사용 데이터가 없습니다.",
"workspace.cost.subscriptionShort": "구독",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API 키",
"workspace.keys.subtitle": "OpenCode 서비스 액세스를 위한 API 키를 관리하세요.",
@@ -480,6 +482,31 @@ export const dict = {
"workspace.black.waitlist.enrolled": "등록됨",
"workspace.black.waitlist.enrollNote": "등록을 클릭하면 구독이 즉시 시작되며 카드에 요금이 청구됩니다.",
+ "workspace.lite.loading": "로드 중...",
+ "workspace.lite.time.day": "일",
+ "workspace.lite.time.days": "일",
+ "workspace.lite.time.hour": "시간",
+ "workspace.lite.time.hours": "시간",
+ "workspace.lite.time.minute": "분",
+ "workspace.lite.time.minutes": "분",
+ "workspace.lite.time.fewSeconds": "몇 초",
+ "workspace.lite.subscription.title": "Lite 구독",
+ "workspace.lite.subscription.message": "현재 OpenCode Lite를 구독 중입니다.",
+ "workspace.lite.subscription.manage": "구독 관리",
+ "workspace.lite.subscription.rollingUsage": "롤링 사용량",
+ "workspace.lite.subscription.weeklyUsage": "주간 사용량",
+ "workspace.lite.subscription.monthlyUsage": "월간 사용량",
+ "workspace.lite.subscription.resetsIn": "초기화까지 남은 시간:",
+ "workspace.lite.subscription.useBalance": "사용 한도 도달 후에는 보유 잔액 사용",
+ "workspace.lite.other.title": "Lite 구독",
+ "workspace.lite.other.message":
+ "이 워크스페이스의 다른 멤버가 이미 OpenCode Lite를 구독 중입니다. 워크스페이스당 한 명의 멤버만 구독할 수 있습니다.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "월 $10의 넉넉한 사용 한도로 최고의 오픈 모델인 Kimi K2.5, GLM-5, MiniMax M2.5에 액세스하세요.",
+ "workspace.lite.promo.subscribe": "Lite 구독하기",
+ "workspace.lite.promo.subscribing": "리디렉션 중...",
+
"download.title": "OpenCode | 다운로드",
"download.meta.description": "macOS, Windows, Linux용 OpenCode 다운로드",
"download.hero.title": "OpenCode 다운로드",
diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts
index abd9ba086..e5bfef989 100644
--- a/packages/console/app/src/i18n/no.ts
+++ b/packages/console/app/src/i18n/no.ts
@@ -348,6 +348,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Output",
"workspace.usage.breakdown.reasoning": "Resonnering",
"workspace.usage.subscription": "abonnement (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Kostnad",
@@ -357,6 +358,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(slettet)",
"workspace.cost.empty": "Ingen bruksdata tilgjengelig for den valgte perioden.",
"workspace.cost.subscriptionShort": "sub",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API-nøkler",
"workspace.keys.subtitle": "Administrer API-nøklene dine for å få tilgang til opencode-tjenester.",
@@ -485,6 +487,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Når du klikker på Meld på, starter abonnementet umiddelbart og kortet ditt belastes.",
+ "workspace.lite.loading": "Laster...",
+ "workspace.lite.time.day": "dag",
+ "workspace.lite.time.days": "dager",
+ "workspace.lite.time.hour": "time",
+ "workspace.lite.time.hours": "timer",
+ "workspace.lite.time.minute": "minutt",
+ "workspace.lite.time.minutes": "minutter",
+ "workspace.lite.time.fewSeconds": "noen få sekunder",
+ "workspace.lite.subscription.title": "Lite-abonnement",
+ "workspace.lite.subscription.message": "Du abonnerer på OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Administrer abonnement",
+ "workspace.lite.subscription.rollingUsage": "Løpende bruk",
+ "workspace.lite.subscription.weeklyUsage": "Ukentlig bruk",
+ "workspace.lite.subscription.monthlyUsage": "Månedlig bruk",
+ "workspace.lite.subscription.resetsIn": "Nullstilles om",
+ "workspace.lite.subscription.useBalance": "Bruk din tilgjengelige saldo etter å ha nådd bruksgrensene",
+ "workspace.lite.other.title": "Lite-abonnement",
+ "workspace.lite.other.message":
+ "Et annet medlem i dette arbeidsområdet abonnerer allerede på OpenCode Lite. Kun ett medlem per arbeidsområde kan abonnere.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Få tilgang til de beste åpne modellene — Kimi K2.5, GLM-5 og MiniMax M2.5 — med generøse bruksgrenser for $10 per måned.",
+ "workspace.lite.promo.subscribe": "Abonner på Lite",
+ "workspace.lite.promo.subscribing": "Omdirigerer...",
+
"download.title": "OpenCode | Last ned",
"download.meta.description": "Last ned OpenCode for macOS, Windows og Linux",
"download.hero.title": "Last ned OpenCode",
diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts
index eeb7ce2b0..c2f9b3712 100644
--- a/packages/console/app/src/i18n/pl.ts
+++ b/packages/console/app/src/i18n/pl.ts
@@ -349,6 +349,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Wyjście",
"workspace.usage.breakdown.reasoning": "Rozumowanie",
"workspace.usage.subscription": "subskrypcja (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Koszt",
@@ -358,6 +359,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(usunięte)",
"workspace.cost.empty": "Brak danych o użyciu dla wybranego okresu.",
"workspace.cost.subscriptionShort": "sub",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "Klucze API",
"workspace.keys.subtitle": "Zarządzaj kluczami API do usług opencode.",
@@ -486,6 +488,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Po kliknięciu Zapisz się, Twoja subskrypcja rozpocznie się natychmiast, a karta zostanie obciążona.",
+ "workspace.lite.loading": "Ładowanie...",
+ "workspace.lite.time.day": "dzień",
+ "workspace.lite.time.days": "dni",
+ "workspace.lite.time.hour": "godzina",
+ "workspace.lite.time.hours": "godzin(y)",
+ "workspace.lite.time.minute": "minuta",
+ "workspace.lite.time.minutes": "minut(y)",
+ "workspace.lite.time.fewSeconds": "kilka sekund",
+ "workspace.lite.subscription.title": "Subskrypcja Lite",
+ "workspace.lite.subscription.message": "Subskrybujesz OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Zarządzaj subskrypcją",
+ "workspace.lite.subscription.rollingUsage": "Użycie kroczące",
+ "workspace.lite.subscription.weeklyUsage": "Użycie tygodniowe",
+ "workspace.lite.subscription.monthlyUsage": "Użycie miesięczne",
+ "workspace.lite.subscription.resetsIn": "Resetuje się za",
+ "workspace.lite.subscription.useBalance": "Użyj dostępnego salda po osiągnięciu limitów użycia",
+ "workspace.lite.other.title": "Subskrypcja Lite",
+ "workspace.lite.other.message":
+ "Inny członek tego obszaru roboczego już subskrybuje OpenCode Lite. Tylko jeden członek na obszar roboczy może subskrybować.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Uzyskaj dostęp do najlepszych otwartych modeli — Kimi K2.5, GLM-5 i MiniMax M2.5 — z hojnymi limitami użycia za $10 miesięcznie.",
+ "workspace.lite.promo.subscribe": "Subskrybuj Lite",
+ "workspace.lite.promo.subscribing": "Przekierowywanie...",
+
"download.title": "OpenCode | Pobierz",
"download.meta.description": "Pobierz OpenCode na macOS, Windows i Linux",
"download.hero.title": "Pobierz OpenCode",
diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts
index 1f752fd59..3bedf80b5 100644
--- a/packages/console/app/src/i18n/ru.ts
+++ b/packages/console/app/src/i18n/ru.ts
@@ -354,6 +354,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Выход",
"workspace.usage.breakdown.reasoning": "Reasoning (рассуждения)",
"workspace.usage.subscription": "подписка (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Расходы",
@@ -363,6 +364,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(удалено)",
"workspace.cost.empty": "Нет данных об использовании за выбранный период.",
"workspace.cost.subscriptionShort": "подписка",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API Ключи",
"workspace.keys.subtitle": "Управляйте вашими API ключами для доступа к сервисам opencode.",
@@ -491,6 +493,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Когда вы нажмете Подключиться, ваша подписка начнется немедленно, и с карты будет списана оплата.",
+ "workspace.lite.loading": "Загрузка...",
+ "workspace.lite.time.day": "день",
+ "workspace.lite.time.days": "дней",
+ "workspace.lite.time.hour": "час",
+ "workspace.lite.time.hours": "часов",
+ "workspace.lite.time.minute": "минута",
+ "workspace.lite.time.minutes": "минут",
+ "workspace.lite.time.fewSeconds": "несколько секунд",
+ "workspace.lite.subscription.title": "Подписка Lite",
+ "workspace.lite.subscription.message": "Вы подписаны на OpenCode Lite.",
+ "workspace.lite.subscription.manage": "Управление подпиской",
+ "workspace.lite.subscription.rollingUsage": "Скользящее использование",
+ "workspace.lite.subscription.weeklyUsage": "Недельное использование",
+ "workspace.lite.subscription.monthlyUsage": "Ежемесячное использование",
+ "workspace.lite.subscription.resetsIn": "Сброс через",
+ "workspace.lite.subscription.useBalance": "Использовать доступный баланс после достижения лимитов",
+ "workspace.lite.other.title": "Подписка Lite",
+ "workspace.lite.other.message":
+ "Другой участник в этом рабочем пространстве уже подписан на OpenCode Lite. Только один участник в рабочем пространстве может оформить подписку.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Получите доступ к лучшим открытым моделям — Kimi K2.5, GLM-5 и MiniMax M2.5 — с щедрыми лимитами использования за $10 в месяц.",
+ "workspace.lite.promo.subscribe": "Подписаться на Lite",
+ "workspace.lite.promo.subscribing": "Перенаправление...",
+
"download.title": "OpenCode | Скачать",
"download.meta.description": "Скачать OpenCode для macOS, Windows и Linux",
"download.hero.title": "Скачать OpenCode",
diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts
index 8196bd9c1..3d36dcbb2 100644
--- a/packages/console/app/src/i18n/th.ts
+++ b/packages/console/app/src/i18n/th.ts
@@ -347,6 +347,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Output",
"workspace.usage.breakdown.reasoning": "Reasoning",
"workspace.usage.subscription": "สมัครสมาชิก (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "ค่าใช้จ่าย",
@@ -356,6 +357,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(ลบแล้ว)",
"workspace.cost.empty": "ไม่มีข้อมูลการใช้งานในช่วงเวลาที่เลือก",
"workspace.cost.subscriptionShort": "sub",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API Keys",
"workspace.keys.subtitle": "จัดการ API keys ของคุณสำหรับการเข้าถึงบริการ OpenCode",
@@ -484,6 +486,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"เมื่อคุณคลิกลงทะเบียน การสมัครสมาชิกของคุณจะเริ่มต้นทันทีและบัตรของคุณจะถูกเรียกเก็บเงิน",
+ "workspace.lite.loading": "กำลังโหลด...",
+ "workspace.lite.time.day": "วัน",
+ "workspace.lite.time.days": "วัน",
+ "workspace.lite.time.hour": "ชั่วโมง",
+ "workspace.lite.time.hours": "ชั่วโมง",
+ "workspace.lite.time.minute": "นาที",
+ "workspace.lite.time.minutes": "นาที",
+ "workspace.lite.time.fewSeconds": "ไม่กี่วินาที",
+ "workspace.lite.subscription.title": "การสมัครสมาชิก Lite",
+ "workspace.lite.subscription.message": "คุณได้สมัครสมาชิก OpenCode Lite แล้ว",
+ "workspace.lite.subscription.manage": "จัดการการสมัครสมาชิก",
+ "workspace.lite.subscription.rollingUsage": "การใช้งานแบบหมุนเวียน",
+ "workspace.lite.subscription.weeklyUsage": "การใช้งานรายสัปดาห์",
+ "workspace.lite.subscription.monthlyUsage": "การใช้งานรายเดือน",
+ "workspace.lite.subscription.resetsIn": "รีเซ็ตใน",
+ "workspace.lite.subscription.useBalance": "ใช้ยอดคงเหลือของคุณหลังจากถึงขีดจำกัดการใช้งาน",
+ "workspace.lite.other.title": "การสมัครสมาชิก Lite",
+ "workspace.lite.other.message":
+ "สมาชิกคนอื่นใน Workspace นี้ได้สมัคร OpenCode Lite แล้ว สามารถสมัครได้เพียงหนึ่งคนต่อหนึ่ง Workspace เท่านั้น",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "เข้าถึงโมเดลเปิดที่ดีที่สุด — Kimi K2.5, GLM-5 และ MiniMax M2.5 — พร้อมขีดจำกัดการใช้งานมากมายในราคา $10 ต่อเดือน",
+ "workspace.lite.promo.subscribe": "สมัครสมาชิก Lite",
+ "workspace.lite.promo.subscribing": "กำลังเปลี่ยนเส้นทาง...",
+
"download.title": "OpenCode | ดาวน์โหลด",
"download.meta.description": "ดาวน์โหลด OpenCode สำหรับ macOS, Windows และ Linux",
"download.hero.title": "ดาวน์โหลด OpenCode",
diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts
index 7ee8b6a75..bfa7d09ae 100644
--- a/packages/console/app/src/i18n/tr.ts
+++ b/packages/console/app/src/i18n/tr.ts
@@ -350,6 +350,7 @@ export const dict = {
"workspace.usage.breakdown.output": "Çıkış",
"workspace.usage.breakdown.reasoning": "Muhakeme",
"workspace.usage.subscription": "abonelik (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "Maliyet",
@@ -359,6 +360,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(silindi)",
"workspace.cost.empty": "Seçilen döneme ait kullanım verisi yok.",
"workspace.cost.subscriptionShort": "abonelik",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API Anahtarları",
"workspace.keys.subtitle": "opencode hizmetlerine erişim için API anahtarlarınızı yönetin.",
@@ -487,6 +489,31 @@ export const dict = {
"workspace.black.waitlist.enrollNote":
"Kayıt Ol'a tıkladığınızda aboneliğiniz hemen başlar ve kartınızdan çekim yapılır.",
+ "workspace.lite.loading": "Yükleniyor...",
+ "workspace.lite.time.day": "gün",
+ "workspace.lite.time.days": "gün",
+ "workspace.lite.time.hour": "saat",
+ "workspace.lite.time.hours": "saat",
+ "workspace.lite.time.minute": "dakika",
+ "workspace.lite.time.minutes": "dakika",
+ "workspace.lite.time.fewSeconds": "birkaç saniye",
+ "workspace.lite.subscription.title": "Lite Aboneliği",
+ "workspace.lite.subscription.message": "OpenCode Lite abonesisiniz.",
+ "workspace.lite.subscription.manage": "Aboneliği Yönet",
+ "workspace.lite.subscription.rollingUsage": "Devam Eden Kullanım",
+ "workspace.lite.subscription.weeklyUsage": "Haftalık Kullanım",
+ "workspace.lite.subscription.monthlyUsage": "Aylık Kullanım",
+ "workspace.lite.subscription.resetsIn": "Sıfırlama süresi",
+ "workspace.lite.subscription.useBalance": "Kullanım limitlerine ulaştıktan sonra mevcut bakiyenizi kullanın",
+ "workspace.lite.other.title": "Lite Aboneliği",
+ "workspace.lite.other.message":
+ "Bu çalışma alanındaki başka bir üye zaten OpenCode Lite abonesi. Çalışma alanı başına yalnızca bir üye abone olabilir.",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "Ayda $10 karşılığında cömert kullanım limitleriyle en iyi açık modellere — Kimi K2.5, GLM-5 ve MiniMax M2.5 — erişin.",
+ "workspace.lite.promo.subscribe": "Lite'a Abone Ol",
+ "workspace.lite.promo.subscribing": "Yönlendiriliyor...",
+
"download.title": "OpenCode | İndir",
"download.meta.description": "OpenCode'u macOS, Windows ve Linux için indirin",
"download.hero.title": "OpenCode'u İndir",
diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts
index e5fae6f3f..2c41be7cf 100644
--- a/packages/console/app/src/i18n/zh.ts
+++ b/packages/console/app/src/i18n/zh.ts
@@ -335,6 +335,7 @@ export const dict = {
"workspace.usage.breakdown.output": "输出",
"workspace.usage.breakdown.reasoning": "推理",
"workspace.usage.subscription": "订阅 (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "成本",
@@ -344,6 +345,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(已删除)",
"workspace.cost.empty": "所选期间无可用使用数据。",
"workspace.cost.subscriptionShort": "订阅",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API 密钥",
"workspace.keys.subtitle": "管理访问 OpenCode 服务的 API 密钥。",
@@ -471,6 +473,30 @@ export const dict = {
"workspace.black.waitlist.enrolled": "已加入",
"workspace.black.waitlist.enrollNote": "点击加入后,您的订阅将立即开始,并将从您的卡中扣费。",
+ "workspace.lite.loading": "加载中...",
+ "workspace.lite.time.day": "天",
+ "workspace.lite.time.days": "天",
+ "workspace.lite.time.hour": "小时",
+ "workspace.lite.time.hours": "小时",
+ "workspace.lite.time.minute": "分钟",
+ "workspace.lite.time.minutes": "分钟",
+ "workspace.lite.time.fewSeconds": "几秒钟",
+ "workspace.lite.subscription.title": "Lite 订阅",
+ "workspace.lite.subscription.message": "您已订阅 OpenCode Lite。",
+ "workspace.lite.subscription.manage": "管理订阅",
+ "workspace.lite.subscription.rollingUsage": "滚动用量",
+ "workspace.lite.subscription.weeklyUsage": "每周用量",
+ "workspace.lite.subscription.monthlyUsage": "每月用量",
+ "workspace.lite.subscription.resetsIn": "重置于",
+ "workspace.lite.subscription.useBalance": "达到使用限额后使用您的可用余额",
+ "workspace.lite.other.title": "Lite 订阅",
+ "workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Lite。每个工作区只有一名成员可以订阅。",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "每月仅需 $10 即可访问最优秀的开源模型 — Kimi K2.5, GLM-5, 和 MiniMax M2.5 — 并享受充裕的使用限额。",
+ "workspace.lite.promo.subscribe": "订阅 Lite",
+ "workspace.lite.promo.subscribing": "正在重定向...",
+
"download.title": "OpenCode | 下载",
"download.meta.description": "下载适用于 macOS, Windows, 和 Linux 的 OpenCode",
"download.hero.title": "下载 OpenCode",
diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts
index 79582b0ce..87fcaa8e8 100644
--- a/packages/console/app/src/i18n/zht.ts
+++ b/packages/console/app/src/i18n/zht.ts
@@ -335,6 +335,7 @@ export const dict = {
"workspace.usage.breakdown.output": "輸出",
"workspace.usage.breakdown.reasoning": "推理",
"workspace.usage.subscription": "訂閱 (${{amount}})",
+ "workspace.usage.lite": "lite (${{amount}})",
"workspace.usage.byok": "BYOK (${{amount}})",
"workspace.cost.title": "成本",
@@ -344,6 +345,7 @@ export const dict = {
"workspace.cost.deletedSuffix": "(已刪除)",
"workspace.cost.empty": "所選期間沒有可用的使用資料。",
"workspace.cost.subscriptionShort": "訂",
+ "workspace.cost.liteShort": "lite",
"workspace.keys.title": "API 金鑰",
"workspace.keys.subtitle": "管理你的 API 金鑰以存取 OpenCode 服務。",
@@ -471,6 +473,30 @@ export const dict = {
"workspace.black.waitlist.enrolled": "已加入",
"workspace.black.waitlist.enrollNote": "當你點選「加入」後,你的訂閱將立即開始,並且將從你的卡片中扣款。",
+ "workspace.lite.loading": "載入中...",
+ "workspace.lite.time.day": "天",
+ "workspace.lite.time.days": "天",
+ "workspace.lite.time.hour": "小時",
+ "workspace.lite.time.hours": "小時",
+ "workspace.lite.time.minute": "分鐘",
+ "workspace.lite.time.minutes": "分鐘",
+ "workspace.lite.time.fewSeconds": "幾秒",
+ "workspace.lite.subscription.title": "Lite 訂閱",
+ "workspace.lite.subscription.message": "您已訂閱 OpenCode Lite。",
+ "workspace.lite.subscription.manage": "管理訂閱",
+ "workspace.lite.subscription.rollingUsage": "滾動使用量",
+ "workspace.lite.subscription.weeklyUsage": "每週使用量",
+ "workspace.lite.subscription.monthlyUsage": "每月使用量",
+ "workspace.lite.subscription.resetsIn": "重置時間:",
+ "workspace.lite.subscription.useBalance": "達到使用限制後使用您的可用餘額",
+ "workspace.lite.other.title": "Lite 訂閱",
+ "workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Lite。每個工作區只能有一位成員訂閱。",
+ "workspace.lite.promo.title": "OpenCode Lite",
+ "workspace.lite.promo.description":
+ "每月只需 $10 即可使用最佳的開放模型 — Kimi K2.5、GLM-5 和 MiniMax M2.5 — 並享有慷慨的使用限制。",
+ "workspace.lite.promo.subscribe": "訂閱 Lite",
+ "workspace.lite.promo.subscribing": "重新導向中...",
+
"download.title": "OpenCode | 下載",
"download.meta.description": "下載適用於 macOS、Windows 與 Linux 的 OpenCode",
"download.hero.title": "下載 OpenCode",
diff --git a/packages/console/app/src/routes/stripe/webhook.ts b/packages/console/app/src/routes/stripe/webhook.ts
index 828eb4c71..47ca442ec 100644
--- a/packages/console/app/src/routes/stripe/webhook.ts
+++ b/packages/console/app/src/routes/stripe/webhook.ts
@@ -1,13 +1,13 @@
import { Billing } from "@opencode-ai/console-core/billing.js"
import type { APIEvent } from "@solidjs/start/server"
-import { and, Database, eq, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
-import { BillingTable, PaymentTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
+import { BillingTable, LiteTable, PaymentTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import { Identifier } from "@opencode-ai/console-core/identifier.js"
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { Resource } from "@opencode-ai/console-resource"
-import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
-import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
+import { LiteData } from "@opencode-ai/console-core/lite.js"
+import { BlackData } from "@opencode-ai/console-core/black.js"
export async function POST(input: APIEvent) {
const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -103,310 +103,93 @@ export async function POST(input: APIEvent) {
})
})
}
- if (body.type === "checkout.session.completed" && body.data.object.mode === "subscription") {
- const workspaceID = body.data.object.custom_fields.find((f) => f.key === "workspaceid")?.text?.value
- const amountInCents = body.data.object.amount_total as number
- const customerID = body.data.object.customer as string
- const customerEmail = body.data.object.customer_details?.email as string
- const invoiceID = body.data.object.invoice as string
- const subscriptionID = body.data.object.subscription as string
- const promoCode = body.data.object.discounts?.[0]?.promotion_code as string
+ if (body.type === "customer.subscription.created") {
+ const type = body.data.object.metadata?.type
+ if (type === "lite") {
+ const workspaceID = body.data.object.metadata?.workspaceID
+ const userID = body.data.object.metadata?.userID
+ const customerID = body.data.object.customer as string
+ const invoiceID = body.data.object.latest_invoice as string
+ const subscriptionID = body.data.object.id as string
- if (!workspaceID) throw new Error("Workspace ID not found")
- if (!customerID) throw new Error("Customer ID not found")
- if (!amountInCents) throw new Error("Amount not found")
- if (!invoiceID) throw new Error("Invoice ID not found")
- if (!subscriptionID) throw new Error("Subscription ID not found")
+ if (!workspaceID) throw new Error("Workspace ID not found")
+ if (!userID) throw new Error("User ID not found")
+ if (!customerID) throw new Error("Customer ID not found")
+ if (!invoiceID) throw new Error("Invoice ID not found")
+ if (!subscriptionID) throw new Error("Subscription ID not found")
- // get payment id from invoice
- const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
- expand: ["payments"],
- })
- const paymentID = invoice.payments?.data[0].payment.payment_intent as string
- if (!paymentID) throw new Error("Payment ID not found")
+ // get payment id from invoice
+ const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
+ expand: ["payments"],
+ })
+ const paymentID = invoice.payments?.data[0].payment.payment_intent as string
+ if (!paymentID) throw new Error("Payment ID not found")
- // get payment method for the payment intent
- const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
- expand: ["payment_method"],
- })
- const paymentMethod = paymentIntent.payment_method
- if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
+ // get payment method for the payment intent
+ const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
+ expand: ["payment_method"],
+ })
+ const paymentMethod = paymentIntent.payment_method
+ if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
- // get coupon id from promotion code
- const couponID = await (async () => {
- if (!promoCode) return
- const coupon = await Billing.stripe().promotionCodes.retrieve(promoCode)
- const couponID = coupon.coupon.id
- if (!couponID) throw new Error("Coupon not found for promotion code")
- return couponID
- })()
+ await Actor.provide("system", { workspaceID }, async () => {
+ // look up current billing
+ const billing = await Billing.get()
+ if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
+ if (billing.customerID && billing.customerID !== customerID) throw new Error("Customer ID mismatch")
- await Actor.provide("system", { workspaceID }, async () => {
- // look up current billing
- const billing = await Billing.get()
- if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
-
- // Temporarily skip this check because during Black drop, user can checkout
- // as a new customer
- //if (billing.customerID !== customerID) throw new Error("Customer ID mismatch")
-
- // Temporarily check the user to apply to. After Black drop, we will allow
- // look up the user to apply to
- const users = await Database.use((tx) =>
- tx
- .select({ id: UserTable.id, email: AuthTable.subject })
- .from(UserTable)
- .innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
- .where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
- )
- const user = users.find((u) => u.email === customerEmail) ?? users[0]
- if (!user) {
- console.error(`Error: User with email ${customerEmail} not found in workspace ${workspaceID}`)
- process.exit(1)
- }
-
- // set customer metadata
- if (!billing?.customerID) {
- await Billing.stripe().customers.update(customerID, {
- metadata: {
- workspaceID,
- },
- })
- }
-
- await Database.transaction(async (tx) => {
- await tx
- .update(BillingTable)
- .set({
- customerID,
- subscriptionID,
- subscription: {
- status: "subscribed",
- coupon: couponID,
- seats: 1,
- plan: "200",
+ // set customer metadata
+ if (!billing?.customerID) {
+ await Billing.stripe().customers.update(customerID, {
+ metadata: {
+ workspaceID,
},
- paymentMethodID: paymentMethod.id,
- paymentMethodLast4: paymentMethod.card?.last4 ?? null,
- paymentMethodType: paymentMethod.type,
})
- .where(eq(BillingTable.workspaceID, workspaceID))
+ }
- await tx.insert(SubscriptionTable).values({
- workspaceID,
- id: Identifier.create("subscription"),
- userID: user.id,
- })
+ await Database.transaction(async (tx) => {
+ await tx
+ .update(BillingTable)
+ .set({
+ customerID,
+ liteSubscriptionID: subscriptionID,
+ lite: {},
+ paymentMethodID: paymentMethod.id,
+ paymentMethodLast4: paymentMethod.card?.last4 ?? null,
+ paymentMethodType: paymentMethod.type,
+ })
+ .where(eq(BillingTable.workspaceID, workspaceID))
- await tx.insert(PaymentTable).values({
- workspaceID,
- id: Identifier.create("payment"),
- amount: centsToMicroCents(amountInCents),
- paymentID,
- invoiceID,
- customerID,
- enrichment: {
- type: "subscription",
- couponID,
- },
+ await tx.insert(LiteTable).values({
+ workspaceID,
+ id: Identifier.create("lite"),
+ userID: userID,
+ })
})
})
- })
- }
- if (body.type === "customer.subscription.created") {
- /*
-{
- id: "evt_1Smq802SrMQ2Fneksse5FMNV",
- object: "event",
- api_version: "2025-07-30.basil",
- created: 1767766916,
- data: {
- object: {
- id: "sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
- object: "subscription",
- application: null,
- application_fee_percent: null,
- automatic_tax: {
- disabled_reason: null,
- enabled: false,
- liability: null,
- },
- billing_cycle_anchor: 1770445200,
- billing_cycle_anchor_config: null,
- billing_mode: {
- flexible: {
- proration_discounts: "included",
- },
- type: "flexible",
- updated_at: 1770445200,
- },
- billing_thresholds: null,
- cancel_at: null,
- cancel_at_period_end: false,
- canceled_at: null,
- cancellation_details: {
- comment: null,
- feedback: null,
- reason: null,
- },
- collection_method: "charge_automatically",
- created: 1770445200,
- currency: "usd",
- customer: "cus_TkKmZZvysJ2wej",
- customer_account: null,
- days_until_due: null,
- default_payment_method: null,
- default_source: "card_1Smq7u2SrMQ2FneknjyOa7sq",
- default_tax_rates: [],
- description: null,
- discounts: [],
- ended_at: null,
- invoice_settings: {
- account_tax_ids: null,
- issuer: {
- type: "self",
- },
- },
- items: {
- object: "list",
- data: [
- {
- id: "si_TkKnBKXFX76t0O",
- object: "subscription_item",
- billing_thresholds: null,
- created: 1770445200,
- current_period_end: 1772864400,
- current_period_start: 1770445200,
- discounts: [],
- metadata: {},
- plan: {
- id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
- object: "plan",
- active: true,
- amount: 20000,
- amount_decimal: "20000",
- billing_scheme: "per_unit",
- created: 1767725082,
- currency: "usd",
- interval: "month",
- interval_count: 1,
- livemode: false,
- metadata: {},
- meter: null,
- nickname: null,
- product: "prod_Tk9LjWT1n0DgYm",
- tiers_mode: null,
- transform_usage: null,
- trial_period_days: null,
- usage_type: "licensed",
- },
- price: {
- id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
- object: "price",
- active: true,
- billing_scheme: "per_unit",
- created: 1767725082,
- currency: "usd",
- custom_unit_amount: null,
- livemode: false,
- lookup_key: null,
- metadata: {},
- nickname: null,
- product: "prod_Tk9LjWT1n0DgYm",
- recurring: {
- interval: "month",
- interval_count: 1,
- meter: null,
- trial_period_days: null,
- usage_type: "licensed",
- },
- tax_behavior: "unspecified",
- tiers_mode: null,
- transform_quantity: null,
- type: "recurring",
- unit_amount: 20000,
- unit_amount_decimal: "20000",
- },
- quantity: 1,
- subscription: "sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
- tax_rates: [],
- },
- ],
- has_more: false,
- total_count: 1,
- url: "/v1/subscription_items?subscription=sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
- },
- latest_invoice: "in_1Smq7x2SrMQ2FnekSJesfPwE",
- livemode: false,
- metadata: {},
- next_pending_invoice_item_invoice: null,
- on_behalf_of: null,
- pause_collection: null,
- payment_settings: {
- payment_method_options: null,
- payment_method_types: null,
- save_default_payment_method: "off",
- },
- pending_invoice_item_interval: null,
- pending_setup_intent: null,
- pending_update: null,
- plan: {
- id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
- object: "plan",
- active: true,
- amount: 20000,
- amount_decimal: "20000",
- billing_scheme: "per_unit",
- created: 1767725082,
- currency: "usd",
- interval: "month",
- interval_count: 1,
- livemode: false,
- metadata: {},
- meter: null,
- nickname: null,
- product: "prod_Tk9LjWT1n0DgYm",
- tiers_mode: null,
- transform_usage: null,
- trial_period_days: null,
- usage_type: "licensed",
- },
- quantity: 1,
- schedule: null,
- start_date: 1770445200,
- status: "active",
- test_clock: "clock_1Smq6n2SrMQ2FnekQw4yt2PZ",
- transfer_data: null,
- trial_end: null,
- trial_settings: {
- end_behavior: {
- missing_payment_method: "create_invoice",
- },
- },
- trial_start: null,
- },
- },
- livemode: false,
- pending_webhooks: 0,
- request: {
- id: "req_6YO9stvB155WJD",
- idempotency_key: "581ba059-6f86-49b2-9c49-0d8450255322",
- },
- type: "customer.subscription.created",
-}
- */
+ }
}
if (body.type === "customer.subscription.updated" && body.data.object.status === "incomplete_expired") {
const subscriptionID = body.data.object.id
if (!subscriptionID) throw new Error("Subscription ID not found")
- await Billing.unsubscribe({ subscriptionID })
+ const productID = body.data.object.items.data[0].price.product as string
+ if (productID === LiteData.productID()) {
+ await Billing.unsubscribeLite({ subscriptionID })
+ } else if (productID === BlackData.productID()) {
+ await Billing.unsubscribeBlack({ subscriptionID })
+ }
}
if (body.type === "customer.subscription.deleted") {
const subscriptionID = body.data.object.id
if (!subscriptionID) throw new Error("Subscription ID not found")
- await Billing.unsubscribe({ subscriptionID })
+ const productID = body.data.object.items.data[0].price.product as string
+ if (productID === LiteData.productID()) {
+ await Billing.unsubscribeLite({ subscriptionID })
+ } else if (productID === BlackData.productID()) {
+ await Billing.unsubscribeBlack({ subscriptionID })
+ }
}
if (body.type === "invoice.payment_succeeded") {
if (
@@ -430,6 +213,7 @@ export async function POST(input: APIEvent) {
typeof subscriptionData.discounts[0] === "string"
? subscriptionData.discounts[0]
: subscriptionData.discounts[0]?.coupon?.id
+ const productID = subscriptionData.items.data[0].price.product as string
// get payment id from invoice
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
@@ -459,7 +243,7 @@ export async function POST(input: APIEvent) {
invoiceID,
customerID,
enrichment: {
- type: "subscription",
+ type: productID === LiteData.productID() ? "lite" : "subscription",
couponID,
},
}),
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
index 5326306e2..b8f089864 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
@@ -90,7 +90,7 @@ const enroll = action(async (workspaceID: string) => {
"use server"
return json(
await withActor(async () => {
- await Billing.subscribe({ seats: 1 })
+ await Billing.subscribeBlack({ seats: 1 })
return { error: undefined }
}, workspaceID).catch((e) => ({ error: e.message as string })),
{ revalidate: [queryBillingInfo.key, querySubscription.key] },
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
index a252a0234..9fbdad2ef 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
@@ -3,7 +3,8 @@ import { BillingSection } from "./billing-section"
import { ReloadSection } from "./reload-section"
import { PaymentSection } from "./payment-section"
import { BlackSection } from "./black-section"
-import { Show } from "solid-js"
+import { LiteSection } from "./lite-section"
+import { createMemo, Show } from "solid-js"
import { createAsync, useParams } from "@solidjs/router"
import { queryBillingInfo, querySessionInfo } from "../../common"
@@ -11,14 +12,18 @@ export default function () {
const params = useParams()
const sessionInfo = createAsync(() => querySessionInfo(params.id!))
const billingInfo = createAsync(() => queryBillingInfo(params.id!))
+ const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked)
return (
-
+
+
+
+
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css
new file mode 100644
index 000000000..20662ab61
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css
@@ -0,0 +1,160 @@
+.root {
+ [data-slot="title-row"] {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: var(--space-4);
+ }
+
+ [data-slot="usage"] {
+ display: flex;
+ gap: var(--space-6);
+ margin-top: var(--space-4);
+
+ @media (max-width: 40rem) {
+ flex-direction: column;
+ gap: var(--space-4);
+ }
+ }
+
+ [data-slot="usage-item"] {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+ }
+
+ [data-slot="usage-header"] {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ }
+
+ [data-slot="usage-label"] {
+ font-size: var(--font-size-md);
+ font-weight: 500;
+ color: var(--color-text);
+ }
+
+ [data-slot="usage-value"] {
+ font-size: var(--font-size-sm);
+ color: var(--color-text-muted);
+ }
+
+ [data-slot="progress"] {
+ height: 8px;
+ background-color: var(--color-bg-surface);
+ border-radius: var(--border-radius-sm);
+ overflow: hidden;
+ }
+
+ [data-slot="progress-bar"] {
+ height: 100%;
+ background-color: var(--color-accent);
+ border-radius: var(--border-radius-sm);
+ transition: width 0.3s ease;
+ }
+
+ [data-slot="reset-time"] {
+ font-size: var(--font-size-sm);
+ color: var(--color-text-muted);
+ }
+
+ [data-slot="setting-row"] {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-3);
+ margin-top: var(--space-4);
+
+ p {
+ font-size: var(--font-size-sm);
+ line-height: 1.5;
+ color: var(--color-text-secondary);
+ margin: 0;
+ }
+ }
+
+ [data-slot="toggle-label"] {
+ position: relative;
+ display: inline-block;
+ width: 2.5rem;
+ height: 1.5rem;
+ cursor: pointer;
+ flex-shrink: 0;
+
+ input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+ }
+
+ span {
+ position: absolute;
+ inset: 0;
+ background-color: #ccc;
+ border: 1px solid #bbb;
+ border-radius: 1.5rem;
+ transition: all 0.3s ease;
+ cursor: pointer;
+
+ &::before {
+ content: "";
+ position: absolute;
+ top: 50%;
+ left: 0.125rem;
+ width: 1.25rem;
+ height: 1.25rem;
+ background-color: white;
+ border: 1px solid #ddd;
+ border-radius: 50%;
+ transform: translateY(-50%);
+ transition: all 0.3s ease;
+ }
+ }
+
+ input:checked + span {
+ background-color: #21ad0e;
+ border-color: #148605;
+
+ &::before {
+ transform: translateX(1rem) translateY(-50%);
+ }
+ }
+
+ &:hover span {
+ box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
+ }
+
+ input:checked:hover + span {
+ box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
+ }
+
+ &:has(input:disabled) {
+ cursor: not-allowed;
+ }
+
+ input:disabled + span {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+ }
+
+ [data-slot="other-message"] {
+ font-size: var(--font-size-sm);
+ color: var(--color-text-muted);
+ line-height: 1.5;
+ }
+
+ [data-slot="promo-description"] {
+ font-size: var(--font-size-sm);
+ color: var(--color-text-secondary);
+ line-height: 1.5;
+ margin-top: var(--space-2);
+ }
+
+ [data-slot="subscribe-button"] {
+ align-self: flex-start;
+ margin-top: var(--space-4);
+ }
+}
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx
new file mode 100644
index 000000000..c9192fdcf
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx
@@ -0,0 +1,269 @@
+import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router"
+import { createStore } from "solid-js/store"
+import { Show } from "solid-js"
+import { Billing } from "@opencode-ai/console-core/billing.js"
+import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
+import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { Actor } from "@opencode-ai/console-core/actor.js"
+import { Subscription } from "@opencode-ai/console-core/subscription.js"
+import { LiteData } from "@opencode-ai/console-core/lite.js"
+import { withActor } from "~/context/auth.withActor"
+import { queryBillingInfo } from "../../common"
+import styles from "./lite-section.module.css"
+import { useI18n } from "~/context/i18n"
+
+const queryLiteSubscription = query(async (workspaceID: string) => {
+ "use server"
+ return withActor(async () => {
+ const row = await Database.use((tx) =>
+ tx
+ .select({
+ userID: LiteTable.userID,
+ rollingUsage: LiteTable.rollingUsage,
+ weeklyUsage: LiteTable.weeklyUsage,
+ monthlyUsage: LiteTable.monthlyUsage,
+ timeRollingUpdated: LiteTable.timeRollingUpdated,
+ timeWeeklyUpdated: LiteTable.timeWeeklyUpdated,
+ timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
+ timeCreated: LiteTable.timeCreated,
+ lite: BillingTable.lite,
+ })
+ .from(BillingTable)
+ .innerJoin(LiteTable, eq(LiteTable.workspaceID, BillingTable.workspaceID))
+ .where(and(eq(LiteTable.workspaceID, Actor.workspace()), isNull(LiteTable.timeDeleted)))
+ .then((r) => r[0]),
+ )
+ if (!row) return null
+
+ const limits = LiteData.getLimits()
+ const mine = row.userID === Actor.userID()
+
+ return {
+ mine,
+ useBalance: row.lite?.useBalance ?? false,
+ rollingUsage: Subscription.analyzeRollingUsage({
+ limit: limits.rollingLimit,
+ window: limits.rollingWindow,
+ usage: row.rollingUsage ?? 0,
+ timeUpdated: row.timeRollingUpdated ?? new Date(),
+ }),
+ weeklyUsage: Subscription.analyzeWeeklyUsage({
+ limit: limits.weeklyLimit,
+ usage: row.weeklyUsage ?? 0,
+ timeUpdated: row.timeWeeklyUpdated ?? new Date(),
+ }),
+ monthlyUsage: Subscription.analyzeMonthlyUsage({
+ limit: limits.monthlyLimit,
+ usage: row.monthlyUsage ?? 0,
+ timeUpdated: row.timeMonthlyUpdated ?? new Date(),
+ timeSubscribed: row.timeCreated,
+ }),
+ }
+ }, workspaceID)
+}, "lite.subscription.get")
+
+function formatResetTime(seconds: number, i18n: ReturnType) {
+ const days = Math.floor(seconds / 86400)
+ if (days >= 1) {
+ const hours = Math.floor((seconds % 86400) / 3600)
+ return `${days} ${days === 1 ? i18n.t("workspace.lite.time.day") : i18n.t("workspace.lite.time.days")} ${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")}`
+ }
+ const hours = Math.floor(seconds / 3600)
+ const minutes = Math.floor((seconds % 3600) / 60)
+ if (hours >= 1)
+ return `${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")} ${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}`
+ if (minutes === 0) return i18n.t("workspace.lite.time.fewSeconds")
+ return `${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}`
+}
+
+const createLiteCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
+ "use server"
+ return json(
+ await withActor(
+ () =>
+ Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl })
+ .then((data) => ({ error: undefined, data }))
+ .catch((e) => ({
+ error: e.message as string,
+ data: undefined,
+ })),
+ workspaceID,
+ ),
+ { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
+ )
+}, "liteCheckoutUrl")
+
+const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
+ "use server"
+ return json(
+ await withActor(
+ () =>
+ Billing.generateSessionUrl({ returnUrl })
+ .then((data) => ({ error: undefined, data }))
+ .catch((e) => ({
+ error: e.message as string,
+ data: undefined,
+ })),
+ workspaceID,
+ ),
+ { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
+ )
+}, "liteSessionUrl")
+
+const setLiteUseBalance = action(async (form: FormData) => {
+ "use server"
+ const workspaceID = form.get("workspaceID")?.toString()
+ if (!workspaceID) return { error: "Workspace ID is required" }
+ const useBalance = form.get("useBalance")?.toString() === "true"
+
+ return json(
+ await withActor(async () => {
+ await Database.use((tx) =>
+ tx
+ .update(BillingTable)
+ .set({
+ lite: useBalance ? { useBalance: true } : {},
+ })
+ .where(eq(BillingTable.workspaceID, workspaceID)),
+ )
+ return { error: undefined }
+ }, workspaceID).catch((e) => ({ error: e.message as string })),
+ { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
+ )
+}, "setLiteUseBalance")
+
+export function LiteSection() {
+ const params = useParams()
+ const i18n = useI18n()
+ const lite = createAsync(() => queryLiteSubscription(params.id!))
+ const sessionAction = useAction(createSessionUrl)
+ const sessionSubmission = useSubmission(createSessionUrl)
+ const checkoutAction = useAction(createLiteCheckoutUrl)
+ const checkoutSubmission = useSubmission(createLiteCheckoutUrl)
+ const useBalanceSubmission = useSubmission(setLiteUseBalance)
+ const [store, setStore] = createStore({
+ redirecting: false,
+ })
+
+ async function onClickSession() {
+ const result = await sessionAction(params.id!, window.location.href)
+ if (result.data) {
+ setStore("redirecting", true)
+ window.location.href = result.data
+ }
+ }
+
+ async function onClickSubscribe() {
+ const result = await checkoutAction(params.id!, window.location.href, window.location.href)
+ if (result.data) {
+ setStore("redirecting", true)
+ window.location.href = result.data
+ }
+ }
+
+ return (
+ <>
+
+ {(sub) => (
+
+
+
{i18n.t("workspace.lite.subscription.title")}
+
+
{i18n.t("workspace.lite.subscription.message")}
+
+
+
+
+
+
+ {i18n.t("workspace.lite.subscription.rollingUsage")}
+ {sub().rollingUsage.usagePercent}%
+
+
+
+ {i18n.t("workspace.lite.subscription.resetsIn")}{" "}
+ {formatResetTime(sub().rollingUsage.resetInSec, i18n)}
+
+
+
+
+ {i18n.t("workspace.lite.subscription.weeklyUsage")}
+ {sub().weeklyUsage.usagePercent}%
+
+
+
+ {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(sub().weeklyUsage.resetInSec, i18n)}
+
+
+
+
+ {i18n.t("workspace.lite.subscription.monthlyUsage")}
+ {sub().monthlyUsage.usagePercent}%
+
+
+
+ {i18n.t("workspace.lite.subscription.resetsIn")}{" "}
+ {formatResetTime(sub().monthlyUsage.resetInSec, i18n)}
+
+
+
+
+
+ )}
+
+
+
+
+
{i18n.t("workspace.lite.other.title")}
+
+ {i18n.t("workspace.lite.other.message")}
+
+
+
+
+
+
{i18n.t("workspace.lite.promo.title")}
+
+ {i18n.t("workspace.lite.promo.description")}
+
+
+
+ >
+ )
+}
diff --git a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx b/packages/console/app/src/routes/workspace/[id]/graph-section.tsx
index f26c7291d..56a31cdd0 100644
--- a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/graph-section.tsx
@@ -36,7 +36,7 @@ async function getCosts(workspaceID: string, year: number, month: number) {
model: UsageTable.model,
totalCost: sum(UsageTable.cost),
keyId: UsageTable.keyID,
- subscription: sql`COALESCE(JSON_EXTRACT(${UsageTable.enrichment}, '$.plan') = 'sub', false)`,
+ plan: sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`,
})
.from(UsageTable)
.where(
@@ -50,13 +50,13 @@ async function getCosts(workspaceID: string, year: number, month: number) {
sql`DATE(${UsageTable.timeCreated})`,
UsageTable.model,
UsageTable.keyID,
- sql`COALESCE(JSON_EXTRACT(${UsageTable.enrichment}, '$.plan') = 'sub', false)`,
+ sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`,
)
.then((x) =>
x.map((r) => ({
...r,
totalCost: r.totalCost ? parseInt(r.totalCost) : 0,
- subscription: Boolean(r.subscription),
+ plan: r.plan as "sub" | "lite" | "byok" | null,
})),
),
)
@@ -218,18 +218,21 @@ export function GraphSection() {
const colorTextSecondary = styles.getPropertyValue("--color-text-secondary").trim()
const colorBorder = styles.getPropertyValue("--color-border").trim()
const subSuffix = ` (${i18n.t("workspace.cost.subscriptionShort")})`
+ const liteSuffix = ` (${i18n.t("workspace.cost.liteShort")})`
+ const dailyDataRegular = new Map>()
const dailyDataSub = new Map>()
- const dailyDataNonSub = new Map>()
+ const dailyDataLite = new Map>()
for (const dateKey of dates) {
+ dailyDataRegular.set(dateKey, new Map())
dailyDataSub.set(dateKey, new Map())
- dailyDataNonSub.set(dateKey, new Map())
+ dailyDataLite.set(dateKey, new Map())
}
data.usage
.filter((row) => (store.key ? row.keyId === store.key : true))
.forEach((row) => {
- const targetMap = row.subscription ? dailyDataSub : dailyDataNonSub
+ const targetMap = row.plan === "sub" ? dailyDataSub : row.plan === "lite" ? dailyDataLite : dailyDataRegular
const dayMap = targetMap.get(row.date)
if (!dayMap) return
dayMap.set(row.model, (dayMap.get(row.model) ?? 0) + row.totalCost)
@@ -237,15 +240,15 @@ export function GraphSection() {
const filteredModels = store.model === null ? getModels() : [store.model]
- // Create datasets: non-subscription first, then subscription (with hatched pattern effect via opacity)
+ // Create datasets: regular first, then subscription, then lite (with visual distinction via opacity)
const datasets = [
...filteredModels
- .filter((model) => dates.some((date) => (dailyDataNonSub.get(date)?.get(model) || 0) > 0))
+ .filter((model) => dates.some((date) => (dailyDataRegular.get(date)?.get(model) || 0) > 0))
.map((model) => {
const color = getModelColor(model)
return {
label: model,
- data: dates.map((date) => (dailyDataNonSub.get(date)?.get(model) || 0) / 100_000_000),
+ data: dates.map((date) => (dailyDataRegular.get(date)?.get(model) || 0) / 100_000_000),
backgroundColor: color,
hoverBackgroundColor: color,
borderWidth: 0,
@@ -266,6 +269,21 @@ export function GraphSection() {
stack: "subscription",
}
}),
+ ...filteredModels
+ .filter((model) => dates.some((date) => (dailyDataLite.get(date)?.get(model) || 0) > 0))
+ .map((model) => {
+ const color = getModelColor(model)
+ return {
+ label: `${model}${liteSuffix}`,
+ data: dates.map((date) => (dailyDataLite.get(date)?.get(model) || 0) / 100_000_000),
+ backgroundColor: addOpacityToColor(color, 0.35),
+ hoverBackgroundColor: addOpacityToColor(color, 0.55),
+ borderWidth: 1,
+ borderColor: addOpacityToColor(color, 0.7),
+ borderDash: [4, 2],
+ stack: "lite",
+ }
+ }),
]
return {
@@ -347,9 +365,18 @@ export function GraphSection() {
const meta = chart.getDatasetMeta(i)
const label = dataset.label || ""
const isSub = label.endsWith(subSuffix)
- const model = isSub ? label.slice(0, -subSuffix.length) : label
+ const isLite = label.endsWith(liteSuffix)
+ const model = isSub
+ ? label.slice(0, -subSuffix.length)
+ : isLite
+ ? label.slice(0, -liteSuffix.length)
+ : label
const baseColor = getModelColor(model)
- const originalColor = isSub ? addOpacityToColor(baseColor, 0.5) : baseColor
+ const originalColor = isSub
+ ? addOpacityToColor(baseColor, 0.5)
+ : isLite
+ ? addOpacityToColor(baseColor, 0.35)
+ : baseColor
const color = i === legendItem.datasetIndex ? originalColor : addOpacityToColor(baseColor, 0.15)
meta.data.forEach((bar: any) => {
bar.options.backgroundColor = color
@@ -363,9 +390,18 @@ export function GraphSection() {
const meta = chart.getDatasetMeta(i)
const label = dataset.label || ""
const isSub = label.endsWith(subSuffix)
- const model = isSub ? label.slice(0, -subSuffix.length) : label
+ const isLite = label.endsWith(liteSuffix)
+ const model = isSub
+ ? label.slice(0, -subSuffix.length)
+ : isLite
+ ? label.slice(0, -liteSuffix.length)
+ : label
const baseColor = getModelColor(model)
- const color = isSub ? addOpacityToColor(baseColor, 0.5) : baseColor
+ const color = isSub
+ ? addOpacityToColor(baseColor, 0.5)
+ : isLite
+ ? addOpacityToColor(baseColor, 0.35)
+ : baseColor
meta.data.forEach((bar: any) => {
bar.options.backgroundColor = color
})
diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx b/packages/console/app/src/routes/workspace/[id]/usage-section.tsx
index 3d6637610..a20a5bf0d 100644
--- a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/usage-section.tsx
@@ -1,6 +1,6 @@
import { Billing } from "@opencode-ai/console-core/billing.js"
import { createAsync, query, useParams } from "@solidjs/router"
-import { createMemo, For, Show, createEffect, createSignal } from "solid-js"
+import { createMemo, For, Show, Switch, Match, createEffect, createSignal } from "solid-js"
import { formatDateUTC, formatDateForTable } from "../common"
import { withActor } from "~/context/auth.withActor"
import { IconChevronLeft, IconChevronRight, IconBreakdown } from "~/component/icon"
@@ -175,23 +175,23 @@ export function UsageSection() {
- ${((usage.cost ?? 0) / 100000000).toFixed(4)}>}
- >
- {i18n.t("workspace.usage.byok", {
- amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
- })}
-
- }
- >
- {i18n.t("workspace.usage.subscription", {
- amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
- })}
-
+ ${((usage.cost ?? 0) / 100000000).toFixed(4)}>}>
+
+ {i18n.t("workspace.usage.subscription", {
+ amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
+ })}
+
+
+ {i18n.t("workspace.usage.lite", {
+ amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
+ })}
+
+
+ {i18n.t("workspace.usage.byok", {
+ amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
+ })}
+
+
|
{usage.sessionID?.slice(-8) ?? "-"} |
diff --git a/packages/console/app/src/routes/workspace/common.tsx b/packages/console/app/src/routes/workspace/common.tsx
index 5cbd67183..d41793dd9 100644
--- a/packages/console/app/src/routes/workspace/common.tsx
+++ b/packages/console/app/src/routes/workspace/common.tsx
@@ -115,6 +115,8 @@ export const queryBillingInfo = query(async (workspaceID: string) => {
subscriptionPlan: billing.subscriptionPlan,
timeSubscriptionBooked: billing.timeSubscriptionBooked,
timeSubscriptionSelected: billing.timeSubscriptionSelected,
+ lite: billing.lite,
+ liteSubscriptionID: billing.liteSubscriptionID,
}
}, workspaceID)
}, "billing.get")
diff --git a/packages/console/app/src/routes/zen/lite/v1/messages.ts b/packages/console/app/src/routes/zen/lite/v1/messages.ts
new file mode 100644
index 000000000..ee401e6aa
--- /dev/null
+++ b/packages/console/app/src/routes/zen/lite/v1/messages.ts
@@ -0,0 +1,12 @@
+import type { APIEvent } from "@solidjs/start/server"
+import { handler } from "~/routes/zen/util/handler"
+
+export function POST(input: APIEvent) {
+ return handler(input, {
+ format: "anthropic",
+ modelList: "lite",
+ parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
+ parseModel: (url: string, body: any) => body.model,
+ parseIsStream: (url: string, body: any) => !!body.stream,
+ })
+}
diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts
index 6096d7378..80a4b3ab7 100644
--- a/packages/console/app/src/routes/zen/util/handler.ts
+++ b/packages/console/app/src/routes/zen/util/handler.ts
@@ -1,9 +1,9 @@
import type { APIEvent } from "@solidjs/start/server"
import { and, Database, eq, isNull, lt, or, sql } from "@opencode-ai/console-core/drizzle/index.js"
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
-import { BillingTable, SubscriptionTable, UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { BillingTable, LiteTable, SubscriptionTable, UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
-import { getWeekBounds } from "@opencode-ai/console-core/util/date.js"
+import { getMonthlyBounds, getWeekBounds } from "@opencode-ai/console-core/util/date.js"
import { Identifier } from "@opencode-ai/console-core/identifier.js"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { Actor } from "@opencode-ai/console-core/actor.js"
@@ -33,13 +33,14 @@ import { createRateLimiter } from "./rateLimiter"
import { createDataDumper } from "./dataDumper"
import { createTrialLimiter } from "./trialLimiter"
import { createStickyTracker } from "./stickyProviderTracker"
+import { LiteData } from "@opencode-ai/console-core/lite.js"
type ZenData = Awaited
>
type RetryOptions = {
excludeProviders: string[]
retryCount: number
}
-type BillingSource = "anonymous" | "free" | "byok" | "subscription" | "balance"
+type BillingSource = "anonymous" | "free" | "byok" | "subscription" | "lite" | "balance"
export async function handler(
input: APIEvent,
@@ -454,6 +455,7 @@ export async function handler(
reloadTrigger: BillingTable.reloadTrigger,
timeReloadLockedTill: BillingTable.timeReloadLockedTill,
subscription: BillingTable.subscription,
+ lite: BillingTable.lite,
},
user: {
id: UserTable.id,
@@ -461,13 +463,23 @@ export async function handler(
monthlyUsage: UserTable.monthlyUsage,
timeMonthlyUsageUpdated: UserTable.timeMonthlyUsageUpdated,
},
- subscription: {
+ black: {
id: SubscriptionTable.id,
rollingUsage: SubscriptionTable.rollingUsage,
fixedUsage: SubscriptionTable.fixedUsage,
timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
},
+ lite: {
+ id: LiteTable.id,
+ timeCreated: LiteTable.timeCreated,
+ rollingUsage: LiteTable.rollingUsage,
+ weeklyUsage: LiteTable.weeklyUsage,
+ monthlyUsage: LiteTable.monthlyUsage,
+ timeRollingUpdated: LiteTable.timeRollingUpdated,
+ timeWeeklyUpdated: LiteTable.timeWeeklyUpdated,
+ timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
+ },
provider: {
credentials: ProviderTable.credentials,
},
@@ -495,6 +507,14 @@ export async function handler(
isNull(SubscriptionTable.timeDeleted),
),
)
+ .leftJoin(
+ LiteTable,
+ and(
+ eq(LiteTable.workspaceID, KeyTable.workspaceID),
+ eq(LiteTable.userID, KeyTable.userID),
+ isNull(LiteTable.timeDeleted),
+ ),
+ )
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
.then((rows) => rows[0]),
)
@@ -503,8 +523,19 @@ export async function handler(
logger.metric({
api_key: data.apiKey,
workspace: data.workspaceID,
- isSubscription: data.subscription ? true : false,
- subscription: data.billing.subscription?.plan,
+ ...(() => {
+ if (data.billing.subscription)
+ return {
+ isSubscription: true,
+ subscription: data.billing.subscription.plan,
+ }
+ if (data.billing.lite)
+ return {
+ isSubscription: true,
+ subscription: "lite",
+ }
+ return {}
+ })(),
})
return {
@@ -512,7 +543,8 @@ export async function handler(
workspaceID: data.workspaceID,
billing: data.billing,
user: data.user,
- subscription: data.subscription,
+ black: data.black,
+ lite: data.lite,
provider: data.provider,
isFree: FREE_WORKSPACES.includes(data.workspaceID),
isDisabled: !!data.timeDisabled,
@@ -525,20 +557,20 @@ export async function handler(
if (authInfo.isFree) return "free"
if (modelInfo.allowAnonymous) return "free"
- // Validate subscription billing
- if (authInfo.billing.subscription && authInfo.subscription) {
- try {
- const sub = authInfo.subscription
- const plan = authInfo.billing.subscription.plan
+ const formatRetryTime = (seconds: number) => {
+ const days = Math.floor(seconds / 86400)
+ if (days >= 1) return `${days} day${days > 1 ? "s" : ""}`
+ const hours = Math.floor(seconds / 3600)
+ const minutes = Math.ceil((seconds % 3600) / 60)
+ if (hours >= 1) return `${hours}hr ${minutes}min`
+ return `${minutes}min`
+ }
- const formatRetryTime = (seconds: number) => {
- const days = Math.floor(seconds / 86400)
- if (days >= 1) return `${days} day${days > 1 ? "s" : ""}`
- const hours = Math.floor(seconds / 3600)
- const minutes = Math.ceil((seconds % 3600) / 60)
- if (hours >= 1) return `${hours}hr ${minutes}min`
- return `${minutes}min`
- }
+ // Validate black subscription billing
+ if (authInfo.billing.subscription && authInfo.black) {
+ try {
+ const sub = authInfo.black
+ const plan = authInfo.billing.subscription.plan
// Check weekly limit
if (sub.fixedUsage && sub.timeFixedUpdated) {
@@ -577,6 +609,62 @@ export async function handler(
}
}
+ // Validate lite subscription billing
+ if (opts.modelList === "lite" && authInfo.billing.lite && authInfo.lite) {
+ try {
+ const sub = authInfo.lite
+ const liteData = LiteData.getLimits()
+
+ // Check weekly limit
+ if (sub.weeklyUsage && sub.timeWeeklyUpdated) {
+ const result = Subscription.analyzeWeeklyUsage({
+ limit: liteData.weeklyLimit,
+ usage: sub.weeklyUsage,
+ timeUpdated: sub.timeWeeklyUpdated,
+ })
+ if (result.status === "rate-limited")
+ throw new SubscriptionUsageLimitError(
+ `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+ result.resetInSec,
+ )
+ }
+
+ // Check monthly limit
+ if (sub.monthlyUsage && sub.timeMonthlyUpdated) {
+ const result = Subscription.analyzeMonthlyUsage({
+ limit: liteData.monthlyLimit,
+ usage: sub.monthlyUsage,
+ timeUpdated: sub.timeMonthlyUpdated,
+ timeSubscribed: sub.timeCreated,
+ })
+ if (result.status === "rate-limited")
+ throw new SubscriptionUsageLimitError(
+ `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+ result.resetInSec,
+ )
+ }
+
+ // Check rolling limit
+ if (sub.monthlyUsage && sub.timeMonthlyUpdated) {
+ const result = Subscription.analyzeRollingUsage({
+ limit: liteData.rollingLimit,
+ window: liteData.rollingWindow,
+ usage: sub.monthlyUsage,
+ timeUpdated: sub.timeMonthlyUpdated,
+ })
+ if (result.status === "rate-limited")
+ throw new SubscriptionUsageLimitError(
+ `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+ result.resetInSec,
+ )
+ }
+
+ return "lite"
+ } catch (e) {
+ if (!authInfo.billing.lite.useBalance) throw e
+ }
+ }
+
// Validate pay as you go billing
const billing = authInfo.billing
if (!billing.paymentMethodID)
@@ -743,6 +831,7 @@ export async function handler(
enrichment: (() => {
if (billingSource === "subscription") return { plan: "sub" }
if (billingSource === "byok") return { plan: "byok" }
+ if (billingSource === "lite") return { plan: "lite" }
return undefined
})(),
}),
@@ -750,74 +839,115 @@ export async function handler(
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))),
- ...(billingSource === "subscription"
- ? (() => {
- const plan = authInfo.billing.subscription!.plan
- const black = BlackData.getLimits({ plan })
- const week = getWeekBounds(new Date())
- const rollingWindowSeconds = black.rollingWindow * 3600
- return [
- db
- .update(SubscriptionTable)
- .set({
- fixedUsage: sql`
+ ...(() => {
+ if (billingSource === "subscription") {
+ const plan = authInfo.billing.subscription!.plan
+ const black = BlackData.getLimits({ plan })
+ const week = getWeekBounds(new Date())
+ const rollingWindowSeconds = black.rollingWindow * 3600
+ return [
+ db
+ .update(SubscriptionTable)
+ .set({
+ fixedUsage: sql`
CASE
WHEN ${SubscriptionTable.timeFixedUpdated} >= ${week.start} THEN ${SubscriptionTable.fixedUsage} + ${cost}
ELSE ${cost}
END
`,
- timeFixedUpdated: sql`now()`,
- rollingUsage: sql`
+ timeFixedUpdated: sql`now()`,
+ rollingUsage: sql`
CASE
WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.rollingUsage} + ${cost}
ELSE ${cost}
END
`,
- timeRollingUpdated: sql`
+ timeRollingUpdated: sql`
CASE
WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.timeRollingUpdated}
ELSE now()
END
`,
- })
- .where(
- and(
- eq(SubscriptionTable.workspaceID, authInfo.workspaceID),
- eq(SubscriptionTable.userID, authInfo.user.id),
- ),
+ })
+ .where(
+ and(
+ eq(SubscriptionTable.workspaceID, authInfo.workspaceID),
+ eq(SubscriptionTable.userID, authInfo.user.id),
),
- ]
- })()
- : [
+ ),
+ ]
+ }
+ if (billingSource === "lite") {
+ const lite = LiteData.getLimits()
+ const week = getWeekBounds(new Date())
+ const month = getMonthlyBounds(new Date(), authInfo.lite!.timeCreated)
+ const rollingWindowSeconds = lite.rollingWindow * 3600
+ return [
db
- .update(BillingTable)
+ .update(LiteTable)
.set({
- balance:
- billingSource === "free" || billingSource === "byok"
- ? sql`${BillingTable.balance} - ${0}`
- : sql`${BillingTable.balance} - ${cost}`,
monthlyUsage: sql`
+ CASE
+ WHEN ${LiteTable.timeMonthlyUpdated} >= ${month.start} THEN ${LiteTable.monthlyUsage} + ${cost}
+ ELSE ${cost}
+ END
+ `,
+ timeMonthlyUpdated: sql`now()`,
+ weeklyUsage: sql`
+ CASE
+ WHEN ${LiteTable.timeWeeklyUpdated} >= ${week.start} THEN ${LiteTable.weeklyUsage} + ${cost}
+ ELSE ${cost}
+ END
+ `,
+ timeWeeklyUpdated: sql`now()`,
+ rollingUsage: sql`
+ CASE
+ WHEN UNIX_TIMESTAMP(${LiteTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${LiteTable.rollingUsage} + ${cost}
+ ELSE ${cost}
+ END
+ `,
+ timeRollingUpdated: sql`
+ CASE
+ WHEN UNIX_TIMESTAMP(${LiteTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${LiteTable.timeRollingUpdated}
+ ELSE now()
+ END
+ `,
+ })
+ .where(and(eq(LiteTable.workspaceID, authInfo.workspaceID), eq(LiteTable.userID, authInfo.user.id))),
+ ]
+ }
+
+ return [
+ db
+ .update(BillingTable)
+ .set({
+ balance:
+ billingSource === "free" || billingSource === "byok"
+ ? sql`${BillingTable.balance} - ${0}`
+ : sql`${BillingTable.balance} - ${cost}`,
+ monthlyUsage: sql`
CASE
WHEN MONTH(${BillingTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${BillingTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${BillingTable.monthlyUsage} + ${cost}
ELSE ${cost}
END
`,
- timeMonthlyUsageUpdated: sql`now()`,
- })
- .where(eq(BillingTable.workspaceID, authInfo.workspaceID)),
- db
- .update(UserTable)
- .set({
- monthlyUsage: sql`
+ timeMonthlyUsageUpdated: sql`now()`,
+ })
+ .where(eq(BillingTable.workspaceID, authInfo.workspaceID)),
+ db
+ .update(UserTable)
+ .set({
+ monthlyUsage: sql`
CASE
WHEN MONTH(${UserTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${UserTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${UserTable.monthlyUsage} + ${cost}
ELSE ${cost}
END
`,
- timeMonthlyUsageUpdated: sql`now()`,
- })
- .where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id))),
- ]),
+ timeMonthlyUsageUpdated: sql`now()`,
+ })
+ .where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id))),
+ ]
+ })(),
]),
)
diff --git a/packages/console/core/migrations/20260224043338_nifty_starjammers/migration.sql b/packages/console/core/migrations/20260224043338_nifty_starjammers/migration.sql
new file mode 100644
index 000000000..1c97afbd9
--- /dev/null
+++ b/packages/console/core/migrations/20260224043338_nifty_starjammers/migration.sql
@@ -0,0 +1,19 @@
+CREATE TABLE `lite` (
+ `id` varchar(30) NOT NULL,
+ `workspace_id` varchar(30) NOT NULL,
+ `time_created` timestamp(3) NOT NULL DEFAULT (now()),
+ `time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+ `time_deleted` timestamp(3),
+ `user_id` varchar(30) NOT NULL,
+ `rolling_usage` bigint,
+ `weekly_usage` bigint,
+ `monthly_usage` bigint,
+ `time_rolling_updated` timestamp(3),
+ `time_weekly_updated` timestamp(3),
+ `time_monthly_updated` timestamp(3),
+ CONSTRAINT `PRIMARY` PRIMARY KEY(`workspace_id`,`id`),
+ CONSTRAINT `workspace_user_id` UNIQUE INDEX(`workspace_id`,`user_id`)
+);
+--> statement-breakpoint
+ALTER TABLE `billing` ADD `lite_subscription_id` varchar(28);--> statement-breakpoint
+ALTER TABLE `billing` ADD `lite` json;
\ No newline at end of file
diff --git a/packages/console/core/migrations/20260224043338_nifty_starjammers/snapshot.json b/packages/console/core/migrations/20260224043338_nifty_starjammers/snapshot.json
new file mode 100644
index 000000000..703ee233f
--- /dev/null
+++ b/packages/console/core/migrations/20260224043338_nifty_starjammers/snapshot.json
@@ -0,0 +1,2505 @@
+{
+ "version": "6",
+ "dialect": "mysql",
+ "id": "5e506dec-61e7-4726-81d1-afa4ffbc61ed",
+ "prevIds": [
+ "4bf45b3f-3edd-4db7-94d5-097aa55ca5f7"
+ ],
+ "ddl": [
+ {
+ "name": "account",
+ "entityType": "tables"
+ },
+ {
+ "name": "auth",
+ "entityType": "tables"
+ },
+ {
+ "name": "benchmark",
+ "entityType": "tables"
+ },
+ {
+ "name": "billing",
+ "entityType": "tables"
+ },
+ {
+ "name": "lite",
+ "entityType": "tables"
+ },
+ {
+ "name": "payment",
+ "entityType": "tables"
+ },
+ {
+ "name": "subscription",
+ "entityType": "tables"
+ },
+ {
+ "name": "usage",
+ "entityType": "tables"
+ },
+ {
+ "name": "ip_rate_limit",
+ "entityType": "tables"
+ },
+ {
+ "name": "ip",
+ "entityType": "tables"
+ },
+ {
+ "name": "key",
+ "entityType": "tables"
+ },
+ {
+ "name": "model",
+ "entityType": "tables"
+ },
+ {
+ "name": "provider",
+ "entityType": "tables"
+ },
+ {
+ "name": "user",
+ "entityType": "tables"
+ },
+ {
+ "name": "workspace",
+ "entityType": "tables"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "account"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "account"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "account"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "account"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "auth"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "auth"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "auth"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "auth"
+ },
+ {
+ "type": "enum('email','github','google')",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "provider",
+ "entityType": "columns",
+ "table": "auth"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "subject",
+ "entityType": "columns",
+ "table": "auth"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "account_id",
+ "entityType": "columns",
+ "table": "auth"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "benchmark"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "benchmark"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "benchmark"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "benchmark"
+ },
+ {
+ "type": "varchar(64)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "model",
+ "entityType": "columns",
+ "table": "benchmark"
+ },
+ {
+ "type": "varchar(64)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "agent",
+ "entityType": "columns",
+ "table": "benchmark"
+ },
+ {
+ "type": "mediumtext",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "result",
+ "entityType": "columns",
+ "table": "benchmark"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "customer_id",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "payment_method_id",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(32)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "payment_method_type",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(4)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "payment_method_last4",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "bigint",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "balance",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "monthly_limit",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "bigint",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "monthly_usage",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_monthly_usage_updated",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "boolean",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "reload",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "reload_trigger",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "reload_amount",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "reload_error",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_reload_error",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_reload_locked_till",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "json",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "subscription",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(28)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "subscription_id",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "enum('20','100','200')",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "subscription_plan",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_subscription_booked",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_subscription_selected",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(28)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "lite_subscription_id",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "json",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "lite",
+ "entityType": "columns",
+ "table": "billing"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "user_id",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "bigint",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "rolling_usage",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "bigint",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "weekly_usage",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "bigint",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "monthly_usage",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_rolling_updated",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_weekly_updated",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_monthly_updated",
+ "entityType": "columns",
+ "table": "lite"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "customer_id",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "invoice_id",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "payment_id",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "bigint",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "amount",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_refunded",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "json",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "enrichment",
+ "entityType": "columns",
+ "table": "payment"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "user_id",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "bigint",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "rolling_usage",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "bigint",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "fixed_usage",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_rolling_updated",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_fixed_updated",
+ "entityType": "columns",
+ "table": "subscription"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "model",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "provider",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "int",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "input_tokens",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "int",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "output_tokens",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "reasoning_tokens",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "cache_read_tokens",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "cache_write_5m_tokens",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "cache_write_1h_tokens",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "bigint",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "cost",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "key_id",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "session_id",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "json",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "enrichment",
+ "entityType": "columns",
+ "table": "usage"
+ },
+ {
+ "type": "varchar(45)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "ip",
+ "entityType": "columns",
+ "table": "ip_rate_limit"
+ },
+ {
+ "type": "varchar(10)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "interval",
+ "entityType": "columns",
+ "table": "ip_rate_limit"
+ },
+ {
+ "type": "int",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "count",
+ "entityType": "columns",
+ "table": "ip_rate_limit"
+ },
+ {
+ "type": "varchar(45)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "ip",
+ "entityType": "columns",
+ "table": "ip"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "ip"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "ip"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "ip"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "usage",
+ "entityType": "columns",
+ "table": "ip"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "name",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "key",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "user_id",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_used",
+ "entityType": "columns",
+ "table": "key"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "model"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "model"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "model"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "model"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "model"
+ },
+ {
+ "type": "varchar(64)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "model",
+ "entityType": "columns",
+ "table": "model"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "provider"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "provider"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "provider"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "provider"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "provider"
+ },
+ {
+ "type": "varchar(64)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "provider",
+ "entityType": "columns",
+ "table": "provider"
+ },
+ {
+ "type": "text",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "credentials",
+ "entityType": "columns",
+ "table": "provider"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "workspace_id",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "account_id",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "email",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "name",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_seen",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "color",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "enum('admin','member')",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "role",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "int",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "monthly_limit",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "bigint",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "monthly_usage",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_monthly_usage_updated",
+ "entityType": "columns",
+ "table": "user"
+ },
+ {
+ "type": "varchar(30)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "id",
+ "entityType": "columns",
+ "table": "workspace"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "slug",
+ "entityType": "columns",
+ "table": "workspace"
+ },
+ {
+ "type": "varchar(255)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "name",
+ "entityType": "columns",
+ "table": "workspace"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(now())",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_created",
+ "entityType": "columns",
+ "table": "workspace"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": true,
+ "autoIncrement": false,
+ "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_updated",
+ "entityType": "columns",
+ "table": "workspace"
+ },
+ {
+ "type": "timestamp(3)",
+ "notNull": false,
+ "autoIncrement": false,
+ "default": null,
+ "onUpdateNow": false,
+ "onUpdateNowFsp": null,
+ "charSet": null,
+ "collation": null,
+ "generated": null,
+ "name": "time_deleted",
+ "entityType": "columns",
+ "table": "workspace"
+ },
+ {
+ "columns": [
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "account",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "auth",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "benchmark",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "billing",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "lite",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "payment",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "subscription",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "usage",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "ip",
+ "interval"
+ ],
+ "name": "PRIMARY",
+ "table": "ip_rate_limit",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "ip"
+ ],
+ "name": "PRIMARY",
+ "table": "ip",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "key",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "model",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "provider",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "workspace_id",
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "user",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ "id"
+ ],
+ "name": "PRIMARY",
+ "table": "workspace",
+ "entityType": "pks"
+ },
+ {
+ "columns": [
+ {
+ "value": "provider",
+ "isExpression": false
+ },
+ {
+ "value": "subject",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "provider",
+ "entityType": "indexes",
+ "table": "auth"
+ },
+ {
+ "columns": [
+ {
+ "value": "account_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "account_id",
+ "entityType": "indexes",
+ "table": "auth"
+ },
+ {
+ "columns": [
+ {
+ "value": "time_created",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "time_created",
+ "entityType": "indexes",
+ "table": "benchmark"
+ },
+ {
+ "columns": [
+ {
+ "value": "customer_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "global_customer_id",
+ "entityType": "indexes",
+ "table": "billing"
+ },
+ {
+ "columns": [
+ {
+ "value": "subscription_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "global_subscription_id",
+ "entityType": "indexes",
+ "table": "billing"
+ },
+ {
+ "columns": [
+ {
+ "value": "workspace_id",
+ "isExpression": false
+ },
+ {
+ "value": "user_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "workspace_user_id",
+ "entityType": "indexes",
+ "table": "lite"
+ },
+ {
+ "columns": [
+ {
+ "value": "workspace_id",
+ "isExpression": false
+ },
+ {
+ "value": "user_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "workspace_user_id",
+ "entityType": "indexes",
+ "table": "subscription"
+ },
+ {
+ "columns": [
+ {
+ "value": "workspace_id",
+ "isExpression": false
+ },
+ {
+ "value": "time_created",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "usage_time_created",
+ "entityType": "indexes",
+ "table": "usage"
+ },
+ {
+ "columns": [
+ {
+ "value": "key",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "global_key",
+ "entityType": "indexes",
+ "table": "key"
+ },
+ {
+ "columns": [
+ {
+ "value": "workspace_id",
+ "isExpression": false
+ },
+ {
+ "value": "model",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "model_workspace_model",
+ "entityType": "indexes",
+ "table": "model"
+ },
+ {
+ "columns": [
+ {
+ "value": "workspace_id",
+ "isExpression": false
+ },
+ {
+ "value": "provider",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "workspace_provider",
+ "entityType": "indexes",
+ "table": "provider"
+ },
+ {
+ "columns": [
+ {
+ "value": "workspace_id",
+ "isExpression": false
+ },
+ {
+ "value": "account_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "user_account_id",
+ "entityType": "indexes",
+ "table": "user"
+ },
+ {
+ "columns": [
+ {
+ "value": "workspace_id",
+ "isExpression": false
+ },
+ {
+ "value": "email",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "user_email",
+ "entityType": "indexes",
+ "table": "user"
+ },
+ {
+ "columns": [
+ {
+ "value": "account_id",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "global_account_id",
+ "entityType": "indexes",
+ "table": "user"
+ },
+ {
+ "columns": [
+ {
+ "value": "email",
+ "isExpression": false
+ }
+ ],
+ "isUnique": false,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "global_email",
+ "entityType": "indexes",
+ "table": "user"
+ },
+ {
+ "columns": [
+ {
+ "value": "slug",
+ "isExpression": false
+ }
+ ],
+ "isUnique": true,
+ "using": null,
+ "algorithm": null,
+ "lock": null,
+ "nameExplicit": true,
+ "name": "slug",
+ "entityType": "indexes",
+ "table": "workspace"
+ }
+ ],
+ "renames": []
+}
\ No newline at end of file
diff --git a/packages/console/core/script/black-select-workspaces.ts b/packages/console/core/script/black-select-workspaces.ts
index f22478e1b..63bfab887 100644
--- a/packages/console/core/script/black-select-workspaces.ts
+++ b/packages/console/core/script/black-select-workspaces.ts
@@ -1,10 +1,10 @@
import { Database, eq, and, sql, inArray, isNull, count } from "../src/drizzle/index.js"
-import { BillingTable, SubscriptionPlan } from "../src/schema/billing.sql.js"
+import { BillingTable, BlackPlans } from "../src/schema/billing.sql.js"
import { UserTable } from "../src/schema/user.sql.js"
import { AuthTable } from "../src/schema/auth.sql.js"
-const plan = process.argv[2] as (typeof SubscriptionPlan)[number]
-if (!SubscriptionPlan.includes(plan)) {
+const plan = process.argv[2] as (typeof BlackPlans)[number]
+if (!BlackPlans.includes(plan)) {
console.error("Usage: bun foo.ts ")
process.exit(1)
}
diff --git a/packages/console/core/script/lookup-user.ts b/packages/console/core/script/lookup-user.ts
index 6367fd89a..0dfda2411 100644
--- a/packages/console/core/script/lookup-user.ts
+++ b/packages/console/core/script/lookup-user.ts
@@ -1,13 +1,7 @@
import { Database, and, eq, sql } from "../src/drizzle/index.js"
import { AuthTable } from "../src/schema/auth.sql.js"
import { UserTable } from "../src/schema/user.sql.js"
-import {
- BillingTable,
- PaymentTable,
- SubscriptionTable,
- SubscriptionPlan,
- UsageTable,
-} from "../src/schema/billing.sql.js"
+import { BillingTable, PaymentTable, SubscriptionTable, BlackPlans, UsageTable } from "../src/schema/billing.sql.js"
import { WorkspaceTable } from "../src/schema/workspace.sql.js"
import { BlackData } from "../src/black.js"
import { centsToMicroCents } from "../src/util/price.js"
@@ -235,7 +229,7 @@ function formatRetryTime(seconds: number) {
function getSubscriptionStatus(row: {
subscription: {
- plan: (typeof SubscriptionPlan)[number]
+ plan: (typeof BlackPlans)[number]
} | null
timeSubscriptionCreated: Date | null
fixedUsage: number | null
diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts
index 2c1cdb068..fcf238a35 100644
--- a/packages/console/core/src/billing.ts
+++ b/packages/console/core/src/billing.ts
@@ -1,6 +1,6 @@
import { Stripe } from "stripe"
import { Database, eq, sql } from "./drizzle"
-import { BillingTable, PaymentTable, SubscriptionTable, UsageTable } from "./schema/billing.sql"
+import { BillingTable, LiteTable, PaymentTable, SubscriptionTable, UsageTable } from "./schema/billing.sql"
import { Actor } from "./actor"
import { fn } from "./util/fn"
import { z } from "zod"
@@ -9,6 +9,7 @@ import { Identifier } from "./identifier"
import { centsToMicroCents } from "./util/price"
import { User } from "./user"
import { BlackData } from "./black"
+import { LiteData } from "./lite"
export namespace Billing {
export const ITEM_CREDIT_NAME = "opencode credits"
@@ -233,6 +234,56 @@ export namespace Billing {
},
)
+ export const generateLiteCheckoutUrl = fn(
+ z.object({
+ successUrl: z.string(),
+ cancelUrl: z.string(),
+ }),
+ async (input) => {
+ const user = Actor.assert("user")
+ const { successUrl, cancelUrl } = input
+
+ const email = await User.getAuthEmail(user.properties.userID)
+ const billing = await Billing.get()
+
+ if (billing.subscriptionID) throw new Error("Already subscribed to Black")
+ if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
+
+ const session = await Billing.stripe().checkout.sessions.create({
+ mode: "subscription",
+ billing_address_collection: "required",
+ line_items: [{ price: LiteData.priceID(), quantity: 1 }],
+ ...(billing.customerID
+ ? {
+ customer: billing.customerID,
+ customer_update: {
+ name: "auto",
+ address: "auto",
+ },
+ }
+ : {
+ customer_email: email!,
+ }),
+ currency: "usd",
+ payment_method_types: ["card"],
+ tax_id_collection: {
+ enabled: true,
+ },
+ success_url: successUrl,
+ cancel_url: cancelUrl,
+ subscription_data: {
+ metadata: {
+ workspaceID: Actor.workspace(),
+ userID: user.properties.userID,
+ type: "lite",
+ },
+ },
+ })
+
+ return session.url
+ },
+ )
+
export const generateSessionUrl = fn(
z.object({
returnUrl: z.string(),
@@ -271,7 +322,7 @@ export namespace Billing {
},
)
- export const subscribe = fn(
+ export const subscribeBlack = fn(
z.object({
seats: z.number(),
coupon: z.string().optional(),
@@ -336,7 +387,7 @@ export namespace Billing {
},
)
- export const unsubscribe = fn(
+ export const unsubscribeBlack = fn(
z.object({
subscriptionID: z.string(),
}),
@@ -360,4 +411,29 @@ export namespace Billing {
})
},
)
+
+ export const unsubscribeLite = fn(
+ z.object({
+ subscriptionID: z.string(),
+ }),
+ async ({ subscriptionID }) => {
+ const workspaceID = await Database.use((tx) =>
+ tx
+ .select({ workspaceID: BillingTable.workspaceID })
+ .from(BillingTable)
+ .where(eq(BillingTable.liteSubscriptionID, subscriptionID))
+ .then((rows) => rows[0]?.workspaceID),
+ )
+ if (!workspaceID) throw new Error("Workspace ID not found for subscription")
+
+ await Database.transaction(async (tx) => {
+ await tx
+ .update(BillingTable)
+ .set({ liteSubscriptionID: null, lite: null })
+ .where(eq(BillingTable.workspaceID, workspaceID))
+
+ await tx.delete(LiteTable).where(eq(LiteTable.workspaceID, workspaceID))
+ })
+ },
+ )
}
diff --git a/packages/console/core/src/black.ts b/packages/console/core/src/black.ts
index b4cc27064..a18c5258d 100644
--- a/packages/console/core/src/black.ts
+++ b/packages/console/core/src/black.ts
@@ -1,7 +1,7 @@
import { z } from "zod"
import { fn } from "./util/fn"
import { Resource } from "@opencode-ai/console-resource"
-import { SubscriptionPlan } from "./schema/billing.sql"
+import { BlackPlans } from "./schema/billing.sql"
export namespace BlackData {
const Schema = z.object({
@@ -28,7 +28,7 @@ export namespace BlackData {
export const getLimits = fn(
z.object({
- plan: z.enum(SubscriptionPlan),
+ plan: z.enum(BlackPlans),
}),
({ plan }) => {
const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value)
@@ -36,9 +36,11 @@ export namespace BlackData {
},
)
+ export const productID = fn(z.void(), () => Resource.ZEN_BLACK_PRICE.product)
+
export const planToPriceID = fn(
z.object({
- plan: z.enum(SubscriptionPlan),
+ plan: z.enum(BlackPlans),
}),
({ plan }) => {
if (plan === "200") return Resource.ZEN_BLACK_PRICE.plan200
diff --git a/packages/console/core/src/identifier.ts b/packages/console/core/src/identifier.ts
index b10bf32f6..8aa324ba0 100644
--- a/packages/console/core/src/identifier.ts
+++ b/packages/console/core/src/identifier.ts
@@ -8,6 +8,7 @@ export namespace Identifier {
benchmark: "ben",
billing: "bil",
key: "key",
+ lite: "lit",
model: "mod",
payment: "pay",
provider: "prv",
diff --git a/packages/console/core/src/lite.ts b/packages/console/core/src/lite.ts
index d6679208d..49d23e59e 100644
--- a/packages/console/core/src/lite.ts
+++ b/packages/console/core/src/lite.ts
@@ -4,9 +4,10 @@ import { Resource } from "@opencode-ai/console-resource"
export namespace LiteData {
const Schema = z.object({
- fixedLimit: z.number().int(),
rollingLimit: z.number().int(),
rollingWindow: z.number().int(),
+ weeklyLimit: z.number().int(),
+ monthlyLimit: z.number().int(),
})
export const validate = fn(Schema, (input) => {
@@ -18,11 +19,7 @@ export namespace LiteData {
return Schema.parse(json)
})
- export const planToPriceID = fn(z.void(), () => {
- return Resource.ZEN_LITE_PRICE.price
- })
-
- export const priceIDToPlan = fn(z.void(), () => {
- return "lite"
- })
+ export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product)
+ export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
+ export const planName = fn(z.void(), () => "lite")
}
diff --git a/packages/console/core/src/schema/billing.sql.ts b/packages/console/core/src/schema/billing.sql.ts
index 6d96fc7eb..a5c70c211 100644
--- a/packages/console/core/src/schema/billing.sql.ts
+++ b/packages/console/core/src/schema/billing.sql.ts
@@ -2,7 +2,7 @@ import { bigint, boolean, index, int, json, mysqlEnum, mysqlTable, uniqueIndex,
import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
-export const SubscriptionPlan = ["20", "100", "200"] as const
+export const BlackPlans = ["20", "100", "200"] as const
export const BillingTable = mysqlTable(
"billing",
{
@@ -25,14 +25,18 @@ export const BillingTable = mysqlTable(
subscription: json("subscription").$type<{
status: "subscribed"
seats: number
- plan: "20" | "100" | "200"
+ plan: (typeof BlackPlans)[number]
useBalance?: boolean
coupon?: string
}>(),
subscriptionID: varchar("subscription_id", { length: 28 }),
- subscriptionPlan: mysqlEnum("subscription_plan", SubscriptionPlan),
+ subscriptionPlan: mysqlEnum("subscription_plan", BlackPlans),
timeSubscriptionBooked: utc("time_subscription_booked"),
timeSubscriptionSelected: utc("time_subscription_selected"),
+ liteSubscriptionID: varchar("lite_subscription_id", { length: 28 }),
+ lite: json("lite").$type<{
+ useBalance?: boolean
+ }>(),
},
(table) => [
...workspaceIndexes(table),
@@ -55,6 +59,22 @@ export const SubscriptionTable = mysqlTable(
(table) => [...workspaceIndexes(table), uniqueIndex("workspace_user_id").on(table.workspaceID, table.userID)],
)
+export const LiteTable = mysqlTable(
+ "lite",
+ {
+ ...workspaceColumns,
+ ...timestamps,
+ userID: ulid("user_id").notNull(),
+ rollingUsage: bigint("rolling_usage", { mode: "number" }),
+ weeklyUsage: bigint("weekly_usage", { mode: "number" }),
+ monthlyUsage: bigint("monthly_usage", { mode: "number" }),
+ timeRollingUpdated: utc("time_rolling_updated"),
+ timeWeeklyUpdated: utc("time_weekly_updated"),
+ timeMonthlyUpdated: utc("time_monthly_updated"),
+ },
+ (table) => [...workspaceIndexes(table), uniqueIndex("workspace_user_id").on(table.workspaceID, table.userID)],
+)
+
export const PaymentTable = mysqlTable(
"payment",
{
diff --git a/packages/console/core/src/subscription.ts b/packages/console/core/src/subscription.ts
index ca3b17042..879f940e0 100644
--- a/packages/console/core/src/subscription.ts
+++ b/packages/console/core/src/subscription.ts
@@ -1,7 +1,7 @@
import { z } from "zod"
import { fn } from "./util/fn"
import { centsToMicroCents } from "./util/price"
-import { getWeekBounds } from "./util/date"
+import { getWeekBounds, getMonthlyBounds } from "./util/date"
export namespace Subscription {
export const analyzeRollingUsage = fn(
@@ -29,7 +29,7 @@ export namespace Subscription {
return {
status: "ok" as const,
resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
- usagePercent: Math.ceil(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
+ usagePercent: Math.floor(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
}
}
return {
@@ -61,7 +61,7 @@ export namespace Subscription {
return {
status: "ok" as const,
resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
- usagePercent: Math.ceil(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
+ usagePercent: Math.floor(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
}
}
@@ -72,4 +72,38 @@ export namespace Subscription {
}
},
)
+
+ export const analyzeMonthlyUsage = fn(
+ z.object({
+ limit: z.number().int(),
+ usage: z.number().int(),
+ timeUpdated: z.date(),
+ timeSubscribed: z.date(),
+ }),
+ ({ limit, usage, timeUpdated, timeSubscribed }) => {
+ const now = new Date()
+ const month = getMonthlyBounds(now, timeSubscribed)
+ const fixedLimitInMicroCents = centsToMicroCents(limit * 100)
+ if (timeUpdated < month.start) {
+ return {
+ status: "ok" as const,
+ resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
+ usagePercent: 0,
+ }
+ }
+ if (usage < fixedLimitInMicroCents) {
+ return {
+ status: "ok" as const,
+ resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
+ usagePercent: Math.floor(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
+ }
+ }
+
+ return {
+ status: "rate-limited" as const,
+ resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
+ usagePercent: 100,
+ }
+ },
+ )
}
diff --git a/packages/console/core/src/util/date.test.ts b/packages/console/core/src/util/date.test.ts
deleted file mode 100644
index 074df8a2f..000000000
--- a/packages/console/core/src/util/date.test.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { describe, expect, test } from "bun:test"
-import { getWeekBounds } from "./date"
-
-describe("util.date.getWeekBounds", () => {
- test("returns a Monday-based week for Sunday dates", () => {
- const date = new Date("2026-01-18T12:00:00Z")
- const bounds = getWeekBounds(date)
-
- expect(bounds.start.toISOString()).toBe("2026-01-12T00:00:00.000Z")
- expect(bounds.end.toISOString()).toBe("2026-01-19T00:00:00.000Z")
- })
-
- test("returns a seven day window", () => {
- const date = new Date("2026-01-14T12:00:00Z")
- const bounds = getWeekBounds(date)
-
- const span = bounds.end.getTime() - bounds.start.getTime()
- expect(span).toBe(7 * 24 * 60 * 60 * 1000)
- })
-})
diff --git a/packages/console/core/src/util/date.ts b/packages/console/core/src/util/date.ts
index 9c1ab12d2..dea9c390e 100644
--- a/packages/console/core/src/util/date.ts
+++ b/packages/console/core/src/util/date.ts
@@ -7,3 +7,32 @@ export function getWeekBounds(date: Date) {
end.setUTCDate(start.getUTCDate() + 7)
return { start, end }
}
+
+export function getMonthlyBounds(now: Date, subscribed: Date) {
+ const day = subscribed.getUTCDate()
+ const hh = subscribed.getUTCHours()
+ const mm = subscribed.getUTCMinutes()
+ const ss = subscribed.getUTCSeconds()
+ const ms = subscribed.getUTCMilliseconds()
+
+ function anchor(year: number, month: number) {
+ const max = new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
+ return new Date(Date.UTC(year, month, Math.min(day, max), hh, mm, ss, ms))
+ }
+
+ function shift(year: number, month: number, delta: number) {
+ const total = year * 12 + month + delta
+ return [Math.floor(total / 12), ((total % 12) + 12) % 12] as const
+ }
+
+ let y = now.getUTCFullYear()
+ let m = now.getUTCMonth()
+ let start = anchor(y, m)
+ if (start > now) {
+ ;[y, m] = shift(y, m, -1)
+ start = anchor(y, m)
+ }
+ const [ny, nm] = shift(y, m, 1)
+ const end = anchor(ny, nm)
+ return { start, end }
+}
diff --git a/packages/console/core/test/date.test.ts b/packages/console/core/test/date.test.ts
new file mode 100644
index 000000000..e5a0a90e5
--- /dev/null
+++ b/packages/console/core/test/date.test.ts
@@ -0,0 +1,76 @@
+import { describe, expect, test } from "bun:test"
+import { getWeekBounds, getMonthlyBounds } from "../src/util/date"
+
+describe("util.date.getWeekBounds", () => {
+ test("returns a Monday-based week for Sunday dates", () => {
+ const date = new Date("2026-01-18T12:00:00Z")
+ const bounds = getWeekBounds(date)
+
+ expect(bounds.start.toISOString()).toBe("2026-01-12T00:00:00.000Z")
+ expect(bounds.end.toISOString()).toBe("2026-01-19T00:00:00.000Z")
+ })
+
+ test("returns a seven day window", () => {
+ const date = new Date("2026-01-14T12:00:00Z")
+ const bounds = getWeekBounds(date)
+
+ const span = bounds.end.getTime() - bounds.start.getTime()
+ expect(span).toBe(7 * 24 * 60 * 60 * 1000)
+ })
+})
+
+describe("util.date.getMonthlyBounds", () => {
+ test("resets on subscription day mid-month", () => {
+ const now = new Date("2026-03-20T10:00:00Z")
+ const subscribed = new Date("2026-01-15T08:00:00Z")
+ const bounds = getMonthlyBounds(now, subscribed)
+
+ expect(bounds.start.toISOString()).toBe("2026-03-15T08:00:00.000Z")
+ expect(bounds.end.toISOString()).toBe("2026-04-15T08:00:00.000Z")
+ })
+
+ test("before subscription day in current month uses previous month anchor", () => {
+ const now = new Date("2026-03-10T10:00:00Z")
+ const subscribed = new Date("2026-01-15T08:00:00Z")
+ const bounds = getMonthlyBounds(now, subscribed)
+
+ expect(bounds.start.toISOString()).toBe("2026-02-15T08:00:00.000Z")
+ expect(bounds.end.toISOString()).toBe("2026-03-15T08:00:00.000Z")
+ })
+
+ test("clamps day for short months", () => {
+ const now = new Date("2026-03-01T10:00:00Z")
+ const subscribed = new Date("2026-01-31T12:00:00Z")
+ const bounds = getMonthlyBounds(now, subscribed)
+
+ expect(bounds.start.toISOString()).toBe("2026-02-28T12:00:00.000Z")
+ expect(bounds.end.toISOString()).toBe("2026-03-31T12:00:00.000Z")
+ })
+
+ test("handles subscription on the 1st", () => {
+ const now = new Date("2026-04-15T00:00:00Z")
+ const subscribed = new Date("2026-01-01T00:00:00Z")
+ const bounds = getMonthlyBounds(now, subscribed)
+
+ expect(bounds.start.toISOString()).toBe("2026-04-01T00:00:00.000Z")
+ expect(bounds.end.toISOString()).toBe("2026-05-01T00:00:00.000Z")
+ })
+
+ test("exactly on the reset boundary uses current period", () => {
+ const now = new Date("2026-03-15T08:00:00Z")
+ const subscribed = new Date("2026-01-15T08:00:00Z")
+ const bounds = getMonthlyBounds(now, subscribed)
+
+ expect(bounds.start.toISOString()).toBe("2026-03-15T08:00:00.000Z")
+ expect(bounds.end.toISOString()).toBe("2026-04-15T08:00:00.000Z")
+ })
+
+ test("february to march with day 30 subscription", () => {
+ const now = new Date("2026-02-15T06:00:00Z")
+ const subscribed = new Date("2025-12-30T06:00:00Z")
+ const bounds = getMonthlyBounds(now, subscribed)
+
+ expect(bounds.start.toISOString()).toBe("2026-01-30T06:00:00.000Z")
+ expect(bounds.end.toISOString()).toBe("2026-02-28T06:00:00.000Z")
+ })
+})
diff --git a/packages/console/core/test/subscription.test.ts b/packages/console/core/test/subscription.test.ts
new file mode 100644
index 000000000..57e63f94c
--- /dev/null
+++ b/packages/console/core/test/subscription.test.ts
@@ -0,0 +1,106 @@
+import { describe, expect, test, setSystemTime, afterEach } from "bun:test"
+import { Subscription } from "../src/subscription"
+import { centsToMicroCents } from "../src/util/price"
+
+afterEach(() => {
+ setSystemTime()
+})
+
+describe("Subscription.analyzeMonthlyUsage", () => {
+ const subscribed = new Date("2026-01-15T08:00:00Z")
+
+ test("returns ok with 0% when usage was last updated before current period", () => {
+ setSystemTime(new Date("2026-03-20T10:00:00Z"))
+ const result = Subscription.analyzeMonthlyUsage({
+ limit: 10,
+ usage: centsToMicroCents(500),
+ timeUpdated: new Date("2026-02-10T00:00:00Z"),
+ timeSubscribed: subscribed,
+ })
+
+ expect(result.status).toBe("ok")
+ expect(result.usagePercent).toBe(0)
+ // reset should be seconds until 2026-04-15T08:00:00Z
+ const expected = Math.ceil(
+ (new Date("2026-04-15T08:00:00Z").getTime() - new Date("2026-03-20T10:00:00Z").getTime()) / 1000,
+ )
+ expect(result.resetInSec).toBe(expected)
+ })
+
+ test("returns ok with usage percent when under limit", () => {
+ setSystemTime(new Date("2026-03-20T10:00:00Z"))
+ const limit = 10 // $10
+ const half = centsToMicroCents(10 * 100) / 2
+ const result = Subscription.analyzeMonthlyUsage({
+ limit,
+ usage: half,
+ timeUpdated: new Date("2026-03-18T00:00:00Z"),
+ timeSubscribed: subscribed,
+ })
+
+ expect(result.status).toBe("ok")
+ expect(result.usagePercent).toBe(50)
+ })
+
+ test("returns rate-limited when at or over limit", () => {
+ setSystemTime(new Date("2026-03-20T10:00:00Z"))
+ const limit = 10
+ const result = Subscription.analyzeMonthlyUsage({
+ limit,
+ usage: centsToMicroCents(limit * 100),
+ timeUpdated: new Date("2026-03-18T00:00:00Z"),
+ timeSubscribed: subscribed,
+ })
+
+ expect(result.status).toBe("rate-limited")
+ expect(result.usagePercent).toBe(100)
+ })
+
+ test("resets usage when crossing monthly boundary", () => {
+ // subscribed on 15th, now is April 16th — period is Apr 15 to May 15
+ // timeUpdated is March 20 (previous period)
+ setSystemTime(new Date("2026-04-16T10:00:00Z"))
+ const result = Subscription.analyzeMonthlyUsage({
+ limit: 10,
+ usage: centsToMicroCents(10 * 100),
+ timeUpdated: new Date("2026-03-20T00:00:00Z"),
+ timeSubscribed: subscribed,
+ })
+
+ expect(result.status).toBe("ok")
+ expect(result.usagePercent).toBe(0)
+ })
+
+ test("caps usage percent at 100", () => {
+ setSystemTime(new Date("2026-03-20T10:00:00Z"))
+ const limit = 10
+ const result = Subscription.analyzeMonthlyUsage({
+ limit,
+ usage: centsToMicroCents(limit * 100) - 1,
+ timeUpdated: new Date("2026-03-18T00:00:00Z"),
+ timeSubscribed: subscribed,
+ })
+
+ expect(result.status).toBe("ok")
+ expect(result.usagePercent).toBeLessThanOrEqual(100)
+ })
+
+ test("handles subscription day 31 in short month", () => {
+ const sub31 = new Date("2026-01-31T12:00:00Z")
+ // now is March 1 — period should be Feb 28 to Mar 31
+ setSystemTime(new Date("2026-03-01T10:00:00Z"))
+ const result = Subscription.analyzeMonthlyUsage({
+ limit: 10,
+ usage: 0,
+ timeUpdated: new Date("2026-03-01T09:00:00Z"),
+ timeSubscribed: sub31,
+ })
+
+ expect(result.status).toBe("ok")
+ expect(result.usagePercent).toBe(0)
+ const expected = Math.ceil(
+ (new Date("2026-03-31T12:00:00Z").getTime() - new Date("2026-03-01T10:00:00Z").getTime()) / 1000,
+ )
+ expect(result.resetInSec).toBe(expected)
+ })
+})