@aglyn/plugins-ai 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +25 -0
- package/package.json +60 -0
- package/src/index.d.ts +21 -0
- package/src/index.js +22 -0
- package/src/index.js.map +1 -0
- package/src/lib/activity/ai-activity-actions.d.ts +106 -0
- package/src/lib/activity/ai-activity-actions.js +209 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -0
- package/src/lib/activity/ai-activity.d.ts +219 -0
- package/src/lib/activity/ai-activity.js +251 -0
- package/src/lib/activity/ai-activity.js.map +1 -0
- package/src/lib/billing/ai-overage-charge.d.ts +102 -0
- package/src/lib/billing/ai-overage-charge.js +258 -0
- package/src/lib/billing/ai-overage-charge.js.map +1 -0
- package/src/lib/billing/ai-overage-close.d.ts +62 -0
- package/src/lib/billing/ai-overage-close.js +129 -0
- package/src/lib/billing/ai-overage-close.js.map +1 -0
- package/src/lib/billing/ai-overage-cutover.d.ts +63 -0
- package/src/lib/billing/ai-overage-cutover.js +114 -0
- package/src/lib/billing/ai-overage-cutover.js.map +1 -0
- package/src/lib/billing/ai-overage-events.d.ts +77 -0
- package/src/lib/billing/ai-overage-events.js +174 -0
- package/src/lib/billing/ai-overage-events.js.map +1 -0
- package/src/lib/billing/ai-overage-gate.d.ts +147 -0
- package/src/lib/billing/ai-overage-gate.js +177 -0
- package/src/lib/billing/ai-overage-gate.js.map +1 -0
- package/src/lib/billing/ai-overage-ledger.d.ts +135 -0
- package/src/lib/billing/ai-overage-ledger.js +278 -0
- package/src/lib/billing/ai-overage-ledger.js.map +1 -0
- package/src/lib/billing/ai-overage-standing-writes.d.ts +110 -0
- package/src/lib/billing/ai-overage-standing-writes.js +205 -0
- package/src/lib/billing/ai-overage-standing-writes.js.map +1 -0
- package/src/lib/billing/ai-overage-standing.d.ts +168 -0
- package/src/lib/billing/ai-overage-standing.js +215 -0
- package/src/lib/billing/ai-overage-standing.js.map +1 -0
- package/src/lib/billing/ai-overage-trigger.d.ts +30 -0
- package/src/lib/billing/ai-overage-trigger.js +76 -0
- package/src/lib/billing/ai-overage-trigger.js.map +1 -0
- package/src/lib/components/ai-allotment-editor.component.d.ts +40 -0
- package/src/lib/components/ai-allotment-editor.component.js +213 -0
- package/src/lib/components/ai-allotment-editor.component.js.map +1 -0
- package/src/lib/components/ai-assist-actions-context.d.ts +13 -0
- package/src/lib/components/ai-assist-actions-context.js +21 -0
- package/src/lib/components/ai-assist-actions-context.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.d.ts +51 -0
- package/src/lib/components/ai-assist-provider.component.js +519 -0
- package/src/lib/components/ai-assist-provider.component.js.map +1 -0
- package/src/lib/components/ai-brief-dialog.component.d.ts +56 -0
- package/src/lib/components/ai-brief-dialog.component.js +319 -0
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -0
- package/src/lib/components/ai-collaborator-permissions-column.component.d.ts +32 -0
- package/src/lib/components/ai-collaborator-permissions-column.component.js +132 -0
- package/src/lib/components/ai-collaborator-permissions-column.component.js.map +1 -0
- package/src/lib/components/ai-credits-card.component.d.ts +34 -0
- package/src/lib/components/ai-credits-card.component.js +131 -0
- package/src/lib/components/ai-credits-card.component.js.map +1 -0
- package/src/lib/components/ai-credits-columns.component.d.ts +56 -0
- package/src/lib/components/ai-credits-columns.component.js +118 -0
- package/src/lib/components/ai-credits-columns.component.js.map +1 -0
- package/src/lib/components/ai-crm-email-draft.component.d.ts +17 -0
- package/src/lib/components/ai-crm-email-draft.component.js +159 -0
- package/src/lib/components/ai-crm-email-draft.component.js.map +1 -0
- package/src/lib/components/ai-crm-import-mapping.component.d.ts +20 -0
- package/src/lib/components/ai-crm-import-mapping.component.js +167 -0
- package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -0
- package/src/lib/components/ai-crm-record-card.component.d.ts +27 -0
- package/src/lib/components/ai-crm-record-card.component.js +263 -0
- package/src/lib/components/ai-crm-record-card.component.js.map +1 -0
- package/src/lib/components/ai-describe-automation.component.d.ts +31 -0
- package/src/lib/components/ai-describe-automation.component.js +226 -0
- package/src/lib/components/ai-describe-automation.component.js.map +1 -0
- package/src/lib/components/ai-describe-button.component.d.ts +39 -0
- package/src/lib/components/ai-describe-button.component.js +87 -0
- package/src/lib/components/ai-describe-button.component.js.map +1 -0
- package/src/lib/components/ai-describe-page.component.d.ts +26 -0
- package/src/lib/components/ai-describe-page.component.js +18 -0
- package/src/lib/components/ai-describe-page.component.js.map +1 -0
- package/src/lib/components/ai-experiment-cards.component.d.ts +75 -0
- package/src/lib/components/ai-experiment-cards.component.js +573 -0
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -0
- package/src/lib/components/ai-explain-automation.component.d.ts +31 -0
- package/src/lib/components/ai-explain-automation.component.js +265 -0
- package/src/lib/components/ai-explain-automation.component.js.map +1 -0
- package/src/lib/components/ai-insight-dialog.component.d.ts +37 -0
- package/src/lib/components/ai-insight-dialog.component.js +441 -0
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.d.ts +46 -0
- package/src/lib/components/ai-job-plan.component.js +192 -0
- package/src/lib/components/ai-job-plan.component.js.map +1 -0
- package/src/lib/components/ai-model-selector.component.d.ts +21 -0
- package/src/lib/components/ai-model-selector.component.js +109 -0
- package/src/lib/components/ai-model-selector.component.js.map +1 -0
- package/src/lib/components/ai-page-brief-dialog.component.d.ts +29 -0
- package/src/lib/components/ai-page-brief-dialog.component.js +27 -0
- package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -0
- package/src/lib/components/ai-permissions-on-host.component.d.ts +55 -0
- package/src/lib/components/ai-permissions-on-host.component.js +91 -0
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -0
- package/src/lib/components/ai-product-copy-card.component.d.ts +25 -0
- package/src/lib/components/ai-product-copy-card.component.js +328 -0
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -0
- package/src/lib/components/ai-product-import-option.component.d.ts +14 -0
- package/src/lib/components/ai-product-import-option.component.js +58 -0
- package/src/lib/components/ai-product-import-option.component.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.d.ts +15 -0
- package/src/lib/components/ai-products-hub-card.component.js +418 -0
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -0
- package/src/lib/components/ai-products-proposals.component.d.ts +19 -0
- package/src/lib/components/ai-products-proposals.component.js +547 -0
- package/src/lib/components/ai-products-proposals.component.js.map +1 -0
- package/src/lib/components/ai-save-as-component.component.d.ts +38 -0
- package/src/lib/components/ai-save-as-component.component.js +231 -0
- package/src/lib/components/ai-save-as-component.component.js.map +1 -0
- package/src/lib/components/ai-seo-audit-card.component.d.ts +9 -0
- package/src/lib/components/ai-seo-audit-card.component.js +620 -0
- package/src/lib/components/ai-seo-audit-card.component.js.map +1 -0
- package/src/lib/components/ai-seo-fields-card.component.d.ts +10 -0
- package/src/lib/components/ai-seo-fields-card.component.js +453 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -0
- package/src/lib/components/ai-site-batch-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-batch-card.component.js +531 -0
- package/src/lib/components/ai-site-batch-card.component.js.map +1 -0
- package/src/lib/components/ai-site-seo-start-card.component.d.ts +26 -0
- package/src/lib/components/ai-site-seo-start-card.component.js +178 -0
- package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +19 -0
- package/src/lib/components/ai-site-start-card.component.js +348 -0
- package/src/lib/components/ai-site-start-card.component.js.map +1 -0
- package/src/lib/components/ai-template-collection-field.component.d.ts +32 -0
- package/src/lib/components/ai-template-collection-field.component.js +63 -0
- package/src/lib/components/ai-template-collection-field.component.js.map +1 -0
- package/src/lib/components/ai-theme-proposal-card.component.d.ts +6 -0
- package/src/lib/components/ai-theme-proposal-card.component.js +619 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -0
- package/src/lib/components/ai-usage-strip.component.d.ts +27 -0
- package/src/lib/components/ai-usage-strip.component.js +145 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -0
- package/src/lib/components/assist-edit-canvas.d.ts +169 -0
- package/src/lib/components/assist-edit-canvas.js +636 -0
- package/src/lib/components/assist-edit-canvas.js.map +1 -0
- package/src/lib/components/assist-edit-card.component.d.ts +16 -0
- package/src/lib/components/assist-edit-card.component.js +181 -0
- package/src/lib/components/assist-edit-card.component.js.map +1 -0
- package/src/lib/components/assist-jobs-drawer.component.d.ts +72 -0
- package/src/lib/components/assist-jobs-drawer.component.js +626 -0
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -0
- package/src/lib/components/assist-panel.component.d.ts +136 -0
- package/src/lib/components/assist-panel.component.js +1099 -0
- package/src/lib/components/assist-panel.component.js.map +1 -0
- package/src/lib/components/assist-signals-page.component.d.ts +3 -0
- package/src/lib/components/assist-signals-page.component.js +1017 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -0
- package/src/lib/components/besigner-ai-controls.component.d.ts +29 -0
- package/src/lib/components/besigner-ai-controls.component.js +81 -0
- package/src/lib/components/besigner-ai-controls.component.js.map +1 -0
- package/src/lib/components/billing-ai-allotments.component.d.ts +36 -0
- package/src/lib/components/billing-ai-allotments.component.js +486 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -0
- package/src/lib/components/billing-ai-top-users.component.d.ts +44 -0
- package/src/lib/components/billing-ai-top-users.component.js +304 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -0
- package/src/lib/components/billing-assist-overage-card.component.d.ts +69 -0
- package/src/lib/components/billing-assist-overage-card.component.js +437 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -0
- package/src/lib/components/host-ai-allotments.component.d.ts +40 -0
- package/src/lib/components/host-ai-allotments.component.js +239 -0
- package/src/lib/components/host-ai-allotments.component.js.map +1 -0
- package/src/lib/components/member-ai-allotment-card.component.d.ts +28 -0
- package/src/lib/components/member-ai-allotment-card.component.js +172 -0
- package/src/lib/components/member-ai-allotment-card.component.js.map +1 -0
- package/src/lib/components/member-ai-usage-card.component.d.ts +28 -0
- package/src/lib/components/member-ai-usage-card.component.js +240 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -0
- package/src/lib/components/staff-org-ai-card.component.d.ts +17 -0
- package/src/lib/components/staff-org-ai-card.component.js +739 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -0
- package/src/lib/components/staff-org-usage-ai-columns.component.d.ts +41 -0
- package/src/lib/components/staff-org-usage-ai-columns.component.js +65 -0
- package/src/lib/components/staff-org-usage-ai-columns.component.js.map +1 -0
- package/src/lib/components/staff-orgs-ai-spend-column.component.d.ts +28 -0
- package/src/lib/components/staff-orgs-ai-spend-column.component.js +89 -0
- package/src/lib/components/staff-orgs-ai-spend-column.component.js.map +1 -0
- package/src/lib/components/staff-user-ai-usage-card.component.d.ts +27 -0
- package/src/lib/components/staff-user-ai-usage-card.component.js +183 -0
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -0
- package/src/lib/components/use-ai-allotments.d.ts +49 -0
- package/src/lib/components/use-ai-allotments.js +171 -0
- package/src/lib/components/use-ai-allotments.js.map +1 -0
- package/src/lib/components/use-ai-crm-answer.d.ts +50 -0
- package/src/lib/components/use-ai-crm-answer.js +220 -0
- package/src/lib/components/use-ai-crm-answer.js.map +1 -0
- package/src/lib/components/use-ai-job-run.d.ts +51 -0
- package/src/lib/components/use-ai-job-run.js +176 -0
- package/src/lib/components/use-ai-job-run.js.map +1 -0
- package/src/lib/components/use-ai-model-choice.d.ts +47 -0
- package/src/lib/components/use-ai-model-choice.js +150 -0
- package/src/lib/components/use-ai-model-choice.js.map +1 -0
- package/src/lib/components/use-ai-products-jobs.d.ts +36 -0
- package/src/lib/components/use-ai-products-jobs.js +190 -0
- package/src/lib/components/use-ai-products-jobs.js.map +1 -0
- package/src/lib/components/use-ai-usage-meter.d.ts +11 -0
- package/src/lib/components/use-ai-usage-meter.js +96 -0
- package/src/lib/components/use-ai-usage-meter.js.map +1 -0
- package/src/lib/components/use-org-ai-usage.d.ts +42 -0
- package/src/lib/components/use-org-ai-usage.js +181 -0
- package/src/lib/components/use-org-ai-usage.js.map +1 -0
- package/src/lib/components/use-staff-orgs-ai-spend.d.ts +18 -0
- package/src/lib/components/use-staff-orgs-ai-spend.js +127 -0
- package/src/lib/components/use-staff-orgs-ai-spend.js.map +1 -0
- package/src/lib/constants.d.ts +24 -0
- package/src/lib/constants.js +23 -0
- package/src/lib/constants.js.map +1 -0
- package/src/lib/declarations.d.ts +48 -0
- package/src/lib/declarations.js +129 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.d.ts +35 -0
- package/src/lib/declarations.server.js +138 -0
- package/src/lib/declarations.server.js.map +1 -0
- package/src/lib/insights/ai-figure-readers.d.ts +90 -0
- package/src/lib/insights/ai-figure-readers.js +863 -0
- package/src/lib/insights/ai-figure-readers.js.map +1 -0
- package/src/lib/insights/ai-insight-digest.d.ts +165 -0
- package/src/lib/insights/ai-insight-digest.js +363 -0
- package/src/lib/insights/ai-insight-digest.js.map +1 -0
- package/src/lib/insights/ai-insight-readers.d.ts +71 -0
- package/src/lib/insights/ai-insight-readers.js +141 -0
- package/src/lib/insights/ai-insight-readers.js.map +1 -0
- package/src/lib/jobs/ai-crm-access.d.ts +61 -0
- package/src/lib/jobs/ai-crm-access.js +94 -0
- package/src/lib/jobs/ai-crm-access.js.map +1 -0
- package/src/lib/jobs/ai-email-bindings.d.ts +97 -0
- package/src/lib/jobs/ai-email-bindings.js +147 -0
- package/src/lib/jobs/ai-email-bindings.js.map +1 -0
- package/src/lib/jobs/ai-job-admission.d.ts +82 -0
- package/src/lib/jobs/ai-job-admission.js +45 -0
- package/src/lib/jobs/ai-job-admission.js.map +1 -0
- package/src/lib/jobs/ai-job-budget.d.ts +167 -0
- package/src/lib/jobs/ai-job-budget.js +161 -0
- package/src/lib/jobs/ai-job-budget.js.map +1 -0
- package/src/lib/jobs/ai-job-campaign-step.d.ts +108 -0
- package/src/lib/jobs/ai-job-campaign-step.js +340 -0
- package/src/lib/jobs/ai-job-campaign-step.js.map +1 -0
- package/src/lib/jobs/ai-job-component-checks.d.ts +74 -0
- package/src/lib/jobs/ai-job-component-checks.js +442 -0
- package/src/lib/jobs/ai-job-component-checks.js.map +1 -0
- package/src/lib/jobs/ai-job-component-step.d.ts +75 -0
- package/src/lib/jobs/ai-job-component-step.js +262 -0
- package/src/lib/jobs/ai-job-component-step.js.map +1 -0
- package/src/lib/jobs/ai-job-crm-step.d.ts +192 -0
- package/src/lib/jobs/ai-job-crm-step.js +734 -0
- package/src/lib/jobs/ai-job-crm-step.js.map +1 -0
- package/src/lib/jobs/ai-job-draft-ids.d.ts +62 -0
- package/src/lib/jobs/ai-job-draft-ids.js +132 -0
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -0
- package/src/lib/jobs/ai-job-drafts.d.ts +304 -0
- package/src/lib/jobs/ai-job-drafts.js +557 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -0
- package/src/lib/jobs/ai-job-email-step.d.ts +195 -0
- package/src/lib/jobs/ai-job-email-step.js +547 -0
- package/src/lib/jobs/ai-job-email-step.js.map +1 -0
- package/src/lib/jobs/ai-job-experiment-step.d.ts +105 -0
- package/src/lib/jobs/ai-job-experiment-step.js +445 -0
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -0
- package/src/lib/jobs/ai-job-form-step.d.ts +250 -0
- package/src/lib/jobs/ai-job-form-step.js +634 -0
- package/src/lib/jobs/ai-job-form-step.js.map +1 -0
- package/src/lib/jobs/ai-job-generation.d.ts +114 -0
- package/src/lib/jobs/ai-job-generation.js +384 -0
- package/src/lib/jobs/ai-job-generation.js.map +1 -0
- package/src/lib/jobs/ai-job-insight-budget.d.ts +41 -0
- package/src/lib/jobs/ai-job-insight-budget.js +46 -0
- package/src/lib/jobs/ai-job-insight-budget.js.map +1 -0
- package/src/lib/jobs/ai-job-insight-step.d.ts +58 -0
- package/src/lib/jobs/ai-job-insight-step.js +350 -0
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-step.d.ts +103 -0
- package/src/lib/jobs/ai-job-layout-step.js +340 -0
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-budget.d.ts +57 -0
- package/src/lib/jobs/ai-job-page-budget.js +60 -0
- package/src/lib/jobs/ai-job-page-budget.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +150 -0
- package/src/lib/jobs/ai-job-page-sections.js +450 -0
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -0
- package/src/lib/jobs/ai-job-page-step.d.ts +198 -0
- package/src/lib/jobs/ai-job-page-step.js +667 -0
- package/src/lib/jobs/ai-job-page-step.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-conformance.d.ts +107 -0
- package/src/lib/jobs/ai-job-plan-conformance.js +164 -0
- package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-step.d.ts +196 -0
- package/src/lib/jobs/ai-job-plan-step.js +458 -0
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -0
- package/src/lib/jobs/ai-job-plugin-drafts.d.ts +76 -0
- package/src/lib/jobs/ai-job-plugin-drafts.js +91 -0
- package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -0
- package/src/lib/jobs/ai-job-products-step.d.ts +106 -0
- package/src/lib/jobs/ai-job-products-step.js +436 -0
- package/src/lib/jobs/ai-job-products-step.js.map +1 -0
- package/src/lib/jobs/ai-job-seo-budget.d.ts +52 -0
- package/src/lib/jobs/ai-job-seo-budget.js +63 -0
- package/src/lib/jobs/ai-job-seo-budget.js.map +1 -0
- package/src/lib/jobs/ai-job-seo-step.d.ts +145 -0
- package/src/lib/jobs/ai-job-seo-step.js +809 -0
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +252 -0
- package/src/lib/jobs/ai-job-site-step.js +538 -0
- package/src/lib/jobs/ai-job-site-step.js.map +1 -0
- package/src/lib/jobs/ai-job-template-step.d.ts +105 -0
- package/src/lib/jobs/ai-job-template-step.js +391 -0
- package/src/lib/jobs/ai-job-template-step.js.map +1 -0
- package/src/lib/jobs/ai-job-text-step.d.ts +125 -0
- package/src/lib/jobs/ai-job-text-step.js +128 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -0
- package/src/lib/jobs/ai-job-theme-budget.d.ts +38 -0
- package/src/lib/jobs/ai-job-theme-budget.js +43 -0
- package/src/lib/jobs/ai-job-theme-budget.js.map +1 -0
- package/src/lib/jobs/ai-job-theme-step.d.ts +115 -0
- package/src/lib/jobs/ai-job-theme-step.js +337 -0
- package/src/lib/jobs/ai-job-theme-step.js.map +1 -0
- package/src/lib/jobs/ai-job-workflow-step.d.ts +139 -0
- package/src/lib/jobs/ai-job-workflow-step.js +530 -0
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -0
- package/src/lib/jobs/ai-jobs-beat.d.ts +78 -0
- package/src/lib/jobs/ai-jobs-beat.js +93 -0
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -0
- package/src/lib/jobs/ai-jobs-pause.d.ts +38 -0
- package/src/lib/jobs/ai-jobs-pause.js +45 -0
- package/src/lib/jobs/ai-jobs-pause.js.map +1 -0
- package/src/lib/jobs/ai-jobs.d.ts +558 -0
- package/src/lib/jobs/ai-jobs.js +1410 -0
- package/src/lib/jobs/ai-jobs.js.map +1 -0
- package/src/lib/jobs/ai-media-asset-server.d.ts +34 -0
- package/src/lib/jobs/ai-media-asset-server.js +34 -0
- package/src/lib/jobs/ai-media-asset-server.js.map +1 -0
- package/src/lib/jobs/ai-media-asset.d.ts +59 -0
- package/src/lib/jobs/ai-media-asset.js +86 -0
- package/src/lib/jobs/ai-media-asset.js.map +1 -0
- package/src/lib/jobs/ai-product-image.d.ts +78 -0
- package/src/lib/jobs/ai-product-image.js +113 -0
- package/src/lib/jobs/ai-product-image.js.map +1 -0
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +125 -0
- package/src/lib/jobs/ai-theme-brand-inputs.js +378 -0
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -0
- package/src/lib/jobs/ai-theme-brand-server.d.ts +33 -0
- package/src/lib/jobs/ai-theme-brand-server.js +33 -0
- package/src/lib/jobs/ai-theme-brand-server.js.map +1 -0
- package/src/lib/jobs/ai-workflow-records.d.ts +84 -0
- package/src/lib/jobs/ai-workflow-records.js +158 -0
- package/src/lib/jobs/ai-workflow-records.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-free-page-built-recording.d.ts +31 -0
- package/src/lib/jobs/fixtures/ai-free-page-built-recording.js +367 -0
- package/src/lib/jobs/fixtures/ai-free-page-built-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-free-page-recording.d.ts +69 -0
- package/src/lib/jobs/fixtures/ai-free-page-recording.js +442 -0
- package/src/lib/jobs/fixtures/ai-free-page-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-page-briefs.d.ts +146 -0
- package/src/lib/jobs/fixtures/ai-page-briefs.js +1850 -0
- package/src/lib/jobs/fixtures/ai-page-briefs.js.map +1 -0
- package/src/lib/model/ai-allotments.d.ts +208 -0
- package/src/lib/model/ai-allotments.js +269 -0
- package/src/lib/model/ai-allotments.js.map +1 -0
- package/src/lib/model/ai-automation-draft.d.ts +74 -0
- package/src/lib/model/ai-automation-draft.js +310 -0
- package/src/lib/model/ai-automation-draft.js.map +1 -0
- package/src/lib/model/ai-automation-outline.d.ts +50 -0
- package/src/lib/model/ai-automation-outline.js +262 -0
- package/src/lib/model/ai-automation-outline.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +197 -0
- package/src/lib/model/ai-build-plan.js +438 -0
- package/src/lib/model/ai-build-plan.js.map +1 -0
- package/src/lib/model/ai-crm.d.ts +237 -0
- package/src/lib/model/ai-crm.js +225 -0
- package/src/lib/model/ai-crm.js.map +1 -0
- package/src/lib/model/ai-experiment-proposal.d.ts +68 -0
- package/src/lib/model/ai-experiment-proposal.js +146 -0
- package/src/lib/model/ai-experiment-proposal.js.map +1 -0
- package/src/lib/model/ai-experiment.d.ts +309 -0
- package/src/lib/model/ai-experiment.js +395 -0
- package/src/lib/model/ai-experiment.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +178 -0
- package/src/lib/model/ai-insight.js +201 -0
- package/src/lib/model/ai-insight.js.map +1 -0
- package/src/lib/model/ai-jobs.types.d.ts +412 -0
- package/src/lib/model/ai-jobs.types.js +42 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -0
- package/src/lib/model/ai-page-job.d.ts +92 -0
- package/src/lib/model/ai-page-job.js +159 -0
- package/src/lib/model/ai-page-job.js.map +1 -0
- package/src/lib/model/ai-permissions.d.ts +49 -0
- package/src/lib/model/ai-permissions.js +97 -0
- package/src/lib/model/ai-permissions.js.map +1 -0
- package/src/lib/model/ai-plan-capabilities.d.ts +139 -0
- package/src/lib/model/ai-plan-capabilities.js +149 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -0
- package/src/lib/model/ai-products.d.ts +251 -0
- package/src/lib/model/ai-products.js +253 -0
- package/src/lib/model/ai-products.js.map +1 -0
- package/src/lib/model/ai-seo.d.ts +208 -0
- package/src/lib/model/ai-seo.js +150 -0
- package/src/lib/model/ai-seo.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +164 -0
- package/src/lib/model/ai-site-inventory.js +145 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +247 -0
- package/src/lib/model/ai-site-job.js +314 -0
- package/src/lib/model/ai-site-job.js.map +1 -0
- package/src/lib/model/ai-site-start-seo.d.ts +103 -0
- package/src/lib/model/ai-site-start-seo.js +154 -0
- package/src/lib/model/ai-site-start-seo.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +122 -0
- package/src/lib/model/ai-site-start.js +159 -0
- package/src/lib/model/ai-site-start.js.map +1 -0
- package/src/lib/model/ai-site-switch.d.ts +56 -0
- package/src/lib/model/ai-site-switch.js +73 -0
- package/src/lib/model/ai-site-switch.js.map +1 -0
- package/src/lib/model/ai-storefront-claims.d.ts +70 -0
- package/src/lib/model/ai-storefront-claims.js +130 -0
- package/src/lib/model/ai-storefront-claims.js.map +1 -0
- package/src/lib/model/ai-template-subjects.d.ts +82 -0
- package/src/lib/model/ai-template-subjects.js +136 -0
- package/src/lib/model/ai-template-subjects.js.map +1 -0
- package/src/lib/model/ai-theme-proposal.d.ts +210 -0
- package/src/lib/model/ai-theme-proposal.js +506 -0
- package/src/lib/model/ai-theme-proposal.js.map +1 -0
- package/src/lib/model/ai-tokens.d.ts +96 -0
- package/src/lib/model/ai-tokens.js +117 -0
- package/src/lib/model/ai-tokens.js.map +1 -0
- package/src/lib/model/ai-usage-by-user.d.ts +120 -0
- package/src/lib/model/ai-usage-by-user.js +139 -0
- package/src/lib/model/ai-usage-by-user.js.map +1 -0
- package/src/lib/model/ai-workflow-job.d.ts +111 -0
- package/src/lib/model/ai-workflow-job.js +183 -0
- package/src/lib/model/ai-workflow-job.js.map +1 -0
- package/src/lib/model/assist-edit.d.ts +261 -0
- package/src/lib/model/assist-edit.js +175 -0
- package/src/lib/model/assist-edit.js.map +1 -0
- package/src/lib/plugin-config.d.ts +33 -0
- package/src/lib/plugin-config.js +118 -0
- package/src/lib/plugin-config.js.map +1 -0
- package/src/lib/plugin.d.ts +30 -0
- package/src/lib/plugin.js +525 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/providers/anthropic.d.ts +84 -0
- package/src/lib/providers/anthropic.js +432 -0
- package/src/lib/providers/anthropic.js.map +1 -0
- package/src/lib/providers/catalog.d.ts +197 -0
- package/src/lib/providers/catalog.js +312 -0
- package/src/lib/providers/catalog.js.map +1 -0
- package/src/lib/providers/contract.d.ts +464 -0
- package/src/lib/providers/contract.js +274 -0
- package/src/lib/providers/contract.js.map +1 -0
- package/src/lib/providers/model-choice.d.ts +119 -0
- package/src/lib/providers/model-choice.js +416 -0
- package/src/lib/providers/model-choice.js.map +1 -0
- package/src/lib/providers/openai-compatible.d.ts +58 -0
- package/src/lib/providers/openai-compatible.js +361 -0
- package/src/lib/providers/openai-compatible.js.map +1 -0
- package/src/lib/providers/registry.d.ts +49 -0
- package/src/lib/providers/registry.js +71 -0
- package/src/lib/providers/registry.js.map +1 -0
- package/src/lib/providers/routing.d.ts +98 -0
- package/src/lib/providers/routing.js +450 -0
- package/src/lib/providers/routing.js.map +1 -0
- package/src/lib/runtime/ai-abuse-guards.d.ts +113 -0
- package/src/lib/runtime/ai-abuse-guards.js +148 -0
- package/src/lib/runtime/ai-abuse-guards.js.map +1 -0
- package/src/lib/runtime/ai-component-bindings.d.ts +67 -0
- package/src/lib/runtime/ai-component-bindings.js +106 -0
- package/src/lib/runtime/ai-component-bindings.js.map +1 -0
- package/src/lib/runtime/ai-device-audit.d.ts +109 -0
- package/src/lib/runtime/ai-device-audit.js +97 -0
- package/src/lib/runtime/ai-device-audit.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +720 -0
- package/src/lib/runtime/ai-doctrine-validators.js +3133 -0
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -0
- package/src/lib/runtime/ai-doctrine.d.ts +372 -0
- package/src/lib/runtime/ai-doctrine.js +944 -0
- package/src/lib/runtime/ai-doctrine.js.map +1 -0
- package/src/lib/runtime/ai-eval-live.d.ts +321 -0
- package/src/lib/runtime/ai-eval-live.js +1257 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -0
- package/src/lib/runtime/ai-eval-memory-firestore.d.ts +51 -0
- package/src/lib/runtime/ai-eval-memory-firestore.js +183 -0
- package/src/lib/runtime/ai-eval-memory-firestore.js.map +1 -0
- package/src/lib/runtime/ai-eval.d.ts +441 -0
- package/src/lib/runtime/ai-eval.js +1151 -0
- package/src/lib/runtime/ai-eval.js.map +1 -0
- package/src/lib/runtime/ai-gate.d.ts +130 -0
- package/src/lib/runtime/ai-gate.js +272 -0
- package/src/lib/runtime/ai-gate.js.map +1 -0
- package/src/lib/runtime/ai-generation-bounds.d.ts +24 -0
- package/src/lib/runtime/ai-generation-bounds.js +23 -0
- package/src/lib/runtime/ai-generation-bounds.js.map +1 -0
- package/src/lib/runtime/ai-insight-check.d.ts +76 -0
- package/src/lib/runtime/ai-insight-check.js +281 -0
- package/src/lib/runtime/ai-insight-check.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +181 -0
- package/src/lib/runtime/ai-node-tree.js +931 -0
- package/src/lib/runtime/ai-node-tree.js.map +1 -0
- package/src/lib/runtime/ai-page-links.d.ts +79 -0
- package/src/lib/runtime/ai-page-links.js +135 -0
- package/src/lib/runtime/ai-page-links.js.map +1 -0
- package/src/lib/runtime/ai-palette.d.ts +252 -0
- package/src/lib/runtime/ai-palette.generated.d.ts +34 -0
- package/src/lib/runtime/ai-palette.generated.js +5263 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -0
- package/src/lib/runtime/ai-palette.js +187 -0
- package/src/lib/runtime/ai-palette.js.map +1 -0
- package/src/lib/runtime/ai-products-generation.d.ts +107 -0
- package/src/lib/runtime/ai-products-generation.js +250 -0
- package/src/lib/runtime/ai-products-generation.js.map +1 -0
- package/src/lib/runtime/ai-repeated-items.d.ts +95 -0
- package/src/lib/runtime/ai-repeated-items.js +397 -0
- package/src/lib/runtime/ai-repeated-items.js.map +1 -0
- package/src/lib/runtime/ai-runtime.d.ts +148 -0
- package/src/lib/runtime/ai-runtime.js +237 -0
- package/src/lib/runtime/ai-runtime.js.map +1 -0
- package/src/lib/runtime/ai-template-examples.d.ts +71 -0
- package/src/lib/runtime/ai-template-examples.js +135 -0
- package/src/lib/runtime/ai-template-examples.js.map +1 -0
- package/src/lib/runtime/assist-answer-cache.d.ts +80 -0
- package/src/lib/runtime/assist-answer-cache.js +184 -0
- package/src/lib/runtime/assist-answer-cache.js.map +1 -0
- package/src/lib/runtime/seo-audit.d.ts +93 -0
- package/src/lib/runtime/seo-audit.js +260 -0
- package/src/lib/runtime/seo-audit.js.map +1 -0
- package/src/lib/runtime/seo-content-fixes.d.ts +41 -0
- package/src/lib/runtime/seo-content-fixes.js +168 -0
- package/src/lib/runtime/seo-content-fixes.js.map +1 -0
- package/src/lib/runtime/seo-fields.d.ts +152 -0
- package/src/lib/runtime/seo-fields.js +215 -0
- package/src/lib/runtime/seo-fields.js.map +1 -0
- package/src/lib/runtime/seo-page-facts.d.ts +61 -0
- package/src/lib/runtime/seo-page-facts.js +150 -0
- package/src/lib/runtime/seo-page-facts.js.map +1 -0
- package/src/lib/runtime/site-inventory.d.ts +63 -0
- package/src/lib/runtime/site-inventory.js +230 -0
- package/src/lib/runtime/site-inventory.js.map +1 -0
- package/src/lib/server/ai-admin-org.d.ts +33 -0
- package/src/lib/server/ai-admin-org.js +381 -0
- package/src/lib/server/ai-admin-org.js.map +1 -0
- package/src/lib/server/ai-admin-orgs-spend.d.ts +40 -0
- package/src/lib/server/ai-admin-orgs-spend.js +129 -0
- package/src/lib/server/ai-admin-orgs-spend.js.map +1 -0
- package/src/lib/server/ai-admin-overage.d.ts +19 -0
- package/src/lib/server/ai-admin-overage.js +246 -0
- package/src/lib/server/ai-admin-overage.js.map +1 -0
- package/src/lib/server/ai-admin-signals.d.ts +34 -0
- package/src/lib/server/ai-admin-signals.js +330 -0
- package/src/lib/server/ai-admin-signals.js.map +1 -0
- package/src/lib/server/ai-admin-user.d.ts +27 -0
- package/src/lib/server/ai-admin-user.js +128 -0
- package/src/lib/server/ai-admin-user.js.map +1 -0
- package/src/lib/server/ai-allotments.d.ts +27 -0
- package/src/lib/server/ai-allotments.js +544 -0
- package/src/lib/server/ai-allotments.js.map +1 -0
- package/src/lib/server/ai-assist-prompts.d.ts +102 -0
- package/src/lib/server/ai-assist-prompts.js +207 -0
- package/src/lib/server/ai-assist-prompts.js.map +1 -0
- package/src/lib/server/ai-assist.d.ts +119 -0
- package/src/lib/server/ai-assist.js +517 -0
- package/src/lib/server/ai-assist.js.map +1 -0
- package/src/lib/server/ai-crm-answer.d.ts +30 -0
- package/src/lib/server/ai-crm-answer.js +98 -0
- package/src/lib/server/ai-crm-answer.js.map +1 -0
- package/src/lib/server/ai-generate-component.d.ts +111 -0
- package/src/lib/server/ai-generate-component.js +643 -0
- package/src/lib/server/ai-generate-component.js.map +1 -0
- package/src/lib/server/ai-host-permissions.d.ts +46 -0
- package/src/lib/server/ai-host-permissions.js +204 -0
- package/src/lib/server/ai-host-permissions.js.map +1 -0
- package/src/lib/server/ai-insight-answer.d.ts +30 -0
- package/src/lib/server/ai-insight-answer.js +83 -0
- package/src/lib/server/ai-insight-answer.js.map +1 -0
- package/src/lib/server/ai-insight-digest-route.d.ts +30 -0
- package/src/lib/server/ai-insight-digest-route.js +156 -0
- package/src/lib/server/ai-insight-digest-route.js.map +1 -0
- package/src/lib/server/ai-jobs-batch.d.ts +48 -0
- package/src/lib/server/ai-jobs-batch.js +306 -0
- package/src/lib/server/ai-jobs-batch.js.map +1 -0
- package/src/lib/server/ai-jobs-beat-route.d.ts +36 -0
- package/src/lib/server/ai-jobs-beat-route.js +86 -0
- package/src/lib/server/ai-jobs-beat-route.js.map +1 -0
- package/src/lib/server/ai-jobs-cancel.d.ts +30 -0
- package/src/lib/server/ai-jobs-cancel.js +74 -0
- package/src/lib/server/ai-jobs-cancel.js.map +1 -0
- package/src/lib/server/ai-jobs-events-route.d.ts +31 -0
- package/src/lib/server/ai-jobs-events-route.js +51 -0
- package/src/lib/server/ai-jobs-events-route.js.map +1 -0
- package/src/lib/server/ai-jobs-events.d.ts +55 -0
- package/src/lib/server/ai-jobs-events.js +144 -0
- package/src/lib/server/ai-jobs-events.js.map +1 -0
- package/src/lib/server/ai-jobs-gate.d.ts +47 -0
- package/src/lib/server/ai-jobs-gate.js +106 -0
- package/src/lib/server/ai-jobs-gate.js.map +1 -0
- package/src/lib/server/ai-jobs-resume.d.ts +23 -0
- package/src/lib/server/ai-jobs-resume.js +209 -0
- package/src/lib/server/ai-jobs-resume.js.map +1 -0
- package/src/lib/server/ai-jobs-route.d.ts +32 -0
- package/src/lib/server/ai-jobs-route.js +307 -0
- package/src/lib/server/ai-jobs-route.js.map +1 -0
- package/src/lib/server/ai-models.d.ts +18 -0
- package/src/lib/server/ai-models.js +134 -0
- package/src/lib/server/ai-models.js.map +1 -0
- package/src/lib/server/ai-seo-apply.d.ts +53 -0
- package/src/lib/server/ai-seo-apply.js +294 -0
- package/src/lib/server/ai-seo-apply.js.map +1 -0
- package/src/lib/server/ai-usage.d.ts +18 -0
- package/src/lib/server/ai-usage.js +220 -0
- package/src/lib/server/ai-usage.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +234 -0
- package/src/lib/server/assist-chat.js +1446 -0
- package/src/lib/server/assist-chat.js.map +1 -0
- package/src/lib/server/assist-edit-applied.d.ts +22 -0
- package/src/lib/server/assist-edit-applied.js +239 -0
- package/src/lib/server/assist-edit-applied.js.map +1 -0
- package/src/lib/server/assist-edit.d.ts +124 -0
- package/src/lib/server/assist-edit.js +1000 -0
- package/src/lib/server/assist-edit.js.map +1 -0
- package/src/lib/server/assist-feedback.d.ts +31 -0
- package/src/lib/server/assist-feedback.js +115 -0
- package/src/lib/server/assist-feedback.js.map +1 -0
- package/src/lib/server/assist-view-context.d.ts +343 -0
- package/src/lib/server/assist-view-context.js +690 -0
- package/src/lib/server/assist-view-context.js.map +1 -0
- package/src/lib/server/billing-credits.d.ts +59 -0
- package/src/lib/server/billing-credits.js +146 -0
- package/src/lib/server/billing-credits.js.map +1 -0
- package/src/lib/server/billing-overage.d.ts +19 -0
- package/src/lib/server/billing-overage.js +337 -0
- package/src/lib/server/billing-overage.js.map +1 -0
- package/src/lib/server.d.ts +32 -0
- package/src/lib/server.js +265 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/subprocessors.d.ts +18 -0
- package/src/lib/subprocessors.js +52 -0
- package/src/lib/subprocessors.js.map +1 -0
- package/src/lib/tools/ai-component-tool.d.ts +99 -0
- package/src/lib/tools/ai-component-tool.js +369 -0
- package/src/lib/tools/ai-component-tool.js.map +1 -0
- package/src/lib/tools/ai-crm-tool.d.ts +106 -0
- package/src/lib/tools/ai-crm-tool.js +488 -0
- package/src/lib/tools/ai-crm-tool.js.map +1 -0
- package/src/lib/tools/ai-duplicate-resource-tool.d.ts +71 -0
- package/src/lib/tools/ai-duplicate-resource-tool.js +164 -0
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -0
- package/src/lib/tools/ai-experiment-tool.d.ts +40 -0
- package/src/lib/tools/ai-experiment-tool.js +155 -0
- package/src/lib/tools/ai-experiment-tool.js.map +1 -0
- package/src/lib/tools/ai-insight-tool.d.ts +46 -0
- package/src/lib/tools/ai-insight-tool.js +200 -0
- package/src/lib/tools/ai-insight-tool.js.map +1 -0
- package/src/lib/tools/ai-inventory-lookup-tool.d.ts +86 -0
- package/src/lib/tools/ai-inventory-lookup-tool.js +164 -0
- package/src/lib/tools/ai-inventory-lookup-tool.js.map +1 -0
- package/src/lib/tools/ai-products-tool.d.ts +65 -0
- package/src/lib/tools/ai-products-tool.js +644 -0
- package/src/lib/tools/ai-products-tool.js.map +1 -0
- package/src/lib/tools/ai-seo-tool.d.ts +162 -0
- package/src/lib/tools/ai-seo-tool.js +546 -0
- package/src/lib/tools/ai-seo-tool.js.map +1 -0
- package/src/lib/tools/ai-theme-tool.d.ts +101 -0
- package/src/lib/tools/ai-theme-tool.js +562 -0
- package/src/lib/tools/ai-theme-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +166 -0
- package/src/lib/tools/ai-workflow-tool.js +860 -0
- package/src/lib/tools/ai-workflow-tool.js.map +1 -0
- package/src/lib/usage/ai-allotment-alerts.d.ts +72 -0
- package/src/lib/usage/ai-allotment-alerts.js +157 -0
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -0
- package/src/lib/usage/ai-allotments.d.ts +125 -0
- package/src/lib/usage/ai-allotments.js +239 -0
- package/src/lib/usage/ai-allotments.js.map +1 -0
- package/src/lib/usage/ai-model-sample.d.ts +36 -0
- package/src/lib/usage/ai-model-sample.js +68 -0
- package/src/lib/usage/ai-model-sample.js.map +1 -0
- package/src/lib/usage/ai-usage-alerts.d.ts +55 -0
- package/src/lib/usage/ai-usage-alerts.js +114 -0
- package/src/lib/usage/ai-usage-alerts.js.map +1 -0
- package/src/lib/usage/ai-usage-by-user.d.ts +161 -0
- package/src/lib/usage/ai-usage-by-user.js +211 -0
- package/src/lib/usage/ai-usage-by-user.js.map +1 -0
- package/src/lib/usage/ai-usage-eraser.d.ts +29 -0
- package/src/lib/usage/ai-usage-eraser.js +40 -0
- package/src/lib/usage/ai-usage-eraser.js.map +1 -0
- package/src/lib/usage/ai-usage-meter.d.ts +39 -0
- package/src/lib/usage/ai-usage-meter.js +85 -0
- package/src/lib/usage/ai-usage-meter.js.map +1 -0
- package/src/lib/usage/ai-usage-wire.d.ts +184 -0
- package/src/lib/usage/ai-usage-wire.js +98 -0
- package/src/lib/usage/ai-usage-wire.js.map +1 -0
- package/src/lib/usage/assist-ceiling.d.ts +87 -0
- package/src/lib/usage/assist-ceiling.js +110 -0
- package/src/lib/usage/assist-ceiling.js.map +1 -0
- package/src/lib/usage/assist-free-taste.d.ts +225 -0
- package/src/lib/usage/assist-free-taste.js +308 -0
- package/src/lib/usage/assist-free-taste.js.map +1 -0
- package/src/lib/usage/assist-refusals.d.ts +69 -0
- package/src/lib/usage/assist-refusals.js +79 -0
- package/src/lib/usage/assist-refusals.js.map +1 -0
- package/src/lib/usage/assist-signal-mining.d.ts +375 -0
- package/src/lib/usage/assist-signal-mining.js +344 -0
- package/src/lib/usage/assist-signal-mining.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +571 -0
- package/src/lib/usage/assist-usage.js +978 -0
- package/src/lib/usage/assist-usage.js.map +1 -0
- package/src/lib/usage/staff-org-ai.d.ts +242 -0
- package/src/lib/usage/staff-org-ai.js +173 -0
- package/src/lib/usage/staff-org-ai.js.map +1 -0
- package/src/lib/usage/staff-org-usage-ai.d.ts +83 -0
- package/src/lib/usage/staff-org-usage-ai.js +59 -0
- package/src/lib/usage/staff-org-usage-ai.js.map +1 -0
- package/src/lib/usage/staff-orgs-ai-spend.d.ts +83 -0
- package/src/lib/usage/staff-orgs-ai-spend.js +114 -0
- package/src/lib/usage/staff-orgs-ai-spend.js.map +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type AssistRefusedBy } from '@aglyn/aglyn/app-utils/assist-credits';
|
|
18
|
+
import type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types';
|
|
19
|
+
/**
|
|
20
|
+
* The Free AI taste's precautions (AGL-2925) — the half of the meter that
|
|
21
|
+
* exists only because the Free band has no invoice behind it.
|
|
22
|
+
*
|
|
23
|
+
* `FREE_AI_TASTE_CREDITS_PER_MONTH` gives every Free workspace 300 credits a
|
|
24
|
+
* month as a wall. A wall per WORKSPACE is not a bound per PERSON: one
|
|
25
|
+
* account may hold three free workspaces (AGL-2265), a script can mint
|
|
26
|
+
* accounts faster than a person can, and a refused brief costs the same
|
|
27
|
+
* tokens as an answered one. So the taste is metered a second time, per
|
|
28
|
+
* account, and bounded three more ways. Everything below is read and
|
|
29
|
+
* decided INSIDE `reserveAssistMessage`'s transaction, so a burst of
|
|
30
|
+
* concurrent requests cannot each read "under" and all proceed — the same
|
|
31
|
+
* property every other rung of that reservation has.
|
|
32
|
+
*
|
|
33
|
+
* ## Documents (both ABSENT from firebase-firestore.rules on purpose)
|
|
34
|
+
*
|
|
35
|
+
* users/{uid}/aiUsage/{YYYY-MM} the ACCOUNT's month:
|
|
36
|
+
* { month, estCostUsd, requests, days: { [YYYY-MM-DD]: { requests,
|
|
37
|
+
* refusals } }, updatedAt }
|
|
38
|
+
* platformAiFreeSpend/{YYYY-MM-DD} the PLATFORM's day of free spend:
|
|
39
|
+
* { day, estCostUsd, requests, refusals, alertedAt?, pausedAt?,
|
|
40
|
+
* updatedAt }
|
|
41
|
+
*
|
|
42
|
+
* The account document sits under `users/{uid}`, whose owner may read and
|
|
43
|
+
* write the user document itself. It may not touch this subcollection: the
|
|
44
|
+
* `users` block matches its subcollections BY NAME and carries no wildcard,
|
|
45
|
+
* so an unmatched name is default-deny for every client, staff included —
|
|
46
|
+
* the same property `assistUsage` relies on one level down. An owner who
|
|
47
|
+
* could write here could reset their own allowance, which is the whole
|
|
48
|
+
* meter. The platform document is a top-level collection alongside
|
|
49
|
+
* `platformCronBeats`, unmatched and therefore closed, and every reader is
|
|
50
|
+
* an Admin-SDK reader behind the staff gate.
|
|
51
|
+
*
|
|
52
|
+
* ## Whose account
|
|
53
|
+
*
|
|
54
|
+
* The WORKSPACE OWNER's. A member invited to someone else's free workspace
|
|
55
|
+
* draws from that workspace's band and from its owner's allowance, never
|
|
56
|
+
* from their own account: the owner is the person the free workspace is
|
|
57
|
+
* attributed to for the ceiling on how many they may hold, and attributing
|
|
58
|
+
* the spend the same way is what keeps the two bounds describing one
|
|
59
|
+
* person. `ownerUid` first, `createdByUid` for an org that predates
|
|
60
|
+
* ownership stamping — the same union the free-workspace count reads.
|
|
61
|
+
*/
|
|
62
|
+
/** Where an account's month of free AI spend lives, under `users/{uid}`. */
|
|
63
|
+
export declare const FREE_AI_ACCOUNT_USAGE_COLLECTION = "aiUsage";
|
|
64
|
+
/** The top-level collection holding one document per UTC day of free spend. */
|
|
65
|
+
export declare const PLATFORM_AI_FREE_SPEND_COLLECTION = "platformAiFreeSpend";
|
|
66
|
+
/**
|
|
67
|
+
* Refusals a day before an account's free generation pauses. Three, not
|
|
68
|
+
* one: a single declined brief is a normal outcome of asking an assistant
|
|
69
|
+
* for things, and pausing on it would punish an ordinary afternoon. Three
|
|
70
|
+
* in a day is somebody probing what the model will write.
|
|
71
|
+
*/
|
|
72
|
+
export declare const AI_FREE_REFUSALS_PER_DAY = 3;
|
|
73
|
+
/** The share of the platform ceiling at which staff are told, once a day. */
|
|
74
|
+
export declare const AI_FREE_PLATFORM_ALERT_SHARE = 0.8;
|
|
75
|
+
/** The account allowance, in USD of provider spend — the taste, per person. */
|
|
76
|
+
export declare const FREE_AI_ACCOUNT_BUDGET_USD: number;
|
|
77
|
+
/**
|
|
78
|
+
* Free requests one account may make a UTC day, across its workspaces
|
|
79
|
+
* (`AI_FREE_DAILY_REQUESTS`, default 30).
|
|
80
|
+
*
|
|
81
|
+
* A request, not a message: this counts every reservation the account's
|
|
82
|
+
* free workspaces take, at either door. Thirty is three times the
|
|
83
|
+
* per-workspace message cap, so one workspace never hits it before its own
|
|
84
|
+
* cap and three workspaces hit it exactly when their caps would have
|
|
85
|
+
* summed — it bounds the multiplier, not the ordinary day. Zero is honored
|
|
86
|
+
* as "no free requests", which is a decision an operator may write down;
|
|
87
|
+
* junk and an empty value take the default.
|
|
88
|
+
*/
|
|
89
|
+
export declare function aiFreeDailyRequests(): number;
|
|
90
|
+
/**
|
|
91
|
+
* The platform-wide ceiling on ONE UTC day of free-tier provider spend
|
|
92
|
+
* (`AI_FREE_DAILY_PLATFORM_CEILING_USD`, default $25).
|
|
93
|
+
*
|
|
94
|
+
* Every other bound here is per something — per workspace, per account,
|
|
95
|
+
* per address — and a wave of new accounts clears all of them one at a
|
|
96
|
+
* time. This is the bound on the sum. At 80% of it staff are mailed; at
|
|
97
|
+
* 100% every free reservation refuses until the day rolls, paid workspaces
|
|
98
|
+
* untouched, and the manual `ai-generate` lockdown key remains the switch
|
|
99
|
+
* for anything the ceiling did not catch.
|
|
100
|
+
*
|
|
101
|
+
* Fails to the DEFAULT, never to "no ceiling", for the reason the operator
|
|
102
|
+
* COGS limit does: an empty, negative, zero or unparseable value is not a
|
|
103
|
+
* decision to run unbounded. There is deliberately no `off` word — a
|
|
104
|
+
* deployment that wants no ceiling sets a figure it is content to spend.
|
|
105
|
+
*/
|
|
106
|
+
export declare function aiFreeDailyPlatformCeilingUsd(): number;
|
|
107
|
+
/** The account a free workspace's spend is attributed to. */
|
|
108
|
+
export interface FreeAssistAccount {
|
|
109
|
+
/**
|
|
110
|
+
* The workspace owner's uid, or `null` for an org document that names no
|
|
111
|
+
* owner and no creator — every org created since ownership was stamped
|
|
112
|
+
* names one, so `null` is the shape of a fixture or a very old record.
|
|
113
|
+
* The account rungs are skipped for it; the org's own band still binds.
|
|
114
|
+
*/
|
|
115
|
+
accountUid: string | null;
|
|
116
|
+
}
|
|
117
|
+
/** The org fields the attribution reads, beside the billing ones. */
|
|
118
|
+
export type AssistMeteredOrg = Partial<AglynOrgBilling> & {
|
|
119
|
+
ownerUid?: string | null;
|
|
120
|
+
createdByUid?: string | null;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The account to meter a workspace's free spend against, or `null` when the
|
|
124
|
+
* workspace is not on the Free plan and none of this applies.
|
|
125
|
+
*
|
|
126
|
+
* `resolveEffectivePlan`, not `org.plan`: a workspace whose subscription
|
|
127
|
+
* died is Free again, and its spend is the taste's spend again.
|
|
128
|
+
*/
|
|
129
|
+
export declare function freeAssistAccount(org: AssistMeteredOrg | null | undefined): FreeAssistAccount | null;
|
|
130
|
+
export declare function freeAccountUsageRef(firestore: FirebaseFirestore.Firestore, accountUid: string, month: string): FirebaseFirestore.DocumentReference;
|
|
131
|
+
export declare function platformFreeSpendRef(firestore: FirebaseFirestore.Firestore, day: string): FirebaseFirestore.DocumentReference;
|
|
132
|
+
/** What the reservation read about the account and the platform. */
|
|
133
|
+
export interface FreeTasteReads {
|
|
134
|
+
accountCostUsd: number;
|
|
135
|
+
accountRequestsToday: number;
|
|
136
|
+
accountRefusalsToday: number;
|
|
137
|
+
platformCostUsd: number;
|
|
138
|
+
platformPaused: boolean;
|
|
139
|
+
}
|
|
140
|
+
/** The two snapshots, as the transaction handed them over, into figures. */
|
|
141
|
+
export declare function freeTasteReadsFrom(account: FirebaseFirestore.DocumentSnapshot | null, platform: FirebaseFirestore.DocumentSnapshot | null, day: string): FreeTasteReads;
|
|
142
|
+
/**
|
|
143
|
+
* Which of the taste's own precautions refuses, or `null` when none does —
|
|
144
|
+
* pure, so the spec can force every rung red without a Firestore.
|
|
145
|
+
*
|
|
146
|
+
* Checked in the order the reads are cheapest to explain: the daily
|
|
147
|
+
* request cap and the refusal pause (both reset on a clock the customer
|
|
148
|
+
* can be told about), then the account's month (an upgrade is the way
|
|
149
|
+
* out), then the platform ceiling (nobody's doing; try again tomorrow).
|
|
150
|
+
* The org's own rungs — its daily message cap, its band — run before this
|
|
151
|
+
* in the reservation, so a workspace at its own band hears about its own
|
|
152
|
+
* band rather than about its owner's other workspaces.
|
|
153
|
+
*
|
|
154
|
+
* Every comparison is `>=` against a count the reservation is about to
|
|
155
|
+
* increment, so a cap of N admits exactly N. A paused platform refuses
|
|
156
|
+
* regardless of the running figure: the pause is a decision recorded on
|
|
157
|
+
* the document, and a ceiling raised by an operator mid-day does not undo
|
|
158
|
+
* it — the day rolls, or staff clear the document.
|
|
159
|
+
*/
|
|
160
|
+
export declare function freeTasteRefusal(reads: FreeTasteReads, limits?: {
|
|
161
|
+
dailyRequests?: number;
|
|
162
|
+
refusalsPerDay?: number;
|
|
163
|
+
accountBudgetUsd?: number;
|
|
164
|
+
platformCeilingUsd?: number;
|
|
165
|
+
}): Extract<AssistRefusedBy, 'requests' | 'refusals' | 'account' | 'platform'> | null;
|
|
166
|
+
/**
|
|
167
|
+
* The account-side write one admitted free reservation owes: a request
|
|
168
|
+
* counted for the month and for the day. `set(…, { merge: true })` with
|
|
169
|
+
* nested increments, so the first request of a month conjures the document
|
|
170
|
+
* and a day's map entry appears the first time that day is counted.
|
|
171
|
+
*/
|
|
172
|
+
export declare function freeAccountReservationWrite(month: string, day: string): Record<string, unknown>;
|
|
173
|
+
/**
|
|
174
|
+
* The two writes one metered free turn owes, beside the org's own rollup.
|
|
175
|
+
*
|
|
176
|
+
* A `refusal` stop is the one case where the meters disagree on purpose. The
|
|
177
|
+
* tokens were spent, so the PLATFORM document — which bounds our money —
|
|
178
|
+
* takes the cost and counts the refusal. The ACCOUNT's credits do not move:
|
|
179
|
+
* a refusal never draws credits, because a customer whose brief was
|
|
180
|
+
* declined has not been given anything, and because a run of declined
|
|
181
|
+
* briefs is answered by the refusal pause rather than by an emptied band.
|
|
182
|
+
* The account's day counts the refusal, which is what the pause reads.
|
|
183
|
+
*/
|
|
184
|
+
export declare function freeTasteMeterWrites(input: {
|
|
185
|
+
estCostUsd: number;
|
|
186
|
+
refused: boolean;
|
|
187
|
+
day: string;
|
|
188
|
+
month: string;
|
|
189
|
+
}): {
|
|
190
|
+
account: Record<string, unknown>;
|
|
191
|
+
platform: Record<string, unknown>;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* After a free turn is metered: tell staff at 80% of the day's ceiling,
|
|
195
|
+
* and record the pause at 100% — each ONCE per UTC day, deduplicated on
|
|
196
|
+
* the day document itself inside a transaction, so two turns landing
|
|
197
|
+
* together cannot both cross a threshold and both announce it.
|
|
198
|
+
*
|
|
199
|
+
* The refusal at the ceiling is NOT decided here; `freeTasteRefusal` reads
|
|
200
|
+
* the figure inside every reservation and refuses on it directly. This
|
|
201
|
+
* only says so out loud: the email, and an `adminAudit` row under
|
|
202
|
+
* `platform.aiFreeSpend.paused` so the staff audit trail shows the day the
|
|
203
|
+
* taste switched itself off. Both are best-effort and neither can fail the
|
|
204
|
+
* turn that triggered them — the turn is already paid for.
|
|
205
|
+
*
|
|
206
|
+
* Returns what it announced, for the spec and for the log.
|
|
207
|
+
*/
|
|
208
|
+
export declare function announcePlatformFreeSpend(firestore: FirebaseFirestore.Firestore, day: string): Promise<{
|
|
209
|
+
alerted: boolean;
|
|
210
|
+
paused: boolean;
|
|
211
|
+
estCostUsd: number;
|
|
212
|
+
}>;
|
|
213
|
+
/** The staff readout of today's free spend, for the assist signals page. */
|
|
214
|
+
export interface PlatformFreeSpendReadout {
|
|
215
|
+
day: string;
|
|
216
|
+
estCostUsd: number;
|
|
217
|
+
requests: number;
|
|
218
|
+
refusals: number;
|
|
219
|
+
ceilingUsd: number;
|
|
220
|
+
/** True once the 80% mail went out today. */
|
|
221
|
+
alerted: boolean;
|
|
222
|
+
/** True while free generation is refused for the day. */
|
|
223
|
+
paused: boolean;
|
|
224
|
+
}
|
|
225
|
+
export declare function readPlatformFreeSpend(firestore: FirebaseFirestore.Firestore, day: string): Promise<PlatformFreeSpendReadout>;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { FieldValue } from "firebase-admin/firestore";
|
|
18
|
+
import { assistUsdFromCredits } from "@aglyn/aglyn/app-utils/assist-credits";
|
|
19
|
+
import { FREE_AI_TASTE_CREDITS_PER_MONTH, resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
20
|
+
import { sendStaffAlertEmail } from "@aglyn/tenant-data-admin/server/staff-alert-email";
|
|
21
|
+
/**
|
|
22
|
+
* The Free AI taste's precautions (AGL-2925) — the half of the meter that
|
|
23
|
+
* exists only because the Free band has no invoice behind it.
|
|
24
|
+
*
|
|
25
|
+
* `FREE_AI_TASTE_CREDITS_PER_MONTH` gives every Free workspace 300 credits a
|
|
26
|
+
* month as a wall. A wall per WORKSPACE is not a bound per PERSON: one
|
|
27
|
+
* account may hold three free workspaces (AGL-2265), a script can mint
|
|
28
|
+
* accounts faster than a person can, and a refused brief costs the same
|
|
29
|
+
* tokens as an answered one. So the taste is metered a second time, per
|
|
30
|
+
* account, and bounded three more ways. Everything below is read and
|
|
31
|
+
* decided INSIDE `reserveAssistMessage`'s transaction, so a burst of
|
|
32
|
+
* concurrent requests cannot each read "under" and all proceed — the same
|
|
33
|
+
* property every other rung of that reservation has.
|
|
34
|
+
*
|
|
35
|
+
* ## Documents (both ABSENT from firebase-firestore.rules on purpose)
|
|
36
|
+
*
|
|
37
|
+
* users/{uid}/aiUsage/{YYYY-MM} the ACCOUNT's month:
|
|
38
|
+
* { month, estCostUsd, requests, days: { [YYYY-MM-DD]: { requests,
|
|
39
|
+
* refusals } }, updatedAt }
|
|
40
|
+
* platformAiFreeSpend/{YYYY-MM-DD} the PLATFORM's day of free spend:
|
|
41
|
+
* { day, estCostUsd, requests, refusals, alertedAt?, pausedAt?,
|
|
42
|
+
* updatedAt }
|
|
43
|
+
*
|
|
44
|
+
* The account document sits under `users/{uid}`, whose owner may read and
|
|
45
|
+
* write the user document itself. It may not touch this subcollection: the
|
|
46
|
+
* `users` block matches its subcollections BY NAME and carries no wildcard,
|
|
47
|
+
* so an unmatched name is default-deny for every client, staff included —
|
|
48
|
+
* the same property `assistUsage` relies on one level down. An owner who
|
|
49
|
+
* could write here could reset their own allowance, which is the whole
|
|
50
|
+
* meter. The platform document is a top-level collection alongside
|
|
51
|
+
* `platformCronBeats`, unmatched and therefore closed, and every reader is
|
|
52
|
+
* an Admin-SDK reader behind the staff gate.
|
|
53
|
+
*
|
|
54
|
+
* ## Whose account
|
|
55
|
+
*
|
|
56
|
+
* The WORKSPACE OWNER's. A member invited to someone else's free workspace
|
|
57
|
+
* draws from that workspace's band and from its owner's allowance, never
|
|
58
|
+
* from their own account: the owner is the person the free workspace is
|
|
59
|
+
* attributed to for the ceiling on how many they may hold, and attributing
|
|
60
|
+
* the spend the same way is what keeps the two bounds describing one
|
|
61
|
+
* person. `ownerUid` first, `createdByUid` for an org that predates
|
|
62
|
+
* ownership stamping — the same union the free-workspace count reads.
|
|
63
|
+
*/ /** Where an account's month of free AI spend lives, under `users/{uid}`. */ export const FREE_AI_ACCOUNT_USAGE_COLLECTION = 'aiUsage';
|
|
64
|
+
/** The top-level collection holding one document per UTC day of free spend. */ export const PLATFORM_AI_FREE_SPEND_COLLECTION = 'platformAiFreeSpend';
|
|
65
|
+
/**
|
|
66
|
+
* Refusals a day before an account's free generation pauses. Three, not
|
|
67
|
+
* one: a single declined brief is a normal outcome of asking an assistant
|
|
68
|
+
* for things, and pausing on it would punish an ordinary afternoon. Three
|
|
69
|
+
* in a day is somebody probing what the model will write.
|
|
70
|
+
*/ export const AI_FREE_REFUSALS_PER_DAY = 3;
|
|
71
|
+
/** The share of the platform ceiling at which staff are told, once a day. */ export const AI_FREE_PLATFORM_ALERT_SHARE = 0.8;
|
|
72
|
+
/** The account allowance, in USD of provider spend — the taste, per person. */ export const FREE_AI_ACCOUNT_BUDGET_USD = assistUsdFromCredits(FREE_AI_TASTE_CREDITS_PER_MONTH);
|
|
73
|
+
/**
|
|
74
|
+
* Free requests one account may make a UTC day, across its workspaces
|
|
75
|
+
* (`AI_FREE_DAILY_REQUESTS`, default 30).
|
|
76
|
+
*
|
|
77
|
+
* A request, not a message: this counts every reservation the account's
|
|
78
|
+
* free workspaces take, at either door. Thirty is three times the
|
|
79
|
+
* per-workspace message cap, so one workspace never hits it before its own
|
|
80
|
+
* cap and three workspaces hit it exactly when their caps would have
|
|
81
|
+
* summed — it bounds the multiplier, not the ordinary day. Zero is honored
|
|
82
|
+
* as "no free requests", which is a decision an operator may write down;
|
|
83
|
+
* junk and an empty value take the default.
|
|
84
|
+
*/ export function aiFreeDailyRequests() {
|
|
85
|
+
const raw = process.env.AI_FREE_DAILY_REQUESTS;
|
|
86
|
+
const parsed = raw ? Number(raw) : Number.NaN;
|
|
87
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 30;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The platform-wide ceiling on ONE UTC day of free-tier provider spend
|
|
91
|
+
* (`AI_FREE_DAILY_PLATFORM_CEILING_USD`, default $25).
|
|
92
|
+
*
|
|
93
|
+
* Every other bound here is per something — per workspace, per account,
|
|
94
|
+
* per address — and a wave of new accounts clears all of them one at a
|
|
95
|
+
* time. This is the bound on the sum. At 80% of it staff are mailed; at
|
|
96
|
+
* 100% every free reservation refuses until the day rolls, paid workspaces
|
|
97
|
+
* untouched, and the manual `ai-generate` lockdown key remains the switch
|
|
98
|
+
* for anything the ceiling did not catch.
|
|
99
|
+
*
|
|
100
|
+
* Fails to the DEFAULT, never to "no ceiling", for the reason the operator
|
|
101
|
+
* COGS limit does: an empty, negative, zero or unparseable value is not a
|
|
102
|
+
* decision to run unbounded. There is deliberately no `off` word — a
|
|
103
|
+
* deployment that wants no ceiling sets a figure it is content to spend.
|
|
104
|
+
*/ export function aiFreeDailyPlatformCeilingUsd() {
|
|
105
|
+
var _process_env_AI_FREE_DAILY_PLATFORM_CEILING_USD;
|
|
106
|
+
const raw = String((_process_env_AI_FREE_DAILY_PLATFORM_CEILING_USD = process.env.AI_FREE_DAILY_PLATFORM_CEILING_USD) != null ? _process_env_AI_FREE_DAILY_PLATFORM_CEILING_USD : '').trim();
|
|
107
|
+
const parsed = raw ? Number(raw) : Number.NaN;
|
|
108
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 25;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The account to meter a workspace's free spend against, or `null` when the
|
|
112
|
+
* workspace is not on the Free plan and none of this applies.
|
|
113
|
+
*
|
|
114
|
+
* `resolveEffectivePlan`, not `org.plan`: a workspace whose subscription
|
|
115
|
+
* died is Free again, and its spend is the taste's spend again.
|
|
116
|
+
*/ export function freeAssistAccount(org) {
|
|
117
|
+
var _ref, _ref1;
|
|
118
|
+
if (resolveEffectivePlan(org) !== 'free') return null;
|
|
119
|
+
const owner = String((_ref = org == null ? void 0 : org.ownerUid) != null ? _ref : '').trim();
|
|
120
|
+
const creator = String((_ref1 = org == null ? void 0 : org.createdByUid) != null ? _ref1 : '').trim();
|
|
121
|
+
return {
|
|
122
|
+
accountUid: owner || creator || null
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function freeAccountUsageRef(firestore, accountUid, month) {
|
|
126
|
+
return firestore.collection('users').doc(accountUid).collection(FREE_AI_ACCOUNT_USAGE_COLLECTION).doc(month);
|
|
127
|
+
}
|
|
128
|
+
export function platformFreeSpendRef(firestore, day) {
|
|
129
|
+
return firestore.collection(PLATFORM_AI_FREE_SPEND_COLLECTION).doc(day);
|
|
130
|
+
}
|
|
131
|
+
/** The two snapshots, as the transaction handed them over, into figures. */ export function freeTasteReadsFrom(account, platform, day) {
|
|
132
|
+
var _ref, _days_day, _ref1, _today_requests, _today_refusals, _ref2;
|
|
133
|
+
const days = (_ref = account == null ? void 0 : account.get('days')) != null ? _ref : {};
|
|
134
|
+
const today = (_days_day = days[day]) != null ? _days_day : {};
|
|
135
|
+
return {
|
|
136
|
+
accountCostUsd: Number((_ref1 = account == null ? void 0 : account.get('estCostUsd')) != null ? _ref1 : 0),
|
|
137
|
+
accountRequestsToday: Number((_today_requests = today.requests) != null ? _today_requests : 0),
|
|
138
|
+
accountRefusalsToday: Number((_today_refusals = today.refusals) != null ? _today_refusals : 0),
|
|
139
|
+
platformCostUsd: Number((_ref2 = platform == null ? void 0 : platform.get('estCostUsd')) != null ? _ref2 : 0),
|
|
140
|
+
platformPaused: Boolean(platform == null ? void 0 : platform.get('pausedAt'))
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Which of the taste's own precautions refuses, or `null` when none does —
|
|
145
|
+
* pure, so the spec can force every rung red without a Firestore.
|
|
146
|
+
*
|
|
147
|
+
* Checked in the order the reads are cheapest to explain: the daily
|
|
148
|
+
* request cap and the refusal pause (both reset on a clock the customer
|
|
149
|
+
* can be told about), then the account's month (an upgrade is the way
|
|
150
|
+
* out), then the platform ceiling (nobody's doing; try again tomorrow).
|
|
151
|
+
* The org's own rungs — its daily message cap, its band — run before this
|
|
152
|
+
* in the reservation, so a workspace at its own band hears about its own
|
|
153
|
+
* band rather than about its owner's other workspaces.
|
|
154
|
+
*
|
|
155
|
+
* Every comparison is `>=` against a count the reservation is about to
|
|
156
|
+
* increment, so a cap of N admits exactly N. A paused platform refuses
|
|
157
|
+
* regardless of the running figure: the pause is a decision recorded on
|
|
158
|
+
* the document, and a ceiling raised by an operator mid-day does not undo
|
|
159
|
+
* it — the day rolls, or staff clear the document.
|
|
160
|
+
*/ export function freeTasteRefusal(reads, limits = {}) {
|
|
161
|
+
var _limits_dailyRequests, _limits_refusalsPerDay, _limits_accountBudgetUsd, _limits_platformCeilingUsd;
|
|
162
|
+
const dailyRequests = (_limits_dailyRequests = limits.dailyRequests) != null ? _limits_dailyRequests : aiFreeDailyRequests();
|
|
163
|
+
const refusalsPerDay = (_limits_refusalsPerDay = limits.refusalsPerDay) != null ? _limits_refusalsPerDay : AI_FREE_REFUSALS_PER_DAY;
|
|
164
|
+
const accountBudgetUsd = (_limits_accountBudgetUsd = limits.accountBudgetUsd) != null ? _limits_accountBudgetUsd : FREE_AI_ACCOUNT_BUDGET_USD;
|
|
165
|
+
const platformCeilingUsd = (_limits_platformCeilingUsd = limits.platformCeilingUsd) != null ? _limits_platformCeilingUsd : aiFreeDailyPlatformCeilingUsd();
|
|
166
|
+
if (reads.accountRequestsToday >= dailyRequests) return 'requests';
|
|
167
|
+
if (reads.accountRefusalsToday >= refusalsPerDay) return 'refusals';
|
|
168
|
+
if (reads.accountCostUsd >= accountBudgetUsd) return 'account';
|
|
169
|
+
if (reads.platformPaused || reads.platformCostUsd >= platformCeilingUsd) {
|
|
170
|
+
return 'platform';
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The account-side write one admitted free reservation owes: a request
|
|
176
|
+
* counted for the month and for the day. `set(…, { merge: true })` with
|
|
177
|
+
* nested increments, so the first request of a month conjures the document
|
|
178
|
+
* and a day's map entry appears the first time that day is counted.
|
|
179
|
+
*/ export function freeAccountReservationWrite(month, day) {
|
|
180
|
+
const increment = FieldValue.increment;
|
|
181
|
+
return {
|
|
182
|
+
month,
|
|
183
|
+
requests: increment(1),
|
|
184
|
+
days: {
|
|
185
|
+
[day]: {
|
|
186
|
+
requests: increment(1)
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The two writes one metered free turn owes, beside the org's own rollup.
|
|
194
|
+
*
|
|
195
|
+
* A `refusal` stop is the one case where the meters disagree on purpose. The
|
|
196
|
+
* tokens were spent, so the PLATFORM document — which bounds our money —
|
|
197
|
+
* takes the cost and counts the refusal. The ACCOUNT's credits do not move:
|
|
198
|
+
* a refusal never draws credits, because a customer whose brief was
|
|
199
|
+
* declined has not been given anything, and because a run of declined
|
|
200
|
+
* briefs is answered by the refusal pause rather than by an emptied band.
|
|
201
|
+
* The account's day counts the refusal, which is what the pause reads.
|
|
202
|
+
*/ export function freeTasteMeterWrites(input) {
|
|
203
|
+
const increment = FieldValue.increment;
|
|
204
|
+
const serverTimestamp = FieldValue.serverTimestamp;
|
|
205
|
+
const { estCostUsd, refused, day, month } = input;
|
|
206
|
+
return {
|
|
207
|
+
account: refused ? {
|
|
208
|
+
month,
|
|
209
|
+
days: {
|
|
210
|
+
[day]: {
|
|
211
|
+
refusals: increment(1)
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
updatedAt: serverTimestamp()
|
|
215
|
+
} : {
|
|
216
|
+
month,
|
|
217
|
+
estCostUsd: increment(estCostUsd),
|
|
218
|
+
updatedAt: serverTimestamp()
|
|
219
|
+
},
|
|
220
|
+
platform: {
|
|
221
|
+
day,
|
|
222
|
+
estCostUsd: increment(estCostUsd),
|
|
223
|
+
requests: increment(1),
|
|
224
|
+
refusals: increment(refused ? 1 : 0),
|
|
225
|
+
updatedAt: serverTimestamp()
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* After a free turn is metered: tell staff at 80% of the day's ceiling,
|
|
231
|
+
* and record the pause at 100% — each ONCE per UTC day, deduplicated on
|
|
232
|
+
* the day document itself inside a transaction, so two turns landing
|
|
233
|
+
* together cannot both cross a threshold and both announce it.
|
|
234
|
+
*
|
|
235
|
+
* The refusal at the ceiling is NOT decided here; `freeTasteRefusal` reads
|
|
236
|
+
* the figure inside every reservation and refuses on it directly. This
|
|
237
|
+
* only says so out loud: the email, and an `adminAudit` row under
|
|
238
|
+
* `platform.aiFreeSpend.paused` so the staff audit trail shows the day the
|
|
239
|
+
* taste switched itself off. Both are best-effort and neither can fail the
|
|
240
|
+
* turn that triggered them — the turn is already paid for.
|
|
241
|
+
*
|
|
242
|
+
* Returns what it announced, for the spec and for the log.
|
|
243
|
+
*/ export async function announcePlatformFreeSpend(firestore, day) {
|
|
244
|
+
const ceilingUsd = aiFreeDailyPlatformCeilingUsd();
|
|
245
|
+
const ref = platformFreeSpendRef(firestore, day);
|
|
246
|
+
const crossed = await firestore.runTransaction(async (tx)=>{
|
|
247
|
+
var _snapshot_get;
|
|
248
|
+
const snapshot = await tx.get(ref);
|
|
249
|
+
const estCostUsd = Number((_snapshot_get = snapshot.get('estCostUsd')) != null ? _snapshot_get : 0);
|
|
250
|
+
const alert = !snapshot.get('alertedAt') && estCostUsd >= ceilingUsd * AI_FREE_PLATFORM_ALERT_SHARE;
|
|
251
|
+
const pause = !snapshot.get('pausedAt') && estCostUsd >= ceilingUsd;
|
|
252
|
+
if (alert || pause) {
|
|
253
|
+
tx.set(ref, _extends({}, alert ? {
|
|
254
|
+
alertedAt: FieldValue.serverTimestamp()
|
|
255
|
+
} : {}, pause ? {
|
|
256
|
+
pausedAt: FieldValue.serverTimestamp()
|
|
257
|
+
} : {}), {
|
|
258
|
+
merge: true
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
alerted: alert,
|
|
263
|
+
paused: pause,
|
|
264
|
+
estCostUsd
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
if (!crossed.alerted && !crossed.paused) return crossed;
|
|
268
|
+
const figure = `$${crossed.estCostUsd.toFixed(2)} of the $${ceilingUsd.toFixed(2)} ceiling`;
|
|
269
|
+
if (crossed.paused) {
|
|
270
|
+
await firestore.collection('adminAudit').add({
|
|
271
|
+
actorUid: 'system:ai-free-spend',
|
|
272
|
+
action: 'platform.aiFreeSpend.paused',
|
|
273
|
+
target: `${PLATFORM_AI_FREE_SPEND_COLLECTION}/${day}`,
|
|
274
|
+
after: {
|
|
275
|
+
day,
|
|
276
|
+
estCostUsd: crossed.estCostUsd,
|
|
277
|
+
ceilingUsd
|
|
278
|
+
},
|
|
279
|
+
at: FieldValue.serverTimestamp()
|
|
280
|
+
}).catch((error)=>console.error('[ai-free-spend] audit row failed', error));
|
|
281
|
+
}
|
|
282
|
+
await sendStaffAlertEmail({
|
|
283
|
+
subject: crossed.paused ? `Free AI generation paused for ${day}: ${figure}` : `Free AI spend at 80% of today's ceiling (${day}): ${figure}`,
|
|
284
|
+
text: crossed.paused ? `Free-tier AI spend for ${day} (UTC) reached ${figure} ` + '(AI_FREE_DAILY_PLATFORM_CEILING_USD). Every Free workspace is ' + 'refused AI generation until the UTC day rolls; paid workspaces are ' + 'unaffected. If this is an abuse wave rather than organic use, the ' + '`ai-generate` feature lock on Staff → Lockdown stops generation ' + 'for everyone, and the assist signals page shows which workspaces ' + 'spent it.' : `Free-tier AI spend for ${day} (UTC) is at ${figure} ` + '(AI_FREE_DAILY_PLATFORM_CEILING_USD). At 100% every Free ' + 'workspace is refused AI generation until the day rolls. The ' + 'assist signals page shows the day so far; the `ai-generate` ' + 'feature lock on Staff → Lockdown is the manual stop.',
|
|
285
|
+
context: 'ai-free-spend'
|
|
286
|
+
});
|
|
287
|
+
return crossed;
|
|
288
|
+
}
|
|
289
|
+
export async function readPlatformFreeSpend(firestore, day) {
|
|
290
|
+
var _snapshot_get, _snapshot_get1, _snapshot_get2;
|
|
291
|
+
const snapshot = await platformFreeSpendRef(firestore, day).get();
|
|
292
|
+
const ceilingUsd = aiFreeDailyPlatformCeilingUsd();
|
|
293
|
+
const estCostUsd = Number((_snapshot_get = snapshot.get('estCostUsd')) != null ? _snapshot_get : 0);
|
|
294
|
+
return {
|
|
295
|
+
day,
|
|
296
|
+
estCostUsd,
|
|
297
|
+
requests: Number((_snapshot_get1 = snapshot.get('requests')) != null ? _snapshot_get1 : 0),
|
|
298
|
+
refusals: Number((_snapshot_get2 = snapshot.get('refusals')) != null ? _snapshot_get2 : 0),
|
|
299
|
+
ceilingUsd,
|
|
300
|
+
alerted: Boolean(snapshot.get('alertedAt')),
|
|
301
|
+
// The pause is the recorded decision OR the live figure: a day that
|
|
302
|
+
// crossed the ceiling before anything announced it is paused all the
|
|
303
|
+
// same, because the reservation refuses on the figure.
|
|
304
|
+
paused: Boolean(snapshot.get('pausedAt')) || estCostUsd >= ceilingUsd
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
//# sourceMappingURL=assist-free-taste.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/assist-free-taste.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n assistUsdFromCredits,\n type AssistRefusedBy,\n} from '@aglyn/aglyn/app-utils/assist-credits'\nimport {\n FREE_AI_TASTE_CREDITS_PER_MONTH,\n resolveEffectivePlan,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { sendStaffAlertEmail } from '@aglyn/tenant-data-admin/server/staff-alert-email'\n\n/**\n * The Free AI taste's precautions (AGL-2925) — the half of the meter that\n * exists only because the Free band has no invoice behind it.\n *\n * `FREE_AI_TASTE_CREDITS_PER_MONTH` gives every Free workspace 300 credits a\n * month as a wall. A wall per WORKSPACE is not a bound per PERSON: one\n * account may hold three free workspaces (AGL-2265), a script can mint\n * accounts faster than a person can, and a refused brief costs the same\n * tokens as an answered one. So the taste is metered a second time, per\n * account, and bounded three more ways. Everything below is read and\n * decided INSIDE `reserveAssistMessage`'s transaction, so a burst of\n * concurrent requests cannot each read \"under\" and all proceed — the same\n * property every other rung of that reservation has.\n *\n * ## Documents (both ABSENT from firebase-firestore.rules on purpose)\n *\n * users/{uid}/aiUsage/{YYYY-MM} the ACCOUNT's month:\n * { month, estCostUsd, requests, days: { [YYYY-MM-DD]: { requests,\n * refusals } }, updatedAt }\n * platformAiFreeSpend/{YYYY-MM-DD} the PLATFORM's day of free spend:\n * { day, estCostUsd, requests, refusals, alertedAt?, pausedAt?,\n * updatedAt }\n *\n * The account document sits under `users/{uid}`, whose owner may read and\n * write the user document itself. It may not touch this subcollection: the\n * `users` block matches its subcollections BY NAME and carries no wildcard,\n * so an unmatched name is default-deny for every client, staff included —\n * the same property `assistUsage` relies on one level down. An owner who\n * could write here could reset their own allowance, which is the whole\n * meter. The platform document is a top-level collection alongside\n * `platformCronBeats`, unmatched and therefore closed, and every reader is\n * an Admin-SDK reader behind the staff gate.\n *\n * ## Whose account\n *\n * The WORKSPACE OWNER's. A member invited to someone else's free workspace\n * draws from that workspace's band and from its owner's allowance, never\n * from their own account: the owner is the person the free workspace is\n * attributed to for the ceiling on how many they may hold, and attributing\n * the spend the same way is what keeps the two bounds describing one\n * person. `ownerUid` first, `createdByUid` for an org that predates\n * ownership stamping — the same union the free-workspace count reads.\n */\n\n/** Where an account's month of free AI spend lives, under `users/{uid}`. */\nexport const FREE_AI_ACCOUNT_USAGE_COLLECTION = 'aiUsage'\n\n/** The top-level collection holding one document per UTC day of free spend. */\nexport const PLATFORM_AI_FREE_SPEND_COLLECTION = 'platformAiFreeSpend'\n\n/**\n * Refusals a day before an account's free generation pauses. Three, not\n * one: a single declined brief is a normal outcome of asking an assistant\n * for things, and pausing on it would punish an ordinary afternoon. Three\n * in a day is somebody probing what the model will write.\n */\nexport const AI_FREE_REFUSALS_PER_DAY = 3\n\n/** The share of the platform ceiling at which staff are told, once a day. */\nexport const AI_FREE_PLATFORM_ALERT_SHARE = 0.8\n\n/** The account allowance, in USD of provider spend — the taste, per person. */\nexport const FREE_AI_ACCOUNT_BUDGET_USD = assistUsdFromCredits(\n FREE_AI_TASTE_CREDITS_PER_MONTH,\n)\n\n/**\n * Free requests one account may make a UTC day, across its workspaces\n * (`AI_FREE_DAILY_REQUESTS`, default 30).\n *\n * A request, not a message: this counts every reservation the account's\n * free workspaces take, at either door. Thirty is three times the\n * per-workspace message cap, so one workspace never hits it before its own\n * cap and three workspaces hit it exactly when their caps would have\n * summed — it bounds the multiplier, not the ordinary day. Zero is honored\n * as \"no free requests\", which is a decision an operator may write down;\n * junk and an empty value take the default.\n */\nexport function aiFreeDailyRequests(): number {\n const raw = process.env.AI_FREE_DAILY_REQUESTS\n const parsed = raw ? Number(raw) : Number.NaN\n return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 30\n}\n\n/**\n * The platform-wide ceiling on ONE UTC day of free-tier provider spend\n * (`AI_FREE_DAILY_PLATFORM_CEILING_USD`, default $25).\n *\n * Every other bound here is per something — per workspace, per account,\n * per address — and a wave of new accounts clears all of them one at a\n * time. This is the bound on the sum. At 80% of it staff are mailed; at\n * 100% every free reservation refuses until the day rolls, paid workspaces\n * untouched, and the manual `ai-generate` lockdown key remains the switch\n * for anything the ceiling did not catch.\n *\n * Fails to the DEFAULT, never to \"no ceiling\", for the reason the operator\n * COGS limit does: an empty, negative, zero or unparseable value is not a\n * decision to run unbounded. There is deliberately no `off` word — a\n * deployment that wants no ceiling sets a figure it is content to spend.\n */\nexport function aiFreeDailyPlatformCeilingUsd(): number {\n const raw = String(process.env.AI_FREE_DAILY_PLATFORM_CEILING_USD ?? '').trim()\n const parsed = raw ? Number(raw) : Number.NaN\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 25\n}\n\n/** The account a free workspace's spend is attributed to. */\nexport interface FreeAssistAccount {\n /**\n * The workspace owner's uid, or `null` for an org document that names no\n * owner and no creator — every org created since ownership was stamped\n * names one, so `null` is the shape of a fixture or a very old record.\n * The account rungs are skipped for it; the org's own band still binds.\n */\n accountUid: string | null\n}\n\n/** The org fields the attribution reads, beside the billing ones. */\nexport type AssistMeteredOrg = Partial<AglynOrgBilling> & {\n ownerUid?: string | null\n createdByUid?: string | null\n}\n\n/**\n * The account to meter a workspace's free spend against, or `null` when the\n * workspace is not on the Free plan and none of this applies.\n *\n * `resolveEffectivePlan`, not `org.plan`: a workspace whose subscription\n * died is Free again, and its spend is the taste's spend again.\n */\nexport function freeAssistAccount(\n org: AssistMeteredOrg | null | undefined,\n): FreeAssistAccount | null {\n if (resolveEffectivePlan(org) !== 'free') return null\n const owner = String(org?.ownerUid ?? '').trim()\n const creator = String(org?.createdByUid ?? '').trim()\n return { accountUid: owner || creator || null }\n}\n\nexport function freeAccountUsageRef(\n firestore: FirebaseFirestore.Firestore,\n accountUid: string,\n month: string,\n): FirebaseFirestore.DocumentReference {\n return firestore\n .collection('users')\n .doc(accountUid)\n .collection(FREE_AI_ACCOUNT_USAGE_COLLECTION)\n .doc(month)\n}\n\nexport function platformFreeSpendRef(\n firestore: FirebaseFirestore.Firestore,\n day: string,\n): FirebaseFirestore.DocumentReference {\n return firestore.collection(PLATFORM_AI_FREE_SPEND_COLLECTION).doc(day)\n}\n\n/** What the reservation read about the account and the platform. */\nexport interface FreeTasteReads {\n accountCostUsd: number\n accountRequestsToday: number\n accountRefusalsToday: number\n platformCostUsd: number\n platformPaused: boolean\n}\n\n/** The two snapshots, as the transaction handed them over, into figures. */\nexport function freeTasteReadsFrom(\n account: FirebaseFirestore.DocumentSnapshot | null,\n platform: FirebaseFirestore.DocumentSnapshot | null,\n day: string,\n): FreeTasteReads {\n const days = (account?.get('days') ?? {}) as Record<\n string,\n { requests?: unknown; refusals?: unknown } | undefined\n >\n const today = days[day] ?? {}\n return {\n accountCostUsd: Number(account?.get('estCostUsd') ?? 0),\n accountRequestsToday: Number(today.requests ?? 0),\n accountRefusalsToday: Number(today.refusals ?? 0),\n platformCostUsd: Number(platform?.get('estCostUsd') ?? 0),\n platformPaused: Boolean(platform?.get('pausedAt')),\n }\n}\n\n/**\n * Which of the taste's own precautions refuses, or `null` when none does —\n * pure, so the spec can force every rung red without a Firestore.\n *\n * Checked in the order the reads are cheapest to explain: the daily\n * request cap and the refusal pause (both reset on a clock the customer\n * can be told about), then the account's month (an upgrade is the way\n * out), then the platform ceiling (nobody's doing; try again tomorrow).\n * The org's own rungs — its daily message cap, its band — run before this\n * in the reservation, so a workspace at its own band hears about its own\n * band rather than about its owner's other workspaces.\n *\n * Every comparison is `>=` against a count the reservation is about to\n * increment, so a cap of N admits exactly N. A paused platform refuses\n * regardless of the running figure: the pause is a decision recorded on\n * the document, and a ceiling raised by an operator mid-day does not undo\n * it — the day rolls, or staff clear the document.\n */\nexport function freeTasteRefusal(\n reads: FreeTasteReads,\n limits: {\n dailyRequests?: number\n refusalsPerDay?: number\n accountBudgetUsd?: number\n platformCeilingUsd?: number\n } = {},\n): Extract<AssistRefusedBy, 'requests' | 'refusals' | 'account' | 'platform'> | null {\n const dailyRequests = limits.dailyRequests ?? aiFreeDailyRequests()\n const refusalsPerDay = limits.refusalsPerDay ?? AI_FREE_REFUSALS_PER_DAY\n const accountBudgetUsd = limits.accountBudgetUsd ?? FREE_AI_ACCOUNT_BUDGET_USD\n const platformCeilingUsd =\n limits.platformCeilingUsd ?? aiFreeDailyPlatformCeilingUsd()\n if (reads.accountRequestsToday >= dailyRequests) return 'requests'\n if (reads.accountRefusalsToday >= refusalsPerDay) return 'refusals'\n if (reads.accountCostUsd >= accountBudgetUsd) return 'account'\n if (reads.platformPaused || reads.platformCostUsd >= platformCeilingUsd) {\n return 'platform'\n }\n return null\n}\n\n/**\n * The account-side write one admitted free reservation owes: a request\n * counted for the month and for the day. `set(…, { merge: true })` with\n * nested increments, so the first request of a month conjures the document\n * and a day's map entry appears the first time that day is counted.\n */\nexport function freeAccountReservationWrite(\n month: string,\n day: string,\n): Record<string, unknown> {\n const increment = FieldValue.increment\n return {\n month,\n requests: increment(1),\n days: { [day]: { requests: increment(1) } },\n updatedAt: FieldValue.serverTimestamp(),\n }\n}\n\n/**\n * The two writes one metered free turn owes, beside the org's own rollup.\n *\n * A `refusal` stop is the one case where the meters disagree on purpose. The\n * tokens were spent, so the PLATFORM document — which bounds our money —\n * takes the cost and counts the refusal. The ACCOUNT's credits do not move:\n * a refusal never draws credits, because a customer whose brief was\n * declined has not been given anything, and because a run of declined\n * briefs is answered by the refusal pause rather than by an emptied band.\n * The account's day counts the refusal, which is what the pause reads.\n */\nexport function freeTasteMeterWrites(input: {\n estCostUsd: number\n refused: boolean\n day: string\n month: string\n}): { account: Record<string, unknown>; platform: Record<string, unknown> } {\n const increment = FieldValue.increment\n const serverTimestamp = FieldValue.serverTimestamp\n const { estCostUsd, refused, day, month } = input\n return {\n account: refused\n ? {\n month,\n days: { [day]: { refusals: increment(1) } },\n updatedAt: serverTimestamp(),\n }\n : { month, estCostUsd: increment(estCostUsd), updatedAt: serverTimestamp() },\n platform: {\n day,\n estCostUsd: increment(estCostUsd),\n requests: increment(1),\n refusals: increment(refused ? 1 : 0),\n updatedAt: serverTimestamp(),\n },\n }\n}\n\n/**\n * After a free turn is metered: tell staff at 80% of the day's ceiling,\n * and record the pause at 100% — each ONCE per UTC day, deduplicated on\n * the day document itself inside a transaction, so two turns landing\n * together cannot both cross a threshold and both announce it.\n *\n * The refusal at the ceiling is NOT decided here; `freeTasteRefusal` reads\n * the figure inside every reservation and refuses on it directly. This\n * only says so out loud: the email, and an `adminAudit` row under\n * `platform.aiFreeSpend.paused` so the staff audit trail shows the day the\n * taste switched itself off. Both are best-effort and neither can fail the\n * turn that triggered them — the turn is already paid for.\n *\n * Returns what it announced, for the spec and for the log.\n */\nexport async function announcePlatformFreeSpend(\n firestore: FirebaseFirestore.Firestore,\n day: string,\n): Promise<{ alerted: boolean; paused: boolean; estCostUsd: number }> {\n const ceilingUsd = aiFreeDailyPlatformCeilingUsd()\n const ref = platformFreeSpendRef(firestore, day)\n const crossed = await firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(ref)\n const estCostUsd = Number(snapshot.get('estCostUsd') ?? 0)\n const alert =\n !snapshot.get('alertedAt') &&\n estCostUsd >= ceilingUsd * AI_FREE_PLATFORM_ALERT_SHARE\n const pause = !snapshot.get('pausedAt') && estCostUsd >= ceilingUsd\n if (alert || pause) {\n tx.set(\n ref,\n {\n ...(alert ? { alertedAt: FieldValue.serverTimestamp() } : {}),\n ...(pause ? { pausedAt: FieldValue.serverTimestamp() } : {}),\n },\n { merge: true },\n )\n }\n return { alerted: alert, paused: pause, estCostUsd }\n })\n if (!crossed.alerted && !crossed.paused) return crossed\n\n const figure = `$${crossed.estCostUsd.toFixed(2)} of the $${ceilingUsd.toFixed(2)} ceiling`\n if (crossed.paused) {\n await firestore\n .collection('adminAudit')\n .add({\n actorUid: 'system:ai-free-spend',\n action: 'platform.aiFreeSpend.paused',\n target: `${PLATFORM_AI_FREE_SPEND_COLLECTION}/${day}`,\n after: { day, estCostUsd: crossed.estCostUsd, ceilingUsd },\n at: FieldValue.serverTimestamp(),\n })\n .catch((error) =>\n console.error('[ai-free-spend] audit row failed', error),\n )\n }\n await sendStaffAlertEmail({\n subject: crossed.paused\n ? `Free AI generation paused for ${day}: ${figure}`\n : `Free AI spend at 80% of today's ceiling (${day}): ${figure}`,\n text: crossed.paused\n ? `Free-tier AI spend for ${day} (UTC) reached ${figure} ` +\n '(AI_FREE_DAILY_PLATFORM_CEILING_USD). Every Free workspace is ' +\n 'refused AI generation until the UTC day rolls; paid workspaces are ' +\n 'unaffected. If this is an abuse wave rather than organic use, the ' +\n '`ai-generate` feature lock on Staff → Lockdown stops generation ' +\n 'for everyone, and the assist signals page shows which workspaces ' +\n 'spent it.'\n : `Free-tier AI spend for ${day} (UTC) is at ${figure} ` +\n '(AI_FREE_DAILY_PLATFORM_CEILING_USD). At 100% every Free ' +\n 'workspace is refused AI generation until the day rolls. The ' +\n 'assist signals page shows the day so far; the `ai-generate` ' +\n 'feature lock on Staff → Lockdown is the manual stop.',\n context: 'ai-free-spend',\n })\n return crossed\n}\n\n/** The staff readout of today's free spend, for the assist signals page. */\nexport interface PlatformFreeSpendReadout {\n day: string\n estCostUsd: number\n requests: number\n refusals: number\n ceilingUsd: number\n /** True once the 80% mail went out today. */\n alerted: boolean\n /** True while free generation is refused for the day. */\n paused: boolean\n}\n\nexport async function readPlatformFreeSpend(\n firestore: FirebaseFirestore.Firestore,\n day: string,\n): Promise<PlatformFreeSpendReadout> {\n const snapshot = await platformFreeSpendRef(firestore, day).get()\n const ceilingUsd = aiFreeDailyPlatformCeilingUsd()\n const estCostUsd = Number(snapshot.get('estCostUsd') ?? 0)\n return {\n day,\n estCostUsd,\n requests: Number(snapshot.get('requests') ?? 0),\n refusals: Number(snapshot.get('refusals') ?? 0),\n ceilingUsd,\n alerted: Boolean(snapshot.get('alertedAt')),\n // The pause is the recorded decision OR the live figure: a day that\n // crossed the ceiling before anything announced it is paused all the\n // same, because the reservation refuses on the figure.\n paused: Boolean(snapshot.get('pausedAt')) || estCostUsd >= ceilingUsd,\n }\n}\n"],"names":["FieldValue","assistUsdFromCredits","FREE_AI_TASTE_CREDITS_PER_MONTH","resolveEffectivePlan","sendStaffAlertEmail","FREE_AI_ACCOUNT_USAGE_COLLECTION","PLATFORM_AI_FREE_SPEND_COLLECTION","AI_FREE_REFUSALS_PER_DAY","AI_FREE_PLATFORM_ALERT_SHARE","FREE_AI_ACCOUNT_BUDGET_USD","aiFreeDailyRequests","raw","process","env","AI_FREE_DAILY_REQUESTS","parsed","Number","NaN","isFinite","Math","floor","aiFreeDailyPlatformCeilingUsd","String","AI_FREE_DAILY_PLATFORM_CEILING_USD","trim","freeAssistAccount","org","owner","ownerUid","creator","createdByUid","accountUid","freeAccountUsageRef","firestore","month","collection","doc","platformFreeSpendRef","day","freeTasteReadsFrom","account","platform","days","today","get","accountCostUsd","accountRequestsToday","requests","accountRefusalsToday","refusals","platformCostUsd","platformPaused","Boolean","freeTasteRefusal","reads","limits","dailyRequests","refusalsPerDay","accountBudgetUsd","platformCeilingUsd","freeAccountReservationWrite","increment","updatedAt","serverTimestamp","freeTasteMeterWrites","input","estCostUsd","refused","announcePlatformFreeSpend","ceilingUsd","ref","crossed","runTransaction","tx","snapshot","alert","pause","set","alertedAt","pausedAt","merge","alerted","paused","figure","toFixed","add","actorUid","action","target","after","at","catch","error","console","subject","text","context","readPlatformFreeSpend"],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiRefusedBy } from '../model/ai-allotments';
|
|
18
|
+
/**
|
|
19
|
+
* THE PER-ORG REFUSAL COUNTER (AGL-2930).
|
|
20
|
+
*
|
|
21
|
+
* `reserveAssistMessage` refuses for four reasons and, until now, recorded
|
|
22
|
+
* none of them: a refusal moved no counter by design, so the month document
|
|
23
|
+
* could say how much an org spent and nothing about how often it was turned
|
|
24
|
+
* away. Staff monitoring needs the second figure — a workspace refused at its
|
|
25
|
+
* band forty times this month is a sales conversation, and one refused at the
|
|
26
|
+
* operator's backstop is an incident — and neither is derivable from spend.
|
|
27
|
+
*
|
|
28
|
+
* The count lives on the SAME document the spend does, as a map:
|
|
29
|
+
*
|
|
30
|
+
* orgs/{orgId}/assistUsage/{YYYY-MM}.refusals.{reason}
|
|
31
|
+
*
|
|
32
|
+
* with `reason` one of the four `AssistRefusedBy` values. A map rather than
|
|
33
|
+
* four top-level fields so a reader can take the whole thing in one `get`,
|
|
34
|
+
* and so a fifth reason lands without a schema change.
|
|
35
|
+
*
|
|
36
|
+
* ## Fire-and-forget, and never throws
|
|
37
|
+
*
|
|
38
|
+
* The write is deliberately OUTSIDE the reservation transaction and off its
|
|
39
|
+
* await path. A refusal is the cheap, no-token exit of the gate, and a
|
|
40
|
+
* counter that could fail the refusal — or slow it, or retry the transaction
|
|
41
|
+
* it was called from — would make the observation more expensive than the
|
|
42
|
+
* thing observed. So this returns synchronously, swallows every error
|
|
43
|
+
* including a double that has no `set`, and the only cost of a lost increment
|
|
44
|
+
* is an undercount on a staff card.
|
|
45
|
+
*/
|
|
46
|
+
/** A reason that actually refused — the `null` (admitted) case has no count. */
|
|
47
|
+
export type AssistRefusalReason = NonNullable<AiRefusedBy>;
|
|
48
|
+
/**
|
|
49
|
+
* Every reason, in the order the card and the leaderboard list them: the
|
|
50
|
+
* workspace's own four first, then an allotment a manager set (AGL-2942),
|
|
51
|
+
* then the Free taste's (AGL-2925), which only a Free workspace can be
|
|
52
|
+
* refused by.
|
|
53
|
+
*/
|
|
54
|
+
export declare const ASSIST_REFUSAL_REASONS: readonly AssistRefusalReason[];
|
|
55
|
+
export declare function recordAssistRefusal(firestore: FirebaseFirestore.Firestore, orgId: string, month: string, reason: AssistRefusalReason): void;
|
|
56
|
+
/** Every reason's count, zero-filled, as the card and the leaderboard read them. */
|
|
57
|
+
export type AssistRefusalCounts = Record<AssistRefusalReason, number> & {
|
|
58
|
+
total: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The `refusals` map off a month document as a complete, zero-filled record.
|
|
62
|
+
*
|
|
63
|
+
* Zero-filled rather than sparse because the readers render every reason:
|
|
64
|
+
* "band 0" beside "cap 12" is the finding, and a sparse map would make the
|
|
65
|
+
* reader guess whether an absent key meant none or not yet counted. A
|
|
66
|
+
* non-numeric or negative value reads as 0 — the counter is only ever
|
|
67
|
+
* incremented, so anything else is a hand edit.
|
|
68
|
+
*/
|
|
69
|
+
export declare function assistRefusalCounts(raw: unknown): AssistRefusalCounts;
|