@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,1410 @@
|
|
|
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
|
+
*/ /**
|
|
18
|
+
* `FieldValue` straight from the SDK rather than through `./firebase-admin`,
|
|
19
|
+
* for the reason `assist-usage.ts` gives: the default-app initialization
|
|
20
|
+
* would ride into every unit test that touches a job, and the statics need
|
|
21
|
+
* no app at all.
|
|
22
|
+
*/ import { FieldValue } from "firebase-admin/firestore";
|
|
23
|
+
import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
|
|
24
|
+
import { checkEntitlement } from "@aglyn/aglyn/server";
|
|
25
|
+
import { assistCreditsFromUsd, assistFreeTasteRefusalText, assistHardCapRefusalText, assistRefusedByHardCap } from "@aglyn/aglyn/app-utils/assist-credits";
|
|
26
|
+
import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
|
|
27
|
+
import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
28
|
+
import { aiOverageReservationRefusal } from "../billing/ai-overage-gate.js";
|
|
29
|
+
import { aiAllotmentRefusalText } from "../model/ai-allotments.js";
|
|
30
|
+
import { aiJobPlanCreditEstimate } from "../model/ai-site-job.js";
|
|
31
|
+
import { AI_OFF_FOR_SITE_COPY, isAiOffForSite } from "../model/ai-site-switch.js";
|
|
32
|
+
import { resolveAiModelChoice } from "../providers/model-choice.js";
|
|
33
|
+
import { aiOutputTargetType } from "../activity/ai-activity-actions.js";
|
|
34
|
+
import { logAiJobCanceled, logAiJobCreated, logAiJobNeedsInput, logAiJobOutput } from "../activity/ai-activity.js";
|
|
35
|
+
import { AI_UPSTREAM_FAILURE_COPY, AiUpstreamError } from "../runtime/ai-runtime.js";
|
|
36
|
+
import { recordUserAiRefusal } from "../usage/ai-usage-by-user.js";
|
|
37
|
+
import { AI_JOB_INLINE_BUDGET_MS, AI_JOB_SWEEP_BUDGET_MS } from "./ai-job-budget.js";
|
|
38
|
+
import { aiMintJobDraftIds } from "./ai-job-draft-ids.js";
|
|
39
|
+
import { AI_JOB_TEXT_STEP_MINIMUM_MS, runAiJobTextStep } from "./ai-job-text-step.js";
|
|
40
|
+
import { AI_JOB_SEO_STEP_MINIMUM_MS } from "./ai-job-seo-budget.js";
|
|
41
|
+
import { AI_JOB_THEME_STEP_MINIMUM_MS } from "./ai-job-theme-budget.js";
|
|
42
|
+
import { assistExchangeExpiry, recordAssistCost, releaseAssistMessage, reserveAssistMessage } from "../usage/assist-usage.js";
|
|
43
|
+
/**
|
|
44
|
+
* AI generation jobs — the Firestore state machine (AGL-2904).
|
|
45
|
+
*
|
|
46
|
+
* One document per job under the org, written only by this module through
|
|
47
|
+
* the Admin SDK; the rules let a member READ their org's jobs and nobody
|
|
48
|
+
* write them. The console route creates a job and runs its first step
|
|
49
|
+
* inline when it can; the AI jobs beat, a console route of its own, claims
|
|
50
|
+
* and runs whatever is queued, across every org, inside a wall-clock budget
|
|
51
|
+
* (`ai-jobs-beat.ts`). Both go through
|
|
52
|
+
* `runAiJobStep`, so there is exactly one place a step is claimed, metered,
|
|
53
|
+
* run and recorded.
|
|
54
|
+
*
|
|
55
|
+
* ── Credits, per step ────────────────────────────────────────────────────
|
|
56
|
+
*
|
|
57
|
+
* Every step is an assist message to the meter: `reserveAssistMessage`
|
|
58
|
+
* BEFORE the provider is called — the same read-and-increment transaction
|
|
59
|
+
* the chat route takes, so a job cannot slip past the org's monthly ceiling
|
|
60
|
+
* by being asynchronous — and `recordAssistCost` after, at the serving
|
|
61
|
+
* model's rates, so the usage rollup and the invoice see a job's tokens
|
|
62
|
+
* exactly as they see a chat turn's. The credit figure on the job is that
|
|
63
|
+
* cost at the plan's credit rate, rounded up like every other credit.
|
|
64
|
+
*
|
|
65
|
+
* A refused reservation is NOT a failure. The org is out of credits, over
|
|
66
|
+
* its cap, or has switched overage off — all things a member can change —
|
|
67
|
+
* so the job parks as `needs_input` with the refusal in customer-safe words
|
|
68
|
+
* and the beat tries it again once it has rested. `failed` is reserved for
|
|
69
|
+
* a step that cannot succeed by waiting.
|
|
70
|
+
*
|
|
71
|
+
* ── What a step may not do ───────────────────────────────────────────────
|
|
72
|
+
*
|
|
73
|
+
* A step runner writes drafts and returns. It does not touch the job
|
|
74
|
+
* document, the meter or the lease: the machine owns those, so a runner
|
|
75
|
+
* written next month cannot forget the reservation or record itself done
|
|
76
|
+
* while the provider is still generating.
|
|
77
|
+
*/ export const AI_JOBS_COLLECTION = 'aiJobs';
|
|
78
|
+
/**
|
|
79
|
+
* How long a claim holds the step: longer than the longest any holder can
|
|
80
|
+
* still be running it, which is the beat route's 300 s function ceiling
|
|
81
|
+
* (AGL-3026). A lease that ran out under a live holder would let the next
|
|
82
|
+
* beat claim the same step and call the provider for it a second time, and
|
|
83
|
+
* the beat fires every minute while a sweep may run for most of five, so
|
|
84
|
+
* this is what keeps overlapping beats apart. A step abandoned by a process
|
|
85
|
+
* that died is recovered once it runs out, by the first beat after that.
|
|
86
|
+
*/ export const AI_JOB_LEASE_MS = 330000;
|
|
87
|
+
/** A step handed back by a retryable provider failure this many times fails. */ export const AI_JOB_STEP_MAX_ATTEMPTS = 3;
|
|
88
|
+
/**
|
|
89
|
+
* The most passes one step may take by asking to continue (AGL-2910), for a
|
|
90
|
+
* kind that registers no bound of its own. A site audit's passes are its
|
|
91
|
+
* units of generated work; this bounds a runner that never finishes, not a
|
|
92
|
+
* real site.
|
|
93
|
+
*/ export const AI_JOB_STEP_MAX_PASSES = 40;
|
|
94
|
+
/**
|
|
95
|
+
* The nominal credit figure shown as held per outstanding step. Not a
|
|
96
|
+
* bound — the reservation and the monthly ceiling are — but the console
|
|
97
|
+
* must show a running job as costing something before its bill is known.
|
|
98
|
+
*
|
|
99
|
+
* NOMINAL, and nothing reads it as more (AGL-3030): the band, the Free
|
|
100
|
+
* taste's account allowance and every refusal are measured on recorded
|
|
101
|
+
* spend, and the one real reservation a step takes is a message, counted
|
|
102
|
+
* when the step is claimed and settled — metered or handed back — before the
|
|
103
|
+
* step is recorded. So a job holds nothing real between steps, and a job
|
|
104
|
+
* parked for a person shows nothing held either: `recordStep` releases the
|
|
105
|
+
* figure when a step parks for review, and `resumeAiJob` holds it again for
|
|
106
|
+
* the steps a person's resume queues.
|
|
107
|
+
*/ export const AI_JOB_STEP_RESERVE_CREDITS = 50;
|
|
108
|
+
/**
|
|
109
|
+
* The inline doors' budget and the beat's, declared beside the time a step is
|
|
110
|
+
* planned against (`ai-job-budget.ts`, AGL-3036) so a step's least time and
|
|
111
|
+
* the budgets it has to fit are read in one place.
|
|
112
|
+
*/ export { AI_JOB_INLINE_BUDGET_MS, AI_JOB_SWEEP_BUDGET_MS };
|
|
113
|
+
/** Jobs one beat reads as candidates; the budget usually stops it first. */ export const AI_JOB_SWEEP_MAX_JOBS = 25;
|
|
114
|
+
/** Parked jobs one beat re-queues, on top of the active candidates. */ export const AI_JOB_SWEEP_MAX_PARKED = 5;
|
|
115
|
+
/** How long a `needs_input` job rests before the beat tries its reservation again. */ export const AI_JOB_NEEDS_INPUT_RETRY_MS = 60 * 60000;
|
|
116
|
+
export const AI_JOB_NOT_AVAILABLE_COPY = 'This kind of AI job is not available yet.';
|
|
117
|
+
export const AI_JOB_REFUSED_COPY = 'The model declined this brief. Try rephrasing it.';
|
|
118
|
+
function toMillis(value) {
|
|
119
|
+
if (value == null) return null;
|
|
120
|
+
if (value instanceof Date) return value.getTime();
|
|
121
|
+
if (typeof value === 'number') return value;
|
|
122
|
+
if (typeof value === 'string') {
|
|
123
|
+
const parsed = Date.parse(value);
|
|
124
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
125
|
+
}
|
|
126
|
+
return typeof value.toMillis === 'function' ? value.toMillis() : null;
|
|
127
|
+
}
|
|
128
|
+
function toIso(value) {
|
|
129
|
+
const millis = toMillis(value);
|
|
130
|
+
return millis === null ? null : new Date(millis).toISOString();
|
|
131
|
+
}
|
|
132
|
+
export function isAiJobTerminal(status) {
|
|
133
|
+
return AI_JOB_TERMINAL_STATUSES.includes(status);
|
|
134
|
+
}
|
|
135
|
+
// ── The step runner registry ──────────────────────────────────────────────
|
|
136
|
+
/** The step a planned kind runs first (AGL-2935). */ export const AI_JOB_PLAN_STEP = 'plan';
|
|
137
|
+
/**
|
|
138
|
+
* The kinds that build site structure, and so plan before they generate
|
|
139
|
+
* (AGL-2935): the plan their first step proposes is what their generation
|
|
140
|
+
* step executes, once a person has confirmed it.
|
|
141
|
+
*/ export const AI_PLANNED_JOB_KINDS = [
|
|
142
|
+
'page',
|
|
143
|
+
'site',
|
|
144
|
+
'component',
|
|
145
|
+
'layout',
|
|
146
|
+
'template',
|
|
147
|
+
'form',
|
|
148
|
+
'email'
|
|
149
|
+
];
|
|
150
|
+
const stepRunners = new Map();
|
|
151
|
+
const stepMinimums = new Map();
|
|
152
|
+
const stepRunMinimums = new Map();
|
|
153
|
+
let planStepRunner = null;
|
|
154
|
+
/** The plan step's least time: one step every planned kind shares, so kept by the step, not by a kind. */ let planStepMinimumMs = 0;
|
|
155
|
+
const stepPassCaps = new Map();
|
|
156
|
+
/** Idempotent per kind; last registration wins. */ export function registerAiJobStep(kind, runner, registration = {}) {
|
|
157
|
+
stepRunners.set(kind, runner);
|
|
158
|
+
if (registration.minimumMs && registration.minimumMs > 0) {
|
|
159
|
+
stepMinimums.set(kind, registration.minimumMs);
|
|
160
|
+
} else {
|
|
161
|
+
stepMinimums.delete(kind);
|
|
162
|
+
}
|
|
163
|
+
if (registration.minimumMsFor) {
|
|
164
|
+
stepRunMinimums.set(kind, registration.minimumMsFor);
|
|
165
|
+
} else {
|
|
166
|
+
stepRunMinimums.delete(kind);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The least time a step needs before it starts: the plan step's own minimum
|
|
171
|
+
* for the plan step of any kind (AGL-3026), and the kind's registered
|
|
172
|
+
* minimum for the kind's own step — the least any run of it needs.
|
|
173
|
+
*/ export function aiJobStepMinimumMs(kind, stepName) {
|
|
174
|
+
var _stepMinimums_get;
|
|
175
|
+
return stepName === AI_JOB_PLAN_STEP ? planStepMinimumMs : (_stepMinimums_get = stepMinimums.get(kind)) != null ? _stepMinimums_get : 0;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The least time the next run of a job's own step needs (AGL-3035): what the
|
|
179
|
+
* kind registered, or more where the kind says this job's next run needs
|
|
180
|
+
* more. A step that delegates a unit to another kind — a page job's creation,
|
|
181
|
+
* a scaffold's page — asks this of the job it derives for the unit.
|
|
182
|
+
*/ export function aiJobStepRunMinimumMs(job) {
|
|
183
|
+
var _stepMinimums_get;
|
|
184
|
+
const least = (_stepMinimums_get = stepMinimums.get(job.kind)) != null ? _stepMinimums_get : 0;
|
|
185
|
+
const forRun = stepRunMinimums.get(job.kind);
|
|
186
|
+
return forRun ? Math.max(least, forRun(job)) : least;
|
|
187
|
+
}
|
|
188
|
+
/** The least time the job's next step needs; 0 when no step is left to run. */ export function aiJobNextStepMinimumMs(job) {
|
|
189
|
+
const index = nextStepIndex(job);
|
|
190
|
+
if (index === -1) return 0;
|
|
191
|
+
return job.steps[index].name === AI_JOB_PLAN_STEP ? planStepMinimumMs : aiJobStepRunMinimumMs(job);
|
|
192
|
+
}
|
|
193
|
+
export function aiJobStepRunnerFor(kind) {
|
|
194
|
+
var _stepRunners_get;
|
|
195
|
+
return (_stepRunners_get = stepRunners.get(kind)) != null ? _stepRunners_get : null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A kind whose step works through more units than the default bounds
|
|
199
|
+
* (AGL-2911). A scaffold's unit is a whole page and its site is eight of
|
|
200
|
+
* them, where an audit's is one page's listing, so the runaway bound the
|
|
201
|
+
* default gives an audit would cut a real site short. The kind registers its
|
|
202
|
+
* own beside its runner, so the machine keeps no list of kinds and the bound
|
|
203
|
+
* sits with the plan that explains it.
|
|
204
|
+
*/ export function registerAiJobStepPasses(kind, maxPasses) {
|
|
205
|
+
stepPassCaps.set(kind, Math.max(1, Math.floor(maxPasses)));
|
|
206
|
+
}
|
|
207
|
+
/** The passes a kind's step may take; the default where it registered none. */ export function aiJobStepMaxPasses(kind) {
|
|
208
|
+
var _stepPassCaps_get;
|
|
209
|
+
return (_stepPassCaps_get = stepPassCaps.get(kind)) != null ? _stepPassCaps_get : AI_JOB_STEP_MAX_PASSES;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The plan step every planned kind runs first. Registered by its own module
|
|
213
|
+
* (`ai-job-plan-step.ts`), which the plugin's server surface loads, so this
|
|
214
|
+
* machine — and every spec that drives it — never loads the inventory
|
|
215
|
+
* reader and the Admin SDK behind it. `null` unregisters it, minimum and all.
|
|
216
|
+
*
|
|
217
|
+
* `minimumMs` means for the plan step what it means for a kind's step
|
|
218
|
+
* (AGL-3026): a plan answered at its ceiling takes longer than an inline
|
|
219
|
+
* door's budget, so the step says so, and both doors leave it for the beat.
|
|
220
|
+
*/ export function registerAiJobPlanStep(runner, registration = {}) {
|
|
221
|
+
planStepRunner = runner;
|
|
222
|
+
planStepMinimumMs = runner && registration.minimumMs && registration.minimumMs > 0 ? registration.minimumMs : 0;
|
|
223
|
+
}
|
|
224
|
+
let pauseReader = null;
|
|
225
|
+
/**
|
|
226
|
+
* The reader `runAiJobStep` asks before it claims a step. Registered by its
|
|
227
|
+
* own module (`ai-jobs-pause.ts`), which the plugin's console surface calls,
|
|
228
|
+
* so this machine — and every spec that drives it — never loads the Admin SDK
|
|
229
|
+
* the lockdown reads run on. `null` unregisters it; a machine with no reader
|
|
230
|
+
* runs every job, as a process with no lockdown carrier would.
|
|
231
|
+
*/ export function registerAiJobPauseReader(reader) {
|
|
232
|
+
pauseReader = reader;
|
|
233
|
+
}
|
|
234
|
+
/** Whether a pause reader is registered: what a surface that runs jobs must have. */ export function aiJobPauseReaderRegistered() {
|
|
235
|
+
return pauseReader !== null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The registered reader's answer. A read that throws is not a pause: the
|
|
239
|
+
* lockdown carriers fail open on an unreachable Firestore, and so does this.
|
|
240
|
+
*/ async function aiJobPausedFor(orgId, jobId, staff) {
|
|
241
|
+
if (!pauseReader) return false;
|
|
242
|
+
try {
|
|
243
|
+
return await pauseReader({
|
|
244
|
+
orgId,
|
|
245
|
+
staff
|
|
246
|
+
});
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('ai job pause read failed', {
|
|
249
|
+
orgId,
|
|
250
|
+
jobId,
|
|
251
|
+
error
|
|
252
|
+
});
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** The runner for one step of one job: the plan step by its name, else the kind's own. */ export function aiJobRunnerForStep(kind, stepName) {
|
|
257
|
+
return stepName === AI_JOB_PLAN_STEP ? planStepRunner : aiJobStepRunnerFor(kind);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The step names a job of this kind carries, in order. A planned kind plans
|
|
261
|
+
* only once something can build the plan: until its generation runner is
|
|
262
|
+
* registered, its job fails fast and free, rather than spending on a plan
|
|
263
|
+
* nothing will execute.
|
|
264
|
+
*/ export function aiJobStepNames(kind) {
|
|
265
|
+
if (kind === 'text') return [
|
|
266
|
+
'draft'
|
|
267
|
+
];
|
|
268
|
+
return AI_PLANNED_JOB_KINDS.includes(kind) && planStepRunner && stepRunners.has(kind) ? [
|
|
269
|
+
AI_JOB_PLAN_STEP,
|
|
270
|
+
'generate'
|
|
271
|
+
] : [
|
|
272
|
+
'generate'
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* The text step (AGL-2904). Every kind with no runner refuses until its issue
|
|
277
|
+
* lands. Its least time fits an inline door's budget (AGL-3035), so the doors
|
|
278
|
+
* run a text job where it is asked for.
|
|
279
|
+
*/ registerAiJobStep('text', runAiJobTextStep, {
|
|
280
|
+
minimumMs: AI_JOB_TEXT_STEP_MINIMUM_MS
|
|
281
|
+
});
|
|
282
|
+
/**
|
|
283
|
+
* The theme step (AGL-2938), loaded the first time a theme job runs rather
|
|
284
|
+
* than when this module does: it builds the brand themes it measures
|
|
285
|
+
* contrast against and may read a site's logo, and a process that never runs
|
|
286
|
+
* a theme job should pay for neither. Its least time is declared apart from
|
|
287
|
+
* the step (`ai-job-theme-budget.ts`, AGL-3035) for the same reason.
|
|
288
|
+
*/ registerAiJobStep('theme', async (context)=>{
|
|
289
|
+
const { runAiJobThemeStep } = await import("./ai-job-theme-step.js");
|
|
290
|
+
return runAiJobThemeStep(context);
|
|
291
|
+
}, {
|
|
292
|
+
minimumMs: AI_JOB_THEME_STEP_MINIMUM_MS
|
|
293
|
+
});
|
|
294
|
+
/**
|
|
295
|
+
* The SEO step (AGL-2910), loaded the first time an SEO job runs rather than
|
|
296
|
+
* when this module does: it reads pages, versions and layouts, and a process
|
|
297
|
+
* that never runs one should load none of that. Its least time is declared
|
|
298
|
+
* apart from the step (`ai-job-seo-budget.ts`, AGL-3035) for the same reason.
|
|
299
|
+
*/ registerAiJobStep('seo', async (context)=>{
|
|
300
|
+
const { runAiJobSeoStep } = await import("./ai-job-seo-step.js");
|
|
301
|
+
return runAiJobSeoStep(context);
|
|
302
|
+
}, {
|
|
303
|
+
minimumMs: AI_JOB_SEO_STEP_MINIMUM_MS
|
|
304
|
+
});
|
|
305
|
+
// ── Documents ─────────────────────────────────────────────────────────────
|
|
306
|
+
function jobsCollection(firestore, orgId) {
|
|
307
|
+
return firestore.collection('orgs').doc(orgId).collection(AI_JOBS_COLLECTION);
|
|
308
|
+
}
|
|
309
|
+
function jobFrom(snapshot) {
|
|
310
|
+
if (!snapshot.exists) return null;
|
|
311
|
+
const data = snapshot.data();
|
|
312
|
+
return _extends({}, data, {
|
|
313
|
+
$id: snapshot.id
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* A new job, `queued`, with its step plan laid out and nothing run.
|
|
318
|
+
*
|
|
319
|
+
* The activity row is written once the document is, so the feed never names
|
|
320
|
+
* a job that does not exist. The brief's length stands in for the brief,
|
|
321
|
+
* which is the customer's own text and is not logged (AGL-2929).
|
|
322
|
+
*/ export async function createAiJob(firestore, input, now = new Date()) {
|
|
323
|
+
var _input_hostId, _input_inputs, _input_model, _input_createdByEmail, _input_hostId1;
|
|
324
|
+
const brief = input.brief.trim();
|
|
325
|
+
if (!brief) throw new Error('an AI job needs a brief');
|
|
326
|
+
// The drafts the kind always writes are named before any step runs, on the
|
|
327
|
+
// step that writes them, so a run repeated after its write finds them.
|
|
328
|
+
const draftIds = aiMintJobDraftIds(input.kind);
|
|
329
|
+
const steps = aiJobStepNames(input.kind).map((name)=>_extends({
|
|
330
|
+
name,
|
|
331
|
+
status: 'pending',
|
|
332
|
+
startedAt: null,
|
|
333
|
+
endedAt: null,
|
|
334
|
+
creditsSpent: 0,
|
|
335
|
+
error: null,
|
|
336
|
+
attempts: 0
|
|
337
|
+
}, draftIds && name !== AI_JOB_PLAN_STEP ? {
|
|
338
|
+
draftIds
|
|
339
|
+
} : {}));
|
|
340
|
+
// A job is a resource the console lists and routes to, named as one.
|
|
341
|
+
const ref = jobsCollection(firestore, input.orgId).doc(createResourceUid());
|
|
342
|
+
const data = {
|
|
343
|
+
orgId: input.orgId,
|
|
344
|
+
hostId: (_input_hostId = input.hostId) != null ? _input_hostId : null,
|
|
345
|
+
kind: input.kind,
|
|
346
|
+
status: 'queued',
|
|
347
|
+
brief,
|
|
348
|
+
inputs: (_input_inputs = input.inputs) != null ? _input_inputs : {},
|
|
349
|
+
model: (_input_model = input.model) != null ? _input_model : null,
|
|
350
|
+
steps,
|
|
351
|
+
outputs: [],
|
|
352
|
+
creditsReserved: steps.length * AI_JOB_STEP_RESERVE_CREDITS,
|
|
353
|
+
creditsSpent: 0,
|
|
354
|
+
createdBy: input.createdBy,
|
|
355
|
+
createdAt: now,
|
|
356
|
+
updatedAt: now,
|
|
357
|
+
// The brief is verbatim customer text, so it expires on the same clock
|
|
358
|
+
// as an assist exchange; the TTL policy keys on this field.
|
|
359
|
+
expiresAt: assistExchangeExpiry(now),
|
|
360
|
+
error: null,
|
|
361
|
+
lease: null
|
|
362
|
+
};
|
|
363
|
+
await ref.set(data);
|
|
364
|
+
await logAiJobCreated(input.orgId, {
|
|
365
|
+
uid: input.createdBy,
|
|
366
|
+
email: (_input_createdByEmail = input.createdByEmail) != null ? _input_createdByEmail : null
|
|
367
|
+
}, {
|
|
368
|
+
jobId: ref.id,
|
|
369
|
+
kind: input.kind,
|
|
370
|
+
briefLength: brief.length,
|
|
371
|
+
hostId: (_input_hostId1 = input.hostId) != null ? _input_hostId1 : null
|
|
372
|
+
});
|
|
373
|
+
return _extends({}, data, {
|
|
374
|
+
$id: ref.id
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
export async function getAiJob(firestore, orgId, jobId) {
|
|
378
|
+
return jobFrom(await jobsCollection(firestore, orgId).doc(jobId).get());
|
|
379
|
+
}
|
|
380
|
+
/** Newest first. Needs the (status, createdAt) index when filtered. */ export async function listAiJobs(firestore, orgId, options = {}) {
|
|
381
|
+
var _options_limit;
|
|
382
|
+
const limit = Math.min(Math.max(1, Math.floor((_options_limit = options.limit) != null ? _options_limit : 20)), 100);
|
|
383
|
+
let query = jobsCollection(firestore, orgId);
|
|
384
|
+
if (options.status) query = query.where('status', '==', options.status);
|
|
385
|
+
const snapshot = await query.orderBy('createdAt', 'desc').limit(limit).get();
|
|
386
|
+
return snapshot.docs.map((doc)=>jobFrom(doc)).filter((job)=>job !== null);
|
|
387
|
+
}
|
|
388
|
+
/** The wire form. `running` is the lease, read against `now`. */ export function aiJobSummary(job, now = new Date()) {
|
|
389
|
+
var _job_hostId, _job_steps, _job_outputs, _job_creditsReserved, _job_creditsSpent, _toIso, _toIso1, _job_error, _job_plan_labels, _job_plan_confirmedBy, _job_review, _job_applied_versions, _job_applied_staged;
|
|
390
|
+
var _job_lease, _job_inputs;
|
|
391
|
+
const leaseUntil = toMillis((_job_lease = job.lease) == null ? void 0 : _job_lease.until);
|
|
392
|
+
return {
|
|
393
|
+
id: job.$id,
|
|
394
|
+
orgId: job.orgId,
|
|
395
|
+
hostId: (_job_hostId = job.hostId) != null ? _job_hostId : null,
|
|
396
|
+
kind: job.kind,
|
|
397
|
+
status: job.status,
|
|
398
|
+
brief: job.brief,
|
|
399
|
+
batch: typeof ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['batchId']) === 'string' ? job.inputs['batchId'] : null,
|
|
400
|
+
steps: ((_job_steps = job.steps) != null ? _job_steps : []).map((step)=>{
|
|
401
|
+
var _step_creditsSpent, _step_error;
|
|
402
|
+
return {
|
|
403
|
+
name: step.name,
|
|
404
|
+
status: step.status,
|
|
405
|
+
startedAt: toIso(step.startedAt),
|
|
406
|
+
endedAt: toIso(step.endedAt),
|
|
407
|
+
creditsSpent: (_step_creditsSpent = step.creditsSpent) != null ? _step_creditsSpent : 0,
|
|
408
|
+
error: (_step_error = step.error) != null ? _step_error : null
|
|
409
|
+
};
|
|
410
|
+
}),
|
|
411
|
+
outputs: (_job_outputs = job.outputs) != null ? _job_outputs : [],
|
|
412
|
+
creditsReserved: (_job_creditsReserved = job.creditsReserved) != null ? _job_creditsReserved : 0,
|
|
413
|
+
creditsSpent: (_job_creditsSpent = job.creditsSpent) != null ? _job_creditsSpent : 0,
|
|
414
|
+
createdBy: job.createdBy,
|
|
415
|
+
createdAt: (_toIso = toIso(job.createdAt)) != null ? _toIso : new Date(0).toISOString(),
|
|
416
|
+
updatedAt: (_toIso1 = toIso(job.updatedAt)) != null ? _toIso1 : new Date(0).toISOString(),
|
|
417
|
+
error: (_job_error = job.error) != null ? _job_error : null,
|
|
418
|
+
running: job.status === 'running' && leaseUntil !== null && leaseUntil > now.getTime(),
|
|
419
|
+
plan: job.plan ? _extends({}, job.plan, {
|
|
420
|
+
labels: (_job_plan_labels = job.plan.labels) != null ? _job_plan_labels : {},
|
|
421
|
+
proposedAt: toIso(job.plan.proposedAt),
|
|
422
|
+
confirmedAt: toIso(job.plan.confirmedAt),
|
|
423
|
+
confirmedBy: (_job_plan_confirmedBy = job.plan.confirmedBy) != null ? _job_plan_confirmedBy : null
|
|
424
|
+
}) : null,
|
|
425
|
+
review: (_job_review = job.review) != null ? _job_review : null,
|
|
426
|
+
applied: job.applied ? {
|
|
427
|
+
at: toIso(job.applied.at),
|
|
428
|
+
by: job.applied.by,
|
|
429
|
+
versions: (_job_applied_versions = job.applied.versions) != null ? _job_applied_versions : {},
|
|
430
|
+
staged: (_job_applied_staged = job.applied.staged) != null ? _job_applied_staged : []
|
|
431
|
+
} : null
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function leaseIsLive(job, owner, nowMs) {
|
|
435
|
+
const lease = job.lease;
|
|
436
|
+
if (!lease) return false;
|
|
437
|
+
if (lease.owner === owner) return false;
|
|
438
|
+
const until = toMillis(lease.until);
|
|
439
|
+
return until !== null && until > nowMs;
|
|
440
|
+
}
|
|
441
|
+
function nextStepIndex(job) {
|
|
442
|
+
var _job_steps;
|
|
443
|
+
return ((_job_steps = job.steps) != null ? _job_steps : []).findIndex((step)=>step.status === 'pending' || step.status === 'running');
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* The step a claim by `owner` would take, or -1 when there is none to take:
|
|
447
|
+
* the job is terminal or parked, has no step left, or ANOTHER owner's lease
|
|
448
|
+
* has not yet expired.
|
|
449
|
+
*/ function claimableStepIndex(job, owner, nowMs) {
|
|
450
|
+
if (job.status !== 'queued' && job.status !== 'running') return -1;
|
|
451
|
+
if (leaseIsLive(job, owner, nowMs)) return -1;
|
|
452
|
+
return nextStepIndex(job);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Take the lease on the job's next step, in one transaction.
|
|
456
|
+
*
|
|
457
|
+
* Refuses — `null` — when the job is terminal or parked, when it has no
|
|
458
|
+
* step left, or when ANOTHER owner's lease has not yet expired. That last
|
|
459
|
+
* clause is the whole point: the console route and two overlapping beats
|
|
460
|
+
* can all look at one job inside the same minute, and only one of them may
|
|
461
|
+
* spend the reservation for its step. An expired lease is claimable, which
|
|
462
|
+
* is how a step abandoned mid-flight by a frozen process is recovered — the
|
|
463
|
+
* stale lease is released by being taken, not by a sweep of its own.
|
|
464
|
+
*/ export async function claimNextStep(firestore, orgId, jobId, owner, now = new Date()) {
|
|
465
|
+
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
466
|
+
return firestore.runTransaction(async (tx)=>{
|
|
467
|
+
const job = jobFrom(await tx.get(ref));
|
|
468
|
+
if (!job) return null;
|
|
469
|
+
const stepIndex = claimableStepIndex(job, owner, now.getTime());
|
|
470
|
+
if (stepIndex === -1) return null;
|
|
471
|
+
const steps = job.steps.map((step, index)=>{
|
|
472
|
+
var _step_startedAt, _step_attempts;
|
|
473
|
+
return index === stepIndex ? _extends({}, step, {
|
|
474
|
+
status: 'running',
|
|
475
|
+
startedAt: (_step_startedAt = step.startedAt) != null ? _step_startedAt : now,
|
|
476
|
+
attempts: ((_step_attempts = step.attempts) != null ? _step_attempts : 0) + 1
|
|
477
|
+
}) : step;
|
|
478
|
+
});
|
|
479
|
+
const lease = {
|
|
480
|
+
owner,
|
|
481
|
+
until: new Date(now.getTime() + AI_JOB_LEASE_MS)
|
|
482
|
+
};
|
|
483
|
+
tx.set(ref, {
|
|
484
|
+
status: 'running',
|
|
485
|
+
steps,
|
|
486
|
+
lease,
|
|
487
|
+
updatedAt: now
|
|
488
|
+
}, {
|
|
489
|
+
merge: true
|
|
490
|
+
});
|
|
491
|
+
return {
|
|
492
|
+
job: _extends({}, job, {
|
|
493
|
+
status: 'running',
|
|
494
|
+
steps: steps,
|
|
495
|
+
lease: lease,
|
|
496
|
+
updatedAt: now
|
|
497
|
+
}),
|
|
498
|
+
stepIndex
|
|
499
|
+
};
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Extend the lease while a long step is still running. `false` when the
|
|
504
|
+
* caller no longer holds it — the step was recovered by someone else, and
|
|
505
|
+
* the caller's result must not overwrite theirs.
|
|
506
|
+
*/ export async function heartbeatStep(firestore, orgId, jobId, owner, now = new Date()) {
|
|
507
|
+
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
508
|
+
return firestore.runTransaction(async (tx)=>{
|
|
509
|
+
var _job_lease;
|
|
510
|
+
const job = jobFrom(await tx.get(ref));
|
|
511
|
+
if (!job || job.status !== 'running' || ((_job_lease = job.lease) == null ? void 0 : _job_lease.owner) !== owner) {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
tx.set(ref, {
|
|
515
|
+
lease: {
|
|
516
|
+
owner,
|
|
517
|
+
until: new Date(now.getTime() + AI_JOB_LEASE_MS)
|
|
518
|
+
},
|
|
519
|
+
updatedAt: now
|
|
520
|
+
}, {
|
|
521
|
+
merge: true
|
|
522
|
+
});
|
|
523
|
+
return true;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Leave a job whose workspace's AI is paused where it is (AGL-3037), moved to
|
|
528
|
+
* the back of the beat's queue. Nothing is claimed: its steps, their attempts
|
|
529
|
+
* and its lease stay exactly as they were, so resuming AI runs the job from
|
|
530
|
+
* where it stopped. Only `updatedAt` moves — the beat's queue is ordered by it,
|
|
531
|
+
* and a paused workspace's jobs left at the front would fill every beat's
|
|
532
|
+
* candidates and starve every other workspace's.
|
|
533
|
+
*
|
|
534
|
+
* `null`, writing nothing, for a job a claim would not take either: terminal,
|
|
535
|
+
* parked, no step left, or another owner's live lease.
|
|
536
|
+
*/ export async function holdPausedAiJob(firestore, orgId, jobId, owner, now = new Date()) {
|
|
537
|
+
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
538
|
+
return firestore.runTransaction(async (tx)=>{
|
|
539
|
+
const job = jobFrom(await tx.get(ref));
|
|
540
|
+
if (!job || claimableStepIndex(job, owner, now.getTime()) === -1) return null;
|
|
541
|
+
tx.set(ref, {
|
|
542
|
+
updatedAt: now
|
|
543
|
+
}, {
|
|
544
|
+
merge: true
|
|
545
|
+
});
|
|
546
|
+
return _extends({}, job, {
|
|
547
|
+
updatedAt: now
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* The runs whose stop reasons a step's record keeps (AGL-3042), newest last.
|
|
553
|
+
* A page job that builds a layout, a form and a component, then five sections
|
|
554
|
+
* and its last pass, runs its generation step nine times: twelve keep such a
|
|
555
|
+
* page whole with room for three retries.
|
|
556
|
+
*
|
|
557
|
+
* What it costs the document: an entry is at most 67 bytes as Firestore sizes
|
|
558
|
+
* it (the two field names, 11 and 7; a stop reason, a provider's ASCII word of
|
|
559
|
+
* at most `AI_JOB_STEP_STOP_REASON_MAX_CHARS` characters, 41; an integer, 8),
|
|
560
|
+
* so a step's list is at most 813 bytes with its field name, and a job's two
|
|
561
|
+
* steps at most 1,626 of the 1,048,576 bytes a document may hold.
|
|
562
|
+
*/ export const AI_JOB_STEP_LAST_RUNS = 12;
|
|
563
|
+
/** The longest stop reason a run's entry keeps; every one a provider names today is shorter. */ export const AI_JOB_STEP_STOP_REASON_MAX_CHARS = 40;
|
|
564
|
+
/**
|
|
565
|
+
* A step's measure with one more run added: the four counts and the time
|
|
566
|
+
* summed, the model and effort taken from the run, and why it stopped
|
|
567
|
+
* appended to the latest runs (AGL-3042).
|
|
568
|
+
*/ export function addAiJobStepTokens(current, run) {
|
|
569
|
+
var _run_effort;
|
|
570
|
+
const count = (value)=>{
|
|
571
|
+
const parsed = Number(value != null ? value : 0);
|
|
572
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
573
|
+
};
|
|
574
|
+
const kept = Array.isArray(current == null ? void 0 : current.lastRuns) ? current.lastRuns : [];
|
|
575
|
+
const stopReason = typeof run.stopReason === 'string' && run.stopReason ? run.stopReason.slice(0, AI_JOB_STEP_STOP_REASON_MAX_CHARS) : null;
|
|
576
|
+
return {
|
|
577
|
+
input: count(current == null ? void 0 : current.input) + count(run.usage.inputTokens),
|
|
578
|
+
cachedRead: count(current == null ? void 0 : current.cachedRead) + count(run.usage.cacheReadTokens),
|
|
579
|
+
cacheWrite: count(current == null ? void 0 : current.cacheWrite) + count(run.usage.cacheWriteTokens),
|
|
580
|
+
output: count(current == null ? void 0 : current.output) + count(run.usage.outputTokens),
|
|
581
|
+
model: run.model || (current == null ? void 0 : current.model) || null,
|
|
582
|
+
effort: (_run_effort = run.effort) != null ? _run_effort : null,
|
|
583
|
+
latencyMs: count(current == null ? void 0 : current.latencyMs) + count(run.latencyMs),
|
|
584
|
+
runs: count(current == null ? void 0 : current.runs) + 1,
|
|
585
|
+
lastRuns: [
|
|
586
|
+
...kept,
|
|
587
|
+
{
|
|
588
|
+
stopReason,
|
|
589
|
+
output: count(run.usage.outputTokens)
|
|
590
|
+
}
|
|
591
|
+
].slice(-AI_JOB_STEP_LAST_RUNS)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Record what a step cost and produced, and hand the lease back.
|
|
596
|
+
*
|
|
597
|
+
* Credits and outputs are recorded whatever the job's status is by now —
|
|
598
|
+
* a job canceled while its provider call was in flight still spent the
|
|
599
|
+
* tokens, and hiding that would make the bill disagree with the document.
|
|
600
|
+
* The status transition is the caller's next call (`completeAiJob`,
|
|
601
|
+
* `failAiJob`), except that a step handed back or a step with more steps
|
|
602
|
+
* behind it re-queues here, so the beat sees it without another write —
|
|
603
|
+
* and a step that stopped for a person parks the job `needs_review` here,
|
|
604
|
+
* in the same transaction, so no beat can claim the next step in between.
|
|
605
|
+
*/ export async function recordStep(firestore, orgId, jobId, owner, stepIndex, input, now = new Date()) {
|
|
606
|
+
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
607
|
+
return firestore.runTransaction(async (tx)=>{
|
|
608
|
+
var _job_outputs, _input_outputs, _job_creditsSpent, _job_creditsReserved, _job_creditsReserved1;
|
|
609
|
+
var _job_lease;
|
|
610
|
+
const job = jobFrom(await tx.get(ref));
|
|
611
|
+
if (!job) throw new Error(`ai job ${orgId}/${jobId} vanished`);
|
|
612
|
+
const steps = job.steps.map((step, index)=>{
|
|
613
|
+
var _step_creditsSpent, _input_error, _step_passes;
|
|
614
|
+
return index === stepIndex ? _extends({}, step, {
|
|
615
|
+
status: input.status,
|
|
616
|
+
endedAt: input.status === 'pending' ? null : now,
|
|
617
|
+
creditsSpent: ((_step_creditsSpent = step.creditsSpent) != null ? _step_creditsSpent : 0) + input.creditsSpent,
|
|
618
|
+
error: (_input_error = input.error) != null ? _input_error : null
|
|
619
|
+
}, input.continued && input.status === 'pending' ? {
|
|
620
|
+
attempts: 0,
|
|
621
|
+
passes: ((_step_passes = step.passes) != null ? _step_passes : 0) + 1
|
|
622
|
+
} : {}, input.tokens ? {
|
|
623
|
+
tokens: addAiJobStepTokens(step.tokens, input.tokens)
|
|
624
|
+
} : {}) : step;
|
|
625
|
+
});
|
|
626
|
+
const remaining = steps.filter((step)=>step.status === 'pending' || step.status === 'running').length;
|
|
627
|
+
const settled = input.status !== 'pending';
|
|
628
|
+
const ownsLease = ((_job_lease = job.lease) == null ? void 0 : _job_lease.owner) === owner;
|
|
629
|
+
const parksForReview = Boolean(input.review) && !isAiJobTerminal(job.status);
|
|
630
|
+
const status = isAiJobTerminal(job.status) ? job.status : job.status === 'needs_input' ? job.status : parksForReview ? 'needs_review' : input.status === 'failed' ? 'running' : remaining > 0 ? 'queued' : 'running';
|
|
631
|
+
const patch = _extends({
|
|
632
|
+
status,
|
|
633
|
+
steps,
|
|
634
|
+
outputs: [
|
|
635
|
+
...(_job_outputs = job.outputs) != null ? _job_outputs : [],
|
|
636
|
+
...(_input_outputs = input.outputs) != null ? _input_outputs : []
|
|
637
|
+
],
|
|
638
|
+
creditsSpent: ((_job_creditsSpent = job.creditsSpent) != null ? _job_creditsSpent : 0) + input.creditsSpent,
|
|
639
|
+
// A job parked for a person holds nothing: no step of it can run until
|
|
640
|
+
// someone resumes it, and the resume holds the figure again (AGL-3030).
|
|
641
|
+
creditsReserved: parksForReview ? 0 : settled ? Math.max(0, ((_job_creditsReserved = job.creditsReserved) != null ? _job_creditsReserved : 0) - AI_JOB_STEP_RESERVE_CREDITS) : (_job_creditsReserved1 = job.creditsReserved) != null ? _job_creditsReserved1 : 0,
|
|
642
|
+
updatedAt: now
|
|
643
|
+
}, ownsLease ? {
|
|
644
|
+
lease: null
|
|
645
|
+
} : {}, input.plan ? {
|
|
646
|
+
plan: input.plan
|
|
647
|
+
} : {}, parksForReview && input.review ? {
|
|
648
|
+
review: input.review,
|
|
649
|
+
error: input.review.reason === 'plan' ? null : input.review.message
|
|
650
|
+
} : {});
|
|
651
|
+
tx.set(ref, patch, {
|
|
652
|
+
merge: true
|
|
653
|
+
});
|
|
654
|
+
return {
|
|
655
|
+
job: _extends({}, job, patch),
|
|
656
|
+
remaining
|
|
657
|
+
};
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
async function transition(firestore, orgId, jobId, apply) {
|
|
661
|
+
const ref = jobsCollection(firestore, orgId).doc(jobId);
|
|
662
|
+
return firestore.runTransaction(async (tx)=>{
|
|
663
|
+
const job = jobFrom(await tx.get(ref));
|
|
664
|
+
if (!job) throw new Error(`ai job ${orgId}/${jobId} vanished`);
|
|
665
|
+
const patch = apply(job);
|
|
666
|
+
if (!patch) return {
|
|
667
|
+
job,
|
|
668
|
+
changed: false
|
|
669
|
+
};
|
|
670
|
+
tx.set(ref, patch, {
|
|
671
|
+
merge: true
|
|
672
|
+
});
|
|
673
|
+
return {
|
|
674
|
+
job: _extends({}, job, patch),
|
|
675
|
+
changed: true
|
|
676
|
+
};
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
/** Every step done: the job is `done` and holds nothing. */ export async function completeAiJob(firestore, orgId, jobId, now = new Date()) {
|
|
680
|
+
const { job } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
681
|
+
status: 'done',
|
|
682
|
+
creditsReserved: 0,
|
|
683
|
+
lease: null,
|
|
684
|
+
error: null,
|
|
685
|
+
updatedAt: now
|
|
686
|
+
});
|
|
687
|
+
return job;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Terminal failure. `message` is the only thing a customer reads, so it is
|
|
691
|
+
* a fixed sentence; whatever the provider or the runner actually said goes
|
|
692
|
+
* to the log beside the ids an operator would search for.
|
|
693
|
+
*/ export async function failAiJob(firestore, orgId, jobId, message, detail, now = new Date()) {
|
|
694
|
+
var _ref, _ref1;
|
|
695
|
+
console.error('ai job failed', {
|
|
696
|
+
orgId,
|
|
697
|
+
jobId,
|
|
698
|
+
stepIndex: (_ref = detail == null ? void 0 : detail.stepIndex) != null ? _ref : null,
|
|
699
|
+
error: (detail == null ? void 0 : detail.error) instanceof Error ? detail.error.message : (_ref1 = detail == null ? void 0 : detail.error) != null ? _ref1 : null
|
|
700
|
+
});
|
|
701
|
+
const { job } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
702
|
+
status: 'failed',
|
|
703
|
+
error: message,
|
|
704
|
+
creditsReserved: 0,
|
|
705
|
+
lease: null,
|
|
706
|
+
steps: current.steps.map((step)=>step.status === 'running' ? _extends({}, step, {
|
|
707
|
+
status: 'failed',
|
|
708
|
+
endedAt: now,
|
|
709
|
+
error: message
|
|
710
|
+
}) : step),
|
|
711
|
+
updatedAt: now
|
|
712
|
+
});
|
|
713
|
+
return job;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Park the job for something only the workspace can change. The running
|
|
717
|
+
* step goes back to `pending` so the same step is what runs when the beat
|
|
718
|
+
* tries again; its attempt is not counted, because nothing was attempted.
|
|
719
|
+
*/ export async function markAiJobNeedsInput(firestore, orgId, jobId, reason, now = new Date()) {
|
|
720
|
+
const { job } = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
721
|
+
status: 'needs_input',
|
|
722
|
+
error: reason,
|
|
723
|
+
lease: null,
|
|
724
|
+
steps: current.steps.map((step)=>{
|
|
725
|
+
var _step_attempts;
|
|
726
|
+
return step.status === 'running' ? _extends({}, step, {
|
|
727
|
+
status: 'pending',
|
|
728
|
+
attempts: Math.max(0, ((_step_attempts = step.attempts) != null ? _step_attempts : 0) - 1)
|
|
729
|
+
}) : step;
|
|
730
|
+
}),
|
|
731
|
+
updatedAt: now
|
|
732
|
+
});
|
|
733
|
+
return job;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Cancel. Idempotent — a terminal job is returned unchanged — and it does
|
|
737
|
+
* not wait for a step in flight: the runner holding the lease finishes its
|
|
738
|
+
* provider call, records what it cost, and finds the job canceled when it
|
|
739
|
+
* goes to complete it.
|
|
740
|
+
*
|
|
741
|
+
* The activity row is written only when the cancel changed something: a
|
|
742
|
+
* second cancel of the same job is not a second act. `actor` is the member
|
|
743
|
+
* who canceled; a cancel with nobody named is recorded as nobody's rather
|
|
744
|
+
* than as the creator's.
|
|
745
|
+
*/ export async function cancelAiJob(firestore, orgId, jobId, now = new Date(), actor = null) {
|
|
746
|
+
const result = await transition(firestore, orgId, jobId, (current)=>isAiJobTerminal(current.status) ? null : {
|
|
747
|
+
status: 'canceled',
|
|
748
|
+
creditsReserved: 0,
|
|
749
|
+
lease: null,
|
|
750
|
+
updatedAt: now
|
|
751
|
+
});
|
|
752
|
+
if (result.changed) {
|
|
753
|
+
await logAiJobCanceled(orgId, actor != null ? actor : {
|
|
754
|
+
uid: null
|
|
755
|
+
}, {
|
|
756
|
+
jobId,
|
|
757
|
+
kind: result.job.kind
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
return result;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Resume a job that stopped for a person (AGL-2935): confirm its plan, or
|
|
764
|
+
* try again the step whose answer broke a building rule. One transaction,
|
|
765
|
+
* so two confirmations land once; anything but a `needs_review` job comes
|
|
766
|
+
* back unchanged. The pending step's attempts start over — a person asking
|
|
767
|
+
* again is not a provider failing again — and a confirmed plan with no step
|
|
768
|
+
* left behind it completes the job rather than queueing nothing. The steps
|
|
769
|
+
* it queues are held at the nominal figure again, which the park released —
|
|
770
|
+
* and a confirmed plan is held at what the whole job is estimated to cost
|
|
771
|
+
* (AGL-3031), creations included, where that is more.
|
|
772
|
+
*/ export async function resumeAiJob(firestore, orgId, jobId, actor, now = new Date()) {
|
|
773
|
+
return transition(firestore, orgId, jobId, (current)=>{
|
|
774
|
+
var _current_review;
|
|
775
|
+
if (current.status !== 'needs_review') return null;
|
|
776
|
+
const steps = current.steps.map((step)=>step.status === 'pending' ? _extends({}, step, {
|
|
777
|
+
attempts: 0
|
|
778
|
+
}) : step);
|
|
779
|
+
const outstanding = steps.filter((step)=>step.status === 'pending').length;
|
|
780
|
+
const pending = outstanding > 0;
|
|
781
|
+
const confirming = ((_current_review = current.review) == null ? void 0 : _current_review.reason) === 'plan' && current.plan ? current.plan : null;
|
|
782
|
+
const confirmed = confirming ? {
|
|
783
|
+
plan: _extends({}, confirming, {
|
|
784
|
+
status: 'confirmed',
|
|
785
|
+
confirmedAt: now,
|
|
786
|
+
confirmedBy: actor.uid
|
|
787
|
+
})
|
|
788
|
+
} : {};
|
|
789
|
+
return _extends({
|
|
790
|
+
status: pending ? 'queued' : 'done',
|
|
791
|
+
steps,
|
|
792
|
+
review: null,
|
|
793
|
+
error: null
|
|
794
|
+
}, confirmed, {
|
|
795
|
+
creditsReserved: pending ? Math.max(outstanding * AI_JOB_STEP_RESERVE_CREDITS, confirming ? aiJobPlanCreditEstimate(current.kind, confirming) : 0) : 0,
|
|
796
|
+
updatedAt: now
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Record what a person applied from a finished job's outputs (AGL-2910): the
|
|
802
|
+
* versions an apply opened and the listings it staged. The apply door calls
|
|
803
|
+
* this after its own writes, so the record names only what exists. It never
|
|
804
|
+
* changes the job's status, its steps or its spend.
|
|
805
|
+
*/ export async function recordAiJobApplied(firestore, orgId, jobId, applied, now = new Date()) {
|
|
806
|
+
const { job } = await transition(firestore, orgId, jobId, ()=>({
|
|
807
|
+
applied,
|
|
808
|
+
updatedAt: now
|
|
809
|
+
}));
|
|
810
|
+
return job;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* One `adminAudit` row, the same writer shape the billing routes use. Never
|
|
814
|
+
* throws: an audit row that fails to land is logged, not turned into a
|
|
815
|
+
* failed job the customer paid for.
|
|
816
|
+
*/ export async function writeAiJobAudit(firestore, entry) {
|
|
817
|
+
try {
|
|
818
|
+
var _entry_actorEmail;
|
|
819
|
+
await firestore.collection('adminAudit').add({
|
|
820
|
+
actorUid: entry.actorUid,
|
|
821
|
+
actorEmail: (_entry_actorEmail = entry.actorEmail) != null ? _entry_actorEmail : null,
|
|
822
|
+
action: entry.action,
|
|
823
|
+
target: `orgs/${entry.orgId}/${AI_JOBS_COLLECTION}/${entry.jobId}`,
|
|
824
|
+
after: entry.after,
|
|
825
|
+
at: FieldValue.serverTimestamp()
|
|
826
|
+
});
|
|
827
|
+
} catch (error) {
|
|
828
|
+
console.error('ai job audit write failed', {
|
|
829
|
+
orgId: entry.orgId,
|
|
830
|
+
jobId: entry.jobId,
|
|
831
|
+
action: entry.action,
|
|
832
|
+
error
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
// ── Running a step ────────────────────────────────────────────────────────
|
|
837
|
+
/**
|
|
838
|
+
* The refusal in customer-safe words. The org's own wall names the switch
|
|
839
|
+
* (AGL-2653); a cap, a band and a budget are told apart because one is a
|
|
840
|
+
* figure the org set, one is what its plan sold, and one is the operator's
|
|
841
|
+
* backstop; the message cap resets on a clock the others do not.
|
|
842
|
+
*/ export function aiJobRefusalText(org, reservation) {
|
|
843
|
+
const refusedBy = reservation.refusedBy;
|
|
844
|
+
// AGLYN'S OWN OVERAGE GUARDS (AGL-3011). First, because a `cap` refusal
|
|
845
|
+
// that carries a reason knows exactly which limit stopped the job, where
|
|
846
|
+
// the `case 'cap'` below can only name the workspace's own.
|
|
847
|
+
const overage = aiOverageReservationRefusal(reservation);
|
|
848
|
+
if (overage) return overage.text;
|
|
849
|
+
// A hard allotment (AGL-2942) — the creator's, theirs on the job's site,
|
|
850
|
+
// or the site's — in the sentence every door gives, naming who can raise
|
|
851
|
+
// it. A member can change that, so the job parks rather than fails.
|
|
852
|
+
if (refusedBy === 'allotment') {
|
|
853
|
+
var _reservation_allotment_refusal, _reservation_allotment;
|
|
854
|
+
return aiAllotmentRefusalText((_reservation_allotment = reservation.allotment) == null ? void 0 : (_reservation_allotment_refusal = _reservation_allotment.refusal) == null ? void 0 : _reservation_allotment_refusal.scope);
|
|
855
|
+
}
|
|
856
|
+
if (assistRefusedByHardCap(org, refusedBy)) {
|
|
857
|
+
return assistHardCapRefusalText(org);
|
|
858
|
+
}
|
|
859
|
+
// The Free taste's own precautions (AGL-2925), in the sentences every
|
|
860
|
+
// other door uses — each names a clock or an upgrade.
|
|
861
|
+
const taste = assistFreeTasteRefusalText(refusedBy);
|
|
862
|
+
if (taste) return taste;
|
|
863
|
+
switch(refusedBy){
|
|
864
|
+
case 'cap':
|
|
865
|
+
return 'This workspace reached the AI spending cap it set for the month';
|
|
866
|
+
case 'band':
|
|
867
|
+
return 'This workspace used its AI credits for the month';
|
|
868
|
+
case 'budget':
|
|
869
|
+
return reservation.budgetUsd === null ? 'This workspace reached its AI spending limit for the month' : 'This workspace used its AI credits for the month';
|
|
870
|
+
default:
|
|
871
|
+
return 'This workspace reached its AI limit for the month';
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* A budget that ended before the provider answered. `fetch` rejects with
|
|
876
|
+
* the signal's reason, which is an `AbortError` for a manual abort and a
|
|
877
|
+
* `TimeoutError` for `AbortSignal.timeout` — the one the route and the
|
|
878
|
+
* sweep actually hand in.
|
|
879
|
+
*/ function isAbort(error) {
|
|
880
|
+
const name = error == null ? void 0 : error.name;
|
|
881
|
+
return name === 'AbortError' || name === 'TimeoutError';
|
|
882
|
+
}
|
|
883
|
+
/** A step outcome that carries no tokens and no cost: the provider was never reached. */ export function aiJobStepSpentNothing(outcome) {
|
|
884
|
+
const { usage } = outcome;
|
|
885
|
+
return outcome.estCostUsd === 0 && usage.inputTokens === 0 && usage.outputTokens === 0 && usage.cacheReadTokens === 0 && usage.cacheWriteTokens === 0;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Pause, claim, reserve, run, meter, record — one step of one job.
|
|
889
|
+
*
|
|
890
|
+
* The order is the invariant. The workspace's AI pause is asked BEFORE the
|
|
891
|
+
* claim, so a paused workspace's job is neither claimed nor reserved for.
|
|
892
|
+
* The reservation is taken AFTER the claim so a refused claim costs nothing,
|
|
893
|
+
* and BEFORE the runner so the org's ceiling binds before a token is spent.
|
|
894
|
+
* The cost is recorded before the step is, so a process cut off between the
|
|
895
|
+
* two leaves the bill right and the step recoverable, never the other way
|
|
896
|
+
* round.
|
|
897
|
+
*/ export async function runAiJobStep(firestore, orgId, jobId, options) {
|
|
898
|
+
var _options_now, _step_attempts, _options_org, _data, _ref, _ref1, _outcome_effort, _step_passes, _options_actor;
|
|
899
|
+
var _reservation_allotment, _reservation_allotment1;
|
|
900
|
+
const now = (_options_now = options.now) != null ? _options_now : new Date();
|
|
901
|
+
// A reservation the caller took before the job existed is a message
|
|
902
|
+
// counted against the org; a step that fails before the provider is
|
|
903
|
+
// reached spends nothing, so the message goes back rather than standing
|
|
904
|
+
// as capacity the org paid for and never used.
|
|
905
|
+
const releaseHeld = async ()=>{
|
|
906
|
+
if (!options.reservation) return;
|
|
907
|
+
await releaseAssistMessage(firestore, orgId, options.reservation).catch((releaseError)=>console.error('ai job release failed', {
|
|
908
|
+
orgId,
|
|
909
|
+
jobId,
|
|
910
|
+
releaseError
|
|
911
|
+
}));
|
|
912
|
+
};
|
|
913
|
+
// A WORKSPACE WHOSE AI STAFF HAVE PAUSED runs none of its jobs (AGL-3037),
|
|
914
|
+
// queued before the pause or not: the pause is a spend stop, and a job the
|
|
915
|
+
// beat kept running would spend through it. The pause is lifted by staff
|
|
916
|
+
// or by its own expiry and leaves the plan, the add-on and every
|
|
917
|
+
// entitlement as they were, so a paused job is held, not failed: nothing is
|
|
918
|
+
// claimed, reserved or run, and resuming AI runs it from where it stopped.
|
|
919
|
+
// Read through the same verdict the jobs doors climb, before the claim.
|
|
920
|
+
if (await aiJobPausedFor(orgId, jobId, options.staff === true)) {
|
|
921
|
+
const held = await holdPausedAiJob(firestore, orgId, jobId, options.owner, now);
|
|
922
|
+
if (!held) return {
|
|
923
|
+
outcome: 'not-claimable'
|
|
924
|
+
};
|
|
925
|
+
await releaseHeld();
|
|
926
|
+
return {
|
|
927
|
+
outcome: 'paused',
|
|
928
|
+
job: held
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
const claimed = await claimNextStep(firestore, orgId, jobId, options.owner, now);
|
|
932
|
+
if (!claimed) return {
|
|
933
|
+
outcome: 'not-claimable'
|
|
934
|
+
};
|
|
935
|
+
const { job, stepIndex } = claimed;
|
|
936
|
+
const step = job.steps[stepIndex];
|
|
937
|
+
const runner = aiJobRunnerForStep(job.kind, step.name);
|
|
938
|
+
if (!runner) {
|
|
939
|
+
await releaseHeld();
|
|
940
|
+
return {
|
|
941
|
+
outcome: 'failed',
|
|
942
|
+
job: await failAiJob(firestore, orgId, jobId, AI_JOB_NOT_AVAILABLE_COPY, {
|
|
943
|
+
stepIndex,
|
|
944
|
+
error: `no runner registered for kind ${job.kind}`
|
|
945
|
+
}, now)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
if (((_step_attempts = step.attempts) != null ? _step_attempts : 0) > AI_JOB_STEP_MAX_ATTEMPTS) {
|
|
949
|
+
await releaseHeld();
|
|
950
|
+
return {
|
|
951
|
+
outcome: 'failed',
|
|
952
|
+
job: await failAiJob(firestore, orgId, jobId, AI_UPSTREAM_FAILURE_COPY, {
|
|
953
|
+
stepIndex,
|
|
954
|
+
error: `step ${step.name} exhausted ${AI_JOB_STEP_MAX_ATTEMPTS} attempts`
|
|
955
|
+
}, now)
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const org = (_options_org = options.org) != null ? _options_org : (_data = (await firestore.collection('orgs').doc(orgId).get()).data()) != null ? _data : {};
|
|
959
|
+
// A site that switched AI off runs none of its jobs (AGL-3028), queued
|
|
960
|
+
// before the switch or not. Asked before the reservation, so the job fails
|
|
961
|
+
// in words a member can act on and nothing is reserved, run or metered.
|
|
962
|
+
if (await isAiOffForSite(firestore, org, job.hostId)) {
|
|
963
|
+
await releaseHeld();
|
|
964
|
+
return {
|
|
965
|
+
outcome: 'failed',
|
|
966
|
+
job: await failAiJob(firestore, orgId, jobId, AI_OFF_FOR_SITE_COPY, {
|
|
967
|
+
stepIndex,
|
|
968
|
+
error: `ai is switched off for site ${job.hostId}`
|
|
969
|
+
}, now)
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
const entitled = checkEntitlement(org, 'aiAssist') || checkEntitlement(org, 'aiGenerative');
|
|
973
|
+
let reservation;
|
|
974
|
+
if (options.reservation) {
|
|
975
|
+
reservation = options.reservation;
|
|
976
|
+
} else {
|
|
977
|
+
try {
|
|
978
|
+
var _job_hostId;
|
|
979
|
+
// The creator's allotments on the job's site (AGL-2942): the step is
|
|
980
|
+
// their spend, as the meter below attributes it.
|
|
981
|
+
reservation = await reserveAssistMessage(firestore, orgId, entitled, now, org, {
|
|
982
|
+
uid: job.createdBy,
|
|
983
|
+
hostId: (_job_hostId = job.hostId) != null ? _job_hostId : null
|
|
984
|
+
});
|
|
985
|
+
} catch (error) {
|
|
986
|
+
// The meter is unreachable, not refusing. Hand the step back; the
|
|
987
|
+
// next beat asks again.
|
|
988
|
+
console.error('ai job reservation failed', {
|
|
989
|
+
orgId,
|
|
990
|
+
jobId,
|
|
991
|
+
error
|
|
992
|
+
});
|
|
993
|
+
const { job: requeued } = await recordStep(firestore, orgId, jobId, options.owner, stepIndex, {
|
|
994
|
+
status: 'pending',
|
|
995
|
+
creditsSpent: 0
|
|
996
|
+
}, now);
|
|
997
|
+
return {
|
|
998
|
+
outcome: 'requeued',
|
|
999
|
+
job: requeued
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
// A refused step is the creator's refusal (AGL-2928), on their month,
|
|
1004
|
+
// beside the org counter the reservation moved; an admitted one records
|
|
1005
|
+
// nothing.
|
|
1006
|
+
recordUserAiRefusal(firestore, orgId, job.createdBy, reservation);
|
|
1007
|
+
if (!reservation.allowed) {
|
|
1008
|
+
const refusal = aiJobRefusalText(org, reservation);
|
|
1009
|
+
const parked = await markAiJobNeedsInput(firestore, orgId, jobId, refusal, now);
|
|
1010
|
+
// Nobody parked the job — the meter did — so the row carries no actor
|
|
1011
|
+
// rather than the creator's name on an act they did not perform. Only
|
|
1012
|
+
// when the job was not already parked for this: the reason the last
|
|
1013
|
+
// park stored survives the beat's re-queue, so a job the meter refuses
|
|
1014
|
+
// again on its hourly retry is one row, not one an hour; a different
|
|
1015
|
+
// ceiling is a new row. The meter names a ceiling on every refusal; the
|
|
1016
|
+
// guard narrows the type.
|
|
1017
|
+
if (reservation.refusedBy && job.error !== refusal) {
|
|
1018
|
+
await logAiJobNeedsInput(orgId, {
|
|
1019
|
+
uid: null
|
|
1020
|
+
}, {
|
|
1021
|
+
jobId,
|
|
1022
|
+
kind: job.kind,
|
|
1023
|
+
reason: reservation.refusedBy
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
outcome: 'needs_input',
|
|
1028
|
+
job: parked
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
// The model each step runs on (AGL-2942): the creator's pick where the
|
|
1032
|
+
// plan and the allotment allowlists the reservation read allow it, the
|
|
1033
|
+
// routing table otherwise. Handed to the runner as a function of the step
|
|
1034
|
+
// kind, because the runner — not the machine — knows what kind it runs.
|
|
1035
|
+
const bounds = {
|
|
1036
|
+
plan: resolveEffectivePlan(org),
|
|
1037
|
+
allotmentModels: (_ref = (_reservation_allotment = reservation.allotment) == null ? void 0 : _reservation_allotment.models) != null ? _ref : null,
|
|
1038
|
+
orgModels: (_ref1 = (_reservation_allotment1 = reservation.allotment) == null ? void 0 : _reservation_allotment1.orgModels) != null ? _ref1 : null
|
|
1039
|
+
};
|
|
1040
|
+
const modelFor = (kind)=>{
|
|
1041
|
+
var _job_model;
|
|
1042
|
+
var _resolveAiModelChoice;
|
|
1043
|
+
return (_resolveAiModelChoice = resolveAiModelChoice(kind, (_job_model = job.model) != null ? _job_model : null, bounds)) == null ? void 0 : _resolveAiModelChoice.model;
|
|
1044
|
+
};
|
|
1045
|
+
let outcome;
|
|
1046
|
+
// The runner's wall-clock time, recorded on the step beside its tokens
|
|
1047
|
+
// (AGL-2937): measured here, around the call, rather than asked of it.
|
|
1048
|
+
const runStarted = Date.now();
|
|
1049
|
+
try {
|
|
1050
|
+
outcome = await runner({
|
|
1051
|
+
job,
|
|
1052
|
+
stepIndex,
|
|
1053
|
+
now,
|
|
1054
|
+
signal: options.signal,
|
|
1055
|
+
firestore,
|
|
1056
|
+
org,
|
|
1057
|
+
modelFor
|
|
1058
|
+
});
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
// The provider refused the request or the budget ended before it
|
|
1061
|
+
// answered: no usage came back, so nothing is metered and the message
|
|
1062
|
+
// goes back to the org. The provider's own words are already in the
|
|
1063
|
+
// log under the request id (AGL-2815).
|
|
1064
|
+
await releaseAssistMessage(firestore, orgId, reservation).catch((releaseError)=>console.error('ai job release failed', {
|
|
1065
|
+
orgId,
|
|
1066
|
+
jobId,
|
|
1067
|
+
releaseError
|
|
1068
|
+
}));
|
|
1069
|
+
const retryable = error instanceof AiUpstreamError && error.retryable || isAbort(error);
|
|
1070
|
+
if (retryable) {
|
|
1071
|
+
const { job: requeued } = await recordStep(firestore, orgId, jobId, options.owner, stepIndex, {
|
|
1072
|
+
status: 'pending',
|
|
1073
|
+
creditsSpent: 0
|
|
1074
|
+
}, now);
|
|
1075
|
+
return {
|
|
1076
|
+
outcome: 'requeued',
|
|
1077
|
+
job: requeued
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
return {
|
|
1081
|
+
outcome: 'failed',
|
|
1082
|
+
job: await failAiJob(firestore, orgId, jobId, AI_UPSTREAM_FAILURE_COPY, {
|
|
1083
|
+
stepIndex,
|
|
1084
|
+
error
|
|
1085
|
+
}, now)
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
const latencyMs = Math.max(0, Date.now() - runStarted);
|
|
1089
|
+
// A step that failed before it reached the provider — the site it builds
|
|
1090
|
+
// from could not be read, say (AGL-2938) — spent nothing: its message goes
|
|
1091
|
+
// back and nothing is metered, exactly as for a request the provider
|
|
1092
|
+
// refused.
|
|
1093
|
+
if (outcome.failure && aiJobStepSpentNothing(outcome)) {
|
|
1094
|
+
await releaseAssistMessage(firestore, orgId, reservation).catch((releaseError)=>console.error('ai job release failed', {
|
|
1095
|
+
orgId,
|
|
1096
|
+
jobId,
|
|
1097
|
+
releaseError
|
|
1098
|
+
}));
|
|
1099
|
+
await recordStep(firestore, orgId, jobId, options.owner, stepIndex, {
|
|
1100
|
+
status: 'failed',
|
|
1101
|
+
creditsSpent: 0,
|
|
1102
|
+
error: outcome.failure
|
|
1103
|
+
}, now);
|
|
1104
|
+
return {
|
|
1105
|
+
outcome: 'failed',
|
|
1106
|
+
job: await failAiJob(firestore, orgId, jobId, outcome.failure, {
|
|
1107
|
+
stepIndex,
|
|
1108
|
+
error: `step failure before the provider: ${outcome.failure}`
|
|
1109
|
+
}, now)
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
const tokens = {
|
|
1113
|
+
usage: outcome.usage,
|
|
1114
|
+
model: outcome.model,
|
|
1115
|
+
effort: (_outcome_effort = outcome.effort) != null ? _outcome_effort : null,
|
|
1116
|
+
latencyMs,
|
|
1117
|
+
stopReason: outcome.stopReason
|
|
1118
|
+
};
|
|
1119
|
+
// A step that stopped before the provider without failing — a site with no
|
|
1120
|
+
// room for the draft (AGL-2909), say — spent nothing too: its message goes
|
|
1121
|
+
// back and there is nothing to meter. What it returns is still recorded
|
|
1122
|
+
// below, as any step's is.
|
|
1123
|
+
const spentNothing = aiJobStepSpentNothing(outcome);
|
|
1124
|
+
if (spentNothing) {
|
|
1125
|
+
await releaseAssistMessage(firestore, orgId, reservation).catch((releaseError)=>console.error('ai job release failed', {
|
|
1126
|
+
orgId,
|
|
1127
|
+
jobId,
|
|
1128
|
+
releaseError
|
|
1129
|
+
}));
|
|
1130
|
+
} else {
|
|
1131
|
+
// Tokens were spent, so the bill is written first and its failure is a
|
|
1132
|
+
// log line, never a failed job — the same rule the chat route keeps.
|
|
1133
|
+
try {
|
|
1134
|
+
var _job_hostId1, _reservation_free;
|
|
1135
|
+
await recordAssistCost(firestore, orgId, {
|
|
1136
|
+
route: 'ai/jobs',
|
|
1137
|
+
hostId: (_job_hostId1 = job.hostId) != null ? _job_hostId1 : null,
|
|
1138
|
+
model: outcome.model,
|
|
1139
|
+
tier: entitled ? 'entitled' : 'free',
|
|
1140
|
+
usage: outcome.usage,
|
|
1141
|
+
docsPaths: [],
|
|
1142
|
+
stopReason: outcome.stopReason,
|
|
1143
|
+
deflected: false,
|
|
1144
|
+
// The account this job drew on, as the reservation decided it
|
|
1145
|
+
// (AGL-2925): a Free job lands on the owner's allowance and the
|
|
1146
|
+
// platform's day; a paid one on neither.
|
|
1147
|
+
free: (_reservation_free = reservation.free) != null ? _reservation_free : null,
|
|
1148
|
+
// The step is the creator's spend, under the job's kind (AGL-2928).
|
|
1149
|
+
uid: job.createdBy,
|
|
1150
|
+
kind: job.kind
|
|
1151
|
+
}, now);
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
console.error('ai job cost record failed', {
|
|
1154
|
+
orgId,
|
|
1155
|
+
jobId,
|
|
1156
|
+
error
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
const credits = assistCreditsFromUsd(outcome.estCostUsd);
|
|
1161
|
+
// A model that declined, or a step that got nothing usable out of the
|
|
1162
|
+
// model's answer (AGL-2938), fails the job with its own sentence. The
|
|
1163
|
+
// tokens are already on the bill above.
|
|
1164
|
+
if (outcome.refused || outcome.failure) {
|
|
1165
|
+
const message = outcome.refused ? AI_JOB_REFUSED_COPY : outcome.failure;
|
|
1166
|
+
await recordStep(firestore, orgId, jobId, options.owner, stepIndex, {
|
|
1167
|
+
status: 'failed',
|
|
1168
|
+
creditsSpent: credits,
|
|
1169
|
+
error: message,
|
|
1170
|
+
tokens
|
|
1171
|
+
}, now);
|
|
1172
|
+
return {
|
|
1173
|
+
outcome: 'failed',
|
|
1174
|
+
job: await failAiJob(firestore, orgId, jobId, message, {
|
|
1175
|
+
stepIndex,
|
|
1176
|
+
error: outcome.refused ? 'stop_reason refusal' : `step failure: ${message}`
|
|
1177
|
+
}, now)
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
// A step that stopped for a person parks the job in the same write that
|
|
1181
|
+
// records it (AGL-2935): a plan review completes the step, so confirming
|
|
1182
|
+
// runs the next one; a doctrine or limit review hands it back, so trying
|
|
1183
|
+
// again runs the same one.
|
|
1184
|
+
//
|
|
1185
|
+
// A step that asks to continue (AGL-2910) is recorded as this pass — its
|
|
1186
|
+
// cost, its credits, its outputs, its audit rows — and handed back as
|
|
1187
|
+
// `pending`, so the next claim runs the same step again. A step that stopped
|
|
1188
|
+
// for a person never continues: the review is what it waits on. The pass
|
|
1189
|
+
// cap is what stops a runner that never finishes: past it the pass is
|
|
1190
|
+
// recorded as the step's last, and what it produced stands.
|
|
1191
|
+
const review = outcome.review;
|
|
1192
|
+
const continuing = !review && Boolean(outcome.continue) && ((_step_passes = step.passes) != null ? _step_passes : 0) + 1 < aiJobStepMaxPasses(job.kind);
|
|
1193
|
+
if (!review && outcome.continue && !continuing) {
|
|
1194
|
+
console.warn('ai job step reached its pass cap', {
|
|
1195
|
+
orgId,
|
|
1196
|
+
jobId,
|
|
1197
|
+
stepIndex
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
const recorded = await recordStep(firestore, orgId, jobId, options.owner, stepIndex, _extends({
|
|
1201
|
+
status: continuing || review && review.reason !== 'plan' ? 'pending' : 'done'
|
|
1202
|
+
}, continuing ? {
|
|
1203
|
+
continued: true
|
|
1204
|
+
} : {}, {
|
|
1205
|
+
creditsSpent: credits
|
|
1206
|
+
}, spentNothing ? {} : {
|
|
1207
|
+
tokens
|
|
1208
|
+
}, {
|
|
1209
|
+
outputs: outcome.outputs
|
|
1210
|
+
}, outcome.plan ? {
|
|
1211
|
+
plan: outcome.plan
|
|
1212
|
+
} : {}, review ? {
|
|
1213
|
+
review
|
|
1214
|
+
} : {}), now);
|
|
1215
|
+
const actor = (_options_actor = options.actor) != null ? _options_actor : {
|
|
1216
|
+
uid: job.createdBy
|
|
1217
|
+
};
|
|
1218
|
+
for (const output of outcome.outputs){
|
|
1219
|
+
var _output_versionId, _output_versionId1;
|
|
1220
|
+
await writeAiJobAudit(firestore, {
|
|
1221
|
+
action: 'ai.job.output',
|
|
1222
|
+
actorUid: job.createdBy,
|
|
1223
|
+
orgId,
|
|
1224
|
+
jobId,
|
|
1225
|
+
after: {
|
|
1226
|
+
resource: output.resource,
|
|
1227
|
+
id: output.id,
|
|
1228
|
+
versionId: (_output_versionId = output.versionId) != null ? _output_versionId : null,
|
|
1229
|
+
hostId: output.hostId,
|
|
1230
|
+
label: output.label,
|
|
1231
|
+
credits
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
// The customer-visible row, one per output: the label names the thing,
|
|
1235
|
+
// never its text, which is the site's content.
|
|
1236
|
+
await logAiJobOutput(orgId, actor, {
|
|
1237
|
+
jobId,
|
|
1238
|
+
hostId: output.hostId,
|
|
1239
|
+
resource: {
|
|
1240
|
+
type: aiOutputTargetType(output.resource),
|
|
1241
|
+
id: output.id,
|
|
1242
|
+
name: output.label,
|
|
1243
|
+
versionId: (_output_versionId1 = output.versionId) != null ? _output_versionId1 : null
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
if (review && recorded.job.status === 'needs_review') {
|
|
1248
|
+
// Nobody parked the job but its own step, so the row names no actor, as
|
|
1249
|
+
// the meter's park does.
|
|
1250
|
+
await logAiJobNeedsInput(orgId, {
|
|
1251
|
+
uid: null
|
|
1252
|
+
}, {
|
|
1253
|
+
jobId,
|
|
1254
|
+
kind: job.kind,
|
|
1255
|
+
reason: review.reason
|
|
1256
|
+
});
|
|
1257
|
+
return {
|
|
1258
|
+
outcome: 'needs_review',
|
|
1259
|
+
job: recorded.job
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
if (recorded.remaining > 0 || isAiJobTerminal(recorded.job.status)) {
|
|
1263
|
+
return {
|
|
1264
|
+
outcome: 'done',
|
|
1265
|
+
job: recorded.job
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
return {
|
|
1269
|
+
outcome: 'done',
|
|
1270
|
+
job: await completeAiJob(firestore, orgId, jobId, now)
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Every job the beat should look at, oldest first: queued and running
|
|
1275
|
+
* across every org, then `needs_input` jobs that have rested long enough
|
|
1276
|
+
* for the org's standing to have changed. Two queries rather than one, so
|
|
1277
|
+
* a workspace with many parked jobs cannot fill the candidate list and
|
|
1278
|
+
* starve the queue behind it. Both use the collection-group index on
|
|
1279
|
+
* (status, updatedAt).
|
|
1280
|
+
*
|
|
1281
|
+
* The ordering is the cursor. A job this sweep touches has its `updatedAt`
|
|
1282
|
+
* moved and goes to the back of the next query; one it did not reach keeps
|
|
1283
|
+
* its place at the front. So there is no stored position to fall behind
|
|
1284
|
+
* or skip past — the same shape `media-move.ts` gives its budgeted loop,
|
|
1285
|
+
* where what was not done is simply what is asked for next.
|
|
1286
|
+
*/ export async function listDueAiJobs(firestore, nowMs, maxJobs = AI_JOB_SWEEP_MAX_JOBS, maxParked = AI_JOB_SWEEP_MAX_PARKED) {
|
|
1287
|
+
const group = firestore.collectionGroup(AI_JOBS_COLLECTION);
|
|
1288
|
+
const [active, parked] = await Promise.all([
|
|
1289
|
+
group.where('status', 'in', [
|
|
1290
|
+
'queued',
|
|
1291
|
+
'running'
|
|
1292
|
+
]).orderBy('updatedAt', 'asc').limit(maxJobs).get(),
|
|
1293
|
+
group.where('status', '==', 'needs_input').where('updatedAt', '<=', new Date(nowMs - AI_JOB_NEEDS_INPUT_RETRY_MS)).orderBy('updatedAt', 'asc').limit(maxParked).get()
|
|
1294
|
+
]);
|
|
1295
|
+
return [
|
|
1296
|
+
...active.docs,
|
|
1297
|
+
...parked.docs
|
|
1298
|
+
].map((doc)=>{
|
|
1299
|
+
var _doc_get;
|
|
1300
|
+
return {
|
|
1301
|
+
orgId: String((_doc_get = doc.get('orgId')) != null ? _doc_get : ''),
|
|
1302
|
+
jobId: doc.id
|
|
1303
|
+
};
|
|
1304
|
+
}).filter((due)=>due.orgId !== '');
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Run due steps until the budget is spent, and account for every candidate.
|
|
1308
|
+
*
|
|
1309
|
+
* The bound is wall clock, not a count, for the reason `media-move.ts`
|
|
1310
|
+
* gives: one step is one provider round trip of unknown length, so any
|
|
1311
|
+
* fixed number would be wrong for some brief. The budget is checked BETWEEN
|
|
1312
|
+
* jobs, and the step in flight gets the time that is left as its abort
|
|
1313
|
+
* signal, so the sweep still finishes whichever step it has started. At
|
|
1314
|
+
* least one candidate is always attempted, so a beat that yields having
|
|
1315
|
+
* done nothing cannot starve a queue behind a slow clock.
|
|
1316
|
+
*
|
|
1317
|
+
* A `needs_input` job is re-queued before its step is claimed, because the
|
|
1318
|
+
* claim admits only queued and running jobs; the reservation inside the
|
|
1319
|
+
* step is what decides whether the org's standing has changed.
|
|
1320
|
+
*/ export async function sweepAiJobs(options) {
|
|
1321
|
+
var _options_now, _options_budgetMs, _options_listDue, _options_runStep;
|
|
1322
|
+
const now = (_options_now = options.now) != null ? _options_now : Date.now;
|
|
1323
|
+
const budgetMs = (_options_budgetMs = options.budgetMs) != null ? _options_budgetMs : AI_JOB_SWEEP_BUDGET_MS;
|
|
1324
|
+
const startedAt = now();
|
|
1325
|
+
const listDue = (_options_listDue = options.listDue) != null ? _options_listDue : ()=>listDueAiJobs(options.firestore, now(), options.maxJobs);
|
|
1326
|
+
const runStep = (_options_runStep = options.runStep) != null ? _options_runStep : (orgId, jobId, stepOptions)=>runAiJobStep(options.firestore, orgId, jobId, stepOptions);
|
|
1327
|
+
const due = await listDue();
|
|
1328
|
+
const result = {
|
|
1329
|
+
due: due.length,
|
|
1330
|
+
ran: 0,
|
|
1331
|
+
skipped: 0,
|
|
1332
|
+
paused: 0,
|
|
1333
|
+
remaining: 0,
|
|
1334
|
+
budgetExhausted: false
|
|
1335
|
+
};
|
|
1336
|
+
// Jobs a run left with more of a timed step to do, in the order they ran.
|
|
1337
|
+
const again = [];
|
|
1338
|
+
const attempt = async (candidate, left, first)=>{
|
|
1339
|
+
const { orgId, jobId } = candidate;
|
|
1340
|
+
try {
|
|
1341
|
+
const current = await getAiJob(options.firestore, orgId, jobId);
|
|
1342
|
+
// A step that needs more time than is left is not started (AGL-2907): a
|
|
1343
|
+
// provider call the abort cuts off is billed upstream and metered
|
|
1344
|
+
// nowhere. The job is left as it is, so it keeps its place at the front
|
|
1345
|
+
// of the next beat's queue.
|
|
1346
|
+
const minimumMs = current ? aiJobNextStepMinimumMs(current) : 0;
|
|
1347
|
+
if (minimumMs > left) {
|
|
1348
|
+
if (first) result.remaining += 1;
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if ((current == null ? void 0 : current.status) === 'needs_input') {
|
|
1352
|
+
await transition(options.firestore, orgId, jobId, (job)=>job.status === 'needs_input' ? {
|
|
1353
|
+
status: 'queued',
|
|
1354
|
+
updatedAt: new Date(now())
|
|
1355
|
+
} : null);
|
|
1356
|
+
}
|
|
1357
|
+
const run = await runStep(orgId, jobId, {
|
|
1358
|
+
owner: options.owner,
|
|
1359
|
+
now: new Date(now()),
|
|
1360
|
+
signal: AbortSignal.timeout(left)
|
|
1361
|
+
});
|
|
1362
|
+
if (run.outcome === 'not-claimable') {
|
|
1363
|
+
if (first) result.skipped += 1;
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
if (run.outcome === 'paused') {
|
|
1367
|
+
if (first) result.paused += 1;
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
result.ran += 1;
|
|
1371
|
+
// Only a step that says how long it needs is run again in this sweep:
|
|
1372
|
+
// one that does not could start with too little time left.
|
|
1373
|
+
if (run.outcome === 'done' && run.job.status === 'queued' && aiJobNextStepMinimumMs(run.job) > 0) {
|
|
1374
|
+
again.push(candidate);
|
|
1375
|
+
}
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
// One job's fault is isolated, as the runner isolates one job's.
|
|
1378
|
+
console.error('ai job sweep step failed', {
|
|
1379
|
+
orgId,
|
|
1380
|
+
jobId,
|
|
1381
|
+
error
|
|
1382
|
+
});
|
|
1383
|
+
if (first) result.skipped += 1;
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
for(let index = 0; index < due.length; index += 1){
|
|
1387
|
+
const elapsed = now() - startedAt;
|
|
1388
|
+
if (index > 0 && elapsed >= budgetMs) {
|
|
1389
|
+
result.budgetExhausted = true;
|
|
1390
|
+
result.remaining += due.length - index;
|
|
1391
|
+
return result;
|
|
1392
|
+
}
|
|
1393
|
+
await attempt(due[index], Math.max(1000, budgetMs - elapsed), true);
|
|
1394
|
+
}
|
|
1395
|
+
// Once every due job has had its turn, a job whose timed step has more to
|
|
1396
|
+
// do runs again while the time it needs is left (AGL-2907): a page built a
|
|
1397
|
+
// section a pass otherwise waits a beat between sections. Bounded by the
|
|
1398
|
+
// budget, and by the sweep's candidate count so no clock is trusted alone.
|
|
1399
|
+
for(let round = 0; again.length && round < AI_JOB_SWEEP_MAX_JOBS; round += 1){
|
|
1400
|
+
const elapsed = now() - startedAt;
|
|
1401
|
+
if (elapsed >= budgetMs) {
|
|
1402
|
+
result.budgetExhausted = true;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
await attempt(again.shift(), budgetMs - elapsed, false);
|
|
1406
|
+
}
|
|
1407
|
+
return result;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
//# sourceMappingURL=ai-jobs.js.map
|