@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,239 @@
|
|
|
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
|
+
*/ // Straight from the SDK, not the admin lib, for the reason `assist-usage.ts`
|
|
18
|
+
// gives at its head: the statics need no app, and the reservation that reads
|
|
19
|
+
// allotments runs in every unit test that touches a counter.
|
|
20
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
21
|
+
import { AI_ALLOTMENTS_COLLECTION, AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentFrom, aiAllotmentModelsAllowed, aiAllotmentSubjectId, aiAllotmentVerdict } from "../model/ai-allotments.js";
|
|
22
|
+
import { aiUsageByUserMonthFrom } from "../model/ai-usage-by-user.js";
|
|
23
|
+
import { userAiUsageMonthRef } from "./ai-usage-by-user.js";
|
|
24
|
+
/**
|
|
25
|
+
* AI ALLOTMENTS (AGL-2942): the reads the gate takes and the writes the
|
|
26
|
+
* route makes. The shape and the arithmetic are `model/ai-allotments.ts`.
|
|
27
|
+
*
|
|
28
|
+
* ## What the gate reads, and where the figures come from
|
|
29
|
+
*
|
|
30
|
+
* One request by `uid`, naming `hostId` or not, is measured against every
|
|
31
|
+
* allotment that applies to it:
|
|
32
|
+
*
|
|
33
|
+
* member:{uid} the person's credits this month, every site —
|
|
34
|
+
* `aiUsageByUser/{uid}/months/{month}.credits`
|
|
35
|
+
* collab:{hostId}:{uid} the person's credits on that site —
|
|
36
|
+
* the same document's `byHost.{hostId}`
|
|
37
|
+
* host:{hostId} the SITE's credits, everyone on it — the org
|
|
38
|
+
* rollup's `assistUsage/{month}.byHost.{hostId}`
|
|
39
|
+
* org no credits: the model restriction
|
|
40
|
+
*
|
|
41
|
+
* The per-person figures are the AGL-2928 rollup. The site figure is a map
|
|
42
|
+
* the meter writes on the org's month in the same batch, because summing the
|
|
43
|
+
* roster's months on every request would be one read per collaborator.
|
|
44
|
+
*
|
|
45
|
+
* Read inside the reservation's transaction, after the workspace's own
|
|
46
|
+
* ceilings admitted the request, so a refusal at the band costs no allotment
|
|
47
|
+
* read at all and an allotment is only ever consulted inside the band.
|
|
48
|
+
*
|
|
49
|
+
* ## Written only here, never by a client
|
|
50
|
+
*
|
|
51
|
+
* The rules deny every client write: an allotment is a spend control, and a
|
|
52
|
+
* collaborator who could write their own could lift it.
|
|
53
|
+
*/ /** The org month map a site's credits accumulate in. */ export const AI_HOST_CREDITS_FIELD = 'byHost';
|
|
54
|
+
export function aiAllotmentsCollection(orgRef) {
|
|
55
|
+
return orgRef.collection(AI_ALLOTMENTS_COLLECTION);
|
|
56
|
+
}
|
|
57
|
+
export function aiAllotmentRef(orgRef, subject) {
|
|
58
|
+
return aiAllotmentsCollection(orgRef).doc(subject);
|
|
59
|
+
}
|
|
60
|
+
const trimmed = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
61
|
+
/** An allotment read by the subject the reader ADDRESSED, not the one it echoes. */ const allotmentOf = (snapshot, subject)=>(snapshot == null ? void 0 : snapshot.exists) && subject ? aiAllotmentFrom(snapshot.data(), subject) : null;
|
|
62
|
+
/**
|
|
63
|
+
* A subject id, or `null` when a segment is not an id. A request's site comes
|
|
64
|
+
* from its body, and a value that could never name a site names no
|
|
65
|
+
* allotment rather than failing the reservation it rides in.
|
|
66
|
+
*/ function subjectIdOrNull(input) {
|
|
67
|
+
try {
|
|
68
|
+
return aiAllotmentSubjectId(input);
|
|
69
|
+
} catch (unused) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** A site's credits this month off the org rollup's month document. */ export function aiHostMonthCredits(orgMonth, hostId) {
|
|
74
|
+
var _ref;
|
|
75
|
+
const map = (orgMonth == null ? void 0 : orgMonth.exists) ? orgMonth.get(AI_HOST_CREDITS_FIELD) : null;
|
|
76
|
+
const value = Number((_ref = map == null ? void 0 : map[hostId]) != null ? _ref : 0);
|
|
77
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read and measure the allotments that apply to one request. `null` when
|
|
81
|
+
* the request names nobody — a meter that cannot say who asked meets no
|
|
82
|
+
* allotment, as it attributes to nobody.
|
|
83
|
+
*
|
|
84
|
+
* `orgMonth` is the org's month document when the caller already read it —
|
|
85
|
+
* the reservation reads it for an entitled workspace anyway — so a site
|
|
86
|
+
* allotment costs no second read of it.
|
|
87
|
+
*/ export async function readAiAllotmentGate(read, orgRef, subject, month, orgMonth = null) {
|
|
88
|
+
var _personal_byHost_hostId, _ref;
|
|
89
|
+
var _allotmentOf;
|
|
90
|
+
const uid = trimmed(subject.uid);
|
|
91
|
+
const memberId = uid ? subjectIdOrNull({
|
|
92
|
+
scope: 'member',
|
|
93
|
+
uid
|
|
94
|
+
}) : null;
|
|
95
|
+
if (!uid || !memberId) return null;
|
|
96
|
+
const requestedHost = trimmed(subject.hostId);
|
|
97
|
+
const hostSubjectId = requestedHost ? subjectIdOrNull({
|
|
98
|
+
scope: 'host',
|
|
99
|
+
hostId: requestedHost
|
|
100
|
+
}) : null;
|
|
101
|
+
const hostId = hostSubjectId ? requestedHost : '';
|
|
102
|
+
const collabId = hostId ? subjectIdOrNull({
|
|
103
|
+
scope: 'collab',
|
|
104
|
+
hostId,
|
|
105
|
+
uid
|
|
106
|
+
}) : null;
|
|
107
|
+
const subjectRef = (id)=>aiAllotmentRef(orgRef, id);
|
|
108
|
+
const [orgDoc, memberDoc, collabDoc, hostDoc, monthDoc] = await Promise.all([
|
|
109
|
+
read(subjectRef(AI_ALLOTMENT_ORG_SUBJECT)),
|
|
110
|
+
read(subjectRef(memberId)),
|
|
111
|
+
collabId ? read(subjectRef(collabId)) : null,
|
|
112
|
+
hostSubjectId ? read(subjectRef(hostSubjectId)) : null,
|
|
113
|
+
read(userAiUsageMonthRef(orgRef, uid, month))
|
|
114
|
+
]);
|
|
115
|
+
const member = allotmentOf(memberDoc, memberId);
|
|
116
|
+
const collab = allotmentOf(collabDoc, collabId);
|
|
117
|
+
const host = allotmentOf(hostDoc, hostSubjectId);
|
|
118
|
+
const personal = aiUsageByUserMonthFrom((monthDoc == null ? void 0 : monthDoc.exists) ? monthDoc.data() : null, uid, month);
|
|
119
|
+
let siteMonth = orgMonth;
|
|
120
|
+
if ((host == null ? void 0 : host.credits) && !siteMonth) {
|
|
121
|
+
siteMonth = await read(orgRef.collection('assistUsage').doc(month));
|
|
122
|
+
}
|
|
123
|
+
const standings = [];
|
|
124
|
+
const measure = (allotment, used)=>{
|
|
125
|
+
if (!allotment || allotment.credits === null || allotment.scope === 'org') return;
|
|
126
|
+
standings.push({
|
|
127
|
+
subject: allotment.subject,
|
|
128
|
+
scope: allotment.scope,
|
|
129
|
+
uid: allotment.uid,
|
|
130
|
+
hostId: allotment.hostId,
|
|
131
|
+
credits: allotment.credits,
|
|
132
|
+
used,
|
|
133
|
+
mode: allotment.mode
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
measure(member, personal.credits);
|
|
137
|
+
measure(collab, hostId ? (_personal_byHost_hostId = personal.byHost[hostId]) != null ? _personal_byHost_hostId : 0 : 0);
|
|
138
|
+
measure(host, hostId ? aiHostMonthCredits(siteMonth, hostId) : 0);
|
|
139
|
+
return _extends({}, aiAllotmentVerdict(standings), {
|
|
140
|
+
personalCredits: personal.credits,
|
|
141
|
+
standings,
|
|
142
|
+
models: aiAllotmentModelsAllowed([
|
|
143
|
+
member,
|
|
144
|
+
collab,
|
|
145
|
+
host
|
|
146
|
+
]),
|
|
147
|
+
orgModels: (_ref = (_allotmentOf = allotmentOf(orgDoc, AI_ALLOTMENT_ORG_SUBJECT)) == null ? void 0 : _allotmentOf.models) != null ? _ref : null
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** Every allotment in an org, optionally narrowed to one person or one site. */ export async function listAiAllotments(firestore, orgId, filter = {}) {
|
|
151
|
+
let query = aiAllotmentsCollection(firestore.collection('orgs').doc(orgId));
|
|
152
|
+
// Single-field equality on fields every write stores, so neither needs a
|
|
153
|
+
// composite index.
|
|
154
|
+
if (filter.uid) query = query.where('uid', '==', filter.uid);
|
|
155
|
+
else if (filter.hostId) query = query.where('hostId', '==', filter.hostId);
|
|
156
|
+
const snapshot = await query.get();
|
|
157
|
+
return snapshot.docs.map((doc)=>aiAllotmentFrom(doc.data(), doc.id)).filter((allotment)=>allotment !== null);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Set and remove allotments in one batch. A set REPLACES the document —
|
|
161
|
+
* credits, mode and allowlist together — and clears the alert marker, so a
|
|
162
|
+
* raised allotment announces its next crossing rather than inheriting the
|
|
163
|
+
* last one's.
|
|
164
|
+
*/ export async function applyAiAllotmentWrites(firestore, orgId, change, actorUid) {
|
|
165
|
+
const orgRef = firestore.collection('orgs').doc(orgId);
|
|
166
|
+
const batch = firestore.batch();
|
|
167
|
+
for (const write of change.set){
|
|
168
|
+
batch.set(aiAllotmentRef(orgRef, write.subject.id), {
|
|
169
|
+
subject: write.subject.id,
|
|
170
|
+
scope: write.subject.scope,
|
|
171
|
+
uid: write.subject.uid,
|
|
172
|
+
hostId: write.subject.hostId,
|
|
173
|
+
credits: write.subject.scope === 'org' ? null : write.credits,
|
|
174
|
+
mode: write.mode,
|
|
175
|
+
models: write.models,
|
|
176
|
+
setBy: actorUid,
|
|
177
|
+
updatedAt: FieldValue.serverTimestamp(),
|
|
178
|
+
alerted: null
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
for (const subject of change.remove){
|
|
182
|
+
batch.delete(aiAllotmentRef(orgRef, subject));
|
|
183
|
+
}
|
|
184
|
+
await batch.commit();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Give a site's month its credits so far, once, when an allotment on the site
|
|
188
|
+
* is set before the site's map has a key for it.
|
|
189
|
+
*
|
|
190
|
+
* The map is written by the meter from the day the field shipped; a month
|
|
191
|
+
* already in progress has spend the map never saw. The roster's own months
|
|
192
|
+
* carry the same figure per person (`byHost`), so the first allotment set on
|
|
193
|
+
* a site sums them and writes the total — inside a transaction that writes
|
|
194
|
+
* nothing when the key is already there, so a request metered in between is
|
|
195
|
+
* counted once, by the meter.
|
|
196
|
+
*/ export async function seedAiHostMonthCredits(firestore, orgId, hostIds, month, personCredits) {
|
|
197
|
+
if (!hostIds.length) return;
|
|
198
|
+
const monthRef = firestore.collection('orgs').doc(orgId).collection('assistUsage').doc(month);
|
|
199
|
+
await firestore.runTransaction(async (tx)=>{
|
|
200
|
+
const snapshot = await tx.get(monthRef);
|
|
201
|
+
const map = snapshot.exists ? snapshot.get(AI_HOST_CREDITS_FIELD) : null;
|
|
202
|
+
const missing = hostIds.filter((hostId)=>(map == null ? void 0 : map[hostId]) === undefined);
|
|
203
|
+
if (!missing.length) return;
|
|
204
|
+
tx.set(monthRef, {
|
|
205
|
+
month,
|
|
206
|
+
[AI_HOST_CREDITS_FIELD]: Object.fromEntries(missing.map((hostId)=>[
|
|
207
|
+
hostId,
|
|
208
|
+
personCredits(hostId)
|
|
209
|
+
]))
|
|
210
|
+
}, {
|
|
211
|
+
merge: true
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Remove a person's allotments on account erasure: `member:{uid}` and every
|
|
217
|
+
* `collab:{hostId}:{uid}`, in each org the erasure names. By path and by a
|
|
218
|
+
* collection-scope equality, so neither needs an index; an org the person
|
|
219
|
+
* had already left keeps a configuration document that names a uid and
|
|
220
|
+
* nothing else, which the next write to that subject replaces.
|
|
221
|
+
*/ export async function eraseAiAllotmentsForUser(firestore, uid, orgIds) {
|
|
222
|
+
let removed = 0;
|
|
223
|
+
for (const orgId of orgIds){
|
|
224
|
+
const allotments = await listAiAllotments(firestore, orgId, {
|
|
225
|
+
uid
|
|
226
|
+
});
|
|
227
|
+
if (!allotments.length) continue;
|
|
228
|
+
const orgRef = firestore.collection('orgs').doc(orgId);
|
|
229
|
+
const batch = firestore.batch();
|
|
230
|
+
for (const allotment of allotments){
|
|
231
|
+
batch.delete(aiAllotmentRef(orgRef, allotment.subject));
|
|
232
|
+
removed += 1;
|
|
233
|
+
}
|
|
234
|
+
await batch.commit();
|
|
235
|
+
}
|
|
236
|
+
return removed;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
//# sourceMappingURL=ai-allotments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/ai-allotments.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\n// Straight from the SDK, not the admin lib, for the reason `assist-usage.ts`\n// gives at its head: the statics need no app, and the reservation that reads\n// allotments runs in every unit test that touches a counter.\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n AI_ALLOTMENTS_COLLECTION,\n AI_ALLOTMENT_ORG_SUBJECT,\n aiAllotmentFrom,\n aiAllotmentModelsAllowed,\n aiAllotmentSubjectId,\n aiAllotmentVerdict,\n type AiAllotment,\n type AiAllotmentMode,\n type AiAllotmentStanding,\n type AiAllotmentSubject,\n type AiAllotmentVerdict,\n} from '../model/ai-allotments'\nimport { aiUsageByUserMonthFrom } from '../model/ai-usage-by-user'\nimport { userAiUsageMonthRef } from './ai-usage-by-user'\n\n/**\n * AI ALLOTMENTS (AGL-2942): the reads the gate takes and the writes the\n * route makes. The shape and the arithmetic are `model/ai-allotments.ts`.\n *\n * ## What the gate reads, and where the figures come from\n *\n * One request by `uid`, naming `hostId` or not, is measured against every\n * allotment that applies to it:\n *\n * member:{uid} the person's credits this month, every site —\n * `aiUsageByUser/{uid}/months/{month}.credits`\n * collab:{hostId}:{uid} the person's credits on that site —\n * the same document's `byHost.{hostId}`\n * host:{hostId} the SITE's credits, everyone on it — the org\n * rollup's `assistUsage/{month}.byHost.{hostId}`\n * org no credits: the model restriction\n *\n * The per-person figures are the AGL-2928 rollup. The site figure is a map\n * the meter writes on the org's month in the same batch, because summing the\n * roster's months on every request would be one read per collaborator.\n *\n * Read inside the reservation's transaction, after the workspace's own\n * ceilings admitted the request, so a refusal at the band costs no allotment\n * read at all and an allotment is only ever consulted inside the band.\n *\n * ## Written only here, never by a client\n *\n * The rules deny every client write: an allotment is a spend control, and a\n * collaborator who could write their own could lift it.\n */\n\n/** The org month map a site's credits accumulate in. */\nexport const AI_HOST_CREDITS_FIELD = 'byHost'\n\nexport function aiAllotmentsCollection(\n orgRef: FirebaseFirestore.DocumentReference,\n): FirebaseFirestore.CollectionReference {\n return orgRef.collection(AI_ALLOTMENTS_COLLECTION)\n}\n\nexport function aiAllotmentRef(\n orgRef: FirebaseFirestore.DocumentReference,\n subject: string,\n): FirebaseFirestore.DocumentReference {\n return aiAllotmentsCollection(orgRef).doc(subject)\n}\n\n/** Who a request is, for the allotments that apply to it. */\nexport interface AiAllotmentRequestSubject {\n /** The person asking; a request with none is measured against no allotment. */\n uid?: string | null\n /** The site the request named, when it named one. */\n hostId?: string | null\n}\n\n/** Everything the reservation learned about allotments, carried to the door. */\nexport interface AiAllotmentGate extends AiAllotmentVerdict {\n /** The caller's own credits this month, before this request. */\n personalCredits: number\n /** Every allotment of credits that applied, measured. */\n standings: AiAllotmentStanding[]\n /** The allotment allowlists that applied, intersected; `null` for none. */\n models: string[] | null\n /** The org-wide model restriction; `null` for none. */\n orgModels: string[] | null\n}\n\n/** A document read, from a transaction or a plain Firestore. */\nexport type AiAllotmentDocReader = (\n ref: FirebaseFirestore.DocumentReference,\n) => Promise<FirebaseFirestore.DocumentSnapshot>\n\nconst trimmed = (value: unknown): string =>\n typeof value === 'string' ? value.trim() : ''\n\n/** An allotment read by the subject the reader ADDRESSED, not the one it echoes. */\nconst allotmentOf = (\n snapshot: FirebaseFirestore.DocumentSnapshot | null | undefined,\n subject: string | null,\n): AiAllotment | null =>\n snapshot?.exists && subject\n ? aiAllotmentFrom(snapshot.data() as Record<string, unknown>, subject)\n : null\n\n/**\n * A subject id, or `null` when a segment is not an id. A request's site comes\n * from its body, and a value that could never name a site names no\n * allotment rather than failing the reservation it rides in.\n */\nfunction subjectIdOrNull(input: Parameters<typeof aiAllotmentSubjectId>[0]): string | null {\n try {\n return aiAllotmentSubjectId(input)\n } catch {\n return null\n }\n}\n\n/** A site's credits this month off the org rollup's month document. */\nexport function aiHostMonthCredits(\n orgMonth: FirebaseFirestore.DocumentSnapshot | null | undefined,\n hostId: string,\n): number {\n const map = (orgMonth?.exists ? orgMonth.get(AI_HOST_CREDITS_FIELD) : null) as\n | Record<string, unknown>\n | null\n | undefined\n const value = Number(map?.[hostId] ?? 0)\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0\n}\n\n/**\n * Read and measure the allotments that apply to one request. `null` when\n * the request names nobody — a meter that cannot say who asked meets no\n * allotment, as it attributes to nobody.\n *\n * `orgMonth` is the org's month document when the caller already read it —\n * the reservation reads it for an entitled workspace anyway — so a site\n * allotment costs no second read of it.\n */\nexport async function readAiAllotmentGate(\n read: AiAllotmentDocReader,\n orgRef: FirebaseFirestore.DocumentReference,\n subject: AiAllotmentRequestSubject,\n month: string,\n orgMonth: FirebaseFirestore.DocumentSnapshot | null = null,\n): Promise<AiAllotmentGate | null> {\n const uid = trimmed(subject.uid)\n const memberId = uid ? subjectIdOrNull({ scope: 'member', uid }) : null\n if (!uid || !memberId) return null\n const requestedHost = trimmed(subject.hostId)\n const hostSubjectId = requestedHost\n ? subjectIdOrNull({ scope: 'host', hostId: requestedHost })\n : null\n const hostId = hostSubjectId ? requestedHost : ''\n const collabId = hostId ? subjectIdOrNull({ scope: 'collab', hostId, uid }) : null\n const subjectRef = (id: string) => aiAllotmentRef(orgRef, id)\n const [orgDoc, memberDoc, collabDoc, hostDoc, monthDoc] = await Promise.all([\n read(subjectRef(AI_ALLOTMENT_ORG_SUBJECT)),\n read(subjectRef(memberId)),\n collabId ? read(subjectRef(collabId)) : null,\n hostSubjectId ? read(subjectRef(hostSubjectId)) : null,\n read(userAiUsageMonthRef(orgRef, uid, month)),\n ])\n const member = allotmentOf(memberDoc, memberId)\n const collab = allotmentOf(collabDoc, collabId)\n const host = allotmentOf(hostDoc, hostSubjectId)\n const personal = aiUsageByUserMonthFrom(\n monthDoc?.exists ? (monthDoc.data() as Record<string, unknown>) : null,\n uid,\n month,\n )\n\n let siteMonth = orgMonth\n if (host?.credits && !siteMonth) {\n siteMonth = await read(orgRef.collection('assistUsage').doc(month))\n }\n\n const standings: AiAllotmentStanding[] = []\n const measure = (allotment: AiAllotment | null, used: number) => {\n if (!allotment || allotment.credits === null || allotment.scope === 'org') return\n standings.push({\n subject: allotment.subject,\n scope: allotment.scope,\n uid: allotment.uid,\n hostId: allotment.hostId,\n credits: allotment.credits,\n used,\n mode: allotment.mode,\n })\n }\n measure(member, personal.credits)\n measure(collab, hostId ? (personal.byHost[hostId] ?? 0) : 0)\n measure(host, hostId ? aiHostMonthCredits(siteMonth, hostId) : 0)\n\n return {\n ...aiAllotmentVerdict(standings),\n personalCredits: personal.credits,\n standings,\n models: aiAllotmentModelsAllowed([member, collab, host]),\n orgModels: allotmentOf(orgDoc, AI_ALLOTMENT_ORG_SUBJECT)?.models ?? null,\n }\n}\n\n/** Every allotment in an org, optionally narrowed to one person or one site. */\nexport async function listAiAllotments(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n filter: { uid?: string; hostId?: string } = {},\n): Promise<AiAllotment[]> {\n let query: FirebaseFirestore.Query = aiAllotmentsCollection(\n firestore.collection('orgs').doc(orgId),\n )\n // Single-field equality on fields every write stores, so neither needs a\n // composite index.\n if (filter.uid) query = query.where('uid', '==', filter.uid)\n else if (filter.hostId) query = query.where('hostId', '==', filter.hostId)\n const snapshot = await query.get()\n return snapshot.docs\n .map((doc) => aiAllotmentFrom(doc.data() as Record<string, unknown>, doc.id))\n .filter((allotment): allotment is AiAllotment => allotment !== null)\n}\n\n/** One allotment a write sets. */\nexport interface AiAllotmentWrite {\n subject: AiAllotmentSubject\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\n/**\n * Set and remove allotments in one batch. A set REPLACES the document —\n * credits, mode and allowlist together — and clears the alert marker, so a\n * raised allotment announces its next crossing rather than inheriting the\n * last one's.\n */\nexport async function applyAiAllotmentWrites(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n change: { set: readonly AiAllotmentWrite[]; remove: readonly string[] },\n actorUid: string,\n): Promise<void> {\n const orgRef = firestore.collection('orgs').doc(orgId)\n const batch = firestore.batch()\n for (const write of change.set) {\n batch.set(aiAllotmentRef(orgRef, write.subject.id), {\n subject: write.subject.id,\n scope: write.subject.scope,\n uid: write.subject.uid,\n hostId: write.subject.hostId,\n credits: write.subject.scope === 'org' ? null : write.credits,\n mode: write.mode,\n models: write.models,\n setBy: actorUid,\n updatedAt: FieldValue.serverTimestamp(),\n alerted: null,\n })\n }\n for (const subject of change.remove) {\n batch.delete(aiAllotmentRef(orgRef, subject))\n }\n await batch.commit()\n}\n\n/**\n * Give a site's month its credits so far, once, when an allotment on the site\n * is set before the site's map has a key for it.\n *\n * The map is written by the meter from the day the field shipped; a month\n * already in progress has spend the map never saw. The roster's own months\n * carry the same figure per person (`byHost`), so the first allotment set on\n * a site sums them and writes the total — inside a transaction that writes\n * nothing when the key is already there, so a request metered in between is\n * counted once, by the meter.\n */\nexport async function seedAiHostMonthCredits(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n hostIds: readonly string[],\n month: string,\n personCredits: (hostId: string) => number,\n): Promise<void> {\n if (!hostIds.length) return\n const monthRef = firestore\n .collection('orgs')\n .doc(orgId)\n .collection('assistUsage')\n .doc(month)\n await firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(monthRef)\n const map = (snapshot.exists ? snapshot.get(AI_HOST_CREDITS_FIELD) : null) as\n | Record<string, unknown>\n | null\n const missing = hostIds.filter((hostId) => map?.[hostId] === undefined)\n if (!missing.length) return\n tx.set(\n monthRef,\n {\n month,\n [AI_HOST_CREDITS_FIELD]: Object.fromEntries(\n missing.map((hostId) => [hostId, personCredits(hostId)]),\n ),\n },\n { merge: true },\n )\n })\n}\n\n/**\n * Remove a person's allotments on account erasure: `member:{uid}` and every\n * `collab:{hostId}:{uid}`, in each org the erasure names. By path and by a\n * collection-scope equality, so neither needs an index; an org the person\n * had already left keeps a configuration document that names a uid and\n * nothing else, which the next write to that subject replaces.\n */\nexport async function eraseAiAllotmentsForUser(\n firestore: FirebaseFirestore.Firestore,\n uid: string,\n orgIds: readonly string[],\n): Promise<number> {\n let removed = 0\n for (const orgId of orgIds) {\n const allotments = await listAiAllotments(firestore, orgId, { uid })\n if (!allotments.length) continue\n const orgRef = firestore.collection('orgs').doc(orgId)\n const batch = firestore.batch()\n for (const allotment of allotments) {\n batch.delete(aiAllotmentRef(orgRef, allotment.subject))\n removed += 1\n }\n await batch.commit()\n }\n return removed\n}\n"],"names":["FieldValue","AI_ALLOTMENTS_COLLECTION","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentFrom","aiAllotmentModelsAllowed","aiAllotmentSubjectId","aiAllotmentVerdict","aiUsageByUserMonthFrom","userAiUsageMonthRef","AI_HOST_CREDITS_FIELD","aiAllotmentsCollection","orgRef","collection","aiAllotmentRef","subject","doc","trimmed","value","trim","allotmentOf","snapshot","exists","data","subjectIdOrNull","input","aiHostMonthCredits","orgMonth","hostId","map","get","Number","isFinite","Math","floor","readAiAllotmentGate","read","month","personal","uid","memberId","scope","requestedHost","hostSubjectId","collabId","subjectRef","id","orgDoc","memberDoc","collabDoc","hostDoc","monthDoc","Promise","all","member","collab","host","siteMonth","credits","standings","measure","allotment","used","push","mode","byHost","personalCredits","models","orgModels","listAiAllotments","firestore","orgId","filter","query","where","docs","applyAiAllotmentWrites","change","actorUid","batch","write","set","setBy","updatedAt","serverTimestamp","alerted","remove","delete","commit","seedAiHostMonthCredits","hostIds","personCredits","length","monthRef","runTransaction","tx","missing","undefined","Object","fromEntries","merge","eraseAiAllotmentsForUser","orgIds","removed","allotments"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,6EAA6E;AAC7E,6EAA6E;AAC7E,6DAA6D;AAC7D,SAASA,UAAU,QAAQ,2BAA0B;AACrD,SACEC,wBAAwB,EACxBC,wBAAwB,EACxBC,eAAe,EACfC,wBAAwB,EACxBC,oBAAoB,EACpBC,kBAAkB,QAMb,4BAAwB;AAC/B,SAASC,sBAAsB,QAAQ,+BAA2B;AAClE,SAASC,mBAAmB,QAAQ,wBAAoB;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GAED,sDAAsD,GACtD,OAAO,MAAMC,wBAAwB,SAAQ;AAE7C,OAAO,SAASC,uBACdC,MAA2C;IAE3C,OAAOA,OAAOC,UAAU,CAACX;AAC3B;AAEA,OAAO,SAASY,eACdF,MAA2C,EAC3CG,OAAe;IAEf,OAAOJ,uBAAuBC,QAAQI,GAAG,CAACD;AAC5C;AA2BA,MAAME,UAAU,CAACC,QACf,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAE7C,kFAAkF,GAClF,MAAMC,cAAc,CAClBC,UACAN,UAEAM,CAAAA,4BAAAA,SAAUC,MAAM,KAAIP,UAChBX,gBAAgBiB,SAASE,IAAI,IAA+BR,WAC5D;AAEN;;;;CAIC,GACD,SAASS,gBAAgBC,KAAiD;IACxE,IAAI;QACF,OAAOnB,qBAAqBmB;IAC9B,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,qEAAqE,GACrE,OAAO,SAASC,mBACdC,QAA+D,EAC/DC,MAAc;;IAEd,MAAMC,MAAOF,CAAAA,4BAAAA,SAAUL,MAAM,IAAGK,SAASG,GAAG,CAACpB,yBAAyB;IAItE,MAAMQ,QAAQa,eAAOF,uBAAAA,GAAK,CAACD,OAAO,mBAAI;IACtC,OAAOG,OAAOC,QAAQ,CAACd,UAAUA,QAAQ,IAAIe,KAAKC,KAAK,CAAChB,SAAS;AACnE;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAeiB,oBACpBC,IAA0B,EAC1BxB,MAA2C,EAC3CG,OAAkC,EAClCsB,KAAa,EACbV,WAAsD,IAAI;QA+ChCW;QAQblB;IArDb,MAAMmB,MAAMtB,QAAQF,QAAQwB,GAAG;IAC/B,MAAMC,WAAWD,MAAMf,gBAAgB;QAAEiB,OAAO;QAAUF;IAAI,KAAK;IACnE,IAAI,CAACA,OAAO,CAACC,UAAU,OAAO;IAC9B,MAAME,gBAAgBzB,QAAQF,QAAQa,MAAM;IAC5C,MAAMe,gBAAgBD,gBAClBlB,gBAAgB;QAAEiB,OAAO;QAAQb,QAAQc;IAAc,KACvD;IACJ,MAAMd,SAASe,gBAAgBD,gBAAgB;IAC/C,MAAME,WAAWhB,SAASJ,gBAAgB;QAAEiB,OAAO;QAAUb;QAAQW;IAAI,KAAK;IAC9E,MAAMM,aAAa,CAACC,KAAehC,eAAeF,QAAQkC;IAC1D,MAAM,CAACC,QAAQC,WAAWC,WAAWC,SAASC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;QAC1EjB,KAAKS,WAAW1C;QAChBiC,KAAKS,WAAWL;QAChBI,WAAWR,KAAKS,WAAWD,aAAa;QACxCD,gBAAgBP,KAAKS,WAAWF,kBAAkB;QAClDP,KAAK3B,oBAAoBG,QAAQ2B,KAAKF;KACvC;IACD,MAAMiB,SAASlC,YAAY4B,WAAWR;IACtC,MAAMe,SAASnC,YAAY6B,WAAWL;IACtC,MAAMY,OAAOpC,YAAY8B,SAASP;IAClC,MAAML,WAAW9B,uBACf2C,CAAAA,4BAAAA,SAAU7B,MAAM,IAAI6B,SAAS5B,IAAI,KAAiC,MAClEgB,KACAF;IAGF,IAAIoB,YAAY9B;IAChB,IAAI6B,CAAAA,wBAAAA,KAAME,OAAO,KAAI,CAACD,WAAW;QAC/BA,YAAY,MAAMrB,KAAKxB,OAAOC,UAAU,CAAC,eAAeG,GAAG,CAACqB;IAC9D;IAEA,MAAMsB,YAAmC,EAAE;IAC3C,MAAMC,UAAU,CAACC,WAA+BC;QAC9C,IAAI,CAACD,aAAaA,UAAUH,OAAO,KAAK,QAAQG,UAAUpB,KAAK,KAAK,OAAO;QAC3EkB,UAAUI,IAAI,CAAC;YACbhD,SAAS8C,UAAU9C,OAAO;YAC1B0B,OAAOoB,UAAUpB,KAAK;YACtBF,KAAKsB,UAAUtB,GAAG;YAClBX,QAAQiC,UAAUjC,MAAM;YACxB8B,SAASG,UAAUH,OAAO;YAC1BI;YACAE,MAAMH,UAAUG,IAAI;QACtB;IACF;IACAJ,QAAQN,QAAQhB,SAASoB,OAAO;IAChCE,QAAQL,QAAQ3B,UAAUU,0BAAAA,SAAS2B,MAAM,CAACrC,OAAO,YAAvBU,0BAA2B,IAAK;IAC1DsB,QAAQJ,MAAM5B,SAASF,mBAAmB+B,WAAW7B,UAAU;IAE/D,OAAO,aACFrB,mBAAmBoD;QACtBO,iBAAiB5B,SAASoB,OAAO;QACjCC;QACAQ,QAAQ9D,yBAAyB;YAACiD;YAAQC;YAAQC;SAAK;QACvDY,SAAS,WAAEhD,eAAAA,YAAY2B,QAAQ5C,8CAApBiB,aAA+C+C,MAAM,mBAAI;;AAExE;AAEA,8EAA8E,GAC9E,OAAO,eAAeE,iBACpBC,SAAsC,EACtCC,KAAa,EACbC,SAA4C,CAAC,CAAC;IAE9C,IAAIC,QAAiC9D,uBACnC2D,UAAUzD,UAAU,CAAC,QAAQG,GAAG,CAACuD;IAEnC,yEAAyE;IACzE,mBAAmB;IACnB,IAAIC,OAAOjC,GAAG,EAAEkC,QAAQA,MAAMC,KAAK,CAAC,OAAO,MAAMF,OAAOjC,GAAG;SACtD,IAAIiC,OAAO5C,MAAM,EAAE6C,QAAQA,MAAMC,KAAK,CAAC,UAAU,MAAMF,OAAO5C,MAAM;IACzE,MAAMP,WAAW,MAAMoD,MAAM3C,GAAG;IAChC,OAAOT,SAASsD,IAAI,CACjB9C,GAAG,CAAC,CAACb,MAAQZ,gBAAgBY,IAAIO,IAAI,IAA+BP,IAAI8B,EAAE,GAC1E0B,MAAM,CAAC,CAACX,YAAwCA,cAAc;AACnE;AAUA;;;;;CAKC,GACD,OAAO,eAAee,uBACpBN,SAAsC,EACtCC,KAAa,EACbM,MAAuE,EACvEC,QAAgB;IAEhB,MAAMlE,SAAS0D,UAAUzD,UAAU,CAAC,QAAQG,GAAG,CAACuD;IAChD,MAAMQ,QAAQT,UAAUS,KAAK;IAC7B,KAAK,MAAMC,SAASH,OAAOI,GAAG,CAAE;QAC9BF,MAAME,GAAG,CAACnE,eAAeF,QAAQoE,MAAMjE,OAAO,CAAC+B,EAAE,GAAG;YAClD/B,SAASiE,MAAMjE,OAAO,CAAC+B,EAAE;YACzBL,OAAOuC,MAAMjE,OAAO,CAAC0B,KAAK;YAC1BF,KAAKyC,MAAMjE,OAAO,CAACwB,GAAG;YACtBX,QAAQoD,MAAMjE,OAAO,CAACa,MAAM;YAC5B8B,SAASsB,MAAMjE,OAAO,CAAC0B,KAAK,KAAK,QAAQ,OAAOuC,MAAMtB,OAAO;YAC7DM,MAAMgB,MAAMhB,IAAI;YAChBG,QAAQa,MAAMb,MAAM;YACpBe,OAAOJ;YACPK,WAAWlF,WAAWmF,eAAe;YACrCC,SAAS;QACX;IACF;IACA,KAAK,MAAMtE,WAAW8D,OAAOS,MAAM,CAAE;QACnCP,MAAMQ,MAAM,CAACzE,eAAeF,QAAQG;IACtC;IACA,MAAMgE,MAAMS,MAAM;AACpB;AAEA;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,uBACpBnB,SAAsC,EACtCC,KAAa,EACbmB,OAA0B,EAC1BrD,KAAa,EACbsD,aAAyC;IAEzC,IAAI,CAACD,QAAQE,MAAM,EAAE;IACrB,MAAMC,WAAWvB,UACdzD,UAAU,CAAC,QACXG,GAAG,CAACuD,OACJ1D,UAAU,CAAC,eACXG,GAAG,CAACqB;IACP,MAAMiC,UAAUwB,cAAc,CAAC,OAAOC;QACpC,MAAM1E,WAAW,MAAM0E,GAAGjE,GAAG,CAAC+D;QAC9B,MAAMhE,MAAOR,SAASC,MAAM,GAAGD,SAASS,GAAG,CAACpB,yBAAyB;QAGrE,MAAMsF,UAAUN,QAAQlB,MAAM,CAAC,CAAC5C,SAAWC,CAAAA,uBAAAA,GAAK,CAACD,OAAO,MAAKqE;QAC7D,IAAI,CAACD,QAAQJ,MAAM,EAAE;QACrBG,GAAGd,GAAG,CACJY,UACA;YACExD;YACA,CAAC3B,sBAAsB,EAAEwF,OAAOC,WAAW,CACzCH,QAAQnE,GAAG,CAAC,CAACD,SAAW;oBAACA;oBAAQ+D,cAAc/D;iBAAQ;QAE3D,GACA;YAAEwE,OAAO;QAAK;IAElB;AACF;AAEA;;;;;;CAMC,GACD,OAAO,eAAeC,yBACpB/B,SAAsC,EACtC/B,GAAW,EACX+D,MAAyB;IAEzB,IAAIC,UAAU;IACd,KAAK,MAAMhC,SAAS+B,OAAQ;QAC1B,MAAME,aAAa,MAAMnC,iBAAiBC,WAAWC,OAAO;YAAEhC;QAAI;QAClE,IAAI,CAACiE,WAAWZ,MAAM,EAAE;QACxB,MAAMhF,SAAS0D,UAAUzD,UAAU,CAAC,QAAQG,GAAG,CAACuD;QAChD,MAAMQ,QAAQT,UAAUS,KAAK;QAC7B,KAAK,MAAMlB,aAAa2C,WAAY;YAClCzB,MAAMQ,MAAM,CAACzE,eAAeF,QAAQiD,UAAU9C,OAAO;YACrDwF,WAAW;QACb;QACA,MAAMxB,MAAMS,MAAM;IACpB;IACA,OAAOe;AACT"}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 AiStepKind } from '../providers/catalog';
|
|
18
|
+
import type { AiUsage } from '../providers/contract';
|
|
19
|
+
/**
|
|
20
|
+
* The workspace's own typical exchange per step kind (AGL-2942), for the
|
|
21
|
+
* price beside each option in the model switch.
|
|
22
|
+
*
|
|
23
|
+
* Read from `orgs/{orgId}/assistSignals` — the derived half of every metered
|
|
24
|
+
* turn, which carries its route, its model and its tokens and no prose and no
|
|
25
|
+
* uid — newest first, bounded, and only when the switch is opened. A signal
|
|
26
|
+
* is matched to a step kind by the route it was metered under, docs answers
|
|
27
|
+
* and cache hits are left out because no model ran, and the median needs
|
|
28
|
+
* `AI_MEASURED_MEDIAN_MIN_SAMPLES` exchanges before it replaces the nominal
|
|
29
|
+
* figure.
|
|
30
|
+
*/
|
|
31
|
+
/** Signals one read considers. */
|
|
32
|
+
export declare const AI_MODEL_SAMPLE_SIGNALS = 60;
|
|
33
|
+
/** Whether a signal's route is the door a step kind is served through. */
|
|
34
|
+
export declare function signalRouteMatchesStep(route: string, kind: AiStepKind): boolean;
|
|
35
|
+
/** The measured median exchange for a step kind, or `null` too few. */
|
|
36
|
+
export declare function readMeasuredStepUsage(firestore: FirebaseFirestore.Firestore, orgId: string, kind: AiStepKind): Promise<AiUsage | null>;
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
*/ import { AI_METER_SENTINELS } from "../providers/catalog.js";
|
|
17
|
+
import { aiMedianUsage } from "../providers/model-choice.js";
|
|
18
|
+
/**
|
|
19
|
+
* The workspace's own typical exchange per step kind (AGL-2942), for the
|
|
20
|
+
* price beside each option in the model switch.
|
|
21
|
+
*
|
|
22
|
+
* Read from `orgs/{orgId}/assistSignals` — the derived half of every metered
|
|
23
|
+
* turn, which carries its route, its model and its tokens and no prose and no
|
|
24
|
+
* uid — newest first, bounded, and only when the switch is opened. A signal
|
|
25
|
+
* is matched to a step kind by the route it was metered under, docs answers
|
|
26
|
+
* and cache hits are left out because no model ran, and the median needs
|
|
27
|
+
* `AI_MEASURED_MEDIAN_MIN_SAMPLES` exchanges before it replaces the nominal
|
|
28
|
+
* figure.
|
|
29
|
+
*/ /** Signals one read considers. */ export const AI_MODEL_SAMPLE_SIGNALS = 60;
|
|
30
|
+
const COPY_ROUTE = '/api/ai/assist/';
|
|
31
|
+
const JOBS_ROUTE = 'ai/jobs';
|
|
32
|
+
/** Whether a signal's route is the door a step kind is served through. */ export function signalRouteMatchesStep(route, kind) {
|
|
33
|
+
switch(kind){
|
|
34
|
+
case 'copy.element':
|
|
35
|
+
return route === `${COPY_ROUTE}element`;
|
|
36
|
+
case 'copy.blog':
|
|
37
|
+
return route === `${COPY_ROUTE}blog`;
|
|
38
|
+
case 'copy.section':
|
|
39
|
+
case 'generate.section':
|
|
40
|
+
return route === `${COPY_ROUTE}section`;
|
|
41
|
+
case 'job.text':
|
|
42
|
+
return route === JOBS_ROUTE;
|
|
43
|
+
default:
|
|
44
|
+
// A chat turn is metered under the console route it was asked from.
|
|
45
|
+
return !route.startsWith(COPY_ROUTE) && route !== JOBS_ROUTE;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const SENTINELS = new Set(Object.values(AI_METER_SENTINELS));
|
|
49
|
+
/** The measured median exchange for a step kind, or `null` too few. */ export async function readMeasuredStepUsage(firestore, orgId, kind) {
|
|
50
|
+
const snapshot = await firestore.collection('orgs').doc(orgId).collection('assistSignals').orderBy('createdAt', 'desc').limit(AI_MODEL_SAMPLE_SIGNALS).get();
|
|
51
|
+
const samples = [];
|
|
52
|
+
for (const doc of snapshot.docs){
|
|
53
|
+
var _data_model, _data_route, _data_inputTokens, _data_outputTokens, _data_cacheReadTokens, _data_cacheWriteTokens;
|
|
54
|
+
const data = doc.data();
|
|
55
|
+
if (data['deflected'] === true) continue;
|
|
56
|
+
if (SENTINELS.has(String((_data_model = data['model']) != null ? _data_model : ''))) continue;
|
|
57
|
+
if (!signalRouteMatchesStep(String((_data_route = data['route']) != null ? _data_route : ''), kind)) continue;
|
|
58
|
+
samples.push({
|
|
59
|
+
inputTokens: Number((_data_inputTokens = data['inputTokens']) != null ? _data_inputTokens : 0),
|
|
60
|
+
outputTokens: Number((_data_outputTokens = data['outputTokens']) != null ? _data_outputTokens : 0),
|
|
61
|
+
cacheReadTokens: Number((_data_cacheReadTokens = data['cacheReadTokens']) != null ? _data_cacheReadTokens : 0),
|
|
62
|
+
cacheWriteTokens: Number((_data_cacheWriteTokens = data['cacheWriteTokens']) != null ? _data_cacheWriteTokens : 0)
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return aiMedianUsage(samples);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=ai-model-sample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/ai-model-sample.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 { AI_METER_SENTINELS, type AiStepKind } from '../providers/catalog'\nimport type { AiUsage } from '../providers/contract'\nimport { aiMedianUsage } from '../providers/model-choice'\n\n/**\n * The workspace's own typical exchange per step kind (AGL-2942), for the\n * price beside each option in the model switch.\n *\n * Read from `orgs/{orgId}/assistSignals` — the derived half of every metered\n * turn, which carries its route, its model and its tokens and no prose and no\n * uid — newest first, bounded, and only when the switch is opened. A signal\n * is matched to a step kind by the route it was metered under, docs answers\n * and cache hits are left out because no model ran, and the median needs\n * `AI_MEASURED_MEDIAN_MIN_SAMPLES` exchanges before it replaces the nominal\n * figure.\n */\n\n/** Signals one read considers. */\nexport const AI_MODEL_SAMPLE_SIGNALS = 60\n\nconst COPY_ROUTE = '/api/ai/assist/'\nconst JOBS_ROUTE = 'ai/jobs'\n\n/** Whether a signal's route is the door a step kind is served through. */\nexport function signalRouteMatchesStep(route: string, kind: AiStepKind): boolean {\n switch (kind) {\n case 'copy.element':\n return route === `${COPY_ROUTE}element`\n case 'copy.blog':\n return route === `${COPY_ROUTE}blog`\n case 'copy.section':\n case 'generate.section':\n return route === `${COPY_ROUTE}section`\n case 'job.text':\n return route === JOBS_ROUTE\n default:\n // A chat turn is metered under the console route it was asked from.\n return !route.startsWith(COPY_ROUTE) && route !== JOBS_ROUTE\n }\n}\n\nconst SENTINELS = new Set<string>(Object.values(AI_METER_SENTINELS))\n\n/** The measured median exchange for a step kind, or `null` too few. */\nexport async function readMeasuredStepUsage(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n kind: AiStepKind,\n): Promise<AiUsage | null> {\n const snapshot = await firestore\n .collection('orgs')\n .doc(orgId)\n .collection('assistSignals')\n .orderBy('createdAt', 'desc')\n .limit(AI_MODEL_SAMPLE_SIGNALS)\n .get()\n const samples: AiUsage[] = []\n for (const doc of snapshot.docs) {\n const data = doc.data() as Record<string, unknown>\n if (data['deflected'] === true) continue\n if (SENTINELS.has(String(data['model'] ?? ''))) continue\n if (!signalRouteMatchesStep(String(data['route'] ?? ''), kind)) continue\n samples.push({\n inputTokens: Number(data['inputTokens'] ?? 0),\n outputTokens: Number(data['outputTokens'] ?? 0),\n cacheReadTokens: Number(data['cacheReadTokens'] ?? 0),\n cacheWriteTokens: Number(data['cacheWriteTokens'] ?? 0),\n })\n }\n return aiMedianUsage(samples)\n}\n"],"names":["AI_METER_SENTINELS","aiMedianUsage","AI_MODEL_SAMPLE_SIGNALS","COPY_ROUTE","JOBS_ROUTE","signalRouteMatchesStep","route","kind","startsWith","SENTINELS","Set","Object","values","readMeasuredStepUsage","firestore","orgId","snapshot","collection","doc","orderBy","limit","get","samples","docs","data","has","String","push","inputTokens","Number","outputTokens","cacheReadTokens","cacheWriteTokens"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,kBAAkB,QAAyB,0BAAsB;AAE1E,SAASC,aAAa,QAAQ,+BAA2B;AAEzD;;;;;;;;;;;CAWC,GAED,gCAAgC,GAChC,OAAO,MAAMC,0BAA0B,GAAE;AAEzC,MAAMC,aAAa;AACnB,MAAMC,aAAa;AAEnB,wEAAwE,GACxE,OAAO,SAASC,uBAAuBC,KAAa,EAAEC,IAAgB;IACpE,OAAQA;QACN,KAAK;YACH,OAAOD,UAAU,GAAGH,WAAW,OAAO,CAAC;QACzC,KAAK;YACH,OAAOG,UAAU,GAAGH,WAAW,IAAI,CAAC;QACtC,KAAK;QACL,KAAK;YACH,OAAOG,UAAU,GAAGH,WAAW,OAAO,CAAC;QACzC,KAAK;YACH,OAAOG,UAAUF;QACnB;YACE,oEAAoE;YACpE,OAAO,CAACE,MAAME,UAAU,CAACL,eAAeG,UAAUF;IACtD;AACF;AAEA,MAAMK,YAAY,IAAIC,IAAYC,OAAOC,MAAM,CAACZ;AAEhD,qEAAqE,GACrE,OAAO,eAAea,sBACpBC,SAAsC,EACtCC,KAAa,EACbR,IAAgB;IAEhB,MAAMS,WAAW,MAAMF,UACpBG,UAAU,CAAC,QACXC,GAAG,CAACH,OACJE,UAAU,CAAC,iBACXE,OAAO,CAAC,aAAa,QACrBC,KAAK,CAAClB,yBACNmB,GAAG;IACN,MAAMC,UAAqB,EAAE;IAC7B,KAAK,MAAMJ,OAAOF,SAASO,IAAI,CAAE;YAGNC,aACUA,aAEbA,mBACCA,oBACGA,uBACCA;QAR3B,MAAMA,OAAON,IAAIM,IAAI;QACrB,IAAIA,IAAI,CAAC,YAAY,KAAK,MAAM;QAChC,IAAIf,UAAUgB,GAAG,CAACC,QAAOF,cAAAA,IAAI,CAAC,QAAQ,YAAbA,cAAiB,MAAM;QAChD,IAAI,CAACnB,uBAAuBqB,QAAOF,cAAAA,IAAI,CAAC,QAAQ,YAAbA,cAAiB,KAAKjB,OAAO;QAChEe,QAAQK,IAAI,CAAC;YACXC,aAAaC,QAAOL,oBAAAA,IAAI,CAAC,cAAc,YAAnBA,oBAAuB;YAC3CM,cAAcD,QAAOL,qBAAAA,IAAI,CAAC,eAAe,YAApBA,qBAAwB;YAC7CO,iBAAiBF,QAAOL,wBAAAA,IAAI,CAAC,kBAAkB,YAAvBA,wBAA2B;YACnDQ,kBAAkBH,QAAOL,yBAAAA,IAAI,CAAC,mBAAmB,YAAxBA,yBAA4B;QACvD;IACF;IACA,OAAOvB,cAAcqB;AACvB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { UsageAlertContext } from '@aglyn/aglyn/plugin-manager/usage-alert-contributors';
|
|
18
|
+
/**
|
|
19
|
+
* The AI plugin's staff alerts on provider spend (AGL-2984), evaluated for
|
|
20
|
+
* every org by the usage-alerts sweep through its usage alert contributor
|
|
21
|
+
* seam. Two alerts, in this order, each deduped under its own guard key:
|
|
22
|
+
*
|
|
23
|
+
* - THE MARGIN GUARD warns. The meter caps an entitled org by MESSAGE count,
|
|
24
|
+
* and a month of long, cache-cold exchanges on a large model is a
|
|
25
|
+
* three-figure provider bill against a subscription that did not move. One
|
|
26
|
+
* org's spend crossing the review threshold tells staff, and every further
|
|
27
|
+
* whole multiple of the threshold tells them again, so an escalating cost
|
|
28
|
+
* never goes quiet because it already spoke.
|
|
29
|
+
* - THE HARD CEILING reports a stop. Past the monthly spend ceiling the
|
|
30
|
+
* reservation refuses every assist request from the org, so its assistant
|
|
31
|
+
* has stopped answering, and staff are told so in those words. It cannot
|
|
32
|
+
* ride the margin guard: that one speaks in whole multiples of its
|
|
33
|
+
* threshold, so an org climbing from the threshold to the ceiling is still
|
|
34
|
+
* at 1x and says nothing. Announced once a month, because crossing is a
|
|
35
|
+
* state rather than an escalating sum.
|
|
36
|
+
*
|
|
37
|
+
* The ceiling is the one THIS org's reservations refuse at —
|
|
38
|
+
* `assistBackstopCeilingUsd` — not the repo default for everyone. That
|
|
39
|
+
* default binds only a workspace with no band of its own; a band sold past,
|
|
40
|
+
* a band that is a wall, and an uncapped staff comp are each measured
|
|
41
|
+
* against something else, and announcing a $40 stop the reservation never
|
|
42
|
+
* makes would send staff after an assistant that is still answering.
|
|
43
|
+
*
|
|
44
|
+
* Both are STAFF alerts. The org is not charged for this spend and has done
|
|
45
|
+
* nothing wrong, so mailing the customer about our cost would be alarming and
|
|
46
|
+
* meaningless. Both read the spend figure the sweep built, record through the
|
|
47
|
+
* sweep's guard and send through the sweep's senders, so the first-sweep
|
|
48
|
+
* seeding and the run's report apply to them exactly as to core's alerts.
|
|
49
|
+
*/
|
|
50
|
+
/** The margin guard's key: the whole multiples announced this month. */
|
|
51
|
+
export declare const AI_MARGIN_GUARD_KEY = "assistCogs";
|
|
52
|
+
/** The hard ceiling's key: announced once for the month. */
|
|
53
|
+
export declare const AI_CEILING_GUARD_KEY = "assistCeiling";
|
|
54
|
+
/** Evaluates one org: the margin guard, then the hard ceiling. */
|
|
55
|
+
export declare function evaluateAiUsageAlerts(context: UsageAlertContext): Promise<void>;
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
*/ import { resolveAssistCreditBudget } from "@aglyn/aglyn/app-utils/assist-credits";
|
|
17
|
+
import { aiAddonName, hasAiAddon, isUncappedPlanComp } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
18
|
+
import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
|
|
19
|
+
import { assistCeilingBreach, assistCogsAlertThresholdUsd, assistMarginBreach, assistMarginMultiple } from "@aglyn/aglyn/app-utils/usage-budget";
|
|
20
|
+
import { assistBackstopCeilingUsd } from "./assist-ceiling.js";
|
|
21
|
+
/**
|
|
22
|
+
* The AI plugin's staff alerts on provider spend (AGL-2984), evaluated for
|
|
23
|
+
* every org by the usage-alerts sweep through its usage alert contributor
|
|
24
|
+
* seam. Two alerts, in this order, each deduped under its own guard key:
|
|
25
|
+
*
|
|
26
|
+
* - THE MARGIN GUARD warns. The meter caps an entitled org by MESSAGE count,
|
|
27
|
+
* and a month of long, cache-cold exchanges on a large model is a
|
|
28
|
+
* three-figure provider bill against a subscription that did not move. One
|
|
29
|
+
* org's spend crossing the review threshold tells staff, and every further
|
|
30
|
+
* whole multiple of the threshold tells them again, so an escalating cost
|
|
31
|
+
* never goes quiet because it already spoke.
|
|
32
|
+
* - THE HARD CEILING reports a stop. Past the monthly spend ceiling the
|
|
33
|
+
* reservation refuses every assist request from the org, so its assistant
|
|
34
|
+
* has stopped answering, and staff are told so in those words. It cannot
|
|
35
|
+
* ride the margin guard: that one speaks in whole multiples of its
|
|
36
|
+
* threshold, so an org climbing from the threshold to the ceiling is still
|
|
37
|
+
* at 1x and says nothing. Announced once a month, because crossing is a
|
|
38
|
+
* state rather than an escalating sum.
|
|
39
|
+
*
|
|
40
|
+
* The ceiling is the one THIS org's reservations refuse at —
|
|
41
|
+
* `assistBackstopCeilingUsd` — not the repo default for everyone. That
|
|
42
|
+
* default binds only a workspace with no band of its own; a band sold past,
|
|
43
|
+
* a band that is a wall, and an uncapped staff comp are each measured
|
|
44
|
+
* against something else, and announcing a $40 stop the reservation never
|
|
45
|
+
* makes would send staff after an assistant that is still answering.
|
|
46
|
+
*
|
|
47
|
+
* Both are STAFF alerts. The org is not charged for this spend and has done
|
|
48
|
+
* nothing wrong, so mailing the customer about our cost would be alarming and
|
|
49
|
+
* meaningless. Both read the spend figure the sweep built, record through the
|
|
50
|
+
* sweep's guard and send through the sweep's senders, so the first-sweep
|
|
51
|
+
* seeding and the run's report apply to them exactly as to core's alerts.
|
|
52
|
+
*/ /** The margin guard's key: the whole multiples announced this month. */ export const AI_MARGIN_GUARD_KEY = 'assistCogs';
|
|
53
|
+
/** The hard ceiling's key: announced once for the month. */ export const AI_CEILING_GUARD_KEY = 'assistCeiling';
|
|
54
|
+
/** Both alerts send staff to the org list. */ const STAFF_ORGS_LINK = '/admin/orgs';
|
|
55
|
+
/** Evaluates one org: the margin guard, then the hard ceiling. */ export async function evaluateAiUsageAlerts(context) {
|
|
56
|
+
await alertOnMarginGuard(context);
|
|
57
|
+
await alertOnHardCeiling(context);
|
|
58
|
+
}
|
|
59
|
+
/** How both alerts name the org: its slug, or its id when it has none. */ function orgName({ orgSlug, orgId }) {
|
|
60
|
+
return orgSlug != null ? orgSlug : orgId;
|
|
61
|
+
}
|
|
62
|
+
async function alertOnMarginGuard(context) {
|
|
63
|
+
const { org, month, spend } = context;
|
|
64
|
+
const thresholdUsd = assistCogsAlertThresholdUsd(process.env.ASSIST_ORG_MONTHLY_COGS_ALERT_USD);
|
|
65
|
+
// The guard records the whole multiple announced, which is 0 for a zero or
|
|
66
|
+
// non-finite threshold — a reading the breach predicate never agrees to.
|
|
67
|
+
const multiple = assistMarginMultiple(spend.assistUsd, thresholdUsd);
|
|
68
|
+
const due = assistMarginBreach({
|
|
69
|
+
assistUsd: spend.assistUsd,
|
|
70
|
+
thresholdUsd,
|
|
71
|
+
guard: context.guards[AI_MARGIN_GUARD_KEY],
|
|
72
|
+
month
|
|
73
|
+
});
|
|
74
|
+
if (!due || !context.recordAlert(AI_MARGIN_GUARD_KEY, multiple)) return;
|
|
75
|
+
const title = `Assist token spend is $${spend.assistUsd.toFixed(2)} for one org this month`;
|
|
76
|
+
// The add-on state and the band: the same dollar figure is a finding on an
|
|
77
|
+
// org paying for the add-on and an incident on one that is not, and the
|
|
78
|
+
// reader should not have to open the console to know which.
|
|
79
|
+
const addonClause = hasAiAddon(org) ? `The ${aiAddonName()} add-on is on` : `The ${aiAddonName()} add-on is off`;
|
|
80
|
+
const bandCredits = resolveAssistCreditBudget(org);
|
|
81
|
+
const bandClause = bandCredits !== null ? `an AI credit band of ${bandCredits.toLocaleString()}` : isUncappedPlanComp(org) ? 'no AI credit band (an uncapped staff comp)' : 'no AI credit band';
|
|
82
|
+
await context.alertStaff({
|
|
83
|
+
quota: AI_MARGIN_GUARD_KEY,
|
|
84
|
+
threshold: multiple,
|
|
85
|
+
title,
|
|
86
|
+
body: `${orgName(context)} has run about ` + `$${spend.assistUsd.toFixed(2)} of ${PLATFORM_BRAND_NAME} Assist ` + `tokens in ${month}, past the $${thresholdUsd.toFixed(0)} review ` + 'threshold. Assist is a plan entitlement with no per-token ' + 'price, so this is margin, not revenue. ' + `${addonClause}, with ${bandClause}.`,
|
|
87
|
+
link: STAFF_ORGS_LINK,
|
|
88
|
+
emailContext: 'assist-margin'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async function alertOnHardCeiling(context) {
|
|
92
|
+
const { org, month, spend } = context;
|
|
93
|
+
// The same composition the reservation refuses on, for this org, so the
|
|
94
|
+
// figure announced and the figure enforced cannot drift apart.
|
|
95
|
+
const ceilingUsd = assistBackstopCeilingUsd(org);
|
|
96
|
+
const due = assistCeilingBreach({
|
|
97
|
+
assistUsd: spend.assistUsd,
|
|
98
|
+
ceilingUsd,
|
|
99
|
+
guard: context.guards[AI_CEILING_GUARD_KEY],
|
|
100
|
+
month
|
|
101
|
+
});
|
|
102
|
+
if (!due || !context.recordAlert(AI_CEILING_GUARD_KEY, 1)) return;
|
|
103
|
+
const ceiling = `$${Number(ceilingUsd).toFixed(0)}`;
|
|
104
|
+
await context.alertStaff({
|
|
105
|
+
quota: AI_CEILING_GUARD_KEY,
|
|
106
|
+
threshold: 1,
|
|
107
|
+
title: `Assist is REFUSING ${orgName(context)} — the ` + `${ceiling} monthly spend ceiling is crossed`,
|
|
108
|
+
body: `${orgName(context)} has run about ` + `$${spend.assistUsd.toFixed(2)} of ${PLATFORM_BRAND_NAME} Assist ` + `tokens in ${month}, past the ` + `${ceiling} ceiling. Every further ` + 'Assist request from this organization is refused until the month ' + 'rolls over — the assistant is not degraded or slowed, it is off. ' + 'The customer keeps whatever messages their plan has left, and the ' + 'refusal says so in its own words.',
|
|
109
|
+
link: STAFF_ORGS_LINK,
|
|
110
|
+
emailContext: 'assist-ceiling'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=ai-usage-alerts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/ai-usage-alerts.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 { resolveAssistCreditBudget } from '@aglyn/aglyn/app-utils/assist-credits'\nimport {\n aiAddonName,\n hasAiAddon,\n isUncappedPlanComp,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport {\n assistCeilingBreach,\n assistCogsAlertThresholdUsd,\n assistMarginBreach,\n assistMarginMultiple,\n} from '@aglyn/aglyn/app-utils/usage-budget'\nimport type { UsageAlertContext } from '@aglyn/aglyn/plugin-manager/usage-alert-contributors'\nimport { assistBackstopCeilingUsd } from './assist-ceiling'\n\n/**\n * The AI plugin's staff alerts on provider spend (AGL-2984), evaluated for\n * every org by the usage-alerts sweep through its usage alert contributor\n * seam. Two alerts, in this order, each deduped under its own guard key:\n *\n * - THE MARGIN GUARD warns. The meter caps an entitled org by MESSAGE count,\n * and a month of long, cache-cold exchanges on a large model is a\n * three-figure provider bill against a subscription that did not move. One\n * org's spend crossing the review threshold tells staff, and every further\n * whole multiple of the threshold tells them again, so an escalating cost\n * never goes quiet because it already spoke.\n * - THE HARD CEILING reports a stop. Past the monthly spend ceiling the\n * reservation refuses every assist request from the org, so its assistant\n * has stopped answering, and staff are told so in those words. It cannot\n * ride the margin guard: that one speaks in whole multiples of its\n * threshold, so an org climbing from the threshold to the ceiling is still\n * at 1x and says nothing. Announced once a month, because crossing is a\n * state rather than an escalating sum.\n *\n * The ceiling is the one THIS org's reservations refuse at —\n * `assistBackstopCeilingUsd` — not the repo default for everyone. That\n * default binds only a workspace with no band of its own; a band sold past,\n * a band that is a wall, and an uncapped staff comp are each measured\n * against something else, and announcing a $40 stop the reservation never\n * makes would send staff after an assistant that is still answering.\n *\n * Both are STAFF alerts. The org is not charged for this spend and has done\n * nothing wrong, so mailing the customer about our cost would be alarming and\n * meaningless. Both read the spend figure the sweep built, record through the\n * sweep's guard and send through the sweep's senders, so the first-sweep\n * seeding and the run's report apply to them exactly as to core's alerts.\n */\n\n/** The margin guard's key: the whole multiples announced this month. */\nexport const AI_MARGIN_GUARD_KEY = 'assistCogs'\n\n/** The hard ceiling's key: announced once for the month. */\nexport const AI_CEILING_GUARD_KEY = 'assistCeiling'\n\n/** Both alerts send staff to the org list. */\nconst STAFF_ORGS_LINK = '/admin/orgs'\n\n/** Evaluates one org: the margin guard, then the hard ceiling. */\nexport async function evaluateAiUsageAlerts(\n context: UsageAlertContext,\n): Promise<void> {\n await alertOnMarginGuard(context)\n await alertOnHardCeiling(context)\n}\n\n/** How both alerts name the org: its slug, or its id when it has none. */\nfunction orgName({ orgSlug, orgId }: UsageAlertContext): string {\n return orgSlug ?? orgId\n}\n\nasync function alertOnMarginGuard(context: UsageAlertContext): Promise<void> {\n const { org, month, spend } = context\n const thresholdUsd = assistCogsAlertThresholdUsd(\n process.env.ASSIST_ORG_MONTHLY_COGS_ALERT_USD,\n )\n // The guard records the whole multiple announced, which is 0 for a zero or\n // non-finite threshold — a reading the breach predicate never agrees to.\n const multiple = assistMarginMultiple(spend.assistUsd, thresholdUsd)\n const due = assistMarginBreach({\n assistUsd: spend.assistUsd,\n thresholdUsd,\n guard: context.guards[AI_MARGIN_GUARD_KEY],\n month,\n })\n if (!due || !context.recordAlert(AI_MARGIN_GUARD_KEY, multiple)) return\n\n const title = `Assist token spend is $${spend.assistUsd.toFixed(2)} for one org this month`\n // The add-on state and the band: the same dollar figure is a finding on an\n // org paying for the add-on and an incident on one that is not, and the\n // reader should not have to open the console to know which.\n const addonClause = hasAiAddon(org as never)\n ? `The ${aiAddonName()} add-on is on`\n : `The ${aiAddonName()} add-on is off`\n const bandCredits = resolveAssistCreditBudget(org as never)\n const bandClause =\n bandCredits !== null\n ? `an AI credit band of ${bandCredits.toLocaleString()}`\n : isUncappedPlanComp(org as never)\n ? 'no AI credit band (an uncapped staff comp)'\n : 'no AI credit band'\n await context.alertStaff({\n quota: AI_MARGIN_GUARD_KEY,\n threshold: multiple,\n title,\n body:\n `${orgName(context)} has run about ` +\n `$${spend.assistUsd.toFixed(2)} of ${PLATFORM_BRAND_NAME} Assist ` +\n `tokens in ${month}, past the $${thresholdUsd.toFixed(0)} review ` +\n 'threshold. Assist is a plan entitlement with no per-token ' +\n 'price, so this is margin, not revenue. ' +\n `${addonClause}, with ${bandClause}.`,\n link: STAFF_ORGS_LINK,\n emailContext: 'assist-margin',\n })\n}\n\nasync function alertOnHardCeiling(context: UsageAlertContext): Promise<void> {\n const { org, month, spend } = context\n // The same composition the reservation refuses on, for this org, so the\n // figure announced and the figure enforced cannot drift apart.\n const ceilingUsd = assistBackstopCeilingUsd(org as never)\n const due = assistCeilingBreach({\n assistUsd: spend.assistUsd,\n ceilingUsd,\n guard: context.guards[AI_CEILING_GUARD_KEY],\n month,\n })\n if (!due || !context.recordAlert(AI_CEILING_GUARD_KEY, 1)) return\n\n const ceiling = `$${Number(ceilingUsd).toFixed(0)}`\n await context.alertStaff({\n quota: AI_CEILING_GUARD_KEY,\n threshold: 1,\n title:\n `Assist is REFUSING ${orgName(context)} — the ` +\n `${ceiling} monthly spend ceiling is crossed`,\n body:\n `${orgName(context)} has run about ` +\n `$${spend.assistUsd.toFixed(2)} of ${PLATFORM_BRAND_NAME} Assist ` +\n `tokens in ${month}, past the ` +\n `${ceiling} ceiling. Every further ` +\n 'Assist request from this organization is refused until the month ' +\n 'rolls over — the assistant is not degraded or slowed, it is off. ' +\n 'The customer keeps whatever messages their plan has left, and the ' +\n 'refusal says so in its own words.',\n link: STAFF_ORGS_LINK,\n emailContext: 'assist-ceiling',\n })\n}\n"],"names":["resolveAssistCreditBudget","aiAddonName","hasAiAddon","isUncappedPlanComp","PLATFORM_BRAND_NAME","assistCeilingBreach","assistCogsAlertThresholdUsd","assistMarginBreach","assistMarginMultiple","assistBackstopCeilingUsd","AI_MARGIN_GUARD_KEY","AI_CEILING_GUARD_KEY","STAFF_ORGS_LINK","evaluateAiUsageAlerts","context","alertOnMarginGuard","alertOnHardCeiling","orgName","orgSlug","orgId","org","month","spend","thresholdUsd","process","env","ASSIST_ORG_MONTHLY_COGS_ALERT_USD","multiple","assistUsd","due","guard","guards","recordAlert","title","toFixed","addonClause","bandCredits","bandClause","toLocaleString","alertStaff","quota","threshold","body","link","emailContext","ceilingUsd","ceiling","Number"],"mappings":"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"}
|