@lastbrain/module-ai 2.0.26 → 2.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -1
- package/dist/ai.build.config.d.ts.map +1 -1
- package/dist/ai.build.config.js +508 -9
- package/dist/api/admin/ai-provider-models/[id].d.ts +18 -0
- package/dist/api/admin/ai-provider-models/[id].d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models/[id].js +58 -0
- package/dist/api/admin/ai-provider-models.d.ts +20 -0
- package/dist/api/admin/ai-provider-models.d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models.js +26 -0
- package/dist/api/admin/ai-providers/[key].d.ts +18 -0
- package/dist/api/admin/ai-providers/[key].d.ts.map +1 -0
- package/dist/api/admin/ai-providers/[key].js +55 -0
- package/dist/api/admin/ai-providers.d.ts +20 -0
- package/dist/api/admin/ai-providers.d.ts.map +1 -0
- package/dist/api/admin/ai-providers.js +26 -0
- package/dist/api/admin/billing-analytics.d.ts +43 -0
- package/dist/api/admin/billing-analytics.d.ts.map +1 -0
- package/dist/api/admin/billing-analytics.js +144 -0
- package/dist/api/admin/global-ai-settings.d.ts +14 -0
- package/dist/api/admin/global-ai-settings.d.ts.map +1 -0
- package/dist/api/admin/global-ai-settings.js +63 -0
- package/dist/api/admin/token-packs/[id].d.ts.map +1 -1
- package/dist/api/admin/token-packs/[id].js +3 -2
- package/dist/api/admin/token-packs.d.ts.map +1 -1
- package/dist/api/admin/token-packs.js +3 -2
- package/dist/api/admin/user-monthly-details.d.ts +49 -0
- package/dist/api/admin/user-monthly-details.d.ts.map +1 -0
- package/dist/api/admin/user-monthly-details.js +140 -0
- package/dist/api/admin/user-quota.d.ts +21 -0
- package/dist/api/admin/user-quota.d.ts.map +1 -0
- package/dist/api/admin/user-quota.js +59 -0
- package/dist/api/admin/user-token/[id].d.ts.map +1 -1
- package/dist/api/admin/user-token/[id].js +2 -1
- package/dist/api/admin/user-token.d.ts +5 -2
- package/dist/api/admin/user-token.d.ts.map +1 -1
- package/dist/api/admin/user-token.js +91 -17
- package/dist/api/admin/user-usage-by-model.d.ts +22 -0
- package/dist/api/admin/user-usage-by-model.d.ts.map +1 -0
- package/dist/api/admin/user-usage-by-model.js +78 -0
- package/dist/api/admin/user-wallet-analytics.d.ts +15 -0
- package/dist/api/admin/user-wallet-analytics.d.ts.map +1 -0
- package/dist/api/admin/user-wallet-analytics.js +67 -0
- package/dist/api/admin/wallet-repair/route.d.ts +30 -0
- package/dist/api/admin/wallet-repair/route.d.ts.map +1 -0
- package/dist/api/admin/wallet-repair/route.js +63 -0
- package/dist/api/auth/ai-model-settings.d.ts +21 -0
- package/dist/api/auth/ai-model-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-model-settings.js +86 -0
- package/dist/api/auth/ai-settings.d.ts +17 -0
- package/dist/api/auth/ai-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-settings.js +87 -0
- package/dist/api/auth/api-keys/[id].d.ts +17 -0
- package/dist/api/auth/api-keys/[id].d.ts.map +1 -0
- package/dist/api/auth/api-keys/[id].js +66 -0
- package/dist/api/auth/api-keys.d.ts +19 -0
- package/dist/api/auth/api-keys.d.ts.map +1 -0
- package/dist/api/auth/api-keys.js +94 -0
- package/dist/api/auth/create-checkout.d.ts +1 -1
- package/dist/api/auth/create-checkout.d.ts.map +1 -1
- package/dist/api/auth/create-checkout.js +8 -6
- package/dist/api/auth/generate-image.d.ts +2 -2
- package/dist/api/auth/generate-image.d.ts.map +1 -1
- package/dist/api/auth/generate-image.js +404 -104
- package/dist/api/auth/generate-text.d.ts +3 -2
- package/dist/api/auth/generate-text.d.ts.map +1 -1
- package/dist/api/auth/generate-text.js +130 -58
- package/dist/api/auth/process-ocr.d.ts +9 -0
- package/dist/api/auth/process-ocr.d.ts.map +1 -0
- package/dist/api/auth/process-ocr.js +43 -0
- package/dist/api/auth/prompts/stats.d.ts +14 -0
- package/dist/api/auth/prompts/stats.d.ts.map +1 -0
- package/dist/api/auth/prompts/stats.js +46 -0
- package/dist/api/auth/prompts.d.ts +26 -0
- package/dist/api/auth/prompts.d.ts.map +1 -0
- package/dist/api/auth/prompts.js +175 -0
- package/dist/api/auth/token-balance.d.ts +26 -0
- package/dist/api/auth/token-balance.d.ts.map +1 -0
- package/dist/api/auth/token-balance.js +47 -0
- package/dist/api/auth/token-checkout.d.ts.map +1 -1
- package/dist/api/auth/token-checkout.js +22 -4
- package/dist/api/auth/token-packs.d.ts.map +1 -1
- package/dist/api/auth/token-packs.js +2 -1
- package/dist/api/auth/usage-by-model.d.ts +25 -0
- package/dist/api/auth/usage-by-model.d.ts.map +1 -0
- package/dist/api/auth/usage-by-model.js +95 -0
- package/dist/api/auth/usage.d.ts +26 -0
- package/dist/api/auth/usage.d.ts.map +1 -0
- package/dist/api/auth/usage.js +127 -0
- package/dist/api/auth/user-tokens.d.ts.map +1 -1
- package/dist/api/auth/user-tokens.js +36 -2
- package/dist/api/auth/wallet/route.d.ts +17 -0
- package/dist/api/auth/wallet/route.d.ts.map +1 -0
- package/dist/api/auth/wallet/route.js +68 -0
- package/dist/api/auth/wallet.d.ts +16 -0
- package/dist/api/auth/wallet.d.ts.map +1 -0
- package/dist/api/auth/wallet.js +71 -0
- package/dist/api/public/gateway-models.d.ts +25 -0
- package/dist/api/public/gateway-models.d.ts.map +1 -0
- package/dist/api/public/gateway-models.js +49 -0
- package/dist/api/public/pricing-summary.d.ts +46 -0
- package/dist/api/public/pricing-summary.d.ts.map +1 -0
- package/dist/api/public/pricing-summary.js +70 -0
- package/dist/api/public/prompts/[id]/stats.d.ts +15 -0
- package/dist/api/public/prompts/[id]/stats.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/stats.js +37 -0
- package/dist/api/public/prompts/[id]/view.d.ts +15 -0
- package/dist/api/public/prompts/[id]/view.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/view.js +41 -0
- package/dist/api/public/prompts/slug/[slug].d.ts +15 -0
- package/dist/api/public/prompts/slug/[slug].d.ts.map +1 -0
- package/dist/api/public/prompts/slug/[slug].js +40 -0
- package/dist/api/public/prompts/user/[username].d.ts +17 -0
- package/dist/api/public/prompts/user/[username].d.ts.map +1 -0
- package/dist/api/public/prompts/user/[username].js +51 -0
- package/dist/api/public/prompts.d.ts +15 -0
- package/dist/api/public/prompts.d.ts.map +1 -0
- package/dist/api/public/prompts.js +45 -0
- package/dist/api/public/token-packs.d.ts +11 -0
- package/dist/api/public/token-packs.d.ts.map +1 -0
- package/dist/api/public/token-packs.js +25 -0
- package/dist/api/public/token-pricing.d.ts +44 -0
- package/dist/api/public/token-pricing.d.ts.map +1 -0
- package/dist/api/public/token-pricing.js +168 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts +52 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/artifacts.js +217 -0
- package/dist/api/public/v1/_lib/auth.d.ts +43 -0
- package/dist/api/public/v1/_lib/auth.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/auth.js +108 -0
- package/dist/api/public/v1/_lib/errors.d.ts +17 -0
- package/dist/api/public/v1/_lib/errors.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/errors.js +16 -0
- package/dist/api/public/v1/_lib/log.d.ts +29 -0
- package/dist/api/public/v1/_lib/log.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/log.js +68 -0
- package/dist/api/public/v1/_lib/quota.d.ts +24 -0
- package/dist/api/public/v1/_lib/quota.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/quota.js +118 -0
- package/dist/api/public/v1/_lib/router.d.ts +54 -0
- package/dist/api/public/v1/_lib/router.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/router.js +119 -0
- package/dist/api/public/v1/connect.d.ts +20 -0
- package/dist/api/public/v1/connect.d.ts.map +1 -0
- package/dist/api/public/v1/connect.js +119 -0
- package/dist/api/public/v1/doc.d.ts +239 -0
- package/dist/api/public/v1/doc.d.ts.map +1 -0
- package/dist/api/public/v1/doc.js +253 -0
- package/dist/api/public/v1/history/[id].d.ts +92 -0
- package/dist/api/public/v1/history/[id].d.ts.map +1 -0
- package/dist/api/public/v1/history/[id].js +176 -0
- package/dist/api/public/v1/history.d.ts +30 -0
- package/dist/api/public/v1/history.d.ts.map +1 -0
- package/dist/api/public/v1/history.js +142 -0
- package/dist/api/public/v1/image-ai.d.ts +24 -0
- package/dist/api/public/v1/image-ai.d.ts.map +1 -0
- package/dist/api/public/v1/image-ai.js +233 -0
- package/dist/api/public/v1/prompts.d.ts +19 -0
- package/dist/api/public/v1/prompts.d.ts.map +1 -0
- package/dist/api/public/v1/prompts.js +107 -0
- package/dist/api/public/v1/provider.d.ts +16 -0
- package/dist/api/public/v1/provider.d.ts.map +1 -0
- package/dist/api/public/v1/provider.js +130 -0
- package/dist/api/public/v1/purchase.d.ts +11 -0
- package/dist/api/public/v1/purchase.d.ts.map +1 -0
- package/dist/api/public/v1/purchase.js +18 -0
- package/dist/api/public/v1/status.d.ts +35 -0
- package/dist/api/public/v1/status.d.ts.map +1 -0
- package/dist/api/public/v1/status.js +163 -0
- package/dist/api/public/v1/text-ai.d.ts +26 -0
- package/dist/api/public/v1/text-ai.d.ts.map +1 -0
- package/dist/api/public/v1/text-ai.js +239 -0
- package/dist/api/public/webhook.d.ts.map +1 -1
- package/dist/api/public/webhook.js +50 -39
- package/dist/api/track-usage.d.ts +12 -0
- package/dist/api/track-usage.d.ts.map +1 -0
- package/dist/api/track-usage.js +37 -0
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/DocUsageCustom.js +6 -6
- package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
- package/dist/components/admin/UserTokenTab.js +170 -23
- package/dist/components/auth/AuthDashboardAi.d.ts +2 -0
- package/dist/components/auth/AuthDashboardAi.d.ts.map +1 -0
- package/dist/components/auth/AuthDashboardAi.js +53 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts +277 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts.map +1 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.js +276 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/scripts/migrate-tokens-to-wallet.d.ts +13 -0
- package/dist/scripts/migrate-tokens-to-wallet.d.ts.map +1 -0
- package/dist/scripts/migrate-tokens-to-wallet.js +165 -0
- package/dist/server/__tests__/billing.test.d.ts +5 -0
- package/dist/server/__tests__/billing.test.d.ts.map +1 -0
- package/dist/server/__tests__/billing.test.js +223 -0
- package/dist/server/ai-client.d.ts +59 -0
- package/dist/server/ai-client.d.ts.map +1 -0
- package/dist/server/ai-client.js +111 -0
- package/dist/server/ai-generation-service.d.ts +66 -0
- package/dist/server/ai-generation-service.d.ts.map +1 -0
- package/dist/server/ai-generation-service.js +274 -0
- package/dist/server/billing.d.ts +200 -0
- package/dist/server/billing.d.ts.map +1 -0
- package/dist/server/billing.js +488 -0
- package/dist/server/gateway-service.d.ts +13 -0
- package/dist/server/gateway-service.d.ts.map +1 -0
- package/dist/server/gateway-service.js +161 -0
- package/dist/server/global-settings.d.ts +16 -0
- package/dist/server/global-settings.d.ts.map +1 -0
- package/dist/server/global-settings.js +42 -0
- package/dist/server/model-filter.d.ts +25 -0
- package/dist/server/model-filter.d.ts.map +1 -0
- package/dist/server/model-filter.js +240 -0
- package/dist/server/ocr.d.ts +39 -0
- package/dist/server/ocr.d.ts.map +1 -0
- package/dist/server/ocr.js +280 -0
- package/dist/server/openai-client.d.ts +19 -0
- package/dist/server/openai-client.d.ts.map +1 -0
- package/dist/server/openai-client.js +26 -0
- package/dist/server/pricing-config.d.ts +18 -0
- package/dist/server/pricing-config.d.ts.map +1 -0
- package/dist/server/pricing-config.js +94 -0
- package/dist/server/pricing-validator.d.ts +41 -0
- package/dist/server/pricing-validator.d.ts.map +1 -0
- package/dist/server/pricing-validator.js +113 -0
- package/dist/server/pricing.d.ts +121 -0
- package/dist/server/pricing.d.ts.map +1 -0
- package/dist/server/pricing.js +225 -0
- package/dist/server/quota.d.ts +66 -0
- package/dist/server/quota.d.ts.map +1 -0
- package/dist/server/quota.js +538 -0
- package/dist/server/wallet-repair.d.ts +32 -0
- package/dist/server/wallet-repair.d.ts.map +1 -0
- package/dist/server/wallet-repair.js +189 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +87 -16
- package/dist/sitemap/handlers/prompts.d.ts +6 -0
- package/dist/sitemap/handlers/prompts.d.ts.map +1 -0
- package/dist/sitemap/handlers/prompts.js +72 -0
- package/dist/sitemap/handlers/users.d.ts +6 -0
- package/dist/sitemap/handlers/users.d.ts.map +1 -0
- package/dist/sitemap/handlers/users.js +80 -0
- package/dist/sitemap/manifest.d.ts +8 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +27 -0
- package/dist/types/gateway.d.ts +40 -0
- package/dist/types/gateway.d.ts.map +1 -0
- package/dist/types/gateway.js +4 -0
- package/dist/types/quota.d.ts +74 -0
- package/dist/types/quota.d.ts.map +1 -0
- package/dist/types/quota.js +11 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +17 -0
- package/dist/web/admin/AdminTokenPacksPage.js +1 -1
- package/dist/web/admin/BillingAnalyticsPage.d.ts +2 -0
- package/dist/web/admin/BillingAnalyticsPage.d.ts.map +1 -0
- package/dist/web/admin/BillingAnalyticsPage.js +141 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts +2 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts.map +1 -0
- package/dist/web/admin/GlobalAISettingsPage.js +93 -0
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
- package/dist/web/admin/UserTokenPage.js +20 -7
- package/dist/web/auth/AISettingsPage.d.ts +2 -0
- package/dist/web/auth/AISettingsPage.d.ts.map +1 -0
- package/dist/web/auth/AISettingsPage.js +258 -0
- package/dist/web/auth/APIKeysPage.d.ts +2 -0
- package/dist/web/auth/APIKeysPage.d.ts.map +1 -0
- package/dist/web/auth/APIKeysPage.js +154 -0
- package/dist/web/auth/HistoryPage.d.ts +2 -0
- package/dist/web/auth/HistoryPage.d.ts.map +1 -0
- package/dist/web/auth/HistoryPage.js +279 -0
- package/dist/web/auth/PromptsPage.d.ts +5 -0
- package/dist/web/auth/PromptsPage.d.ts.map +1 -0
- package/dist/web/auth/PromptsPage.js +137 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +88 -31
- package/dist/web/auth/UsageAndTokensPage.d.ts +2 -0
- package/dist/web/auth/UsageAndTokensPage.d.ts.map +1 -0
- package/dist/web/auth/UsageAndTokensPage.js +157 -0
- package/dist/web/auth/UsagePage.d.ts +2 -0
- package/dist/web/auth/UsagePage.d.ts.map +1 -0
- package/dist/web/auth/UsagePage.js +62 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts +13 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts.map +1 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.js +16 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts +19 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts.map +1 -0
- package/dist/web/auth/components/ModelUsageTable.js +37 -0
- package/dist/web/auth/components/PurchaseButton.d.ts +7 -0
- package/dist/web/auth/components/PurchaseButton.d.ts.map +1 -0
- package/dist/web/auth/components/PurchaseButton.js +13 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts +20 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts.map +1 -0
- package/dist/web/auth/components/TokenHistoryCard.js +76 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts +24 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts.map +1 -0
- package/dist/web/auth/components/TokenKpiGrid.js +38 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts +11 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByDayChart.js +32 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts +12 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByModelBarChart.js +32 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts +9 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts.map +1 -0
- package/dist/web/auth/components/WalletStatusCard.js +50 -0
- package/dist/web/components/ImageGenerative.d.ts +3 -1
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +139 -52
- package/dist/web/components/TextareaGenerative.d.ts +3 -1
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
- package/dist/web/components/TextareaGenerative.js +10 -5
- package/dist/web/public/PromptDetailPage.d.ts +25 -0
- package/dist/web/public/PromptDetailPage.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPage.js +71 -0
- package/dist/web/public/PromptDetailPageServer.d.ts +15 -0
- package/dist/web/public/PromptDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPageServer.js +68 -0
- package/dist/web/public/PublicPromptsPage.d.ts +5 -0
- package/dist/web/public/PublicPromptsPage.d.ts.map +1 -0
- package/dist/web/public/PublicPromptsPage.js +110 -0
- package/dist/web/public/PurchaseTokensPage.d.ts +2 -0
- package/dist/web/public/PurchaseTokensPage.d.ts.map +1 -0
- package/dist/web/public/PurchaseTokensPage.js +98 -0
- package/dist/web/public/UserAvatar.d.ts +13 -0
- package/dist/web/public/UserAvatar.d.ts.map +1 -0
- package/dist/web/public/UserAvatar.js +13 -0
- package/dist/web/public/UserDetailPageServer.d.ts +15 -0
- package/dist/web/public/UserDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/UserDetailPageServer.js +31 -0
- package/dist/web/public/UserPromptsPage.d.ts +9 -0
- package/dist/web/public/UserPromptsPage.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPage.js +112 -0
- package/dist/web/public/UserPromptsPageServer.d.ts +15 -0
- package/dist/web/public/UserPromptsPageServer.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPageServer.js +31 -0
- package/package.json +18 -9
- package/supabase/migrations/20251125000000_ai_tokens.sql +7 -0
- package/supabase/migrations/20260123100002_user_token_quota_monthly copy.sql +173 -0
- package/supabase/migrations/20260128100003_update_and_add_table.sql +368 -0
- package/supabase/migrations/20260128120000_seed_providers_models.sql +78 -0
- package/supabase/migrations/20260128131405_add_api_key_id_to_ledgers.sql +41 -0
- package/supabase/migrations/20260128140000_ai_artifacts_storage.sql +99 -0
- package/supabase/migrations/20260128140002_ai_user_settings.sql +57 -0
- package/supabase/migrations/20260128150000_drop_ai_user_settings.sql +21 -0
- package/supabase/migrations/20260128160000_wallet_billing_system.sql +192 -0
- package/supabase/migrations/20260128160001_wallet_rpc_functions.sql +165 -0
- package/supabase/migrations/20260128170000_add_pack_coef_to_token_packs.sql +30 -0
- package/supabase/migrations/20260129120000_wallet_view_rpc.sql +41 -0
- package/supabase/migrations/20260129220003_update_pack_margins.sql +31 -0
- package/supabase/migrations/20260129330004_ai_user_prompts.sql +151 -0
- package/supabase/migrations/20260129330005_ai_prompts_ip_tracking.sql +92 -0
- package/supabase/migrations/20260129330006_ai_prompts_slug.sql +64 -0
- package/supabase/migrations/20260129330007_ai_prompts_view_slug.sql +26 -0
- package/supabase/migrations/20260129440000_ai_prompts_view_username.sql +33 -0
- package/supabase/migrations/20260129450000_ai_prompts_add_lang.sql +40 -0
- package/supabase/migrations/20260131000000_extract_model_prompt_in_ledger.sql +92 -0
- package/supabase/migrations/20260131140000_fix_duplicate_purchases.sql +64 -0
- package/supabase/migrations/20260201120000_module-ai_default_models.sql +63 -0
- package/supabase/migrations/20260201130000_module-ai_remove_provider_tables.sql +17 -0
- package/supabase/migrations-down/20251217120000_user_token_quota_monthly.sql +34 -0
- package/supabase/migrations-down/20260128131405_add_api_key_id_to_ledgers.sql +25 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core billing functions for wallet-based AI token system
|
|
3
|
+
*
|
|
4
|
+
* RÈGLE ÉCONOMIQUE FONDAMENTALE :
|
|
5
|
+
* ================================
|
|
6
|
+
* Un pack vendu X$ donne un budget fournisseur limité (ex: 5$ vendu => 2$ budget provider)
|
|
7
|
+
* Le reste (3$) = marge + infrastructure.
|
|
8
|
+
*
|
|
9
|
+
* L'utilisateur NE PEUT PAS générer des milliers de recettes pour 5$.
|
|
10
|
+
* Le système ne doit JAMAIS permettre d'être à perte.
|
|
11
|
+
*
|
|
12
|
+
* Pour garantir cela, CHAQUE action a un coût minimum de vente (minSellUsd):
|
|
13
|
+
* - generate-recipe-text: min 0.20$ (action à forte valeur)
|
|
14
|
+
* - generate-image: min calculé selon le modèle
|
|
15
|
+
* - autocomplete UI (input/textarea): min 0$ (micro-calls)
|
|
16
|
+
*
|
|
17
|
+
* Le calcul de marge cible est 60% sur le prix de vente:
|
|
18
|
+
* sellCostUsd = providerCostUsd / (1 - 0.60) = providerCostUsd / 0.40
|
|
19
|
+
*
|
|
20
|
+
* MAIS on respecte TOUJOURS la contrainte pack:
|
|
21
|
+
* - on ne peut pas dépasser le budget provider restant
|
|
22
|
+
* - on applique le minimum par action si configuré
|
|
23
|
+
*/
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// CORE FUNCTIONS
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Compute provider cost in USD from usage and pricing
|
|
29
|
+
* @param usage Actual tokens/images used
|
|
30
|
+
* @param pricing Provider pricing ($/token, $/image)
|
|
31
|
+
* @returns Total provider cost in USD
|
|
32
|
+
*/
|
|
33
|
+
export function computeProviderCostUsd(usage, pricing) {
|
|
34
|
+
let cost = 0;
|
|
35
|
+
console.log("[BILLING] computeProviderCostUsd START", {
|
|
36
|
+
usage,
|
|
37
|
+
pricing,
|
|
38
|
+
});
|
|
39
|
+
// Input tokens
|
|
40
|
+
if (usage.inputTokens && pricing.inputUsdPerToken) {
|
|
41
|
+
const inputCost = usage.inputTokens * pricing.inputUsdPerToken;
|
|
42
|
+
console.log("[BILLING] Input tokens cost:", {
|
|
43
|
+
inputTokens: usage.inputTokens,
|
|
44
|
+
inputUsdPerToken: pricing.inputUsdPerToken,
|
|
45
|
+
inputCost,
|
|
46
|
+
});
|
|
47
|
+
cost += inputCost;
|
|
48
|
+
}
|
|
49
|
+
// Output tokens
|
|
50
|
+
if (usage.outputTokens && pricing.outputUsdPerToken) {
|
|
51
|
+
const outputCost = usage.outputTokens * pricing.outputUsdPerToken;
|
|
52
|
+
console.log("[BILLING] Output tokens cost:", {
|
|
53
|
+
outputTokens: usage.outputTokens,
|
|
54
|
+
outputUsdPerToken: pricing.outputUsdPerToken,
|
|
55
|
+
outputCost,
|
|
56
|
+
});
|
|
57
|
+
cost += outputCost;
|
|
58
|
+
}
|
|
59
|
+
// Cache read tokens
|
|
60
|
+
if (usage.cacheReadTokens && pricing.cacheReadUsdPerToken) {
|
|
61
|
+
const cacheCost = usage.cacheReadTokens * pricing.cacheReadUsdPerToken;
|
|
62
|
+
console.log("[BILLING] Cache read tokens cost:", {
|
|
63
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
64
|
+
cacheReadUsdPerToken: pricing.cacheReadUsdPerToken,
|
|
65
|
+
cacheCost,
|
|
66
|
+
});
|
|
67
|
+
cost += cacheCost;
|
|
68
|
+
}
|
|
69
|
+
// Web search
|
|
70
|
+
if (usage.webSearchUnits && pricing.webSearchUsdPerUnit) {
|
|
71
|
+
const searchCost = usage.webSearchUnits * pricing.webSearchUsdPerUnit;
|
|
72
|
+
console.log("[BILLING] Web search cost:", {
|
|
73
|
+
webSearchUnits: usage.webSearchUnits,
|
|
74
|
+
webSearchUsdPerUnit: pricing.webSearchUsdPerUnit,
|
|
75
|
+
searchCost,
|
|
76
|
+
});
|
|
77
|
+
cost += searchCost;
|
|
78
|
+
}
|
|
79
|
+
// Images
|
|
80
|
+
if (usage.images && pricing.imageUsdPerImage) {
|
|
81
|
+
const imageCost = usage.images * pricing.imageUsdPerImage;
|
|
82
|
+
console.log("[BILLING] Images cost:", {
|
|
83
|
+
images: usage.images,
|
|
84
|
+
imageUsdPerImage: pricing.imageUsdPerImage,
|
|
85
|
+
imageCost,
|
|
86
|
+
});
|
|
87
|
+
cost += imageCost;
|
|
88
|
+
}
|
|
89
|
+
const roundedCost = roundUsd(cost);
|
|
90
|
+
console.log("[BILLING] computeProviderCostUsd RESULT:", {
|
|
91
|
+
rawCost: cost,
|
|
92
|
+
roundedCost,
|
|
93
|
+
});
|
|
94
|
+
return roundedCost;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Compute token debit from wallet (with automatic margin smoothing)
|
|
98
|
+
* @param wallet Current wallet state
|
|
99
|
+
* @param providerCostUsd Provider cost for this transaction
|
|
100
|
+
* @returns Debit details (tokens, sell value, margin)
|
|
101
|
+
*/
|
|
102
|
+
export function computeDebitFromWallet(wallet, providerCostUsd) {
|
|
103
|
+
console.log("[BILLING] computeDebitFromWallet START", {
|
|
104
|
+
wallet,
|
|
105
|
+
providerCostUsd,
|
|
106
|
+
});
|
|
107
|
+
// Validate wallet state
|
|
108
|
+
if (wallet.tokenBalance <= 0) {
|
|
109
|
+
throw new Error("Insufficient token balance");
|
|
110
|
+
}
|
|
111
|
+
// FALLBACK: If wallet is at 0 USD but tokens exist (legacy credits, manual edits, etc.)
|
|
112
|
+
// Use default pack_coef = 1.35 (35% margin) to compute rates
|
|
113
|
+
const DEFAULT_PACK_COEF = 1.35;
|
|
114
|
+
const hasWalletBudget = wallet.walletProviderBudgetUsd > 0 && wallet.walletSellValueUsd > 0;
|
|
115
|
+
let usdProviderPerToken;
|
|
116
|
+
let usdSellPerToken;
|
|
117
|
+
console.log("[BILLING] Wallet budget status:", {
|
|
118
|
+
hasWalletBudget,
|
|
119
|
+
walletProviderBudgetUsd: wallet.walletProviderBudgetUsd,
|
|
120
|
+
walletSellValueUsd: wallet.walletSellValueUsd,
|
|
121
|
+
tokenBalance: wallet.tokenBalance,
|
|
122
|
+
});
|
|
123
|
+
if (hasWalletBudget) {
|
|
124
|
+
// Normal case: use smoothed rates from wallet
|
|
125
|
+
usdProviderPerToken = wallet.walletProviderBudgetUsd / wallet.tokenBalance;
|
|
126
|
+
usdSellPerToken = wallet.walletSellValueUsd / wallet.tokenBalance;
|
|
127
|
+
console.log("[BILLING] Using wallet smoothed rates:", {
|
|
128
|
+
usdProviderPerToken,
|
|
129
|
+
usdSellPerToken,
|
|
130
|
+
margin: ((usdSellPerToken - usdProviderPerToken) / usdSellPerToken) * 100 + "%",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Fallback case: reconstruct rates using default coef
|
|
135
|
+
// Assume token price = provider cost × coef
|
|
136
|
+
// We estimate: 1 token ≈ $0.00005 sell (typical for 100K @ $5)
|
|
137
|
+
const FALLBACK_USD_SELL_PER_TOKEN = 0.00005;
|
|
138
|
+
usdSellPerToken = FALLBACK_USD_SELL_PER_TOKEN;
|
|
139
|
+
usdProviderPerToken = usdSellPerToken / DEFAULT_PACK_COEF;
|
|
140
|
+
console.log("[BILLING] Using fallback rates:", {
|
|
141
|
+
DEFAULT_PACK_COEF,
|
|
142
|
+
FALLBACK_USD_SELL_PER_TOKEN,
|
|
143
|
+
usdProviderPerToken,
|
|
144
|
+
usdSellPerToken,
|
|
145
|
+
margin: ((usdSellPerToken - usdProviderPerToken) / usdSellPerToken) * 100 + "%",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Compute tokens to debit (round up)
|
|
149
|
+
const debitTokens = Math.ceil(providerCostUsd / usdProviderPerToken);
|
|
150
|
+
// Compute sell value and margin
|
|
151
|
+
const sellUsd = roundUsd(debitTokens * usdSellPerToken);
|
|
152
|
+
const marginUsd = roundUsd(sellUsd - providerCostUsd);
|
|
153
|
+
console.log("[BILLING] Debit calculation:", {
|
|
154
|
+
providerCostUsd,
|
|
155
|
+
usdProviderPerToken,
|
|
156
|
+
debitTokens,
|
|
157
|
+
usdSellPerToken,
|
|
158
|
+
sellUsd,
|
|
159
|
+
marginUsd,
|
|
160
|
+
marginPercent: ((marginUsd / sellUsd) * 100).toFixed(2) + "%",
|
|
161
|
+
});
|
|
162
|
+
// Safety check
|
|
163
|
+
if (debitTokens > wallet.tokenBalance) {
|
|
164
|
+
throw new Error(`Insufficient balance: need ${debitTokens}, have ${wallet.tokenBalance}`);
|
|
165
|
+
}
|
|
166
|
+
console.log("[BILLING] computeDebitFromWallet RESULT:", {
|
|
167
|
+
debitTokens,
|
|
168
|
+
sellUsd,
|
|
169
|
+
marginUsd,
|
|
170
|
+
usdProviderPerToken: roundUsd(usdProviderPerToken),
|
|
171
|
+
usdSellPerToken: roundUsd(usdSellPerToken),
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
debitTokens,
|
|
175
|
+
sellUsd,
|
|
176
|
+
marginUsd,
|
|
177
|
+
usdProviderPerToken: roundUsd(usdProviderPerToken),
|
|
178
|
+
usdSellPerToken: roundUsd(usdSellPerToken),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Compute wallet update for pack purchase
|
|
183
|
+
* @param pack Pack details (tokens, price, coef)
|
|
184
|
+
* @returns Wallet deltas to add
|
|
185
|
+
*/
|
|
186
|
+
export function computeWalletUpdateForPurchase(pack) {
|
|
187
|
+
// Provider budget = what we can spend on provider APIs
|
|
188
|
+
const providerBudgetAddedUsd = roundUsd(pack.packPriceUsd / pack.packCoef);
|
|
189
|
+
// Sell value = what user paid (our revenue)
|
|
190
|
+
const sellValueAddedUsd = roundUsd(pack.packPriceUsd);
|
|
191
|
+
return {
|
|
192
|
+
providerBudgetAddedUsd,
|
|
193
|
+
sellValueAddedUsd,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Estimate debit tokens for display (using pack coef)
|
|
198
|
+
* Useful for public pricing page before user has wallet
|
|
199
|
+
* @param providerCostUsd Provider cost
|
|
200
|
+
* @param packCoef Pack margin coefficient
|
|
201
|
+
* @param packTokens Pack size
|
|
202
|
+
* @param packPriceUsd Pack price
|
|
203
|
+
* @returns Estimated tokens
|
|
204
|
+
*/
|
|
205
|
+
export function estimateDebitTokens(providerCostUsd, packCoef, packTokens, packPriceUsd) {
|
|
206
|
+
// Simulate wallet rates for this pack
|
|
207
|
+
const providerBudgetUsd = packPriceUsd / packCoef;
|
|
208
|
+
const usdProviderPerToken = providerBudgetUsd / packTokens;
|
|
209
|
+
return Math.ceil(providerCostUsd / usdProviderPerToken);
|
|
210
|
+
}
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// UTILITIES
|
|
213
|
+
// ============================================================================
|
|
214
|
+
/**
|
|
215
|
+
* Round USD to 10 decimals (handles nano-dollar precision)
|
|
216
|
+
*/
|
|
217
|
+
function roundUsd(value) {
|
|
218
|
+
return Math.round(value * 10_000_000_000) / 10_000_000_000;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Validate that pricing has required fields for usage type
|
|
222
|
+
*/
|
|
223
|
+
export function validatePricing(pricing, usageType) {
|
|
224
|
+
if (usageType === "image") {
|
|
225
|
+
return pricing.imageUsdPerImage !== undefined;
|
|
226
|
+
}
|
|
227
|
+
return (pricing.inputUsdPerToken !== undefined ||
|
|
228
|
+
pricing.outputUsdPerToken !== undefined);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Minimum sell cost per action type (USD)
|
|
232
|
+
* Ces minimums garantissent qu'on ne sera jamais à perte
|
|
233
|
+
* et que les actions à forte valeur ajoutée sont facturées correctement
|
|
234
|
+
*/
|
|
235
|
+
const ACTION_MIN_SELL_USD = {
|
|
236
|
+
"generate-recipe-text": 0.1, // Génération complète de recette : min 0.10$
|
|
237
|
+
"generate-image": 0.1, // Génération image : min 0.10$
|
|
238
|
+
"generate-text": 0.001, // Génération texte générique : min 0.001$ (évite coûts quasi-nuls)
|
|
239
|
+
autocomplete: 0.0, // Autocomplétion UI : micro-calls, pas de minimum
|
|
240
|
+
"suggestion-input": 0.0, // Suggestions : micro-calls
|
|
241
|
+
"suggestion-textarea": 0.0, // Suggestions : micro-calls
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Get minimum sell cost for action type
|
|
245
|
+
*/
|
|
246
|
+
function getActionMinSellUsd(actionType) {
|
|
247
|
+
return ACTION_MIN_SELL_USD[actionType] || 0;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get current wallet state for a user
|
|
251
|
+
* CENTRALIZED - This is the single source of truth for wallet state
|
|
252
|
+
*/
|
|
253
|
+
export async function getWalletState(userId) {
|
|
254
|
+
const { getSupabaseServiceClient } = await import("@lastbrain/core/server");
|
|
255
|
+
const { logger } = await import("@lastbrain/core");
|
|
256
|
+
const supabase = await getSupabaseServiceClient();
|
|
257
|
+
logger.info(`[getWalletState] Fetching wallet for user: ${userId}`);
|
|
258
|
+
// Get token balance (ledger uses owner_id not user_id)
|
|
259
|
+
const { data: ledgerData, error: ledgerError } = await supabase
|
|
260
|
+
.from("user_token_ledger")
|
|
261
|
+
.select("amount")
|
|
262
|
+
.eq("owner_id", userId);
|
|
263
|
+
if (ledgerError) {
|
|
264
|
+
logger.error("[getWalletState] Error fetching ledger:", ledgerError);
|
|
265
|
+
throw new Error("Failed to fetch token balance");
|
|
266
|
+
}
|
|
267
|
+
const tokenBalance = (ledgerData || []).reduce((sum, row) => sum + (row.amount || 0), 0);
|
|
268
|
+
logger.info(`[getWalletState] Token balance for user ${userId}: ${tokenBalance}`);
|
|
269
|
+
// Get wallet USD values (wallet uses user_id)
|
|
270
|
+
const { data: walletData, error: walletError } = await supabase
|
|
271
|
+
.from("user_token_wallet")
|
|
272
|
+
.select("wallet_provider_budget_usd, wallet_sell_value_usd")
|
|
273
|
+
.eq("user_id", userId)
|
|
274
|
+
.single();
|
|
275
|
+
logger.info(`[getWalletState] Wallet query result - data: ${JSON.stringify(walletData)}, error: ${JSON.stringify(walletError)}`);
|
|
276
|
+
if (walletError || !walletData) {
|
|
277
|
+
logger.warn(`[getWalletState] No wallet found for user ${userId}, creating new wallet`);
|
|
278
|
+
// Initialize wallet
|
|
279
|
+
const { data: insertData, error: insertError } = await supabase
|
|
280
|
+
.from("user_token_wallet")
|
|
281
|
+
.insert({
|
|
282
|
+
user_id: userId,
|
|
283
|
+
wallet_provider_budget_usd: 0,
|
|
284
|
+
wallet_sell_value_usd: 0,
|
|
285
|
+
})
|
|
286
|
+
.select()
|
|
287
|
+
.single();
|
|
288
|
+
logger.info(`[getWalletState] Insert result - data: ${JSON.stringify(insertData)}, error: ${JSON.stringify(insertError)}`);
|
|
289
|
+
return {
|
|
290
|
+
tokenBalance,
|
|
291
|
+
walletProviderBudgetUsd: 0,
|
|
292
|
+
walletSellValueUsd: 0,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const result = {
|
|
296
|
+
tokenBalance,
|
|
297
|
+
walletProviderBudgetUsd: parseFloat(walletData.wallet_provider_budget_usd.toString()),
|
|
298
|
+
walletSellValueUsd: parseFloat(walletData.wallet_sell_value_usd.toString()),
|
|
299
|
+
};
|
|
300
|
+
logger.info(`[getWalletState] Returning wallet state for user ${userId}: ${JSON.stringify(result)}`);
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* FONCTION CENTRALE DE BILLING
|
|
305
|
+
* ============================
|
|
306
|
+
* Calcule et débite l'utilisation IA selon les règles économiques.
|
|
307
|
+
*
|
|
308
|
+
* Cette fonction est la SEULE source de vérité pour tous les calculs de billing.
|
|
309
|
+
* Elle est appelée par TOUS les endpoints (generate-text, generate-image, recipes, etc.)
|
|
310
|
+
*
|
|
311
|
+
* RÈGLES DE CALCUL :
|
|
312
|
+
* 1. Marge cible = 60% du prix de vente
|
|
313
|
+
* => sellCostUsd = providerCostUsd / (1 - 0.60) = providerCostUsd / 0.40 = providerCostUsd * 2.5
|
|
314
|
+
*
|
|
315
|
+
* 2. Minimum par action (si configuré)
|
|
316
|
+
* => sellCostUsd = max(sellCostUsd calculé, minSellUsd)
|
|
317
|
+
*
|
|
318
|
+
* 3. Contrainte budget provider
|
|
319
|
+
* => on ne peut jamais débiter plus que le budget provider restant
|
|
320
|
+
*
|
|
321
|
+
* 4. Arrondi safe (ceil) pour éviter sous-facturation
|
|
322
|
+
*
|
|
323
|
+
* LOGGING : log ANONYME de chaque débit pour contrôle
|
|
324
|
+
*/
|
|
325
|
+
export function computeAndDebitAIUsage(params) {
|
|
326
|
+
const { userId, actionType, providerCostUsd, wallet, metadata, minSellUsdOverride, } = params;
|
|
327
|
+
// Hash user ID for anonymous logging (first 8 chars)
|
|
328
|
+
const userHash = userId.substring(0, 8);
|
|
329
|
+
// 1. Calculate initial costs
|
|
330
|
+
// Formula: sellCost = providerCost / (1 - margin%) = providerCost / 0.40
|
|
331
|
+
const MARGIN_TARGET = 0.6; // 60% margin on sell price
|
|
332
|
+
let sellCostUsd = providerCostUsd / (1 - MARGIN_TARGET);
|
|
333
|
+
let adjustedProviderCostUsd = providerCostUsd;
|
|
334
|
+
// 2. Apply minimum sell cost for action type
|
|
335
|
+
const minSellUsd = minSellUsdOverride !== undefined
|
|
336
|
+
? minSellUsdOverride
|
|
337
|
+
: getActionMinSellUsd(actionType);
|
|
338
|
+
if (sellCostUsd < minSellUsd) {
|
|
339
|
+
sellCostUsd = minSellUsd;
|
|
340
|
+
// IMPORTANT: When minimum is applied, recalculate provider cost proportionally
|
|
341
|
+
// to maintain the 60% margin: providerCost = sellCost * (1 - 0.60) = sellCost * 0.40
|
|
342
|
+
adjustedProviderCostUsd = sellCostUsd * (1 - MARGIN_TARGET);
|
|
343
|
+
}
|
|
344
|
+
// 3. Round up (safe rounding to avoid under-charging)
|
|
345
|
+
sellCostUsd = Math.ceil(sellCostUsd * 1_000_000) / 1_000_000;
|
|
346
|
+
adjustedProviderCostUsd =
|
|
347
|
+
Math.ceil(adjustedProviderCostUsd * 1_000_000) / 1_000_000;
|
|
348
|
+
// 4. Check provider budget sufficiency (use adjusted cost)
|
|
349
|
+
if (wallet.walletProviderBudgetUsd < adjustedProviderCostUsd) {
|
|
350
|
+
const errorMsg = `Insufficient provider budget: have $${wallet.walletProviderBudgetUsd.toFixed(6)}, need $${adjustedProviderCostUsd.toFixed(6)}`;
|
|
351
|
+
console.error(`[BILLING] user=${userHash} action=${actionType} ERROR: ${errorMsg}`);
|
|
352
|
+
return {
|
|
353
|
+
success: false,
|
|
354
|
+
error: "insufficient_credits",
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
// 5. Check sell budget sufficiency
|
|
358
|
+
if (wallet.walletSellValueUsd < sellCostUsd) {
|
|
359
|
+
const errorMsg = `Insufficient sell value: have $${wallet.walletSellValueUsd.toFixed(6)}, need $${sellCostUsd.toFixed(6)}`;
|
|
360
|
+
console.error(`[BILLING] user=${userHash} action=${actionType} ERROR: ${errorMsg}`);
|
|
361
|
+
return {
|
|
362
|
+
success: false,
|
|
363
|
+
error: "insufficient_credits",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
// 6. Calculate margin (use adjusted provider cost)
|
|
367
|
+
const marginUsd = roundUsd(sellCostUsd - adjustedProviderCostUsd);
|
|
368
|
+
const marginPercent = sellCostUsd > 0 ? (marginUsd / sellCostUsd) * 100 : 0;
|
|
369
|
+
// 7. Calculate new wallet state (debit adjusted provider cost)
|
|
370
|
+
const newProviderBudget = wallet.walletProviderBudgetUsd - adjustedProviderCostUsd;
|
|
371
|
+
const newSellValue = wallet.walletSellValueUsd - sellCostUsd;
|
|
372
|
+
const percentRemaining = wallet.walletSellValueUsd > 0
|
|
373
|
+
? (newSellValue / wallet.walletSellValueUsd) * 100
|
|
374
|
+
: 0;
|
|
375
|
+
// 8. Calculate tokens debited (for backward compatibility with ledger)
|
|
376
|
+
// Use current wallet rates
|
|
377
|
+
const tokensDebited = wallet.tokenBalance > 0 && wallet.walletSellValueUsd > 0
|
|
378
|
+
? Math.ceil((sellCostUsd / wallet.walletSellValueUsd) * wallet.tokenBalance)
|
|
379
|
+
: 0;
|
|
380
|
+
// 9. ANONYMOUS LOGGING (no prompt, no content)
|
|
381
|
+
console.log(`[BILLING] user=${userHash} action=${actionType} ` +
|
|
382
|
+
`model=${metadata.model || "unknown"} ` +
|
|
383
|
+
`provider=${metadata.provider || "unknown"} ` +
|
|
384
|
+
`adjustedCost=$${adjustedProviderCostUsd.toFixed(6)} ` +
|
|
385
|
+
`sell=$${sellCostUsd.toFixed(6)} ` +
|
|
386
|
+
`margin=$${marginUsd.toFixed(6)} (${marginPercent.toFixed(2)}%) ` +
|
|
387
|
+
`providerLeft=$${newProviderBudget.toFixed(6)} ` +
|
|
388
|
+
`sellLeft=$${newSellValue.toFixed(6)} ` +
|
|
389
|
+
`remaining=${percentRemaining.toFixed(1)}%`);
|
|
390
|
+
return {
|
|
391
|
+
success: true,
|
|
392
|
+
sellCostUsd,
|
|
393
|
+
providerCostUsd: adjustedProviderCostUsd, // Return adjusted cost for ledger
|
|
394
|
+
marginUsd,
|
|
395
|
+
marginPercent,
|
|
396
|
+
tokensDebited,
|
|
397
|
+
newWallet: {
|
|
398
|
+
walletProviderBudgetUsd: newProviderBudget,
|
|
399
|
+
walletSellValueUsd: newSellValue,
|
|
400
|
+
percentRemaining,
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
// ============================================================================
|
|
405
|
+
// ADMIN WALLET CREDITS MANAGEMENT
|
|
406
|
+
// ============================================================================
|
|
407
|
+
/**
|
|
408
|
+
* Add wallet credits to a user (admin function)
|
|
409
|
+
* Adds both provider budget and sell value proportionally
|
|
410
|
+
* @param userId User ID
|
|
411
|
+
* @param sellValueUsd Amount in USD to add to sell value
|
|
412
|
+
* @param reason Admin reason for adding credits
|
|
413
|
+
* @returns Success status and new wallet state
|
|
414
|
+
*/
|
|
415
|
+
export async function addWalletCredits(userId, sellValueUsd, reason) {
|
|
416
|
+
const { getSupabaseServiceClient } = await import("@lastbrain/core/server");
|
|
417
|
+
const { logger } = await import("@lastbrain/core");
|
|
418
|
+
const supabase = await getSupabaseServiceClient();
|
|
419
|
+
try {
|
|
420
|
+
// Calculate provider budget proportionally (40% of sell value for 60% margin)
|
|
421
|
+
const MARGIN_TARGET = 0.6;
|
|
422
|
+
const providerBudgetUsd = sellValueUsd * (1 - MARGIN_TARGET); // 40% goes to provider
|
|
423
|
+
// Insert into ledger
|
|
424
|
+
const { error: ledgerError } = await supabase
|
|
425
|
+
.from("user_token_ledger")
|
|
426
|
+
.insert({
|
|
427
|
+
owner_id: userId,
|
|
428
|
+
type: "gift", // Manual credit addition by admin (gift from admin)
|
|
429
|
+
amount: 0, // Tokens not used anymore, but keep for backward compat
|
|
430
|
+
sell_value_added_usd: sellValueUsd,
|
|
431
|
+
provider_budget_added_usd: providerBudgetUsd,
|
|
432
|
+
meta: {
|
|
433
|
+
reason,
|
|
434
|
+
admin_action: true,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
if (ledgerError) {
|
|
438
|
+
logger.error("[addWalletCredits] Ledger insert error:", ledgerError);
|
|
439
|
+
return { success: false, error: ledgerError.message };
|
|
440
|
+
}
|
|
441
|
+
// Update wallet table - First, get current wallet values
|
|
442
|
+
const { data: currentWallet } = await supabase
|
|
443
|
+
.from("user_token_wallet")
|
|
444
|
+
.select("wallet_provider_budget_usd, wallet_sell_value_usd")
|
|
445
|
+
.eq("user_id", userId)
|
|
446
|
+
.single();
|
|
447
|
+
// Calculate new values
|
|
448
|
+
const newProviderBudget = (currentWallet?.wallet_provider_budget_usd || 0) + providerBudgetUsd;
|
|
449
|
+
const newSellValue = (currentWallet?.wallet_sell_value_usd || 0) + sellValueUsd;
|
|
450
|
+
// Upsert wallet
|
|
451
|
+
const { error: upsertError } = await supabase
|
|
452
|
+
.from("user_token_wallet")
|
|
453
|
+
.upsert({
|
|
454
|
+
user_id: userId,
|
|
455
|
+
wallet_provider_budget_usd: newProviderBudget,
|
|
456
|
+
wallet_sell_value_usd: newSellValue,
|
|
457
|
+
updated_at: new Date().toISOString(),
|
|
458
|
+
}, {
|
|
459
|
+
onConflict: "user_id",
|
|
460
|
+
});
|
|
461
|
+
if (upsertError) {
|
|
462
|
+
logger.error("[addWalletCredits] Wallet update error:", upsertError);
|
|
463
|
+
return { success: false, error: upsertError.message };
|
|
464
|
+
}
|
|
465
|
+
// Get new balance from wallet view
|
|
466
|
+
const { data: walletData, error: walletError } = await supabase
|
|
467
|
+
.from("user_token_wallet")
|
|
468
|
+
.select("wallet_sell_value_usd")
|
|
469
|
+
.eq("user_id", userId)
|
|
470
|
+
.single();
|
|
471
|
+
if (walletError || !walletData) {
|
|
472
|
+
logger.error("[addWalletCredits] Wallet query error:", walletError);
|
|
473
|
+
return {
|
|
474
|
+
success: false,
|
|
475
|
+
error: walletError?.message || "Wallet not found",
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
logger.info(`[addWalletCredits] Added $${sellValueUsd} to user ${userId}, new balance: $${walletData.wallet_sell_value_usd}`);
|
|
479
|
+
return {
|
|
480
|
+
success: true,
|
|
481
|
+
newBalance: walletData.wallet_sell_value_usd,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
logger.error("[addWalletCredits] Error:", error);
|
|
486
|
+
return { success: false, error: error.message };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for fetching and caching AI models from Vercel AI Gateway
|
|
3
|
+
*/
|
|
4
|
+
import type { GatewayModelsResponse } from "../types/gateway";
|
|
5
|
+
/**
|
|
6
|
+
* Fetch models from Vercel AI Gateway with fallback cache
|
|
7
|
+
*/
|
|
8
|
+
export declare function fetchGatewayModels(): Promise<GatewayModelsResponse>;
|
|
9
|
+
/**
|
|
10
|
+
* Clear cache (useful for testing or manual refresh)
|
|
11
|
+
*/
|
|
12
|
+
export declare function clearGatewayCache(): void;
|
|
13
|
+
//# sourceMappingURL=gateway-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-service.d.ts","sourceRoot":"","sources":["../../src/server/gateway-service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,kBAAkB,CAAC;AAU1B;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,qBAAqB,CAAC,CA0DzE;AAiHD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for fetching and caching AI models from Vercel AI Gateway
|
|
3
|
+
*/
|
|
4
|
+
import { logger } from "@lastbrain/core";
|
|
5
|
+
const GATEWAY_BASE_URL = process.env.AI_GATEWAY_BASE_URL || "https://ai-gateway.vercel.sh";
|
|
6
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
7
|
+
// In-memory cache fallback
|
|
8
|
+
let cachedModels = null;
|
|
9
|
+
let cacheTimestamp = null;
|
|
10
|
+
/**
|
|
11
|
+
* Fetch models from Vercel AI Gateway with fallback cache
|
|
12
|
+
*/
|
|
13
|
+
export async function fetchGatewayModels() {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
// Check if cache is still valid
|
|
16
|
+
if (cachedModels && cacheTimestamp && now - cacheTimestamp < CACHE_TTL_MS) {
|
|
17
|
+
logger.debug("[Gateway] Returning cached models (still valid)");
|
|
18
|
+
return cachedModels;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(`${GATEWAY_BASE_URL}/v1/models`, {
|
|
22
|
+
method: "GET",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
// Timeout after 10 seconds
|
|
27
|
+
signal: AbortSignal.timeout(10000),
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error(`Gateway returned ${response.status}: ${response.statusText}`);
|
|
31
|
+
}
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
// Transform gateway response to our format
|
|
34
|
+
const transformed = transformGatewayResponse(data);
|
|
35
|
+
// Update cache
|
|
36
|
+
cachedModels = transformed;
|
|
37
|
+
cacheTimestamp = now;
|
|
38
|
+
return transformed;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
logger.error("[Gateway] Failed to fetch models:", error);
|
|
42
|
+
// Return stale cache if available
|
|
43
|
+
if (cachedModels) {
|
|
44
|
+
return {
|
|
45
|
+
...cachedModels,
|
|
46
|
+
stale: true,
|
|
47
|
+
cached_at: cacheTimestamp
|
|
48
|
+
? new Date(cacheTimestamp).toISOString()
|
|
49
|
+
: undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// No cache available, return empty response
|
|
53
|
+
logger.error("[Gateway] No cache available, returning empty providers list");
|
|
54
|
+
return {
|
|
55
|
+
providers: [],
|
|
56
|
+
stale: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Transform gateway API response to our internal format
|
|
62
|
+
*/
|
|
63
|
+
function transformGatewayResponse(data) {
|
|
64
|
+
// Gateway response format: { object: "list", data: [...models] }
|
|
65
|
+
const providers = [];
|
|
66
|
+
// Try data.data first (Vercel Gateway format), fallback to data.models
|
|
67
|
+
const modelsArray = data.data || data.models || [];
|
|
68
|
+
if (Array.isArray(modelsArray) && modelsArray.length > 0) {
|
|
69
|
+
// Group models by provider
|
|
70
|
+
const providerMap = new Map();
|
|
71
|
+
for (const model of modelsArray) {
|
|
72
|
+
const modelData = {
|
|
73
|
+
id: model.id || model.model_id,
|
|
74
|
+
name: model.name || model.display_name || model.id,
|
|
75
|
+
description: model.description,
|
|
76
|
+
type: model.type || inferModelType(model.id),
|
|
77
|
+
provider: extractProvider(model.id),
|
|
78
|
+
context_window: model.context_window || model.context_length,
|
|
79
|
+
max_tokens: model.max_output_tokens || model.max_tokens,
|
|
80
|
+
pricing: model.pricing
|
|
81
|
+
? {
|
|
82
|
+
input: model.pricing.input || model.pricing.prompt || 0,
|
|
83
|
+
output: model.pricing.output || model.pricing.completion || 0,
|
|
84
|
+
image: model.pricing.image,
|
|
85
|
+
request: model.pricing.request,
|
|
86
|
+
web_search: model.pricing.web_search,
|
|
87
|
+
}
|
|
88
|
+
: undefined,
|
|
89
|
+
tags: model.tags || [],
|
|
90
|
+
supports_tools: model.supports_tools || model.supports_function_calling,
|
|
91
|
+
supports_vision: model.supports_vision || model.modalities?.includes("vision"),
|
|
92
|
+
};
|
|
93
|
+
const providerName = modelData.provider;
|
|
94
|
+
if (!providerMap.has(providerName)) {
|
|
95
|
+
providerMap.set(providerName, []);
|
|
96
|
+
}
|
|
97
|
+
providerMap.get(providerName).push(modelData);
|
|
98
|
+
}
|
|
99
|
+
// Convert map to providers array
|
|
100
|
+
for (const [providerName, models] of providerMap) {
|
|
101
|
+
providers.push({
|
|
102
|
+
name: providerName,
|
|
103
|
+
display_name: formatProviderName(providerName),
|
|
104
|
+
models,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { providers };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Extract provider name from model ID (e.g., "openai/gpt-4" -> "openai")
|
|
112
|
+
*/
|
|
113
|
+
function extractProvider(modelId) {
|
|
114
|
+
if (modelId.includes("/")) {
|
|
115
|
+
return modelId.split("/")[0] || "unknown";
|
|
116
|
+
}
|
|
117
|
+
return "unknown";
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Infer model type from ID
|
|
121
|
+
*/
|
|
122
|
+
function inferModelType(modelId) {
|
|
123
|
+
const id = modelId.toLowerCase();
|
|
124
|
+
if (id.includes("dall-e") || id.includes("image") || id.includes("imagen")) {
|
|
125
|
+
return "image";
|
|
126
|
+
}
|
|
127
|
+
if (id.includes("embedding") || id.includes("ada")) {
|
|
128
|
+
return "embedding";
|
|
129
|
+
}
|
|
130
|
+
if (id.includes("whisper") || id.includes("audio")) {
|
|
131
|
+
return "audio";
|
|
132
|
+
}
|
|
133
|
+
return "text";
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Format provider name for display
|
|
137
|
+
*/
|
|
138
|
+
function formatProviderName(provider) {
|
|
139
|
+
const names = {
|
|
140
|
+
openai: "OpenAI",
|
|
141
|
+
anthropic: "Anthropic",
|
|
142
|
+
google: "Google",
|
|
143
|
+
mistral: "Mistral AI",
|
|
144
|
+
meta: "Meta",
|
|
145
|
+
cohere: "Cohere",
|
|
146
|
+
};
|
|
147
|
+
return (names[provider] || provider.charAt(0).toUpperCase() + provider.slice(1));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Count total models across all providers
|
|
151
|
+
*/
|
|
152
|
+
function countModels(response) {
|
|
153
|
+
return response.providers.reduce((sum, p) => sum + p.models.length, 0);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Clear cache (useful for testing or manual refresh)
|
|
157
|
+
*/
|
|
158
|
+
export function clearGatewayCache() {
|
|
159
|
+
cachedModels = null;
|
|
160
|
+
cacheTimestamp = null;
|
|
161
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get global AI configuration (default models for text and image)
|
|
3
|
+
* This is the single source of truth for all AI generation calls
|
|
4
|
+
*/
|
|
5
|
+
export declare function getGlobalAISettings(): Promise<any>;
|
|
6
|
+
/**
|
|
7
|
+
* Get default text model from global settings
|
|
8
|
+
* Returns "provider/model" format
|
|
9
|
+
*/
|
|
10
|
+
export declare function getDefaultTextModel(): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Get default image model from global settings
|
|
13
|
+
* Returns "provider/model" format
|
|
14
|
+
*/
|
|
15
|
+
export declare function getDefaultImageModel(): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=global-settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global-settings.d.ts","sourceRoot":"","sources":["../../src/server/global-settings.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,mBAAmB,iBAyBxC;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAG3D;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAG5D"}
|