@aglyn/plugins-ai 1.0.0-beta.218 → 1.0.0-beta.220
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +18 -18
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/activity/ai-activity-actions.js +8 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/billing/ai-month-meter.d.ts +51 -0
- package/src/lib/billing/ai-month-meter.js +89 -0
- package/src/lib/billing/ai-month-meter.js.map +1 -0
- package/src/lib/billing/ai-overage-close.d.ts +11 -33
- package/src/lib/billing/ai-overage-close.js +4 -3
- package/src/lib/billing/ai-overage-close.js.map +1 -1
- package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
- package/src/lib/billing/ai-overage-cutover.js +1 -28
- package/src/lib/billing/ai-overage-cutover.js.map +1 -1
- package/src/lib/billing/ai-overage-ledger.js +1 -1
- package/src/lib/billing/ai-overage-ledger.js.map +1 -1
- package/src/lib/billing/ai-overage-standing.js +1 -1
- package/src/lib/billing/ai-overage-standing.js.map +1 -1
- package/src/lib/components/ai-allotment-editor.component.js +1 -1
- package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
- package/src/lib/components/ai-assist-provider.component.js +5 -4
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +63 -0
- package/src/lib/components/ai-automation-zones.js +31 -0
- package/src/lib/components/ai-automation-zones.js.map +1 -0
- package/src/lib/components/ai-brief-dialog.component.js +3 -3
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +6 -3
- package/src/lib/components/ai-describe-button.component.js +4 -1
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +1 -1
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +8 -0
- package/src/lib/components/ai-job-plan.component.js +21 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
- package/src/lib/components/ai-permissions-on-host.component.js +12 -3
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
- package/src/lib/components/ai-product-import-option.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +124 -0
- package/src/lib/components/ai-product-zones.js +32 -0
- package/src/lib/components/ai-product-zones.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-seo-audit-card.component.js +35 -92
- package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +21 -15
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +2 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -2
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +1 -1
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/constants.d.ts +5 -0
- package/src/lib/constants.js +4 -0
- package/src/lib/constants.js.map +1 -1
- package/src/lib/declarations.js +2 -0
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +24 -10
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
- package/src/lib/insights/ai-dataset-catalog.js +57 -0
- package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
- package/src/lib/insights/ai-figure-readers.d.ts +6 -39
- package/src/lib/insights/ai-figure-readers.js +2 -391
- package/src/lib/insights/ai-figure-readers.js.map +1 -1
- package/src/lib/insights/ai-insight-digest.d.ts +10 -1
- package/src/lib/insights/ai-insight-digest.js +16 -4
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/insights/ai-insight-readers.d.ts +1 -1
- package/src/lib/insights/ai-insight-readers.js.map +1 -1
- package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
- package/src/lib/jobs/ai-email-bindings.js +75 -42
- package/src/lib/jobs/ai-email-bindings.js.map +1 -1
- package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-campaign-step.js +5 -10
- package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
- package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
- package/src/lib/jobs/ai-job-component-step.js +32 -9
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-draft-ids.js +11 -0
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.js +1 -1
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +5 -3
- package/src/lib/jobs/ai-job-email-step.js +11 -4
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
- package/src/lib/jobs/ai-job-form-step.js +60 -8
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +22 -1
- package/src/lib/jobs/ai-job-generation.js +63 -3
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +1 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
- package/src/lib/jobs/ai-job-layout-step.js +46 -13
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
- package/src/lib/jobs/ai-job-page-sections.js +7 -4
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +7 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
- package/src/lib/jobs/ai-job-plan-conformance.js +40 -1
- package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
- package/src/lib/jobs/ai-job-plan-creations.js +199 -0
- package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-step.d.ts +11 -2
- package/src/lib/jobs/ai-job-plan-step.js +49 -23
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
- package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
- package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-products-step.js +54 -32
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
- package/src/lib/jobs/ai-job-seo-step.js +35 -108
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
- package/src/lib/jobs/ai-job-site-step.js +6 -6
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
- package/src/lib/jobs/ai-job-template-step.js +73 -15
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-theme-step.js +1 -1
- package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +17 -9
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +1 -1
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +20 -11
- package/src/lib/jobs/ai-workflow-records.js +80 -43
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-allotments.d.ts +27 -6
- package/src/lib/model/ai-allotments.js +54 -14
- package/src/lib/model/ai-allotments.js.map +1 -1
- package/src/lib/model/ai-automation-outline.d.ts +21 -2
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +57 -0
- package/src/lib/model/ai-build-plan.js +116 -7
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-job-creations.d.ts +55 -0
- package/src/lib/model/ai-job-creations.js +85 -0
- package/src/lib/model/ai-job-creations.js.map +1 -0
- package/src/lib/model/ai-page-job.d.ts +3 -2
- package/src/lib/model/ai-page-job.js +4 -3
- package/src/lib/model/ai-page-job.js.map +1 -1
- package/src/lib/model/ai-seo.d.ts +11 -51
- package/src/lib/model/ai-seo.js +0 -17
- package/src/lib/model/ai-seo.js.map +1 -1
- package/src/lib/model/ai-site-inventory.js +1 -1
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +4 -2
- package/src/lib/model/ai-site-job.js +8 -4
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-template-subjects.d.ts +10 -0
- package/src/lib/model/ai-template-subjects.js +16 -2
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/model/ai-theme-proposal.d.ts +1 -1
- package/src/lib/model/ai-theme-proposal.js +1 -1
- package/src/lib/model/ai-theme-proposal.js.map +1 -1
- package/src/lib/model/ai-tokens.js +1 -1
- package/src/lib/model/ai-tokens.js.map +1 -1
- package/src/lib/model/ai-usage-by-user.js +1 -1
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +1 -3
- package/src/lib/model/ai-workflow-job.js +1 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +149 -0
- package/src/lib/plan-entitlements.js +168 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +1 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.js +1 -1
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/model-choice.js +1 -1
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/runtime/ai-device-audit.d.ts +1 -1
- package/src/lib/runtime/ai-device-audit.js +1 -1
- package/src/lib/runtime/ai-device-audit.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
- package/src/lib/runtime/ai-doctrine-validators.js +178 -21
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +30 -15
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +9 -2
- package/src/lib/runtime/ai-eval.js +29 -2
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-gate.js +1 -1
- package/src/lib/runtime/ai-gate.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.js +3 -3
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +7 -1
- package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
- package/src/lib/runtime/ai-palette.generated.js +16 -13
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +7 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +13 -63
- package/src/lib/runtime/seo-audit.js +22 -207
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +29 -23
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-org.js +1 -1
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-orgs-spend.js +1 -1
- package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
- package/src/lib/server/ai-admin-signals.js +1 -1
- package/src/lib/server/ai-admin-signals.js.map +1 -1
- package/src/lib/server/ai-allotments.js +1 -1
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-assist-prompts.js +3 -3
- package/src/lib/server/ai-assist-prompts.js.map +1 -1
- package/src/lib/server/ai-assist.js +2 -2
- package/src/lib/server/ai-assist.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +1 -1
- package/src/lib/server/assist-chat.js +13 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-view-context.js +23 -23
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server/billing-credits.d.ts +5 -0
- package/src/lib/server/billing-credits.js +8 -2
- package/src/lib/server/billing-credits.js.map +1 -1
- package/src/lib/server/billing-overage.js +2 -2
- package/src/lib/server/billing-overage.js.map +1 -1
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
- package/src/lib/testing/stand-in-automation-index.js +102 -0
- package/src/lib/testing/stand-in-automation-index.js.map +1 -0
- package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
- package/src/lib/testing/stand-in-dataset-index.js +80 -0
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
- package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
- package/src/lib/testing/stand-in-list-send-time.js +77 -0
- package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
- package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
- package/src/lib/testing/stand-in-pipeline-index.js +59 -0
- package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
- package/src/lib/testing/stand-in-product-index.d.ts +20 -0
- package/src/lib/testing/stand-in-product-index.js +105 -0
- package/src/lib/testing/stand-in-product-index.js.map +1 -0
- package/src/lib/tools/ai-component-tool.js +2 -2
- package/src/lib/tools/ai-component-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +6 -18
- package/src/lib/tools/ai-seo-tool.js +7 -38
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
- package/src/lib/tools/ai-workflow-tool.js +1 -1
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
- package/src/lib/usage/ai-allotment-alerts.js +39 -15
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
- package/src/lib/usage/ai-quota-checks.d.ts +37 -0
- package/src/lib/usage/ai-quota-checks.js +69 -0
- package/src/lib/usage/ai-quota-checks.js.map +1 -0
- package/src/lib/usage/ai-usage-alerts.js +22 -10
- package/src/lib/usage/ai-usage-alerts.js.map +1 -1
- package/src/lib/usage/ai-usage-by-user.js +1 -1
- package/src/lib/usage/ai-usage-by-user.js.map +1 -1
- package/src/lib/usage/assist-ceiling.d.ts +2 -2
- package/src/lib/usage/assist-ceiling.js +4 -4
- package/src/lib/usage/assist-ceiling.js.map +1 -1
- package/src/lib/usage/assist-credits.d.ts +494 -0
- package/src/lib/usage/assist-credits.js +524 -0
- package/src/lib/usage/assist-credits.js.map +1 -0
- package/src/lib/usage/assist-free-taste.d.ts +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -2
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-signal-mining.js +1 -1
- package/src/lib/usage/assist-signal-mining.js.map +1 -1
- package/src/lib/usage/assist-spend-guards.d.ts +153 -0
- package/src/lib/usage/assist-spend-guards.js +182 -0
- package/src/lib/usage/assist-spend-guards.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +1 -1
- package/src/lib/usage/assist-usage.js +1 -1
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai.js +1 -1
- package/src/lib/usage/staff-org-ai.js.map +1 -1
- package/src/lib/usage-axes.d.ts +49 -0
- package/src/lib/usage-axes.js +108 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/runtime/seo-page-facts.d.ts +0 -61
- package/src/lib/runtime/seo-page-facts.js +0 -150
- package/src/lib/runtime/seo-page-facts.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-allotments.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n assistCreditsFromUsd,\n resolveAssistCreditBudget,\n} from '@aglyn/aglyn/app-utils/assist-credits'\nimport { hostRoleFor, isOrgWideMember } from '@aglyn/aglyn/app-utils/organizations'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { invalidIdTokenResponse } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { logAiAllotmentChanged } from '../activity/ai-activity'\nimport {\n AI_ALLOTMENT_MAX_CREDITS,\n AI_ALLOTMENT_MAX_MODELS,\n aiAllotmentCredits,\n parseAiAllotmentSubject,\n type AiAllotment,\n type AiAllotmentMode,\n type AiAllotmentSubject,\n} from '../model/ai-allotments'\nimport { aiUsageByUserMonthFrom, aiUsageMonthKeys } from '../model/ai-usage-by-user'\nimport { AI_MODEL_CATALOG } from '../providers/catalog'\nimport { AI_MODEL_RESTRICTION_PLANS } from '../providers/model-choice'\nimport { resolveAiProvider } from '../providers/routing'\nimport {\n AI_HOST_CREDITS_FIELD,\n applyAiAllotmentWrites,\n listAiAllotments,\n seedAiHostMonthCredits,\n type AiAllotmentWrite,\n} from '../usage/ai-allotments'\nimport { userAiUsageMonthRef } from '../usage/ai-usage-by-user'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n AiAllotmentsWire,\n} from '../usage/ai-usage-wire'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\n\n// lockdown-423: exempt — a spend control, the posture of ai/billing/overage\n// beside it: a billing-locked workspace must still be able to LOWER what its\n// people may spend, and a 423 would trap it spending.\n\n/**\n * AI ALLOTMENTS (AGL-2942) — read and set them.\n *\n * ## Who may read\n *\n * - The whole org's allotments (`orgId`): `billing.view`, the Usage page's\n * gate — every allotment is a figure about the pool it is part of.\n * - One site's (`hostId`): the same, or the site's admin when they are a\n * collaborator scoped to it — the collaborators card is theirs.\n * - One person's (`uid`): the same, or the person themselves.\n *\n * ## Who may write\n *\n * - A member's, a site's and the org-wide model restriction:\n * `billing.manage`. Each moves how much of the paid pool someone may\n * spend.\n * - A collaborator's on one site: `billing.manage`, or the admin of that\n * site when they are a collaborator scoped to it — never on their own\n * allotment, which they could otherwise raise.\n *\n * Staff pass, as at the other org routes. The org-wide restriction is set\n * only on a plan that offers it (`AI_MODEL_RESTRICTION_PLANS`); clearing it\n * is always allowed.\n *\n * ## One write, many subjects\n *\n * `POST { orgId, set, remove, everySite }`: `set` replaces allotments,\n * `remove` deletes them, and `everySite` sets the same allotment on every\n * site the org has — \"same for every client site\" — expanded here, from the\n * org's sites, rather than trusted from a client's list.\n */\n\nconst json = (body: unknown, status: number) => Response.json(body, { status })\n\n/** Batch writes a request may make; Firestore commits at most 500. */\nconst MAX_WRITES = 450\n\n/** Sites one `everySite` expands to. */\nconst MAX_SITES = 400\n\n/** Member months one `getAll` carries. */\nconst GET_ALL_CHUNK = 100\n\ninterface Caller {\n uid: string\n email: string | null\n staff: boolean\n member: AglynOrgMember | null\n}\n\nasync function authenticate(\n headers: Partial<Record<string, string>>,\n): Promise<Response | { decoded: { uid: string; email?: string; staff: boolean } }> {\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return json({ error: 'Unauthenticated' }, 401)\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n return { decoded: { uid: decoded.uid, email: decoded.email, staff: decoded['staff'] === true } }\n}\n\n/** A collaborator scoped to the site who holds its admin role there. */\nfunction isSiteAdminCollaborator(member: AglynOrgMember | null, hostId: string): boolean {\n if (!member || !hostId || isOrgWideMember(member)) return false\n return hostRoleFor(member, hostId) === 'admin'\n}\n\ninterface RosterMonth {\n member: AglynOrgMember\n credits: number\n byHost: Record<string, number>\n}\n\n/** The roster and each member's month, by path — no index, one getAll a hundred. */\nasync function readRosterMonths(\n firestore: FirebaseFirestore.Firestore,\n orgRef: FirebaseFirestore.DocumentReference,\n month: string,\n): Promise<RosterMonth[]> {\n const roster = await orgRef.collection('members').get()\n const members = roster.docs.map(\n (doc) => ({ $id: doc.id, ...doc.data() }) as AglynOrgMember,\n )\n const out: RosterMonth[] = []\n for (let start = 0; start < members.length; start += GET_ALL_CHUNK) {\n const chunk = members.slice(start, start + GET_ALL_CHUNK)\n const snapshots = chunk.length\n ? await firestore.getAll(\n ...chunk.map((member) => userAiUsageMonthRef(orgRef, member.$id, month)),\n )\n : []\n snapshots.forEach((snapshot, index) => {\n const member = chunk[index]\n const usage = aiUsageByUserMonthFrom(\n snapshot.exists ? (snapshot.data() as Record<string, unknown>) : null,\n member.$id,\n month,\n )\n out.push({ member, credits: usage.credits, byHost: usage.byHost })\n })\n }\n return out\n}\n\nconst memberName = (member: AglynOrgMember): string =>\n String(member.displayName ?? '').trim() || String(member.email ?? '').trim() || member.$id\n\n/** A site's credits this month: the org map, else its people's months summed. */\nfunction siteCredits(\n orgMonth: FirebaseFirestore.DocumentSnapshot,\n roster: readonly RosterMonth[],\n hostId: string,\n): number {\n const map = (orgMonth.exists ? orgMonth.get(AI_HOST_CREDITS_FIELD) : null) as\n | Record<string, unknown>\n | null\n if (map && map[hostId] !== undefined) {\n const value = Number(map[hostId])\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0\n }\n return roster.reduce((sum, entry) => sum + (entry.byHost[hostId] ?? 0), 0)\n}\n\nasync function handleGet(request: Request): Promise<Response> {\n const { query, headers } = await pluginRequestFromWeb(request)\n const params = (query ?? {}) as Record<string, unknown>\n const hostId = String(params['hostId'] ?? '').trim()\n const subjectUid = String(params['uid'] ?? '').trim()\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const orgId =\n String(params['orgId'] ?? '').trim() || (hostId ? ((await resolveOrgIdForHost(hostId)) ?? '') : '')\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const member = membership?.member ?? null\n if (!member && !decoded.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n decoded.staff || (await memberHasOrgPermission(orgId, member, 'billing.manage'))\n const canView =\n canManage || (await memberHasOrgPermission(orgId, member, 'billing.view'))\n const siteAdmin = isSiteAdminCollaborator(member, hostId)\n const allowed = subjectUid\n ? canView || subjectUid === decoded.uid || siteAdmin\n : hostId\n ? canView || siteAdmin\n : canView\n if (!allowed) return json({ error: 'billing.view required' }, 403)\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const month = aiUsageMonthKeys()[0]\n const [orgSnapshot, orgMonth, allotments, roster, hostDocs] = await Promise.all([\n orgRef.get(),\n orgRef.collection('assistUsage').doc(month).get(),\n listAiAllotments(firestore, orgId, subjectUid ? { uid: subjectUid } : hostId ? { hostId } : {}),\n readRosterMonths(firestore, orgRef, month),\n hostId\n ? firestore.collection('hosts').doc(hostId).get().then((doc) => (doc.exists ? [doc] : []))\n : firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get().then((snapshot) => snapshot.docs),\n ])\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n const byUid = new Map(roster.map((entry) => [entry.member.$id, entry]))\n\n const used = (allotment: AiAllotment): number => {\n const person = allotment.uid ? byUid.get(allotment.uid) : undefined\n switch (allotment.scope) {\n case 'member':\n return person?.credits ?? 0\n case 'collab':\n return allotment.hostId ? (person?.byHost[allotment.hostId] ?? 0) : 0\n case 'host':\n return allotment.hostId ? siteCredits(orgMonth, roster, allotment.hostId) : 0\n default:\n return 0\n }\n }\n const rows: AiAllotmentRowWire[] = allotments\n // A collaborator reading their own sees their own rows and nothing else.\n .filter((allotment) => canView || siteAdmin || allotment.uid === decoded.uid)\n .map((allotment) => ({\n subject: allotment.subject,\n scope: allotment.scope,\n uid: allotment.uid,\n hostId: allotment.hostId,\n credits: allotment.credits,\n mode: allotment.mode,\n models: allotment.models,\n used: used(allotment),\n }))\n\n const visibleMembers = roster.filter((entry) =>\n subjectUid\n ? entry.member.$id === subjectUid\n : hostId\n ? hostRoleFor(entry.member, hostId) !== null\n : canView,\n )\n const members: AiAllotmentsMemberWire[] = visibleMembers.map((entry) => ({\n uid: entry.member.$id,\n name: memberName(entry.member),\n email: typeof entry.member.email === 'string' ? entry.member.email : null,\n role: typeof entry.member.role === 'string' ? entry.member.role : null,\n orgWide: isOrgWideMember(entry.member),\n credits: entry.credits,\n byHost: entry.byHost,\n }))\n const hosts: AiAllotmentsHostWire[] = hostDocs.map((doc) => ({\n hostId: doc.id,\n name: String(doc.get('displayName') ?? '').trim() || String(doc.get('subdomain') ?? '').trim() || doc.id,\n credits: siteCredits(orgMonth, roster, doc.id),\n }))\n const provider = resolveAiProvider()\n const wire: AiAllotmentsWire = {\n orgId,\n month,\n pool: {\n used: assistCreditsFromUsd(Number(orgMonth.get('estCostUsd') ?? 0)),\n limit: resolveAssistCreditBudget(org as never),\n },\n allotments: rows,\n members,\n hosts,\n models: AI_MODEL_CATALOG.filter((entry) => !provider || entry.provider === provider.id).map(\n (entry) => ({ id: entry.id, label: entry.label, tier: entry.tier }),\n ),\n restrictionAvailable: AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never)),\n canEdit: {\n billing: canManage,\n collaborators: canManage || siteAdmin,\n },\n callerUid: decoded.uid,\n }\n return Response.json(wire, { status: 200, headers: { 'Cache-Control': 'no-store, private' } })\n}\n\ninterface ParsedWrite {\n subject: AiAllotmentSubject\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nconst CATALOG_IDS = new Set(AI_MODEL_CATALOG.map((entry) => entry.id))\n\n/** One entry of `set`, validated; a string names what is wrong. */\nexport function parseAllotmentWrite(raw: unknown): ParsedWrite | string {\n const entry = (raw ?? {}) as Record<string, unknown>\n const subject = parseAiAllotmentSubject(entry['subject'])\n if (!subject) return 'subject must be member:{uid}, collab:{hostId}:{uid}, host:{hostId} or org'\n let credits: number | null = null\n if (entry['credits'] !== null && entry['credits'] !== undefined) {\n credits = typeof entry['credits'] === 'number' ? aiAllotmentCredits(entry['credits']) : null\n if (credits === null || (entry['credits'] as number) > AI_ALLOTMENT_MAX_CREDITS) {\n return `credits must be a whole number from 1 to ${AI_ALLOTMENT_MAX_CREDITS.toLocaleString('en-US')}`\n }\n }\n const mode = entry['mode'] === undefined ? 'hard' : entry['mode']\n if (mode !== 'hard' && mode !== 'soft') return 'mode must be hard or soft'\n let models: string[] | null = null\n if (entry['models'] !== null && entry['models'] !== undefined) {\n if (!Array.isArray(entry['models'])) return 'models must be a list of model ids'\n const ids = [...new Set(entry['models'].map((id) => String(id ?? '').trim()))].filter(Boolean)\n if (ids.length > AI_ALLOTMENT_MAX_MODELS) return `name at most ${AI_ALLOTMENT_MAX_MODELS} models`\n const unknown = ids.find((id) => !CATALOG_IDS.has(id))\n if (unknown) return `${unknown} is not a model this platform offers`\n models = ids.length ? ids : null\n }\n if (subject.scope === 'org' && credits !== null) {\n return 'the org-wide restriction carries models, not credits'\n }\n if (subject.scope !== 'org' && credits === null && models === null) {\n return 'set credits or models, or remove the allotment'\n }\n return { subject, credits, mode, models }\n}\n\nasync function handlePost(request: Request): Promise<Response> {\n const { body, headers } = await pluginRequestFromWeb(request)\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const payload = (body ?? {}) as Record<string, unknown>\n const orgId = String(payload['orgId'] ?? '').trim()\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const rawSet = Array.isArray(payload['set']) ? payload['set'] : []\n const rawRemove = Array.isArray(payload['remove']) ? payload['remove'] : []\n const rawEverySite = payload['everySite'] ?? null\n if (!rawSet.length && !rawRemove.length && !rawEverySite) {\n return json({ error: 'Nothing to change' }, 400)\n }\n if (rawSet.length + rawRemove.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n\n const writes: ParsedWrite[] = []\n for (const raw of rawSet) {\n const parsed = parseAllotmentWrite(raw)\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n writes.push(parsed)\n }\n const removals: AiAllotmentSubject[] = []\n for (const raw of rawRemove) {\n const subject = parseAiAllotmentSubject(raw)\n if (!subject) return json({ error: `${String(raw)} is not an allotment subject` }, 400)\n removals.push(subject)\n }\n let everySite: Omit<ParsedWrite, 'subject'> | null = null\n if (rawEverySite) {\n const parsed = parseAllotmentWrite({ ...(rawEverySite as object), subject: 'host:every-site' })\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n everySite = { credits: parsed.credits, mode: parsed.mode, models: parsed.models }\n }\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const caller: Caller = {\n uid: decoded.uid,\n email: decoded.email ?? null,\n staff: decoded.staff,\n member: membership?.member ?? null,\n }\n if (!caller.member && !caller.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n caller.staff || (await memberHasOrgPermission(orgId, caller.member, 'billing.manage'))\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const orgSnapshot = await orgRef.get()\n if (!orgSnapshot.exists) return json({ error: 'Unknown organization' }, 404)\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n\n // Every subject the request touches, set or removed, is decided the same\n // way: the permission first, then that the person or site belongs here.\n const touched = [...writes.map((write) => write.subject), ...removals]\n if (everySite && !canManage) return json({ error: 'billing.manage required' }, 403)\n const memberDocs = new Map<string, AglynOrgMember | null>()\n const memberOf = async (uid: string): Promise<AglynOrgMember | null> => {\n if (!memberDocs.has(uid)) {\n const snapshot = await orgRef.collection('members').doc(uid).get()\n memberDocs.set(uid, snapshot.exists ? ({ $id: uid, ...snapshot.data() } as AglynOrgMember) : null)\n }\n return memberDocs.get(uid) ?? null\n }\n const hostNames = new Map<string, string>()\n for (const subject of touched) {\n if (subject.scope === 'collab') {\n const siteAdmin = isSiteAdminCollaborator(caller.member, subject.hostId ?? '')\n if (!canManage && !siteAdmin) return json({ error: 'billing.manage required' }, 403)\n if (!canManage && subject.uid === caller.uid) {\n return json({ error: 'You cannot change your own AI allotment', code: 'own_allotment' }, 403)\n }\n const target = await memberOf(subject.uid ?? '')\n if (!target || hostRoleFor(target, subject.hostId ?? '') === null) {\n return json({ error: 'That person is not a collaborator on that site' }, 404)\n }\n // A site's admin sets allotments for the site's COLLABORATORS: a team\n // member's AI is the organization's to bound, not one site's.\n if (!canManage && isOrgWideMember(target)) {\n return json({ error: 'billing.manage required' }, 403)\n }\n } else if (!canManage) {\n return json({ error: 'billing.manage required' }, 403)\n } else if (subject.scope === 'member') {\n if (!(await memberOf(subject.uid ?? ''))) {\n return json({ error: 'That person is not a member of this organization' }, 404)\n }\n } else if (subject.scope === 'host') {\n const host = await firestore.collection('hosts').doc(subject.hostId ?? '').get()\n if (!host.exists || host.get('orgId') !== orgId) {\n return json({ error: 'That site is not part of this organization' }, 404)\n }\n hostNames.set(host.id, String(host.get('displayName') ?? '').trim() || host.id)\n }\n }\n const restricting = writes.some((write) => write.subject.scope === 'org' && write.models)\n if (restricting && !AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never))) {\n return json(\n {\n error: 'Restricting models for the whole organization is available on Agency and Enterprise.',\n code: 'not_available',\n },\n 409,\n )\n }\n\n const set: AiAllotmentWrite[] = [...writes]\n if (everySite) {\n const sites = await firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get()\n for (const site of sites.docs) {\n hostNames.set(site.id, String(site.get('displayName') ?? '').trim() || site.id)\n const subject = parseAiAllotmentSubject(`host:${site.id}`)\n if (subject) set.push({ subject, ...everySite })\n }\n if (set.length + removals.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n }\n\n await applyAiAllotmentWrites(\n firestore,\n orgId,\n { set, remove: removals.map((subject) => subject.id) },\n caller.uid,\n )\n\n // A site allotment set in a month the site's map has no key for yet is\n // given the month so far from its people's own months, once.\n const month = aiUsageMonthKeys()[0]\n const siteIds = set\n .filter((write) => write.subject.scope === 'host' && write.credits !== null)\n .map((write) => write.subject.hostId as string)\n if (siteIds.length) {\n const roster = await readRosterMonths(firestore, orgRef, month)\n await seedAiHostMonthCredits(firestore, orgId, siteIds, month, (siteId) =>\n roster.reduce((sum, entry) => sum + (entry.byHost[siteId] ?? 0), 0),\n ).catch((error) => console.error('[ai/allotments] site month seed failed', orgId, error))\n }\n\n void addAdminAudit(firestore, {\n actorUid: caller.uid,\n actorEmail: caller.email,\n action: 'ai.allotments.set',\n target: `orgs/${orgId}/aiAllotments`,\n before: null,\n after: {\n set: set.map((write) => ({ subject: write.subject.id, credits: write.credits, mode: write.mode, models: write.models })),\n removed: removals.map((subject) => subject.id),\n },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n\n const actor = { uid: caller.uid, email: caller.email }\n const feedRow = (subject: AiAllotmentSubject) => ({\n type: subject.scope === 'host' ? ('host' as const) : subject.scope === 'org' ? ('org' as const) : ('member' as const),\n id: subject.scope === 'host' ? subject.hostId : subject.uid,\n name:\n subject.scope === 'host'\n ? hostNames.get(subject.hostId ?? '') ?? subject.hostId\n : subject.scope === 'org'\n ? 'Organization model restriction'\n : [\n memberDocs.get(subject.uid ?? '') ? memberName(memberDocs.get(subject.uid ?? '') as AglynOrgMember) : subject.uid,\n subject.scope === 'collab' ? `on ${subject.hostId}` : null,\n ]\n .filter(Boolean)\n .join(' '),\n })\n await Promise.all([\n ...set.map((write) =>\n logAiAllotmentChanged(orgId, actor, {\n subject: feedRow(write.subject),\n after: { credits: write.credits, mode: write.mode, models: write.models },\n }).catch(() => undefined),\n ),\n ...removals.map((subject) =>\n logAiAllotmentChanged(orgId, actor, { subject: feedRow(subject), after: null }).catch(\n () => undefined,\n ),\n ),\n ])\n\n return json({ ok: true, set: set.length, removed: removals.length }, 200)\n}\n\nasync function handler(request: Request): Promise<Response> {\n try {\n if (request.method === 'GET') return await handleGet(request)\n if (request.method === 'POST') return await handlePost(request)\n return json({ error: 'Method not allowed' }, 405)\n } catch (error) {\n // A refused credential is a 401, not a fault of ours (AGL-1993).\n const unauthenticated = invalidIdTokenResponse(error)\n if (unauthenticated) return unauthenticated\n console.error('[ai/allotments] failed', error)\n return json({ error: 'AI allotments unavailable' }, 500)\n }\n}\n\nexport { handler as GET, handler as POST }\n"],"names":["pluginRequestFromWeb","assistCreditsFromUsd","resolveAssistCreditBudget","hostRoleFor","isOrgWideMember","resolveEffectivePlan","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","memberHasOrgPermission","resolveOrgMembership","invalidIdTokenResponse","resolveOrgIdForHost","FieldValue","logAiAllotmentChanged","AI_ALLOTMENT_MAX_CREDITS","AI_ALLOTMENT_MAX_MODELS","aiAllotmentCredits","parseAiAllotmentSubject","aiUsageByUserMonthFrom","aiUsageMonthKeys","AI_MODEL_CATALOG","AI_MODEL_RESTRICTION_PLANS","resolveAiProvider","AI_HOST_CREDITS_FIELD","applyAiAllotmentWrites","listAiAllotments","seedAiHostMonthCredits","userAiUsageMonthRef","addAdminAudit","json","body","status","Response","MAX_WRITES","MAX_SITES","GET_ALL_CHUNK","authenticate","headers","authorization","idToken","startsWith","slice","length","undefined","error","decoded","app","auth","verifyIdToken","email_verified","uid","email","staff","isSiteAdminCollaborator","member","hostId","readRosterMonths","firestore","orgRef","month","roster","collection","get","members","docs","map","doc","$id","id","data","out","start","chunk","snapshots","getAll","forEach","snapshot","index","usage","exists","push","credits","byHost","memberName","String","displayName","trim","siteCredits","orgMonth","value","Number","isFinite","Math","floor","reduce","sum","entry","handleGet","request","params","orgSnapshot","query","subjectUid","orgId","membership","canManage","canView","siteAdmin","allowed","allotments","hostDocs","Promise","all","then","where","limit","org","byUid","Map","used","allotment","person","scope","rows","filter","subject","mode","models","visibleMembers","name","role","orgWide","hosts","provider","wire","pool","label","tier","restrictionAvailable","includes","canEdit","billing","collaborators","callerUid","CATALOG_IDS","Set","parseAllotmentWrite","raw","toLocaleString","Array","isArray","ids","Boolean","unknown","find","has","handlePost","payload","rawSet","rawRemove","rawEverySite","writes","parsed","code","removals","everySite","caller","touched","write","memberDocs","memberOf","set","hostNames","target","host","restricting","some","sites","site","remove","siteIds","siteId","catch","console","actorUid","actorEmail","action","before","after","removed","at","serverTimestamp","actor","feedRow","type","join","ok","handler","method","unauthenticated","GET","POST"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-allotments.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n assistCreditsFromUsd,\n resolveAssistCreditBudget,\n} from '../usage/assist-credits'\nimport { hostRoleFor, isOrgWideMember } from '@aglyn/aglyn/app-utils/organizations'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n memberHasOrgPermission,\n resolveOrgMembership,\n} from '@aglyn/tenant-data-admin'\nimport { invalidIdTokenResponse } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { logAiAllotmentChanged } from '../activity/ai-activity'\nimport {\n AI_ALLOTMENT_MAX_CREDITS,\n AI_ALLOTMENT_MAX_MODELS,\n aiAllotmentCredits,\n parseAiAllotmentSubject,\n type AiAllotment,\n type AiAllotmentMode,\n type AiAllotmentSubject,\n} from '../model/ai-allotments'\nimport { aiUsageByUserMonthFrom, aiUsageMonthKeys } from '../model/ai-usage-by-user'\nimport { AI_MODEL_CATALOG } from '../providers/catalog'\nimport { AI_MODEL_RESTRICTION_PLANS } from '../providers/model-choice'\nimport { resolveAiProvider } from '../providers/routing'\nimport {\n AI_HOST_CREDITS_FIELD,\n applyAiAllotmentWrites,\n listAiAllotments,\n seedAiHostMonthCredits,\n type AiAllotmentWrite,\n} from '../usage/ai-allotments'\nimport { userAiUsageMonthRef } from '../usage/ai-usage-by-user'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n AiAllotmentsWire,\n} from '../usage/ai-usage-wire'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\n\n// lockdown-423: exempt — a spend control, the posture of ai/billing/overage\n// beside it: a billing-locked workspace must still be able to LOWER what its\n// people may spend, and a 423 would trap it spending.\n\n/**\n * AI ALLOTMENTS (AGL-2942) — read and set them.\n *\n * ## Who may read\n *\n * - The whole org's allotments (`orgId`): `billing.view`, the Usage page's\n * gate — every allotment is a figure about the pool it is part of.\n * - One site's (`hostId`): the same, or the site's admin when they are a\n * collaborator scoped to it — the collaborators card is theirs.\n * - One person's (`uid`): the same, or the person themselves.\n *\n * ## Who may write\n *\n * - A member's, a site's and the org-wide model restriction:\n * `billing.manage`. Each moves how much of the paid pool someone may\n * spend.\n * - A collaborator's on one site: `billing.manage`, or the admin of that\n * site when they are a collaborator scoped to it — never on their own\n * allotment, which they could otherwise raise.\n *\n * Staff pass, as at the other org routes. The org-wide restriction is set\n * only on a plan that offers it (`AI_MODEL_RESTRICTION_PLANS`); clearing it\n * is always allowed.\n *\n * ## One write, many subjects\n *\n * `POST { orgId, set, remove, everySite }`: `set` replaces allotments,\n * `remove` deletes them, and `everySite` sets the same allotment on every\n * site the org has — \"same for every client site\" — expanded here, from the\n * org's sites, rather than trusted from a client's list.\n */\n\nconst json = (body: unknown, status: number) => Response.json(body, { status })\n\n/** Batch writes a request may make; Firestore commits at most 500. */\nconst MAX_WRITES = 450\n\n/** Sites one `everySite` expands to. */\nconst MAX_SITES = 400\n\n/** Member months one `getAll` carries. */\nconst GET_ALL_CHUNK = 100\n\ninterface Caller {\n uid: string\n email: string | null\n staff: boolean\n member: AglynOrgMember | null\n}\n\nasync function authenticate(\n headers: Partial<Record<string, string>>,\n): Promise<Response | { decoded: { uid: string; email?: string; staff: boolean } }> {\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return json({ error: 'Unauthenticated' }, 401)\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n return { decoded: { uid: decoded.uid, email: decoded.email, staff: decoded['staff'] === true } }\n}\n\n/** A collaborator scoped to the site who holds its admin role there. */\nfunction isSiteAdminCollaborator(member: AglynOrgMember | null, hostId: string): boolean {\n if (!member || !hostId || isOrgWideMember(member)) return false\n return hostRoleFor(member, hostId) === 'admin'\n}\n\ninterface RosterMonth {\n member: AglynOrgMember\n credits: number\n byHost: Record<string, number>\n}\n\n/** The roster and each member's month, by path — no index, one getAll a hundred. */\nasync function readRosterMonths(\n firestore: FirebaseFirestore.Firestore,\n orgRef: FirebaseFirestore.DocumentReference,\n month: string,\n): Promise<RosterMonth[]> {\n const roster = await orgRef.collection('members').get()\n const members = roster.docs.map(\n (doc) => ({ $id: doc.id, ...doc.data() }) as AglynOrgMember,\n )\n const out: RosterMonth[] = []\n for (let start = 0; start < members.length; start += GET_ALL_CHUNK) {\n const chunk = members.slice(start, start + GET_ALL_CHUNK)\n const snapshots = chunk.length\n ? await firestore.getAll(\n ...chunk.map((member) => userAiUsageMonthRef(orgRef, member.$id, month)),\n )\n : []\n snapshots.forEach((snapshot, index) => {\n const member = chunk[index]\n const usage = aiUsageByUserMonthFrom(\n snapshot.exists ? (snapshot.data() as Record<string, unknown>) : null,\n member.$id,\n month,\n )\n out.push({ member, credits: usage.credits, byHost: usage.byHost })\n })\n }\n return out\n}\n\nconst memberName = (member: AglynOrgMember): string =>\n String(member.displayName ?? '').trim() || String(member.email ?? '').trim() || member.$id\n\n/** A site's credits this month: the org map, else its people's months summed. */\nfunction siteCredits(\n orgMonth: FirebaseFirestore.DocumentSnapshot,\n roster: readonly RosterMonth[],\n hostId: string,\n): number {\n const map = (orgMonth.exists ? orgMonth.get(AI_HOST_CREDITS_FIELD) : null) as\n | Record<string, unknown>\n | null\n if (map && map[hostId] !== undefined) {\n const value = Number(map[hostId])\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0\n }\n return roster.reduce((sum, entry) => sum + (entry.byHost[hostId] ?? 0), 0)\n}\n\nasync function handleGet(request: Request): Promise<Response> {\n const { query, headers } = await pluginRequestFromWeb(request)\n const params = (query ?? {}) as Record<string, unknown>\n const hostId = String(params['hostId'] ?? '').trim()\n const subjectUid = String(params['uid'] ?? '').trim()\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const orgId =\n String(params['orgId'] ?? '').trim() || (hostId ? ((await resolveOrgIdForHost(hostId)) ?? '') : '')\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const member = membership?.member ?? null\n if (!member && !decoded.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n decoded.staff || (await memberHasOrgPermission(orgId, member, 'billing.manage'))\n const canView =\n canManage || (await memberHasOrgPermission(orgId, member, 'billing.view'))\n const siteAdmin = isSiteAdminCollaborator(member, hostId)\n const allowed = subjectUid\n ? canView || subjectUid === decoded.uid || siteAdmin\n : hostId\n ? canView || siteAdmin\n : canView\n if (!allowed) return json({ error: 'billing.view required' }, 403)\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const month = aiUsageMonthKeys()[0]\n const [orgSnapshot, orgMonth, allotments, roster, hostDocs] = await Promise.all([\n orgRef.get(),\n orgRef.collection('assistUsage').doc(month).get(),\n listAiAllotments(firestore, orgId, subjectUid ? { uid: subjectUid } : hostId ? { hostId } : {}),\n readRosterMonths(firestore, orgRef, month),\n hostId\n ? firestore.collection('hosts').doc(hostId).get().then((doc) => (doc.exists ? [doc] : []))\n : firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get().then((snapshot) => snapshot.docs),\n ])\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n const byUid = new Map(roster.map((entry) => [entry.member.$id, entry]))\n\n const used = (allotment: AiAllotment): number => {\n const person = allotment.uid ? byUid.get(allotment.uid) : undefined\n switch (allotment.scope) {\n case 'member':\n return person?.credits ?? 0\n case 'collab':\n return allotment.hostId ? (person?.byHost[allotment.hostId] ?? 0) : 0\n case 'host':\n return allotment.hostId ? siteCredits(orgMonth, roster, allotment.hostId) : 0\n default:\n return 0\n }\n }\n const rows: AiAllotmentRowWire[] = allotments\n // A collaborator reading their own sees their own rows and nothing else.\n .filter((allotment) => canView || siteAdmin || allotment.uid === decoded.uid)\n .map((allotment) => ({\n subject: allotment.subject,\n scope: allotment.scope,\n uid: allotment.uid,\n hostId: allotment.hostId,\n credits: allotment.credits,\n mode: allotment.mode,\n models: allotment.models,\n used: used(allotment),\n }))\n\n const visibleMembers = roster.filter((entry) =>\n subjectUid\n ? entry.member.$id === subjectUid\n : hostId\n ? hostRoleFor(entry.member, hostId) !== null\n : canView,\n )\n const members: AiAllotmentsMemberWire[] = visibleMembers.map((entry) => ({\n uid: entry.member.$id,\n name: memberName(entry.member),\n email: typeof entry.member.email === 'string' ? entry.member.email : null,\n role: typeof entry.member.role === 'string' ? entry.member.role : null,\n orgWide: isOrgWideMember(entry.member),\n credits: entry.credits,\n byHost: entry.byHost,\n }))\n const hosts: AiAllotmentsHostWire[] = hostDocs.map((doc) => ({\n hostId: doc.id,\n name: String(doc.get('displayName') ?? '').trim() || String(doc.get('subdomain') ?? '').trim() || doc.id,\n credits: siteCredits(orgMonth, roster, doc.id),\n }))\n const provider = resolveAiProvider()\n const wire: AiAllotmentsWire = {\n orgId,\n month,\n pool: {\n used: assistCreditsFromUsd(Number(orgMonth.get('estCostUsd') ?? 0)),\n limit: resolveAssistCreditBudget(org as never),\n },\n allotments: rows,\n members,\n hosts,\n models: AI_MODEL_CATALOG.filter((entry) => !provider || entry.provider === provider.id).map(\n (entry) => ({ id: entry.id, label: entry.label, tier: entry.tier }),\n ),\n restrictionAvailable: AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never)),\n canEdit: {\n billing: canManage,\n collaborators: canManage || siteAdmin,\n },\n callerUid: decoded.uid,\n }\n return Response.json(wire, { status: 200, headers: { 'Cache-Control': 'no-store, private' } })\n}\n\ninterface ParsedWrite {\n subject: AiAllotmentSubject\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nconst CATALOG_IDS = new Set(AI_MODEL_CATALOG.map((entry) => entry.id))\n\n/** One entry of `set`, validated; a string names what is wrong. */\nexport function parseAllotmentWrite(raw: unknown): ParsedWrite | string {\n const entry = (raw ?? {}) as Record<string, unknown>\n const subject = parseAiAllotmentSubject(entry['subject'])\n if (!subject) return 'subject must be member:{uid}, collab:{hostId}:{uid}, host:{hostId} or org'\n let credits: number | null = null\n if (entry['credits'] !== null && entry['credits'] !== undefined) {\n credits = typeof entry['credits'] === 'number' ? aiAllotmentCredits(entry['credits']) : null\n if (credits === null || (entry['credits'] as number) > AI_ALLOTMENT_MAX_CREDITS) {\n return `credits must be a whole number from 1 to ${AI_ALLOTMENT_MAX_CREDITS.toLocaleString('en-US')}`\n }\n }\n const mode = entry['mode'] === undefined ? 'hard' : entry['mode']\n if (mode !== 'hard' && mode !== 'soft') return 'mode must be hard or soft'\n let models: string[] | null = null\n if (entry['models'] !== null && entry['models'] !== undefined) {\n if (!Array.isArray(entry['models'])) return 'models must be a list of model ids'\n const ids = [...new Set(entry['models'].map((id) => String(id ?? '').trim()))].filter(Boolean)\n if (ids.length > AI_ALLOTMENT_MAX_MODELS) return `name at most ${AI_ALLOTMENT_MAX_MODELS} models`\n const unknown = ids.find((id) => !CATALOG_IDS.has(id))\n if (unknown) return `${unknown} is not a model this platform offers`\n models = ids.length ? ids : null\n }\n if (subject.scope === 'org' && credits !== null) {\n return 'the org-wide restriction carries models, not credits'\n }\n if (subject.scope !== 'org' && credits === null && models === null) {\n return 'set credits or models, or remove the allotment'\n }\n return { subject, credits, mode, models }\n}\n\nasync function handlePost(request: Request): Promise<Response> {\n const { body, headers } = await pluginRequestFromWeb(request)\n const auth = await authenticate(headers as Partial<Record<string, string>>)\n if (auth instanceof Response) return auth\n const { decoded } = auth\n const payload = (body ?? {}) as Record<string, unknown>\n const orgId = String(payload['orgId'] ?? '').trim()\n if (!orgId) return json({ error: 'Missing orgId' }, 400)\n\n const rawSet = Array.isArray(payload['set']) ? payload['set'] : []\n const rawRemove = Array.isArray(payload['remove']) ? payload['remove'] : []\n const rawEverySite = payload['everySite'] ?? null\n if (!rawSet.length && !rawRemove.length && !rawEverySite) {\n return json({ error: 'Nothing to change' }, 400)\n }\n if (rawSet.length + rawRemove.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n\n const writes: ParsedWrite[] = []\n for (const raw of rawSet) {\n const parsed = parseAllotmentWrite(raw)\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n writes.push(parsed)\n }\n const removals: AiAllotmentSubject[] = []\n for (const raw of rawRemove) {\n const subject = parseAiAllotmentSubject(raw)\n if (!subject) return json({ error: `${String(raw)} is not an allotment subject` }, 400)\n removals.push(subject)\n }\n let everySite: Omit<ParsedWrite, 'subject'> | null = null\n if (rawEverySite) {\n const parsed = parseAllotmentWrite({ ...(rawEverySite as object), subject: 'host:every-site' })\n if (typeof parsed === 'string') return json({ error: parsed, code: 'invalid_allotment' }, 400)\n everySite = { credits: parsed.credits, mode: parsed.mode, models: parsed.models }\n }\n\n const membership = await resolveOrgMembership(decoded.uid, orgId)\n const caller: Caller = {\n uid: decoded.uid,\n email: decoded.email ?? null,\n staff: decoded.staff,\n member: membership?.member ?? null,\n }\n if (!caller.member && !caller.staff) return json({ error: 'Not found' }, 404)\n const canManage =\n caller.staff || (await memberHasOrgPermission(orgId, caller.member, 'billing.manage'))\n\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n const orgSnapshot = await orgRef.get()\n if (!orgSnapshot.exists) return json({ error: 'Unknown organization' }, 404)\n const org = (orgSnapshot.data() ?? {}) as Record<string, unknown>\n\n // Every subject the request touches, set or removed, is decided the same\n // way: the permission first, then that the person or site belongs here.\n const touched = [...writes.map((write) => write.subject), ...removals]\n if (everySite && !canManage) return json({ error: 'billing.manage required' }, 403)\n const memberDocs = new Map<string, AglynOrgMember | null>()\n const memberOf = async (uid: string): Promise<AglynOrgMember | null> => {\n if (!memberDocs.has(uid)) {\n const snapshot = await orgRef.collection('members').doc(uid).get()\n memberDocs.set(uid, snapshot.exists ? ({ $id: uid, ...snapshot.data() } as AglynOrgMember) : null)\n }\n return memberDocs.get(uid) ?? null\n }\n const hostNames = new Map<string, string>()\n for (const subject of touched) {\n if (subject.scope === 'collab') {\n const siteAdmin = isSiteAdminCollaborator(caller.member, subject.hostId ?? '')\n if (!canManage && !siteAdmin) return json({ error: 'billing.manage required' }, 403)\n if (!canManage && subject.uid === caller.uid) {\n return json({ error: 'You cannot change your own AI allotment', code: 'own_allotment' }, 403)\n }\n const target = await memberOf(subject.uid ?? '')\n if (!target || hostRoleFor(target, subject.hostId ?? '') === null) {\n return json({ error: 'That person is not a collaborator on that site' }, 404)\n }\n // A site's admin sets allotments for the site's COLLABORATORS: a team\n // member's AI is the organization's to bound, not one site's.\n if (!canManage && isOrgWideMember(target)) {\n return json({ error: 'billing.manage required' }, 403)\n }\n } else if (!canManage) {\n return json({ error: 'billing.manage required' }, 403)\n } else if (subject.scope === 'member') {\n if (!(await memberOf(subject.uid ?? ''))) {\n return json({ error: 'That person is not a member of this organization' }, 404)\n }\n } else if (subject.scope === 'host') {\n const host = await firestore.collection('hosts').doc(subject.hostId ?? '').get()\n if (!host.exists || host.get('orgId') !== orgId) {\n return json({ error: 'That site is not part of this organization' }, 404)\n }\n hostNames.set(host.id, String(host.get('displayName') ?? '').trim() || host.id)\n }\n }\n const restricting = writes.some((write) => write.subject.scope === 'org' && write.models)\n if (restricting && !AI_MODEL_RESTRICTION_PLANS.includes(resolveEffectivePlan(org as never))) {\n return json(\n {\n error: 'Restricting models for the whole organization is available on Agency and Enterprise.',\n code: 'not_available',\n },\n 409,\n )\n }\n\n const set: AiAllotmentWrite[] = [...writes]\n if (everySite) {\n const sites = await firestore.collection('hosts').where('orgId', '==', orgId).limit(MAX_SITES).get()\n for (const site of sites.docs) {\n hostNames.set(site.id, String(site.get('displayName') ?? '').trim() || site.id)\n const subject = parseAiAllotmentSubject(`host:${site.id}`)\n if (subject) set.push({ subject, ...everySite })\n }\n if (set.length + removals.length > MAX_WRITES) {\n return json({ error: `Change at most ${MAX_WRITES} allotments at once` }, 400)\n }\n }\n\n await applyAiAllotmentWrites(\n firestore,\n orgId,\n { set, remove: removals.map((subject) => subject.id) },\n caller.uid,\n )\n\n // A site allotment set in a month the site's map has no key for yet is\n // given the month so far from its people's own months, once.\n const month = aiUsageMonthKeys()[0]\n const siteIds = set\n .filter((write) => write.subject.scope === 'host' && write.credits !== null)\n .map((write) => write.subject.hostId as string)\n if (siteIds.length) {\n const roster = await readRosterMonths(firestore, orgRef, month)\n await seedAiHostMonthCredits(firestore, orgId, siteIds, month, (siteId) =>\n roster.reduce((sum, entry) => sum + (entry.byHost[siteId] ?? 0), 0),\n ).catch((error) => console.error('[ai/allotments] site month seed failed', orgId, error))\n }\n\n void addAdminAudit(firestore, {\n actorUid: caller.uid,\n actorEmail: caller.email,\n action: 'ai.allotments.set',\n target: `orgs/${orgId}/aiAllotments`,\n before: null,\n after: {\n set: set.map((write) => ({ subject: write.subject.id, credits: write.credits, mode: write.mode, models: write.models })),\n removed: removals.map((subject) => subject.id),\n },\n at: FieldValue.serverTimestamp(),\n })\n .catch(() => undefined)\n\n const actor = { uid: caller.uid, email: caller.email }\n const feedRow = (subject: AiAllotmentSubject) => ({\n type: subject.scope === 'host' ? ('host' as const) : subject.scope === 'org' ? ('org' as const) : ('member' as const),\n id: subject.scope === 'host' ? subject.hostId : subject.uid,\n name:\n subject.scope === 'host'\n ? hostNames.get(subject.hostId ?? '') ?? subject.hostId\n : subject.scope === 'org'\n ? 'Organization model restriction'\n : [\n memberDocs.get(subject.uid ?? '') ? memberName(memberDocs.get(subject.uid ?? '') as AglynOrgMember) : subject.uid,\n subject.scope === 'collab' ? `on ${subject.hostId}` : null,\n ]\n .filter(Boolean)\n .join(' '),\n })\n await Promise.all([\n ...set.map((write) =>\n logAiAllotmentChanged(orgId, actor, {\n subject: feedRow(write.subject),\n after: { credits: write.credits, mode: write.mode, models: write.models },\n }).catch(() => undefined),\n ),\n ...removals.map((subject) =>\n logAiAllotmentChanged(orgId, actor, { subject: feedRow(subject), after: null }).catch(\n () => undefined,\n ),\n ),\n ])\n\n return json({ ok: true, set: set.length, removed: removals.length }, 200)\n}\n\nasync function handler(request: Request): Promise<Response> {\n try {\n if (request.method === 'GET') return await handleGet(request)\n if (request.method === 'POST') return await handlePost(request)\n return json({ error: 'Method not allowed' }, 405)\n } catch (error) {\n // A refused credential is a 401, not a fault of ours (AGL-1993).\n const unauthenticated = invalidIdTokenResponse(error)\n if (unauthenticated) return unauthenticated\n console.error('[ai/allotments] failed', error)\n return json({ error: 'AI allotments unavailable' }, 500)\n }\n}\n\nexport { handler as GET, handler as POST }\n"],"names":["pluginRequestFromWeb","assistCreditsFromUsd","resolveAssistCreditBudget","hostRoleFor","isOrgWideMember","resolveEffectivePlan","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","memberHasOrgPermission","resolveOrgMembership","invalidIdTokenResponse","resolveOrgIdForHost","FieldValue","logAiAllotmentChanged","AI_ALLOTMENT_MAX_CREDITS","AI_ALLOTMENT_MAX_MODELS","aiAllotmentCredits","parseAiAllotmentSubject","aiUsageByUserMonthFrom","aiUsageMonthKeys","AI_MODEL_CATALOG","AI_MODEL_RESTRICTION_PLANS","resolveAiProvider","AI_HOST_CREDITS_FIELD","applyAiAllotmentWrites","listAiAllotments","seedAiHostMonthCredits","userAiUsageMonthRef","addAdminAudit","json","body","status","Response","MAX_WRITES","MAX_SITES","GET_ALL_CHUNK","authenticate","headers","authorization","idToken","startsWith","slice","length","undefined","error","decoded","app","auth","verifyIdToken","email_verified","uid","email","staff","isSiteAdminCollaborator","member","hostId","readRosterMonths","firestore","orgRef","month","roster","collection","get","members","docs","map","doc","$id","id","data","out","start","chunk","snapshots","getAll","forEach","snapshot","index","usage","exists","push","credits","byHost","memberName","String","displayName","trim","siteCredits","orgMonth","value","Number","isFinite","Math","floor","reduce","sum","entry","handleGet","request","params","orgSnapshot","query","subjectUid","orgId","membership","canManage","canView","siteAdmin","allowed","allotments","hostDocs","Promise","all","then","where","limit","org","byUid","Map","used","allotment","person","scope","rows","filter","subject","mode","models","visibleMembers","name","role","orgWide","hosts","provider","wire","pool","label","tier","restrictionAvailable","includes","canEdit","billing","collaborators","callerUid","CATALOG_IDS","Set","parseAllotmentWrite","raw","toLocaleString","Array","isArray","ids","Boolean","unknown","find","has","handlePost","payload","rawSet","rawRemove","rawEverySite","writes","parsed","code","removals","everySite","caller","touched","write","memberDocs","memberOf","set","hostNames","target","host","restricting","some","sites","site","remove","siteIds","siteId","catch","console","actorUid","actorEmail","action","before","after","removed","at","serverTimestamp","actor","feedRow","type","join","ok","handler","method","unauthenticated","GET","POST"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/ import {
|
|
16
|
+
*/ import { PORTABLE_COMPONENT_ID_ALLOWLIST, sanitizePortableDefinition } from "@aglyn/aglyn/app-utils/node-definition-sanitizer";
|
|
17
17
|
import { AI_PALETTE } from "../runtime/ai-palette.generated.js";
|
|
18
18
|
import { AI_ACCEPTABLE_USE_BLOCK } from "../runtime/ai-runtime.js";
|
|
19
19
|
/** The mode prompts. Static text: no byte of any workspace is in one. */ const MODE_PROMPT = {
|
|
@@ -57,7 +57,7 @@ const strictObject = (properties)=>({
|
|
|
57
57
|
componentId: {
|
|
58
58
|
type: 'string',
|
|
59
59
|
enum: [
|
|
60
|
-
...
|
|
60
|
+
...PORTABLE_COMPONENT_ID_ALLOWLIST
|
|
61
61
|
]
|
|
62
62
|
},
|
|
63
63
|
parentId: STRING,
|
|
@@ -189,7 +189,7 @@ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.
|
|
|
189
189
|
status: 'unreadable',
|
|
190
190
|
error: 'AI returned invalid JSON'
|
|
191
191
|
};
|
|
192
|
-
const sanitized =
|
|
192
|
+
const sanitized = sanitizePortableDefinition(definition);
|
|
193
193
|
// `=== false`, not `!`: see `AssistSectionRead`.
|
|
194
194
|
if (sanitized.ok === false) return {
|
|
195
195
|
status: 'rejected',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-assist-prompts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n MARKETPLACE_COMPONENT_ID_ALLOWLIST,\n sanitizeMarketplaceDefinition,\n} from '@aglyn/aglyn/app-utils/node-definition-sanitizer'\nimport { AI_PALETTE } from '../runtime/ai-palette.generated'\nimport { AI_ACCEPTABLE_USE_BLOCK, type AiSystemBlock, type AiTool } from '../runtime/ai-runtime'\nimport type { AiResult } from '../providers/contract'\n\n/**\n * The copy assistant's prompts and its section tool (AGL-2937), by their own\n * module so a spec can read them without a Firestore, an ID token or a\n * provider — the handler beside them needs all three.\n *\n * ── Why the section mode answers through a tool ───────────────────────────\n *\n * It asked for \"ONLY a JSON object, no prose or code fences\" and then parsed\n * whatever came back, stripping a fence if it found one. When that parse\n * failed the handler answered 502 and the tokens were already spent: the\n * whole request, prompt and answer, bought nothing, and the member's next\n * move is to press the button again and spend it twice. A strict tool is the\n * cheaper shape for exactly that reason — the answer arrives as validated\n * arguments rather than as text that has to be guessed at — and it is what\n * AGL-2937 asks of every door.\n *\n * The parse stays as a fallback. A provider whose adapter has no tool\n * support, or a model that answers in prose anyway, still gets read rather\n * than refused: the tool is how the answer is asked for, not the only way it\n * can arrive.\n *\n * ── Why the flat node list ────────────────────────────────────────────────\n *\n * A strict schema forbids additional properties, so a map keyed by node id\n * cannot be described in one: every key would have to be a declared\n * property. The tool therefore takes a LIST of nodes, each naming its own id\n * and its children by id, and `readAssistSection` rebuilds the map the\n * besigner consumer already reads. The same reason `assistEditTool` takes\n * name/value pairs rather than a props object.\n *\n * ── Caching ───────────────────────────────────────────────────────────────\n *\n * Each mode's static text carries a breakpoint. None of the three is long\n * enough for its model to cache today — `runtime/ai-prompt-cache.spec.ts`\n * measures all three against their minimums and records the verdict — so the\n * marker is a statement about the block rather than a saving. It costs\n * nothing, it is correct if a mode is ever served by a model with a lower\n * minimum, and the ledger is what says whether it pays.\n */\n\nexport type AiAssistMode = 'element' | 'blog' | 'section'\n\n/** The mode prompts. Static text: no byte of any workspace is in one. */\nconst MODE_PROMPT: Readonly<Record<AiAssistMode, string>> = {\n element:\n 'You write website copy inside a site builder. Reply with ONLY the final text for the element — no quotes, preamble, or markdown. Keep roughly the same length and role as the current text unless the instruction says otherwise.',\n blog:\n 'You write blog posts inside a site builder. Reply with ONLY the post body in markdown-lite: **bold**, *italic*, ## headings, - lists, [links](https://url). No front matter, title line, or preamble — the title renders separately.',\n section:\n 'You design one website section inside a site builder. Answer by calling ' +\n `${'submit_section'} exactly once, with a flat list of nodes: each node ` +\n 'names its own id, the component it is, its parent (empty for the root) ' +\n 'and its children by id, and fills props as name/value pairs. Useful ' +\n 'props — muiStack: direction (row|column), spacing, justifyContent, ' +\n 'alignItems; muiTypography: children, variant (h1..h6, body1, ' +\n 'subtitle1); muiButton: children, variant (contained|outlined), color; ' +\n 'image: src, alt; muiContainer: maxWidth (sm|md|lg). Keep it small: 4-12 ' +\n 'nodes, one root container. Leave image src empty. Write real copy, not ' +\n 'lorem ipsum.',\n}\n\n/**\n * One mode's system blocks: the acceptable-use rules ahead of the mode's own\n * prompt (AGL-2925), as one cached block with no per-org byte in it.\n */\nexport function assistModeSystemBlocks(mode: AiAssistMode): AiSystemBlock[] {\n return [{ text: `${AI_ACCEPTABLE_USE_BLOCK}\\n\\n${MODE_PROMPT[mode]}`, cacheBreakpoint: true }]\n}\n\nexport const ASSIST_SECTION_TOOL_NAME = 'submit_section'\n\nconst STRING = { type: 'string' } as const\n\nconst strictObject = (properties: Record<string, unknown>) => ({\n type: 'object',\n properties,\n required: Object.keys(properties),\n additionalProperties: false,\n})\n\n/**\n * The strict tool a generated section arrives through. `componentId` is an\n * enum of the marketplace allowlist — the same list the sanitizer holds the\n * answer to afterward — so an unpublishable component is refused by the\n * schema before it costs a re-ask.\n */\nexport function assistSectionTool(): AiTool {\n const pair = strictObject({ name: STRING, value: STRING })\n const node = strictObject({\n id: STRING,\n componentId: { type: 'string', enum: [...MARKETPLACE_COMPONENT_ID_ALLOWLIST] },\n parentId: STRING,\n children: { type: 'array', items: STRING },\n props: { type: 'array', items: pair },\n })\n return {\n name: ASSIST_SECTION_TOOL_NAME,\n description:\n 'Submit the section you designed, as a flat list of nodes. The first ' +\n 'node with an empty parentId is the root. The member reviews the ' +\n 'section and places it, or discards it.',\n inputSchema: {\n type: 'object',\n properties: { nodes: { type: 'array', items: node } },\n required: ['nodes'],\n additionalProperties: false,\n },\n strict: true,\n }\n}\n\n/** The sanitized section, in the shape the besigner consumer already reads. */\nexport interface AssistSection {\n rootId: string\n nodes: Record<string, unknown>\n}\n\ninterface RawNode {\n id: string\n componentId: string\n parentId: string\n children: string[]\n props: Array<{ name: string; value: string }>\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * A pair's value in the type the component's own schema declares for that\n * prop, since a strict tool can only carry strings. `children` stays text\n * whatever a schema says: a number in it is copy a visitor reads, not a\n * count.\n */\nfunction propValue(componentId: string, name: string, value: string): unknown {\n if (name === 'children') return value\n const declared = AI_PALETTE[componentId]?.propsSchema?.properties?.[name] as\n | { type?: string }\n | undefined\n const type = declared?.type\n if (type === 'boolean') return value === 'true'\n if (type === 'number' || type === 'integer') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : value\n }\n if (type) return value\n // No schema for this prop: a bare number is read as one, so `spacing: \"2\"`\n // does not reach the canvas as text. The palette's own typing wins above,\n // which is why muiStack's string-typed `spacing` stays a string.\n const parsed = Number(value)\n return value.trim() !== '' && Number.isFinite(parsed) ? parsed : value\n}\n\n/** The flat node list as the stored map: `{ $id, componentId, parentId, props, nodes }`. */\nfunction definitionFromNodes(nodes: readonly RawNode[]): {\n rootId: string\n nodes: Record<string, unknown>\n} {\n const ids = new Set(nodes.map((node) => node.id))\n const root = nodes.find((node) => !node.parentId || !ids.has(node.parentId))\n const map: Record<string, unknown> = {}\n for (const node of nodes) {\n const props: Record<string, unknown> = {}\n for (const { name, value } of node.props) {\n if (name) props[name] = propValue(node.componentId, name, value)\n }\n map[node.id] = {\n $id: node.id,\n componentId: node.componentId,\n parentId: node === root ? null : node.parentId,\n props,\n nodes: node.children.filter((child) => ids.has(child)),\n }\n }\n return { rootId: root?.id ?? '', nodes: map }\n}\n\n/** A tool call's arguments as nodes, or `null` when the shape is not one. */\nfunction nodesFromToolUse(input: Record<string, unknown>): RawNode[] | null {\n const raw = input['nodes']\n if (!Array.isArray(raw) || !raw.length) return null\n const nodes: RawNode[] = []\n for (const entry of raw) {\n if (!isRecord(entry)) return null\n const id = String(entry['id'] ?? '')\n const componentId = String(entry['componentId'] ?? '')\n if (!id || !componentId) return null\n const props = Array.isArray(entry['props']) ? entry['props'] : []\n nodes.push({\n id,\n componentId,\n parentId: String(entry['parentId'] ?? ''),\n children: (Array.isArray(entry['children']) ? entry['children'] : []).map(String),\n props: props.filter(isRecord).map((pair) => ({\n name: String(pair['name'] ?? ''),\n value: String(pair['value'] ?? ''),\n })),\n })\n }\n return nodes\n}\n\n/** The old shape: a JSON object of `{ rootId, nodes }`, possibly fenced. */\nfunction definitionFromText(text: string): { rootId: string; nodes: Record<string, unknown> } | null {\n const raw = text\n .trim()\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```$/, '')\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return null\n }\n if (!isRecord(parsed)) return null\n return {\n rootId: String(parsed['rootId'] ?? ''),\n nodes: isRecord(parsed['nodes']) ? parsed['nodes'] : {},\n }\n}\n\n/**\n * What reading a section answer came to. Discriminated by a string rather\n * than a boolean, like every other result in the plugin: this tree compiles\n * without `strictNullChecks`, where a `true`/`false` discriminant does not\n * narrow.\n */\nexport type AssistSectionRead =\n | { status: 'ok'; section: AssistSection }\n /** Neither a tool call nor JSON: nothing could be read out of the answer. */\n | { status: 'unreadable'; error: string }\n /** Read, and refused by the sanitizer every marketplace install passes. */\n | { status: 'rejected'; error: string }\n\n/**\n * Read a section answer: the tool call first, the text parse behind it, then\n * the marketplace sanitizer every generated subtree has always passed before\n * it reaches a canvas.\n */\nexport function readAssistSection(result: AiResult): AssistSectionRead {\n const call =\n result.kind === 'completion'\n ? result.toolUse.find((use) => use.name === ASSIST_SECTION_TOOL_NAME)\n : undefined\n const nodes = call ? nodesFromToolUse(call.input) : null\n const definition = nodes\n ? definitionFromNodes(nodes)\n : definitionFromText(result.kind === 'completion' ? result.text : '')\n if (!definition) return { status: 'unreadable', error: 'AI returned invalid JSON' }\n const sanitized = sanitizeMarketplaceDefinition(definition)\n // `=== false`, not `!`: see `AssistSectionRead`.\n if (sanitized.ok === false) return { status: 'rejected', error: sanitized.error }\n return { status: 'ok', section: { rootId: sanitized.rootId, nodes: sanitized.nodes } }\n}\n"],"names":["MARKETPLACE_COMPONENT_ID_ALLOWLIST","sanitizeMarketplaceDefinition","AI_PALETTE","AI_ACCEPTABLE_USE_BLOCK","MODE_PROMPT","element","blog","section","assistModeSystemBlocks","mode","text","cacheBreakpoint","ASSIST_SECTION_TOOL_NAME","STRING","type","strictObject","properties","required","Object","keys","additionalProperties","assistSectionTool","pair","name","value","node","id","componentId","enum","parentId","children","items","props","description","inputSchema","nodes","strict","isRecord","Array","isArray","propValue","declared","propsSchema","parsed","Number","isFinite","trim","definitionFromNodes","ids","Set","map","root","find","has","$id","filter","child","rootId","nodesFromToolUse","input","raw","length","entry","String","push","definitionFromText","replace","JSON","parse","readAssistSection","result","call","kind","toolUse","use","undefined","definition","status","error","sanitized","ok"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,kCAAkC,EAClCC,6BAA6B,QACxB,mDAAkD;AACzD,SAASC,UAAU,QAAQ,qCAAiC;AAC5D,SAASC,uBAAuB,QAAyC,2BAAuB;AA6ChG,uEAAuE,GACvE,MAAMC,cAAsD;IAC1DC,SACE;IACFC,MACE;IACFC,SACE,6EACA,GAAG,iBAAiB,oDAAoD,CAAC,GACzE,4EACA,yEACA,wEACA,kEACA,2EACA,6EACA,4EACA;AACJ;AAEA;;;CAGC,GACD,OAAO,SAASC,uBAAuBC,IAAkB;IACvD,OAAO;QAAC;YAAEC,MAAM,GAAGP,wBAAwB,IAAI,EAAEC,WAAW,CAACK,KAAK,EAAE;YAAEE,iBAAiB;QAAK;KAAE;AAChG;AAEA,OAAO,MAAMC,2BAA2B,iBAAgB;AAExD,MAAMC,SAAS;IAAEC,MAAM;AAAS;AAEhC,MAAMC,eAAe,CAACC,aAAyC,CAAA;QAC7DF,MAAM;QACNE;QACAC,UAAUC,OAAOC,IAAI,CAACH;QACtBI,sBAAsB;IACxB,CAAA;AAEA;;;;;CAKC,GACD,OAAO,SAASC;IACd,MAAMC,OAAOP,aAAa;QAAEQ,MAAMV;QAAQW,OAAOX;IAAO;IACxD,MAAMY,OAAOV,aAAa;QACxBW,IAAIb;QACJc,aAAa;YAAEb,MAAM;YAAUc,MAAM;mBAAI5B;aAAmC;QAAC;QAC7E6B,UAAUhB;QACViB,UAAU;YAAEhB,MAAM;YAASiB,OAAOlB;QAAO;QACzCmB,OAAO;YAAElB,MAAM;YAASiB,OAAOT;QAAK;IACtC;IACA,OAAO;QACLC,MAAMX;QACNqB,aACE,yEACA,qEACA;QACFC,aAAa;YACXpB,MAAM;YACNE,YAAY;gBAAEmB,OAAO;oBAAErB,MAAM;oBAASiB,OAAON;gBAAK;YAAE;YACpDR,UAAU;gBAAC;aAAQ;YACnBG,sBAAsB;QACxB;QACAgB,QAAQ;IACV;AACF;AAgBA,MAAMC,WAAW,CAACb,QAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACc,MAAMC,OAAO,CAACf;AAEhE;;;;;CAKC,GACD,SAASgB,UAAUb,WAAmB,EAAEJ,IAAY,EAAEC,KAAa;QAEhDtB,gDAAAA,qCAAAA;IADjB,IAAIqB,SAAS,YAAY,OAAOC;IAChC,MAAMiB,YAAWvC,0BAAAA,UAAU,CAACyB,YAAY,sBAAvBzB,sCAAAA,wBAAyBwC,WAAW,sBAApCxC,iDAAAA,oCAAsCc,UAAU,qBAAhDd,8CAAkD,CAACqB,KAAK;IAGzE,MAAMT,OAAO2B,4BAAAA,SAAU3B,IAAI;IAC3B,IAAIA,SAAS,WAAW,OAAOU,UAAU;IACzC,IAAIV,SAAS,YAAYA,SAAS,WAAW;QAC3C,MAAM6B,SAASC,OAAOpB;QACtB,OAAOoB,OAAOC,QAAQ,CAACF,UAAUA,SAASnB;IAC5C;IACA,IAAIV,MAAM,OAAOU;IACjB,2EAA2E;IAC3E,0EAA0E;IAC1E,iEAAiE;IACjE,MAAMmB,SAASC,OAAOpB;IACtB,OAAOA,MAAMsB,IAAI,OAAO,MAAMF,OAAOC,QAAQ,CAACF,UAAUA,SAASnB;AACnE;AAEA,0FAA0F,GAC1F,SAASuB,oBAAoBZ,KAAyB;;IAIpD,MAAMa,MAAM,IAAIC,IAAId,MAAMe,GAAG,CAAC,CAACzB,OAASA,KAAKC,EAAE;IAC/C,MAAMyB,OAAOhB,MAAMiB,IAAI,CAAC,CAAC3B,OAAS,CAACA,KAAKI,QAAQ,IAAI,CAACmB,IAAIK,GAAG,CAAC5B,KAAKI,QAAQ;IAC1E,MAAMqB,MAA+B,CAAC;IACtC,KAAK,MAAMzB,QAAQU,MAAO;QACxB,MAAMH,QAAiC,CAAC;QACxC,KAAK,MAAM,EAAET,IAAI,EAAEC,KAAK,EAAE,IAAIC,KAAKO,KAAK,CAAE;YACxC,IAAIT,MAAMS,KAAK,CAACT,KAAK,GAAGiB,UAAUf,KAAKE,WAAW,EAAEJ,MAAMC;QAC5D;QACA0B,GAAG,CAACzB,KAAKC,EAAE,CAAC,GAAG;YACb4B,KAAK7B,KAAKC,EAAE;YACZC,aAAaF,KAAKE,WAAW;YAC7BE,UAAUJ,SAAS0B,OAAO,OAAO1B,KAAKI,QAAQ;YAC9CG;YACAG,OAAOV,KAAKK,QAAQ,CAACyB,MAAM,CAAC,CAACC,QAAUR,IAAIK,GAAG,CAACG;QACjD;IACF;IACA,OAAO;QAAEC,MAAM,UAAEN,wBAAAA,KAAMzB,EAAE,mBAAI;QAAIS,OAAOe;IAAI;AAC9C;AAEA,2EAA2E,GAC3E,SAASQ,iBAAiBC,KAA8B;IACtD,MAAMC,MAAMD,KAAK,CAAC,QAAQ;IAC1B,IAAI,CAACrB,MAAMC,OAAO,CAACqB,QAAQ,CAACA,IAAIC,MAAM,EAAE,OAAO;IAC/C,MAAM1B,QAAmB,EAAE;IAC3B,KAAK,MAAM2B,SAASF,IAAK;YAELE,WACSA,oBAMRA;QARnB,IAAI,CAACzB,SAASyB,QAAQ,OAAO;QAC7B,MAAMpC,KAAKqC,QAAOD,YAAAA,KAAK,CAAC,KAAK,YAAXA,YAAe;QACjC,MAAMnC,cAAcoC,QAAOD,qBAAAA,KAAK,CAAC,cAAc,YAApBA,qBAAwB;QACnD,IAAI,CAACpC,MAAM,CAACC,aAAa,OAAO;QAChC,MAAMK,QAAQM,MAAMC,OAAO,CAACuB,KAAK,CAAC,QAAQ,IAAIA,KAAK,CAAC,QAAQ,GAAG,EAAE;QACjE3B,MAAM6B,IAAI,CAAC;YACTtC;YACAC;YACAE,UAAUkC,QAAOD,kBAAAA,KAAK,CAAC,WAAW,YAAjBA,kBAAqB;YACtChC,UAAU,AAACQ,CAAAA,MAAMC,OAAO,CAACuB,KAAK,CAAC,WAAW,IAAIA,KAAK,CAAC,WAAW,GAAG,EAAE,AAAD,EAAGZ,GAAG,CAACa;YAC1E/B,OAAOA,MAAMuB,MAAM,CAAClB,UAAUa,GAAG,CAAC,CAAC5B;oBACpBA,YACCA;uBAF6B;oBAC3CC,MAAMwC,QAAOzC,aAAAA,IAAI,CAAC,OAAO,YAAZA,aAAgB;oBAC7BE,OAAOuC,QAAOzC,cAAAA,IAAI,CAAC,QAAQ,YAAbA,cAAiB;gBACjC;;QACF;IACF;IACA,OAAOa;AACT;AAEA,0EAA0E,GAC1E,SAAS8B,mBAAmBvD,IAAY;QAarBiC;IAZjB,MAAMiB,MAAMlD,KACToC,IAAI,GACJoB,OAAO,CAAC,qBAAqB,IAC7BA,OAAO,CAAC,WAAW;IACtB,IAAIvB;IACJ,IAAI;QACFA,SAASwB,KAAKC,KAAK,CAACR;IACtB,EAAE,eAAM;QACN,OAAO;IACT;IACA,IAAI,CAACvB,SAASM,SAAS,OAAO;IAC9B,OAAO;QACLc,QAAQM,QAAOpB,iBAAAA,MAAM,CAAC,SAAS,YAAhBA,iBAAoB;QACnCR,OAAOE,SAASM,MAAM,CAAC,QAAQ,IAAIA,MAAM,CAAC,QAAQ,GAAG,CAAC;IACxD;AACF;AAeA;;;;CAIC,GACD,OAAO,SAAS0B,kBAAkBC,MAAgB;IAChD,MAAMC,OACJD,OAAOE,IAAI,KAAK,eACZF,OAAOG,OAAO,CAACrB,IAAI,CAAC,CAACsB,MAAQA,IAAInD,IAAI,KAAKX,4BAC1C+D;IACN,MAAMxC,QAAQoC,OAAOb,iBAAiBa,KAAKZ,KAAK,IAAI;IACpD,MAAMiB,aAAazC,QACfY,oBAAoBZ,SACpB8B,mBAAmBK,OAAOE,IAAI,KAAK,eAAeF,OAAO5D,IAAI,GAAG;IACpE,IAAI,CAACkE,YAAY,OAAO;QAAEC,QAAQ;QAAcC,OAAO;IAA2B;IAClF,MAAMC,YAAY9E,8BAA8B2E;IAChD,iDAAiD;IACjD,IAAIG,UAAUC,EAAE,KAAK,OAAO,OAAO;QAAEH,QAAQ;QAAYC,OAAOC,UAAUD,KAAK;IAAC;IAChF,OAAO;QAAED,QAAQ;QAAMtE,SAAS;YAAEkD,QAAQsB,UAAUtB,MAAM;YAAEtB,OAAO4C,UAAU5C,KAAK;QAAC;IAAE;AACvF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-assist-prompts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PORTABLE_COMPONENT_ID_ALLOWLIST,\n sanitizePortableDefinition,\n} from '@aglyn/aglyn/app-utils/node-definition-sanitizer'\nimport { AI_PALETTE } from '../runtime/ai-palette.generated'\nimport { AI_ACCEPTABLE_USE_BLOCK, type AiSystemBlock, type AiTool } from '../runtime/ai-runtime'\nimport type { AiResult } from '../providers/contract'\n\n/**\n * The copy assistant's prompts and its section tool (AGL-2937), by their own\n * module so a spec can read them without a Firestore, an ID token or a\n * provider — the handler beside them needs all three.\n *\n * ── Why the section mode answers through a tool ───────────────────────────\n *\n * It asked for \"ONLY a JSON object, no prose or code fences\" and then parsed\n * whatever came back, stripping a fence if it found one. When that parse\n * failed the handler answered 502 and the tokens were already spent: the\n * whole request, prompt and answer, bought nothing, and the member's next\n * move is to press the button again and spend it twice. A strict tool is the\n * cheaper shape for exactly that reason — the answer arrives as validated\n * arguments rather than as text that has to be guessed at — and it is what\n * AGL-2937 asks of every door.\n *\n * The parse stays as a fallback. A provider whose adapter has no tool\n * support, or a model that answers in prose anyway, still gets read rather\n * than refused: the tool is how the answer is asked for, not the only way it\n * can arrive.\n *\n * ── Why the flat node list ────────────────────────────────────────────────\n *\n * A strict schema forbids additional properties, so a map keyed by node id\n * cannot be described in one: every key would have to be a declared\n * property. The tool therefore takes a LIST of nodes, each naming its own id\n * and its children by id, and `readAssistSection` rebuilds the map the\n * besigner consumer already reads. The same reason `assistEditTool` takes\n * name/value pairs rather than a props object.\n *\n * ── Caching ───────────────────────────────────────────────────────────────\n *\n * Each mode's static text carries a breakpoint. None of the three is long\n * enough for its model to cache today — `runtime/ai-prompt-cache.spec.ts`\n * measures all three against their minimums and records the verdict — so the\n * marker is a statement about the block rather than a saving. It costs\n * nothing, it is correct if a mode is ever served by a model with a lower\n * minimum, and the ledger is what says whether it pays.\n */\n\nexport type AiAssistMode = 'element' | 'blog' | 'section'\n\n/** The mode prompts. Static text: no byte of any workspace is in one. */\nconst MODE_PROMPT: Readonly<Record<AiAssistMode, string>> = {\n element:\n 'You write website copy inside a site builder. Reply with ONLY the final text for the element — no quotes, preamble, or markdown. Keep roughly the same length and role as the current text unless the instruction says otherwise.',\n blog:\n 'You write blog posts inside a site builder. Reply with ONLY the post body in markdown-lite: **bold**, *italic*, ## headings, - lists, [links](https://url). No front matter, title line, or preamble — the title renders separately.',\n section:\n 'You design one website section inside a site builder. Answer by calling ' +\n `${'submit_section'} exactly once, with a flat list of nodes: each node ` +\n 'names its own id, the component it is, its parent (empty for the root) ' +\n 'and its children by id, and fills props as name/value pairs. Useful ' +\n 'props — muiStack: direction (row|column), spacing, justifyContent, ' +\n 'alignItems; muiTypography: children, variant (h1..h6, body1, ' +\n 'subtitle1); muiButton: children, variant (contained|outlined), color; ' +\n 'image: src, alt; muiContainer: maxWidth (sm|md|lg). Keep it small: 4-12 ' +\n 'nodes, one root container. Leave image src empty. Write real copy, not ' +\n 'lorem ipsum.',\n}\n\n/**\n * One mode's system blocks: the acceptable-use rules ahead of the mode's own\n * prompt (AGL-2925), as one cached block with no per-org byte in it.\n */\nexport function assistModeSystemBlocks(mode: AiAssistMode): AiSystemBlock[] {\n return [{ text: `${AI_ACCEPTABLE_USE_BLOCK}\\n\\n${MODE_PROMPT[mode]}`, cacheBreakpoint: true }]\n}\n\nexport const ASSIST_SECTION_TOOL_NAME = 'submit_section'\n\nconst STRING = { type: 'string' } as const\n\nconst strictObject = (properties: Record<string, unknown>) => ({\n type: 'object',\n properties,\n required: Object.keys(properties),\n additionalProperties: false,\n})\n\n/**\n * The strict tool a generated section arrives through. `componentId` is an\n * enum of the marketplace allowlist — the same list the sanitizer holds the\n * answer to afterward — so an unpublishable component is refused by the\n * schema before it costs a re-ask.\n */\nexport function assistSectionTool(): AiTool {\n const pair = strictObject({ name: STRING, value: STRING })\n const node = strictObject({\n id: STRING,\n componentId: { type: 'string', enum: [...PORTABLE_COMPONENT_ID_ALLOWLIST] },\n parentId: STRING,\n children: { type: 'array', items: STRING },\n props: { type: 'array', items: pair },\n })\n return {\n name: ASSIST_SECTION_TOOL_NAME,\n description:\n 'Submit the section you designed, as a flat list of nodes. The first ' +\n 'node with an empty parentId is the root. The member reviews the ' +\n 'section and places it, or discards it.',\n inputSchema: {\n type: 'object',\n properties: { nodes: { type: 'array', items: node } },\n required: ['nodes'],\n additionalProperties: false,\n },\n strict: true,\n }\n}\n\n/** The sanitized section, in the shape the besigner consumer already reads. */\nexport interface AssistSection {\n rootId: string\n nodes: Record<string, unknown>\n}\n\ninterface RawNode {\n id: string\n componentId: string\n parentId: string\n children: string[]\n props: Array<{ name: string; value: string }>\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * A pair's value in the type the component's own schema declares for that\n * prop, since a strict tool can only carry strings. `children` stays text\n * whatever a schema says: a number in it is copy a visitor reads, not a\n * count.\n */\nfunction propValue(componentId: string, name: string, value: string): unknown {\n if (name === 'children') return value\n const declared = AI_PALETTE[componentId]?.propsSchema?.properties?.[name] as\n | { type?: string }\n | undefined\n const type = declared?.type\n if (type === 'boolean') return value === 'true'\n if (type === 'number' || type === 'integer') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : value\n }\n if (type) return value\n // No schema for this prop: a bare number is read as one, so `spacing: \"2\"`\n // does not reach the canvas as text. The palette's own typing wins above,\n // which is why muiStack's string-typed `spacing` stays a string.\n const parsed = Number(value)\n return value.trim() !== '' && Number.isFinite(parsed) ? parsed : value\n}\n\n/** The flat node list as the stored map: `{ $id, componentId, parentId, props, nodes }`. */\nfunction definitionFromNodes(nodes: readonly RawNode[]): {\n rootId: string\n nodes: Record<string, unknown>\n} {\n const ids = new Set(nodes.map((node) => node.id))\n const root = nodes.find((node) => !node.parentId || !ids.has(node.parentId))\n const map: Record<string, unknown> = {}\n for (const node of nodes) {\n const props: Record<string, unknown> = {}\n for (const { name, value } of node.props) {\n if (name) props[name] = propValue(node.componentId, name, value)\n }\n map[node.id] = {\n $id: node.id,\n componentId: node.componentId,\n parentId: node === root ? null : node.parentId,\n props,\n nodes: node.children.filter((child) => ids.has(child)),\n }\n }\n return { rootId: root?.id ?? '', nodes: map }\n}\n\n/** A tool call's arguments as nodes, or `null` when the shape is not one. */\nfunction nodesFromToolUse(input: Record<string, unknown>): RawNode[] | null {\n const raw = input['nodes']\n if (!Array.isArray(raw) || !raw.length) return null\n const nodes: RawNode[] = []\n for (const entry of raw) {\n if (!isRecord(entry)) return null\n const id = String(entry['id'] ?? '')\n const componentId = String(entry['componentId'] ?? '')\n if (!id || !componentId) return null\n const props = Array.isArray(entry['props']) ? entry['props'] : []\n nodes.push({\n id,\n componentId,\n parentId: String(entry['parentId'] ?? ''),\n children: (Array.isArray(entry['children']) ? entry['children'] : []).map(String),\n props: props.filter(isRecord).map((pair) => ({\n name: String(pair['name'] ?? ''),\n value: String(pair['value'] ?? ''),\n })),\n })\n }\n return nodes\n}\n\n/** The old shape: a JSON object of `{ rootId, nodes }`, possibly fenced. */\nfunction definitionFromText(text: string): { rootId: string; nodes: Record<string, unknown> } | null {\n const raw = text\n .trim()\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```$/, '')\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return null\n }\n if (!isRecord(parsed)) return null\n return {\n rootId: String(parsed['rootId'] ?? ''),\n nodes: isRecord(parsed['nodes']) ? parsed['nodes'] : {},\n }\n}\n\n/**\n * What reading a section answer came to. Discriminated by a string rather\n * than a boolean, like every other result in the plugin: this tree compiles\n * without `strictNullChecks`, where a `true`/`false` discriminant does not\n * narrow.\n */\nexport type AssistSectionRead =\n | { status: 'ok'; section: AssistSection }\n /** Neither a tool call nor JSON: nothing could be read out of the answer. */\n | { status: 'unreadable'; error: string }\n /** Read, and refused by the sanitizer every marketplace install passes. */\n | { status: 'rejected'; error: string }\n\n/**\n * Read a section answer: the tool call first, the text parse behind it, then\n * the marketplace sanitizer every generated subtree has always passed before\n * it reaches a canvas.\n */\nexport function readAssistSection(result: AiResult): AssistSectionRead {\n const call =\n result.kind === 'completion'\n ? result.toolUse.find((use) => use.name === ASSIST_SECTION_TOOL_NAME)\n : undefined\n const nodes = call ? nodesFromToolUse(call.input) : null\n const definition = nodes\n ? definitionFromNodes(nodes)\n : definitionFromText(result.kind === 'completion' ? result.text : '')\n if (!definition) return { status: 'unreadable', error: 'AI returned invalid JSON' }\n const sanitized = sanitizePortableDefinition(definition)\n // `=== false`, not `!`: see `AssistSectionRead`.\n if (sanitized.ok === false) return { status: 'rejected', error: sanitized.error }\n return { status: 'ok', section: { rootId: sanitized.rootId, nodes: sanitized.nodes } }\n}\n"],"names":["PORTABLE_COMPONENT_ID_ALLOWLIST","sanitizePortableDefinition","AI_PALETTE","AI_ACCEPTABLE_USE_BLOCK","MODE_PROMPT","element","blog","section","assistModeSystemBlocks","mode","text","cacheBreakpoint","ASSIST_SECTION_TOOL_NAME","STRING","type","strictObject","properties","required","Object","keys","additionalProperties","assistSectionTool","pair","name","value","node","id","componentId","enum","parentId","children","items","props","description","inputSchema","nodes","strict","isRecord","Array","isArray","propValue","declared","propsSchema","parsed","Number","isFinite","trim","definitionFromNodes","ids","Set","map","root","find","has","$id","filter","child","rootId","nodesFromToolUse","input","raw","length","entry","String","push","definitionFromText","replace","JSON","parse","readAssistSection","result","call","kind","toolUse","use","undefined","definition","status","error","sanitized","ok"],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { checkEntitlement } from "@aglyn/aglyn/server";
|
|
18
|
-
import { assistCreditsFromUsd, assistFreeTasteRefusalText, assistOwnControlRefusalText } from "
|
|
18
|
+
import { assistCreditsFromUsd, assistFreeTasteRefusalText, assistNotOnPlanText, assistOwnControlRefusalText } from "../usage/assist-credits.js";
|
|
19
19
|
import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
20
20
|
import { aiAllotmentRefusalText } from "../model/ai-allotments.js";
|
|
21
21
|
import { resolveAiModelChoice } from "../providers/model-choice.js";
|
|
@@ -237,7 +237,7 @@ const ELEMENT_MAX_TOKENS = AI_ROUTING_TABLE['copy.element'].maxTokens;
|
|
|
237
237
|
const entitled = checkEntitlement(org, 'aiAssist');
|
|
238
238
|
if (!entitled) {
|
|
239
239
|
return res.status(403).json({
|
|
240
|
-
error:
|
|
240
|
+
error: assistNotOnPlanText()
|
|
241
241
|
});
|
|
242
242
|
}
|
|
243
243
|
// Org-scoped lockdown. The dispatcher already evaluates platform and user
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-assist.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n checkEntitlement,\n type PluginApiHandler,\n type PluginApiResponse,\n} from '@aglyn/aglyn/server'\nimport {\n assistCreditsFromUsd,\n assistFreeTasteRefusalText,\n assistOwnControlRefusalText,\n} from '@aglyn/aglyn/app-utils/assist-credits'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { aiAllotmentRefusalText } from '../model/ai-allotments'\nimport { resolveAiModelChoice } from '../providers/model-choice'\nimport { aiUsageMeter } from '../usage/ai-usage-meter'\nimport {\n permissionRefusal,\n checkRateLimit,\n featureLockdownRefusal,\n firebaseAdmin,\n getOrgForUser,\n lockdownRefusal,\n memberHasPermissionOnHost,\n rateLimitHeaders,\n} from '@aglyn/tenant-data-admin'\nimport { aiOverageReservationRefusal } from '../billing/ai-overage-gate'\nimport { recordUserAiRefusal } from '../usage/ai-usage-by-user'\nimport {\n estimateAssistCostUsd,\n publicAssistQuota,\n recordAssistCost,\n releaseAssistMessage,\n reserveAssistMessage,\n type AssistReservation,\n} from '../usage/assist-usage'\nimport { logAiAssistSection } from '../activity/ai-activity'\n// By its own entry point rather than the barrel (AGL-2903): this handler's\n// spec replaces the barrel with a closed-world factory, and nothing replaces\n// the entry point, so the spec exercises the real request shape and the real\n// error boundary rather than a stub that agrees with the handler.\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AiUpstreamError,\n aiProviderReady,\n runAiRequest,\n type AiResult,\n type AiTool,\n} from '../runtime/ai-runtime'\nimport {\n ASSIST_SECTION_TOOL_NAME,\n assistModeSystemBlocks,\n assistSectionTool,\n readAssistSection,\n type AiAssistMode,\n} from './ai-assist-prompts'\n// By its own entry point for the same reason (AGL-2925): the per-address\n// rung is real in the spec, keyed on whatever address the harness sends.\nimport { checkAiClientIpRateLimit } from '../runtime/ai-abuse-guards'\n\n/**\n * The model each mode is served by, through the routing table — tiered by\n * what the mode actually asks for (AGL-2486), not one model for all three.\n *\n * - **element** → the fast tier (`copy.element`). Rewriting one button label\n * or one heading to an instruction is a short, bounded transformation with\n * the source text supplied: the least model-shaped work on this route and\n * the highest-volume (a user clicking \"improve this\" runs it repeatedly on\n * the same element).\n * - **blog** → the balanced tier (`copy.blog`). A post body is real long-form\n * writing, and the difference between tiers is legible to the reader.\n * - **section** → the balanced tier (`copy.section`). A constrained-JSON node\n * subtree over the published-component allowlist is a structured-generation\n * task with a schema the sanitizer will reject if it is got wrong, and a\n * rejected generation costs the whole request rather than part of it.\n *\n * ⚠️ NO PROMPT CACHING IS LOST BY THE FAST RUNG. Each mode's static text now\n * carries a breakpoint (`assistModeSystemBlocks`), but none of the three is\n * long enough for its model to cache: the catalog states each model's\n * minimum and `runtime/ai-prompt-cache.spec.ts` measures every door against\n * it. That is why the tier drop is free here and NOT free on\n * `/api/assist/chat`, whose ~1,030-token prefix clears the balanced tier's\n * minimum and would stop caching entirely on the fast tier's — cheaper per\n * token, dearer per request, and invisible either way without that\n * measurement. See the header of `assist-chat.ts`.\n *\n * The cost meter prices by model id, so the value returned here MUST be the\n * one passed to `recordAssistCost` — same call, same variable. A tiering\n * change that updates the request and not the meter reports one tier's money\n * for another tier's tokens, which is the failure the by-model rate table\n * exists to stop.\n */\nexport function assistModelForMode(mode: AiAssistMode): string {\n return aiModelForStep(assistStepKindForMode(mode))\n}\n\n/** The routing table's step kind for each mode. */\nexport function assistStepKindForMode(\n mode: AiAssistMode,\n): 'copy.element' | 'copy.blog' | 'copy.section' {\n return mode === 'element' ? 'copy.element' : mode === 'blog' ? 'copy.blog' : 'copy.section'\n}\n\n/**\n * Per-uid burst limit, matched to `/api/assist/chat`'s 20/min so a client\n * cannot arbitrage the two assist doors against each other.\n */\nconst ASSIST_RATE_LIMIT = 20\nconst ASSIST_RATE_WINDOW_MS = 60_000\n\nconst SECTION_MAX_TOKENS = AI_ROUTING_TABLE['copy.section'].maxTokens\nconst BLOG_MAX_TOKENS = AI_ROUTING_TABLE['copy.blog'].maxTokens\nconst ELEMENT_MAX_TOKENS = AI_ROUTING_TABLE['copy.element'].maxTokens\n\n/**\n * What a `refusal` stop is answered with. The model declined the brief —\n * a policy verdict on the instruction, not a fault — so the sentence asks\n * for a different one rather than a retry. The tokens were spent and are\n * metered before this is sent.\n */\nconst REFUSED = {\n error: 'The AI declined that instruction — try a different one.',\n}\n\n/**\n * AI assist (AGL-89/130/169), the besigner copy assistant's door — the\n * section mode composes over the published-component allowlist and every\n * generated subtree passes the node-definition sanitizer a marketplace\n * install passes, before it reaches a canvas. `/api/ai/assist` is served by\n * the plugin API dispatcher under the `ai` prefix. Gated on a ready provider,\n * one registered with its key set (501 without, the same degrade pattern as\n * Stripe); auth via Firebase ID token.\n *\n * The provider call is `runAiRequest`, the runtime every AI door shares\n * (AGL-2903). This handler owns the ladder below and the response shapes;\n * `ai-assist-prompts.ts` owns the three prompts and the section tool; the\n * runtime owns the wire, the usage arithmetic and the error boundary. The\n * request keeps the model per mode, the `max_tokens` and the absent\n * `thinking` field it has always had — a fast-tier model rejects an explicit\n * one, and the balanced modes keep the model default.\n *\n * A section arrives through a strict tool (AGL-2937). It used to be asked\n * for as bare JSON and parsed: a parse that failed answered 502 with the\n * tokens already spent, so the whole request bought nothing and the member's\n * next move was to spend it again. The text parse is kept behind the tool,\n * for a provider whose adapter has none.\n *\n * ── The spend ladder (AGL-2073) ────────────────────────────────────────────\n *\n * This route reached the provider behind nothing but an entitlement check. Free\n * orgs could not get in, so the free tier was safe — but for any Pro org it\n * had no rate limit, no quota, no per-org cost telemetry, and it resolved the\n * org from the USER rather than the request, so someone with one paid org got\n * assist charged against whichever org `getOrgForUser` happened to return. One\n * subscription, unbounded and unmeasured provider spend.\n *\n * The ladder now, in order, each rung able to go red on its own:\n * 405 → 501 no ready provider → 401 no token → 400 bad body (the request\n * must NAME the org it is metered against) → 403 not a member of that org →\n * 423 the workspace's AI pause → 403 the member's role lacks `ai.use`, or\n * `ai.generate` for a section (AGL-2927; staff pass) → 403 no `aiAssist`\n * entitlement → 423 org lockdown → 429 rate limit → 429 quota → the model\n * call → meter.\n *\n * Two deliberate choices about which way each rung fails:\n *\n * - **The rate limiter fails SOFT and is not the spend bound.** It is a\n * per-instance in-memory window, so N serverless instances admit N × 20/min;\n * it smooths bursts and nothing more. Treating it as the cap would be the\n * fail-open this issue is about.\n * - **The reservation fails CLOSED.** It is the only global, atomic bound on\n * provider spend, so a reservation that cannot be taken — a Firestore\n * outage, a contended transaction — refuses the request with 503 rather than\n * calling the provider uncapped. An unavailable meter must stop the spending,\n * not wave it through.\n *\n * The reservation is `reserveAssistMessage` — the same shared counter the\n * console chat route moves (AGL-2057), not a second mechanism. That is what\n * closes the \"uncapped for paid\" hole the earlier audit named: an entitled org\n * carries the monthly runaway guard, and because both doors move the SAME\n * counter, an org's monthly assist budget is shared across them rather than\n * doubled by having two.\n *\n * Metering is `recordAssistCost`, the signal + rollup half of the assist\n * meters WITHOUT the verbatim half: what a customer types here is their own\n * site copy and blog bodies, and retaining that for 180 days is a data flow\n * the published privacy disclosure does not describe. The margin question is\n * answered by the signal alone.\n */\nexport const aiAssistHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n if (!aiProviderReady()) {\n return res\n .status(501)\n .json({ error: 'AI assist is not configured (no AI provider key).' })\n }\n\n const headers = req.headers as Partial<Record<string, string>>\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n const body = req.body ?? {}\n const text = String(body?.text ?? '').slice(0, 12000)\n const instruction = String(body?.instruction ?? '').slice(0, 500)\n // Modes (AGL-130): 'element' rewrites short copy; 'blog' writes or\n // improves markdown-lite entry bodies with title/excerpt context.\n const mode =\n body?.mode === 'blog'\n ? 'blog'\n : body?.mode === 'section'\n ? 'section'\n : 'element'\n const title = String(body?.title ?? '').slice(0, 200)\n const excerpt = String(body?.excerpt ?? '').slice(0, 500)\n // The request must NAME the org it is about to be metered against\n // (AGL-2073, the AGL-1934 rule applied to this door). Resolving the org\n // from the user alone charged a multi-org user's assist to whichever org\n // came back first — and handed their free workspaces a paid feature.\n const orgId = String(body?.orgId ?? '').trim()\n const hostId = String(body?.hostId ?? '')\n .trim()\n .slice(0, 128)\n if (!instruction) {\n return res.status(400).json({ error: 'Missing instruction' })\n }\n if (!orgId) {\n return res\n .status(400)\n .json({ error: 'Open a workspace before using AI assist' })\n }\n\n let reservation: AssistReservation | null = null\n let providerAnswered = false\n let firestore: FirebaseFirestore.Firestore | null = null\n\n try {\n const app = firebaseAdmin.app()\n firestore = app.firestore()\n const decoded = await app.auth().verifyIdToken(idToken)\n\n // Plan gate (AGL-469): AI assist is a Pro+ entitlement with real token\n // cost — resolve the caller's org and check it server-side; a plan-less\n // org resolves as `free` and is denied. Scoped to the NAMED org, so\n // membership of some other paid org is not a key to this one.\n const resolved = await getOrgForUser(decoded.uid, orgId)\n if (!resolved || resolved.orgId !== orgId) {\n return res\n .status(403)\n .json({ error: 'You are not a member of that organization' })\n }\n const org = resolved.org ?? {}\n\n // Permission (AGL-2927), directly after membership. The element and\n // blog modes are assistance and sell under `ai.use`; a generated\n // section is a generation and sells under `ai.generate`. Decided on the\n // caller's own axis — the org catalog for an org-wide member, the site\n // the body named for a collaborator, who is refused when it named none.\n // A fact about the caller, so it is answered before the plan is. Staff\n // pass, as at every other org route. Beside it, the workspace-scoped\n // pause on `ai-assist`: the dispatcher already applied the platform-wide\n // switch by path, before it knew which org the body would name.\n const staff = decoded['staff'] === true\n const aiPaused = await featureLockdownRefusal({\n feature: 'ai-assist',\n staff,\n orgId,\n })\n if (aiPaused) return forwardRefusal(res, aiPaused)\n const permission = mode === 'section' ? 'ai.generate' : 'ai.use'\n if (\n !staff &&\n !(await memberHasPermissionOnHost(orgId, hostId, resolved.member, permission))\n ) {\n return forwardRefusal(res, permissionRefusal(permission))\n }\n\n const entitled = checkEntitlement(org, 'aiAssist')\n if (!entitled) {\n return res.status(403).json({ error: 'AI assist requires a Pro plan' })\n }\n\n // Org-scoped lockdown. The dispatcher already evaluates platform and user\n // scope plus the `ai-assist` feature kill switch, but its org scope needs\n // a `hostId` this route's callers do not send — so a suspended workspace\n // kept spending. The org doc is already in hand, so the verdict is free.\n const locked = await lockdownRefusal({\n request: { method: req.method },\n staff,\n uid: decoded.uid,\n org: org as Record<string, unknown>,\n })\n if (locked) return forwardRefusal(res, locked)\n\n const rate = checkRateLimit(`ai-assist:${decoded.uid}`, {\n limit: ASSIST_RATE_LIMIT,\n windowMs: ASSIST_RATE_WINDOW_MS,\n })\n if (!rate.allowed) {\n for (const [name, value] of Object.entries(rateLimitHeaders(rate))) {\n res.setHeader(name, value)\n }\n return res\n .status(429)\n .json({ error: 'Too many AI requests — slow down a moment' })\n }\n // The same window keyed on the trusted-hop client address (AGL-2925):\n // accounts rotated behind one address share one budget.\n const ipRate = checkAiClientIpRateLimit(headers)\n if (ipRate && !ipRate.allowed) {\n for (const [name, value] of Object.entries(rateLimitHeaders(ipRate))) {\n res.setHeader(name, value)\n }\n return res\n .status(429)\n .json({ error: 'Too many AI requests — slow down a moment' })\n }\n\n // RESERVED, not merely checked (AGL-2057/2073). Read-and-increment in one\n // transaction, BEFORE a single token is spent, so N concurrent requests\n // cannot all read the same \"under the cap\" and a client that drops the\n // connection has already been counted.\n try {\n // `org` is the document `entitled` came from, and it is what makes\n // the plan's own assist band bind rather than the operator backstop\n // alone. Generative building is the dearest thing this route serves,\n // so the band it spends against has to be the one that was sold.\n reservation = await reserveAssistMessage(\n firestore,\n orgId,\n entitled,\n new Date(),\n org,\n // The allotments that apply (AGL-2942): the asker's own, theirs on\n // the canvas's site, and the site's.\n { uid: decoded.uid, hostId: hostId || null },\n )\n } catch (error) {\n // FAIL CLOSED. The reservation is the only global bound on what this\n // route can spend at the provider; if it cannot be taken there is no cap,\n // and no cap is worse than no answer.\n console.error('assist reservation failed', error)\n return res\n .status(503)\n .json({ error: 'AI assist is temporarily unavailable' })\n }\n // A refusal is the asker's as well as the workspace's (AGL-2928): their\n // month counts it beside the org counter the reservation moved.\n recordUserAiRefusal(firestore, orgId, decoded.uid, reservation)\n if (!reservation.allowed) {\n const refusedBy = reservation.refusedBy\n // A hard allotment (AGL-2942): the monthly line a manager drew for\n // the asker or the site, with the sentence naming who can raise it.\n if (refusedBy === 'allotment') {\n return res.status(429).json({\n error: aiAllotmentRefusalText(reservation.allotment?.refusal?.scope),\n reason: 'quota',\n quota: publicAssistQuota(reservation),\n meter: aiUsageMeter(reservation),\n })\n }\n // The org's own controls are a 402, not a 429: credits past the band\n // are for sale and this workspace either switched the sale off\n // (AGL-2653) or capped what it would buy (AGL-2898), so the sentence\n // names the control that refused — the same sentence the console\n // assistant gives, from the same helper. Every other refusal keeps\n // the 429.\n // AGLYN'S OWN OVERAGE GUARDS (AGL-3011): a card, a pause, this\n // month's ceiling or a settling balance, each with the status that\n // says whether the workspace can act on it. Before the workspace's\n // own controls, which answer only for the controls it set itself.\n const overage = aiOverageReservationRefusal(reservation)\n if (overage) {\n return res.status(overage.status).json({\n error: overage.text,\n reason: 'quota',\n quota: publicAssistQuota(reservation),\n meter: aiUsageMeter(reservation),\n })\n }\n const ownControl = assistOwnControlRefusalText(org, refusedBy)\n return res.status(ownControl ? 402 : 429).json({\n error:\n ownControl ??\n // The Free taste's own precautions (AGL-2925), in the sentences\n // the console assistant uses.\n assistFreeTasteRefusalText(refusedBy) ??\n 'This workspace reached its AI assist limit for the month — contact support if you need a higher cap',\n // CREDITS, never the reservation itself — see `publicAssistQuota`.\n quota: publicAssistQuota(reservation),\n meter: aiUsageMeter(reservation),\n })\n }\n\n const tier: 'free' | 'entitled' = entitled ? 'entitled' : 'free'\n // ONE resolution, read by both the request and the meter — see\n // `assistModelForMode`. Two call sites would let them drift. A model the\n // asker picked (AGL-2942) replaces the routing table's answer only when\n // the plan, the org's restriction and the allotment allowlists all allow\n // it; anything else runs on Auto.\n const held = reservation\n const choice = resolveAiModelChoice(assistStepKindForMode(mode), body?.model, {\n plan: resolveEffectivePlan(org),\n allotmentModels: held.allotment?.models ?? null,\n orgModels: held.allotment?.orgModels ?? null,\n })\n const model = choice?.model ?? assistModelForMode(mode)\n /** The usage strip's envelope for an answered request (AGL-2942). */\n const meterFor = (result: AiResult) =>\n aiUsageMeter(held, {\n lastCredits: assistCreditsFromUsd(estimateAssistCostUsd(result.usage, model)),\n model: { id: model, auto: choice?.auto ?? true },\n })\n /** Meter what the provider actually charged us for, per org. */\n // The account the reservation drew on (AGL-2925), read once here: the\n // closure below runs after `reservation` may have been cleared by a\n // refund, and a refund never reaches the meter.\n const free = reservation?.free ?? null\n const meter = (result: AiResult): Promise<unknown> =>\n recordAssistCost(firestore, orgId, {\n route: `/api/ai/assist/${mode}`,\n hostId: hostId || null,\n model,\n tier,\n usage: result.usage,\n docsPaths: [],\n stopReason: result.stopReason,\n free,\n }).catch((error) => console.error('assist cost metering failed', error))\n\n /**\n * One provider call, with the refund decided at the boundary: a refused\n * request (a non-2xx answer) spent nothing, so the reservation goes back\n * — against the keys it RECORDED, never against \"now\" — and the caller\n * gets the runtime's fixed sentence, never the provider's (AGL-2815).\n * A result, even a refusal, means the tokens ARE spent: every exit after\n * it meters and none refunds. Anything else thrown never reached the\n * provider and is left to the outer catch, which refunds too.\n */\n const ask = async (\n content: string,\n maxTokens: number,\n tools?: AiTool[],\n ): Promise<AiResult | null> => {\n try {\n const result = await runAiRequest({\n model,\n maxTokens,\n stream: false,\n // The mode's prompt with the acceptable-use rules ahead of it\n // (AGL-2925), from `ai-assist-prompts.ts` so a spec can read what\n // this route sends without standing up the route.\n system: assistModeSystemBlocks(mode),\n messages: [{ role: 'user', content }],\n ...(tools ? { tools } : {}),\n })\n providerAnswered = true\n await meter(result)\n return result\n } catch (error) {\n if (!(error instanceof AiUpstreamError)) throw error\n providerAnswered = true\n await releaseReservation(firestore, orgId, reservation as AssistReservation)\n reservation = null\n res.status(502).json({ error: error.message })\n return null\n }\n }\n\n // Generate section (AGL-169): a constrained-JSON node subtree over the\n // marketplace component allowlist; the response passes the same\n // sanitizer as marketplace installs before it ever reaches a canvas.\n if (mode === 'section') {\n const result = await ask(\n `Section to design: ${instruction}`,\n SECTION_MAX_TOKENS,\n [assistSectionTool()],\n )\n if (!result) return\n if (result.kind === 'refusal') {\n return res.status(502).json({ ...REFUSED, meter: meterFor(result) })\n }\n const read = readAssistSection(result)\n if (read.status !== 'ok') {\n // Unreadable is the provider's fault and rejected is the answer's,\n // the two statuses this route has always drawn that line between.\n return res.status(read.status === 'unreadable' ? 502 : 422).json({ error: read.error })\n }\n // A subtree is going back (AGL-2929): the act, its size and its site in\n // the customer's feed — never the instruction or the copy generated.\n await logAiAssistSection(\n { uid: decoded.uid, email: decoded.email ?? null },\n {\n orgId,\n hostId: hostId || null,\n nodeCount: Object.keys(read.section.nodes).length,\n },\n )\n return res.status(200).json({ section: read.section, meter: meterFor(result) })\n }\n\n const result = await ask(\n mode === 'blog'\n ? [\n title && `Title: ${title}`,\n excerpt && `Excerpt: ${excerpt}`,\n text && `Current body:\\n${text}`,\n `Instruction: ${instruction}`,\n ]\n .filter(Boolean)\n .join('\\n\\n')\n : text\n ? `Current element text:\\n${text}\\n\\nInstruction: ${instruction}`\n : `Write website element text. Instruction: ${instruction}`,\n mode === 'blog' ? BLOG_MAX_TOKENS : ELEMENT_MAX_TOKENS,\n )\n if (!result) return\n if (result.kind === 'refusal') {\n return res.status(502).json({ ...REFUSED, meter: meterFor(result) })\n }\n const output = result.text.trim()\n if (!output) {\n return res.status(502).json({ error: 'Empty AI response', meter: meterFor(result) })\n }\n return res.status(200).json({ text: output, meter: meterFor(result) })\n } catch (error) {\n console.error(error)\n // The provider was never reached — a token verification failure, a DNS\n // error, a socket reset before any response. Nothing was spent, so the\n // reservation must not be. If the provider DID answer, the tokens are\n // real and the reservation stays consumed however the request ends.\n if (firestore && reservation && !providerAnswered) {\n await releaseReservation(firestore, orgId, reservation)\n }\n return res.status(500).json({ error: 'Assist failed' })\n }\n}\n\n/**\n * Hand a reservation back, never letting the refund itself fail the request.\n * Released against the keys the RESERVATION recorded — a reservation taken at\n * 23:59:59 and released at 00:00:01 must not credit the next day.\n */\nasync function releaseReservation(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n reservation: AssistReservation,\n): Promise<void> {\n await releaseAssistMessage(firestore, orgId, reservation).catch((error) =>\n console.error('assist reservation release failed', error),\n )\n}\n\n/** Re-emit a lockdown `Response` through the plugin API's res object. */\nasync function forwardRefusal(\n res: PluginApiResponse,\n refusal: Response,\n): Promise<void> {\n const retryAfter = refusal.headers.get('Retry-After')\n if (retryAfter) res.setHeader('Retry-After', retryAfter)\n const body = await refusal.json().catch(() => ({ error: 'locked' }))\n res.status(refusal.status).json(body)\n}\n"],"names":["checkEntitlement","assistCreditsFromUsd","assistFreeTasteRefusalText","assistOwnControlRefusalText","resolveEffectivePlan","aiAllotmentRefusalText","resolveAiModelChoice","aiUsageMeter","permissionRefusal","checkRateLimit","featureLockdownRefusal","firebaseAdmin","getOrgForUser","lockdownRefusal","memberHasPermissionOnHost","rateLimitHeaders","aiOverageReservationRefusal","recordUserAiRefusal","estimateAssistCostUsd","publicAssistQuota","recordAssistCost","releaseAssistMessage","reserveAssistMessage","logAiAssistSection","AI_ROUTING_TABLE","aiModelForStep","AiUpstreamError","aiProviderReady","runAiRequest","assistModeSystemBlocks","assistSectionTool","readAssistSection","checkAiClientIpRateLimit","assistModelForMode","mode","assistStepKindForMode","ASSIST_RATE_LIMIT","ASSIST_RATE_WINDOW_MS","SECTION_MAX_TOKENS","maxTokens","BLOG_MAX_TOKENS","ELEMENT_MAX_TOKENS","REFUSED","error","aiAssistHandler","req","res","headers","method","status","json","authorization","idToken","startsWith","slice","length","undefined","body","text","String","instruction","title","excerpt","orgId","trim","hostId","reservation","providerAnswered","firestore","resolved","held","app","decoded","auth","verifyIdToken","uid","org","staff","aiPaused","feature","forwardRefusal","permission","member","entitled","locked","request","rate","limit","windowMs","allowed","name","value","Object","entries","setHeader","ipRate","Date","console","ownControl","refusedBy","allotment","refusal","scope","reason","quota","meter","overage","tier","choice","model","plan","allotmentModels","models","orgModels","meterFor","result","lastCredits","usage","id","auto","free","route","docsPaths","stopReason","catch","ask","content","tools","stream","system","messages","role","releaseReservation","message","kind","read","email","nodeCount","keys","section","nodes","filter","Boolean","join","output","retryAfter","get"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-assist.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n checkEntitlement,\n type PluginApiHandler,\n type PluginApiResponse,\n} from '@aglyn/aglyn/server'\nimport {\n assistCreditsFromUsd,\n assistFreeTasteRefusalText,\n assistNotOnPlanText,\n assistOwnControlRefusalText,\n} from '../usage/assist-credits'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { aiAllotmentRefusalText } from '../model/ai-allotments'\nimport { resolveAiModelChoice } from '../providers/model-choice'\nimport { aiUsageMeter } from '../usage/ai-usage-meter'\nimport {\n permissionRefusal,\n checkRateLimit,\n featureLockdownRefusal,\n firebaseAdmin,\n getOrgForUser,\n lockdownRefusal,\n memberHasPermissionOnHost,\n rateLimitHeaders,\n} from '@aglyn/tenant-data-admin'\nimport { aiOverageReservationRefusal } from '../billing/ai-overage-gate'\nimport { recordUserAiRefusal } from '../usage/ai-usage-by-user'\nimport {\n estimateAssistCostUsd,\n publicAssistQuota,\n recordAssistCost,\n releaseAssistMessage,\n reserveAssistMessage,\n type AssistReservation,\n} from '../usage/assist-usage'\nimport { logAiAssistSection } from '../activity/ai-activity'\n// By its own entry point rather than the barrel (AGL-2903): this handler's\n// spec replaces the barrel with a closed-world factory, and nothing replaces\n// the entry point, so the spec exercises the real request shape and the real\n// error boundary rather than a stub that agrees with the handler.\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AiUpstreamError,\n aiProviderReady,\n runAiRequest,\n type AiResult,\n type AiTool,\n} from '../runtime/ai-runtime'\nimport {\n ASSIST_SECTION_TOOL_NAME,\n assistModeSystemBlocks,\n assistSectionTool,\n readAssistSection,\n type AiAssistMode,\n} from './ai-assist-prompts'\n// By its own entry point for the same reason (AGL-2925): the per-address\n// rung is real in the spec, keyed on whatever address the harness sends.\nimport { checkAiClientIpRateLimit } from '../runtime/ai-abuse-guards'\n\n/**\n * The model each mode is served by, through the routing table — tiered by\n * what the mode actually asks for (AGL-2486), not one model for all three.\n *\n * - **element** → the fast tier (`copy.element`). Rewriting one button label\n * or one heading to an instruction is a short, bounded transformation with\n * the source text supplied: the least model-shaped work on this route and\n * the highest-volume (a user clicking \"improve this\" runs it repeatedly on\n * the same element).\n * - **blog** → the balanced tier (`copy.blog`). A post body is real long-form\n * writing, and the difference between tiers is legible to the reader.\n * - **section** → the balanced tier (`copy.section`). A constrained-JSON node\n * subtree over the published-component allowlist is a structured-generation\n * task with a schema the sanitizer will reject if it is got wrong, and a\n * rejected generation costs the whole request rather than part of it.\n *\n * ⚠️ NO PROMPT CACHING IS LOST BY THE FAST RUNG. Each mode's static text now\n * carries a breakpoint (`assistModeSystemBlocks`), but none of the three is\n * long enough for its model to cache: the catalog states each model's\n * minimum and `runtime/ai-prompt-cache.spec.ts` measures every door against\n * it. That is why the tier drop is free here and NOT free on\n * `/api/assist/chat`, whose ~1,030-token prefix clears the balanced tier's\n * minimum and would stop caching entirely on the fast tier's — cheaper per\n * token, dearer per request, and invisible either way without that\n * measurement. See the header of `assist-chat.ts`.\n *\n * The cost meter prices by model id, so the value returned here MUST be the\n * one passed to `recordAssistCost` — same call, same variable. A tiering\n * change that updates the request and not the meter reports one tier's money\n * for another tier's tokens, which is the failure the by-model rate table\n * exists to stop.\n */\nexport function assistModelForMode(mode: AiAssistMode): string {\n return aiModelForStep(assistStepKindForMode(mode))\n}\n\n/** The routing table's step kind for each mode. */\nexport function assistStepKindForMode(\n mode: AiAssistMode,\n): 'copy.element' | 'copy.blog' | 'copy.section' {\n return mode === 'element' ? 'copy.element' : mode === 'blog' ? 'copy.blog' : 'copy.section'\n}\n\n/**\n * Per-uid burst limit, matched to `/api/assist/chat`'s 20/min so a client\n * cannot arbitrage the two assist doors against each other.\n */\nconst ASSIST_RATE_LIMIT = 20\nconst ASSIST_RATE_WINDOW_MS = 60_000\n\nconst SECTION_MAX_TOKENS = AI_ROUTING_TABLE['copy.section'].maxTokens\nconst BLOG_MAX_TOKENS = AI_ROUTING_TABLE['copy.blog'].maxTokens\nconst ELEMENT_MAX_TOKENS = AI_ROUTING_TABLE['copy.element'].maxTokens\n\n/**\n * What a `refusal` stop is answered with. The model declined the brief —\n * a policy verdict on the instruction, not a fault — so the sentence asks\n * for a different one rather than a retry. The tokens were spent and are\n * metered before this is sent.\n */\nconst REFUSED = {\n error: 'The AI declined that instruction — try a different one.',\n}\n\n/**\n * AI assist (AGL-89/130/169), the besigner copy assistant's door — the\n * section mode composes over the published-component allowlist and every\n * generated subtree passes the node-definition sanitizer a marketplace\n * install passes, before it reaches a canvas. `/api/ai/assist` is served by\n * the plugin API dispatcher under the `ai` prefix. Gated on a ready provider,\n * one registered with its key set (501 without, the same degrade pattern as\n * Stripe); auth via Firebase ID token.\n *\n * The provider call is `runAiRequest`, the runtime every AI door shares\n * (AGL-2903). This handler owns the ladder below and the response shapes;\n * `ai-assist-prompts.ts` owns the three prompts and the section tool; the\n * runtime owns the wire, the usage arithmetic and the error boundary. The\n * request keeps the model per mode, the `max_tokens` and the absent\n * `thinking` field it has always had — a fast-tier model rejects an explicit\n * one, and the balanced modes keep the model default.\n *\n * A section arrives through a strict tool (AGL-2937). It used to be asked\n * for as bare JSON and parsed: a parse that failed answered 502 with the\n * tokens already spent, so the whole request bought nothing and the member's\n * next move was to spend it again. The text parse is kept behind the tool,\n * for a provider whose adapter has none.\n *\n * ── The spend ladder (AGL-2073) ────────────────────────────────────────────\n *\n * This route reached the provider behind nothing but an entitlement check. Free\n * orgs could not get in, so the free tier was safe — but for any Pro org it\n * had no rate limit, no quota, no per-org cost telemetry, and it resolved the\n * org from the USER rather than the request, so someone with one paid org got\n * assist charged against whichever org `getOrgForUser` happened to return. One\n * subscription, unbounded and unmeasured provider spend.\n *\n * The ladder now, in order, each rung able to go red on its own:\n * 405 → 501 no ready provider → 401 no token → 400 bad body (the request\n * must NAME the org it is metered against) → 403 not a member of that org →\n * 423 the workspace's AI pause → 403 the member's role lacks `ai.use`, or\n * `ai.generate` for a section (AGL-2927; staff pass) → 403 no `aiAssist`\n * entitlement → 423 org lockdown → 429 rate limit → 429 quota → the model\n * call → meter.\n *\n * Two deliberate choices about which way each rung fails:\n *\n * - **The rate limiter fails SOFT and is not the spend bound.** It is a\n * per-instance in-memory window, so N serverless instances admit N × 20/min;\n * it smooths bursts and nothing more. Treating it as the cap would be the\n * fail-open this issue is about.\n * - **The reservation fails CLOSED.** It is the only global, atomic bound on\n * provider spend, so a reservation that cannot be taken — a Firestore\n * outage, a contended transaction — refuses the request with 503 rather than\n * calling the provider uncapped. An unavailable meter must stop the spending,\n * not wave it through.\n *\n * The reservation is `reserveAssistMessage` — the same shared counter the\n * console chat route moves (AGL-2057), not a second mechanism. That is what\n * closes the \"uncapped for paid\" hole the earlier audit named: an entitled org\n * carries the monthly runaway guard, and because both doors move the SAME\n * counter, an org's monthly assist budget is shared across them rather than\n * doubled by having two.\n *\n * Metering is `recordAssistCost`, the signal + rollup half of the assist\n * meters WITHOUT the verbatim half: what a customer types here is their own\n * site copy and blog bodies, and retaining that for 180 days is a data flow\n * the published privacy disclosure does not describe. The margin question is\n * answered by the signal alone.\n */\nexport const aiAssistHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n if (!aiProviderReady()) {\n return res\n .status(501)\n .json({ error: 'AI assist is not configured (no AI provider key).' })\n }\n\n const headers = req.headers as Partial<Record<string, string>>\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n const body = req.body ?? {}\n const text = String(body?.text ?? '').slice(0, 12000)\n const instruction = String(body?.instruction ?? '').slice(0, 500)\n // Modes (AGL-130): 'element' rewrites short copy; 'blog' writes or\n // improves markdown-lite entry bodies with title/excerpt context.\n const mode =\n body?.mode === 'blog'\n ? 'blog'\n : body?.mode === 'section'\n ? 'section'\n : 'element'\n const title = String(body?.title ?? '').slice(0, 200)\n const excerpt = String(body?.excerpt ?? '').slice(0, 500)\n // The request must NAME the org it is about to be metered against\n // (AGL-2073, the AGL-1934 rule applied to this door). Resolving the org\n // from the user alone charged a multi-org user's assist to whichever org\n // came back first — and handed their free workspaces a paid feature.\n const orgId = String(body?.orgId ?? '').trim()\n const hostId = String(body?.hostId ?? '')\n .trim()\n .slice(0, 128)\n if (!instruction) {\n return res.status(400).json({ error: 'Missing instruction' })\n }\n if (!orgId) {\n return res\n .status(400)\n .json({ error: 'Open a workspace before using AI assist' })\n }\n\n let reservation: AssistReservation | null = null\n let providerAnswered = false\n let firestore: FirebaseFirestore.Firestore | null = null\n\n try {\n const app = firebaseAdmin.app()\n firestore = app.firestore()\n const decoded = await app.auth().verifyIdToken(idToken)\n\n // Plan gate (AGL-469): AI assist is a Pro+ entitlement with real token\n // cost — resolve the caller's org and check it server-side; a plan-less\n // org resolves as `free` and is denied. Scoped to the NAMED org, so\n // membership of some other paid org is not a key to this one.\n const resolved = await getOrgForUser(decoded.uid, orgId)\n if (!resolved || resolved.orgId !== orgId) {\n return res\n .status(403)\n .json({ error: 'You are not a member of that organization' })\n }\n const org = resolved.org ?? {}\n\n // Permission (AGL-2927), directly after membership. The element and\n // blog modes are assistance and sell under `ai.use`; a generated\n // section is a generation and sells under `ai.generate`. Decided on the\n // caller's own axis — the org catalog for an org-wide member, the site\n // the body named for a collaborator, who is refused when it named none.\n // A fact about the caller, so it is answered before the plan is. Staff\n // pass, as at every other org route. Beside it, the workspace-scoped\n // pause on `ai-assist`: the dispatcher already applied the platform-wide\n // switch by path, before it knew which org the body would name.\n const staff = decoded['staff'] === true\n const aiPaused = await featureLockdownRefusal({\n feature: 'ai-assist',\n staff,\n orgId,\n })\n if (aiPaused) return forwardRefusal(res, aiPaused)\n const permission = mode === 'section' ? 'ai.generate' : 'ai.use'\n if (\n !staff &&\n !(await memberHasPermissionOnHost(orgId, hostId, resolved.member, permission))\n ) {\n return forwardRefusal(res, permissionRefusal(permission))\n }\n\n const entitled = checkEntitlement(org, 'aiAssist')\n if (!entitled) {\n return res.status(403).json({ error: assistNotOnPlanText() })\n }\n\n // Org-scoped lockdown. The dispatcher already evaluates platform and user\n // scope plus the `ai-assist` feature kill switch, but its org scope needs\n // a `hostId` this route's callers do not send — so a suspended workspace\n // kept spending. The org doc is already in hand, so the verdict is free.\n const locked = await lockdownRefusal({\n request: { method: req.method },\n staff,\n uid: decoded.uid,\n org: org as Record<string, unknown>,\n })\n if (locked) return forwardRefusal(res, locked)\n\n const rate = checkRateLimit(`ai-assist:${decoded.uid}`, {\n limit: ASSIST_RATE_LIMIT,\n windowMs: ASSIST_RATE_WINDOW_MS,\n })\n if (!rate.allowed) {\n for (const [name, value] of Object.entries(rateLimitHeaders(rate))) {\n res.setHeader(name, value)\n }\n return res\n .status(429)\n .json({ error: 'Too many AI requests — slow down a moment' })\n }\n // The same window keyed on the trusted-hop client address (AGL-2925):\n // accounts rotated behind one address share one budget.\n const ipRate = checkAiClientIpRateLimit(headers)\n if (ipRate && !ipRate.allowed) {\n for (const [name, value] of Object.entries(rateLimitHeaders(ipRate))) {\n res.setHeader(name, value)\n }\n return res\n .status(429)\n .json({ error: 'Too many AI requests — slow down a moment' })\n }\n\n // RESERVED, not merely checked (AGL-2057/2073). Read-and-increment in one\n // transaction, BEFORE a single token is spent, so N concurrent requests\n // cannot all read the same \"under the cap\" and a client that drops the\n // connection has already been counted.\n try {\n // `org` is the document `entitled` came from, and it is what makes\n // the plan's own assist band bind rather than the operator backstop\n // alone. Generative building is the dearest thing this route serves,\n // so the band it spends against has to be the one that was sold.\n reservation = await reserveAssistMessage(\n firestore,\n orgId,\n entitled,\n new Date(),\n org,\n // The allotments that apply (AGL-2942): the asker's own, theirs on\n // the canvas's site, and the site's.\n { uid: decoded.uid, hostId: hostId || null },\n )\n } catch (error) {\n // FAIL CLOSED. The reservation is the only global bound on what this\n // route can spend at the provider; if it cannot be taken there is no cap,\n // and no cap is worse than no answer.\n console.error('assist reservation failed', error)\n return res\n .status(503)\n .json({ error: 'AI assist is temporarily unavailable' })\n }\n // A refusal is the asker's as well as the workspace's (AGL-2928): their\n // month counts it beside the org counter the reservation moved.\n recordUserAiRefusal(firestore, orgId, decoded.uid, reservation)\n if (!reservation.allowed) {\n const refusedBy = reservation.refusedBy\n // A hard allotment (AGL-2942): the monthly line a manager drew for\n // the asker or the site, with the sentence naming who can raise it.\n if (refusedBy === 'allotment') {\n return res.status(429).json({\n error: aiAllotmentRefusalText(reservation.allotment?.refusal?.scope),\n reason: 'quota',\n quota: publicAssistQuota(reservation),\n meter: aiUsageMeter(reservation),\n })\n }\n // The org's own controls are a 402, not a 429: credits past the band\n // are for sale and this workspace either switched the sale off\n // (AGL-2653) or capped what it would buy (AGL-2898), so the sentence\n // names the control that refused — the same sentence the console\n // assistant gives, from the same helper. Every other refusal keeps\n // the 429.\n // AGLYN'S OWN OVERAGE GUARDS (AGL-3011): a card, a pause, this\n // month's ceiling or a settling balance, each with the status that\n // says whether the workspace can act on it. Before the workspace's\n // own controls, which answer only for the controls it set itself.\n const overage = aiOverageReservationRefusal(reservation)\n if (overage) {\n return res.status(overage.status).json({\n error: overage.text,\n reason: 'quota',\n quota: publicAssistQuota(reservation),\n meter: aiUsageMeter(reservation),\n })\n }\n const ownControl = assistOwnControlRefusalText(org, refusedBy)\n return res.status(ownControl ? 402 : 429).json({\n error:\n ownControl ??\n // The Free taste's own precautions (AGL-2925), in the sentences\n // the console assistant uses.\n assistFreeTasteRefusalText(refusedBy) ??\n 'This workspace reached its AI assist limit for the month — contact support if you need a higher cap',\n // CREDITS, never the reservation itself — see `publicAssistQuota`.\n quota: publicAssistQuota(reservation),\n meter: aiUsageMeter(reservation),\n })\n }\n\n const tier: 'free' | 'entitled' = entitled ? 'entitled' : 'free'\n // ONE resolution, read by both the request and the meter — see\n // `assistModelForMode`. Two call sites would let them drift. A model the\n // asker picked (AGL-2942) replaces the routing table's answer only when\n // the plan, the org's restriction and the allotment allowlists all allow\n // it; anything else runs on Auto.\n const held = reservation\n const choice = resolveAiModelChoice(assistStepKindForMode(mode), body?.model, {\n plan: resolveEffectivePlan(org),\n allotmentModels: held.allotment?.models ?? null,\n orgModels: held.allotment?.orgModels ?? null,\n })\n const model = choice?.model ?? assistModelForMode(mode)\n /** The usage strip's envelope for an answered request (AGL-2942). */\n const meterFor = (result: AiResult) =>\n aiUsageMeter(held, {\n lastCredits: assistCreditsFromUsd(estimateAssistCostUsd(result.usage, model)),\n model: { id: model, auto: choice?.auto ?? true },\n })\n /** Meter what the provider actually charged us for, per org. */\n // The account the reservation drew on (AGL-2925), read once here: the\n // closure below runs after `reservation` may have been cleared by a\n // refund, and a refund never reaches the meter.\n const free = reservation?.free ?? null\n const meter = (result: AiResult): Promise<unknown> =>\n recordAssistCost(firestore, orgId, {\n route: `/api/ai/assist/${mode}`,\n hostId: hostId || null,\n model,\n tier,\n usage: result.usage,\n docsPaths: [],\n stopReason: result.stopReason,\n free,\n }).catch((error) => console.error('assist cost metering failed', error))\n\n /**\n * One provider call, with the refund decided at the boundary: a refused\n * request (a non-2xx answer) spent nothing, so the reservation goes back\n * — against the keys it RECORDED, never against \"now\" — and the caller\n * gets the runtime's fixed sentence, never the provider's (AGL-2815).\n * A result, even a refusal, means the tokens ARE spent: every exit after\n * it meters and none refunds. Anything else thrown never reached the\n * provider and is left to the outer catch, which refunds too.\n */\n const ask = async (\n content: string,\n maxTokens: number,\n tools?: AiTool[],\n ): Promise<AiResult | null> => {\n try {\n const result = await runAiRequest({\n model,\n maxTokens,\n stream: false,\n // The mode's prompt with the acceptable-use rules ahead of it\n // (AGL-2925), from `ai-assist-prompts.ts` so a spec can read what\n // this route sends without standing up the route.\n system: assistModeSystemBlocks(mode),\n messages: [{ role: 'user', content }],\n ...(tools ? { tools } : {}),\n })\n providerAnswered = true\n await meter(result)\n return result\n } catch (error) {\n if (!(error instanceof AiUpstreamError)) throw error\n providerAnswered = true\n await releaseReservation(firestore, orgId, reservation as AssistReservation)\n reservation = null\n res.status(502).json({ error: error.message })\n return null\n }\n }\n\n // Generate section (AGL-169): a constrained-JSON node subtree over the\n // marketplace component allowlist; the response passes the same\n // sanitizer as marketplace installs before it ever reaches a canvas.\n if (mode === 'section') {\n const result = await ask(\n `Section to design: ${instruction}`,\n SECTION_MAX_TOKENS,\n [assistSectionTool()],\n )\n if (!result) return\n if (result.kind === 'refusal') {\n return res.status(502).json({ ...REFUSED, meter: meterFor(result) })\n }\n const read = readAssistSection(result)\n if (read.status !== 'ok') {\n // Unreadable is the provider's fault and rejected is the answer's,\n // the two statuses this route has always drawn that line between.\n return res.status(read.status === 'unreadable' ? 502 : 422).json({ error: read.error })\n }\n // A subtree is going back (AGL-2929): the act, its size and its site in\n // the customer's feed — never the instruction or the copy generated.\n await logAiAssistSection(\n { uid: decoded.uid, email: decoded.email ?? null },\n {\n orgId,\n hostId: hostId || null,\n nodeCount: Object.keys(read.section.nodes).length,\n },\n )\n return res.status(200).json({ section: read.section, meter: meterFor(result) })\n }\n\n const result = await ask(\n mode === 'blog'\n ? [\n title && `Title: ${title}`,\n excerpt && `Excerpt: ${excerpt}`,\n text && `Current body:\\n${text}`,\n `Instruction: ${instruction}`,\n ]\n .filter(Boolean)\n .join('\\n\\n')\n : text\n ? `Current element text:\\n${text}\\n\\nInstruction: ${instruction}`\n : `Write website element text. Instruction: ${instruction}`,\n mode === 'blog' ? BLOG_MAX_TOKENS : ELEMENT_MAX_TOKENS,\n )\n if (!result) return\n if (result.kind === 'refusal') {\n return res.status(502).json({ ...REFUSED, meter: meterFor(result) })\n }\n const output = result.text.trim()\n if (!output) {\n return res.status(502).json({ error: 'Empty AI response', meter: meterFor(result) })\n }\n return res.status(200).json({ text: output, meter: meterFor(result) })\n } catch (error) {\n console.error(error)\n // The provider was never reached — a token verification failure, a DNS\n // error, a socket reset before any response. Nothing was spent, so the\n // reservation must not be. If the provider DID answer, the tokens are\n // real and the reservation stays consumed however the request ends.\n if (firestore && reservation && !providerAnswered) {\n await releaseReservation(firestore, orgId, reservation)\n }\n return res.status(500).json({ error: 'Assist failed' })\n }\n}\n\n/**\n * Hand a reservation back, never letting the refund itself fail the request.\n * Released against the keys the RESERVATION recorded — a reservation taken at\n * 23:59:59 and released at 00:00:01 must not credit the next day.\n */\nasync function releaseReservation(\n firestore: FirebaseFirestore.Firestore,\n orgId: string,\n reservation: AssistReservation,\n): Promise<void> {\n await releaseAssistMessage(firestore, orgId, reservation).catch((error) =>\n console.error('assist reservation release failed', error),\n )\n}\n\n/** Re-emit a lockdown `Response` through the plugin API's res object. */\nasync function forwardRefusal(\n res: PluginApiResponse,\n refusal: Response,\n): Promise<void> {\n const retryAfter = refusal.headers.get('Retry-After')\n if (retryAfter) res.setHeader('Retry-After', retryAfter)\n const body = await refusal.json().catch(() => ({ error: 'locked' }))\n res.status(refusal.status).json(body)\n}\n"],"names":["checkEntitlement","assistCreditsFromUsd","assistFreeTasteRefusalText","assistNotOnPlanText","assistOwnControlRefusalText","resolveEffectivePlan","aiAllotmentRefusalText","resolveAiModelChoice","aiUsageMeter","permissionRefusal","checkRateLimit","featureLockdownRefusal","firebaseAdmin","getOrgForUser","lockdownRefusal","memberHasPermissionOnHost","rateLimitHeaders","aiOverageReservationRefusal","recordUserAiRefusal","estimateAssistCostUsd","publicAssistQuota","recordAssistCost","releaseAssistMessage","reserveAssistMessage","logAiAssistSection","AI_ROUTING_TABLE","aiModelForStep","AiUpstreamError","aiProviderReady","runAiRequest","assistModeSystemBlocks","assistSectionTool","readAssistSection","checkAiClientIpRateLimit","assistModelForMode","mode","assistStepKindForMode","ASSIST_RATE_LIMIT","ASSIST_RATE_WINDOW_MS","SECTION_MAX_TOKENS","maxTokens","BLOG_MAX_TOKENS","ELEMENT_MAX_TOKENS","REFUSED","error","aiAssistHandler","req","res","headers","method","status","json","authorization","idToken","startsWith","slice","length","undefined","body","text","String","instruction","title","excerpt","orgId","trim","hostId","reservation","providerAnswered","firestore","resolved","held","app","decoded","auth","verifyIdToken","uid","org","staff","aiPaused","feature","forwardRefusal","permission","member","entitled","locked","request","rate","limit","windowMs","allowed","name","value","Object","entries","setHeader","ipRate","Date","console","ownControl","refusedBy","allotment","refusal","scope","reason","quota","meter","overage","tier","choice","model","plan","allotmentModels","models","orgModels","meterFor","result","lastCredits","usage","id","auto","free","route","docsPaths","stopReason","catch","ask","content","tools","stream","system","messages","role","releaseReservation","message","kind","read","email","nodeCount","keys","section","nodes","filter","Boolean","join","output","retryAfter","get"],"mappings":"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"}
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
* in the author's editor, on confirm. Below the rung the request is answered
|
|
75
75
|
* exactly as it would be without a canvas, and the outline is never parsed.
|
|
76
76
|
*
|
|
77
|
-
* Capability tiers: entitled orgs (`aiAssist`,
|
|
77
|
+
* Capability tiers: entitled orgs (`aiAssist`, Starter+) get docs-grounded
|
|
78
78
|
* answers PLUS page-context awareness (level 2 — the current route/host is
|
|
79
79
|
* injected so the assistant can walk the user through the view they are
|
|
80
80
|
* on). Free orgs get level 1 only: docs-grounded answers and deep links;
|