@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.231
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 +3 -3
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +7 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +38 -0
- package/src/lib/components/ai-automation-zones.js +5 -1
- package/src/lib/components/ai-automation-zones.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
- package/src/lib/components/ai-brief-dialog.component.js +87 -8
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
- package/src/lib/components/ai-campaign-create.component.js +270 -0
- package/src/lib/components/ai-campaign-create.component.js.map +1 -0
- package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
- package/src/lib/components/ai-describe-automation.component.js +15 -6
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +20 -8
- package/src/lib/components/ai-describe-button.component.js +19 -36
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-describe-email.component.d.ts +31 -0
- package/src/lib/components/ai-describe-email.component.js +22 -0
- package/src/lib/components/ai-describe-email.component.js.map +1 -0
- package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
- package/src/lib/components/ai-describe-org-automation.component.js +238 -0
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
- package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
- package/src/lib/components/ai-experiment-cards.component.js +104 -18
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -5
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
- package/src/lib/components/ai-funnel-zones.component.js +178 -0
- package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
- package/src/lib/components/ai-insight-card.component.d.ts +42 -0
- package/src/lib/components/ai-insight-card.component.js +137 -0
- package/src/lib/components/ai-insight-card.component.js.map +1 -0
- package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
- package/src/lib/components/ai-insight-dialog.component.js +9 -3
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +7 -2
- package/src/lib/components/ai-job-plan.component.js +38 -6
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +17 -1
- package/src/lib/components/ai-job-requests.js +39 -3
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-logic-zones.d.ts +64 -0
- package/src/lib/components/ai-logic-zones.js +18 -0
- package/src/lib/components/ai-logic-zones.js.map +1 -0
- package/src/lib/components/ai-logic.component.d.ts +77 -0
- package/src/lib/components/ai-logic.component.js +553 -0
- package/src/lib/components/ai-logic.component.js.map +1 -0
- package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
- package/src/lib/components/ai-marketing-insight.component.js +80 -0
- package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
- package/src/lib/components/ai-media-create.component.d.ts +55 -0
- package/src/lib/components/ai-media-create.component.js +433 -0
- package/src/lib/components/ai-media-create.component.js.map +1 -0
- package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
- package/src/lib/components/ai-overlay-cards.component.js +505 -0
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
- package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +5 -0
- package/src/lib/components/ai-product-zones.js +1 -1
- package/src/lib/components/ai-product-zones.js.map +1 -1
- package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
- package/src/lib/components/ai-products-create-button.component.js +67 -0
- package/src/lib/components/ai-products-create-button.component.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.js +100 -81
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
- package/src/lib/components/ai-revise-automation.component.js +207 -0
- package/src/lib/components/ai-revise-automation.component.js.map +1 -0
- package/src/lib/components/ai-site-build-page.component.js +160 -24
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
- package/src/lib/components/ai-upsell-dialog.component.js +177 -0
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.d.ts +42 -0
- package/src/lib/components/assist-build-card.component.js +183 -0
- package/src/lib/components/assist-build-card.component.js.map +1 -0
- package/src/lib/components/assist-edit-canvas.d.ts +2 -0
- package/src/lib/components/assist-edit-canvas.js +56 -1
- package/src/lib/components/assist-edit-canvas.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +48 -3
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +51 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -14
- package/src/lib/components/use-ai-job-run.js +0 -50
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
- package/src/lib/jobs/ai-build-capabilities.js +256 -0
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
- package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
- package/src/lib/jobs/ai-build-unit-outcome.js +140 -0
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
- package/src/lib/jobs/ai-job-build-step.js +668 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -0
- package/src/lib/jobs/ai-job-draft-ids.js +9 -1
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
- package/src/lib/jobs/ai-job-drafts.js +25 -4
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +8 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
- package/src/lib/jobs/ai-job-layout-language.js +194 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +105 -5
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
- package/src/lib/jobs/ai-job-logic-step.js +395 -0
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
- package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
- package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
- package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
- package/src/lib/jobs/ai-job-page-language.js +355 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +111 -20
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +27 -2
- package/src/lib/jobs/ai-job-plan-step.js +87 -13
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +19 -1
- package/src/lib/jobs/ai-job-site-step.js +220 -61
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
- package/src/lib/jobs/ai-job-text-step.js +12 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
- package/src/lib/jobs/ai-job-workflow-step.js +320 -15
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +80 -2
- package/src/lib/jobs/ai-jobs.js +391 -18
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/jobs/ai-logic-records.d.ts +40 -0
- package/src/lib/jobs/ai-logic-records.js +55 -0
- package/src/lib/jobs/ai-logic-records.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +7 -0
- package/src/lib/jobs/ai-site-publish.js +9 -0
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
- package/src/lib/jobs/ai-workflow-records.js +40 -1
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +457 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
- package/src/lib/layout-language/ai-layout-language.js +446 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
- package/src/lib/layout-language/ai-layout-store.js +69 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-automation-draft.d.ts +18 -0
- package/src/lib/model/ai-automation-draft.js +86 -0
- package/src/lib/model/ai-automation-draft.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +201 -0
- package/src/lib/model/ai-build-job.js +472 -0
- package/src/lib/model/ai-build-job.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +61 -0
- package/src/lib/model/ai-build-plan.js +154 -9
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +73 -0
- package/src/lib/model/ai-build-progress.js +170 -0
- package/src/lib/model/ai-build-progress.js.map +1 -0
- package/src/lib/model/ai-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +20 -10
- package/src/lib/model/ai-insight.js +72 -9
- package/src/lib/model/ai-insight.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +25 -2
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +99 -2
- package/src/lib/model/ai-jobs.types.js +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +108 -0
- package/src/lib/model/ai-logic-job.js +327 -0
- package/src/lib/model/ai-logic-job.js.map +1 -0
- package/src/lib/model/ai-media-credits.d.ts +52 -0
- package/src/lib/model/ai-media-credits.js +61 -0
- package/src/lib/model/ai-media-credits.js.map +1 -0
- package/src/lib/model/ai-overlay-copy.d.ts +114 -0
- package/src/lib/model/ai-overlay-copy.js +175 -0
- package/src/lib/model/ai-overlay-copy.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +25 -4
- package/src/lib/model/ai-site-build-progress.js +194 -11
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +8 -0
- package/src/lib/model/ai-site-job.js +9 -0
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +5 -2
- package/src/lib/model/ai-site-start.js +5 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-svg.d.ts +69 -0
- package/src/lib/model/ai-svg.js +265 -0
- package/src/lib/model/ai-svg.js.map +1 -0
- package/src/lib/model/ai-usage-by-user.d.ts +3 -2
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +92 -4
- package/src/lib/model/ai-workflow-job.js +124 -7
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/model/assist-build.d.ts +58 -0
- package/src/lib/model/assist-build.js +52 -0
- package/src/lib/model/assist-build.js.map +1 -0
- package/src/lib/model/assist-edit.d.ts +48 -3
- package/src/lib/model/assist-edit.js +48 -4
- package/src/lib/model/assist-edit.js.map +1 -1
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +308 -47
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +82 -5
- package/src/lib/providers/catalog.js +83 -12
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +116 -0
- package/src/lib/providers/image-contract.js +74 -0
- package/src/lib/providers/image-contract.js.map +1 -0
- package/src/lib/providers/model-choice.js +11 -0
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image.d.ts +121 -0
- package/src/lib/providers/vertex-image.js +312 -0
- package/src/lib/providers/vertex-image.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +101 -0
- package/src/lib/runtime/ai-doctrine-validators.js +525 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +21 -6
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +15 -1
- package/src/lib/runtime/ai-eval.js +52 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
- package/src/lib/runtime/experiment-variant-copy.js +102 -0
- package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
- package/src/lib/server/ai-experiment-versions.d.ts +51 -0
- package/src/lib/server/ai-experiment-versions.js +270 -0
- package/src/lib/server/ai-experiment-versions.js.map +1 -0
- package/src/lib/server/ai-jobs-resume.js +74 -4
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-media-image.d.ts +66 -0
- package/src/lib/server/ai-media-image.js +521 -0
- package/src/lib/server/ai-media-image.js.map +1 -0
- package/src/lib/server/ai-media-svg.d.ts +91 -0
- package/src/lib/server/ai-media-svg.js +186 -0
- package/src/lib/server/ai-media-svg.js.map +1 -0
- package/src/lib/server/assist-build.d.ts +54 -0
- package/src/lib/server/assist-build.js +121 -0
- package/src/lib/server/assist-build.js.map +1 -0
- package/src/lib/server/assist-chat.js +108 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +213 -3
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-view-context.js +242 -4
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tools/ai-logic-tool.d.ts +27 -0
- package/src/lib/tools/ai-logic-tool.js +207 -0
- package/src/lib/tools/ai-logic-tool.js.map +1 -0
- package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
- package/src/lib/tools/ai-overlay-tool.js +107 -0
- package/src/lib/tools/ai-overlay-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +17 -2
- package/src/lib/tools/ai-workflow-tool.js +47 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +28 -1
- package/src/lib/usage/assist-job-refund.js +44 -9
- package/src/lib/usage/assist-job-refund.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +5 -0
- package/src/lib/usage/assist-usage.js +2 -0
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/catalog.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 type { PluginSubprocessorDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-subprocessors'\nimport type { AiModelDescriptor, AiUsage } from './contract'\n\n/**\n * The model catalog (AGL-2939): every model id the plugin will route to,\n * with its provider, its display name, its capabilities and its list rates.\n * The ONE place a model id is written outside a provider adapter — the\n * routing table maps a step kind to an id through here, the meter prices\n * usage by id through here, and a settings form offers ids from here.\n *\n * ## Two rates, because one figure was answering two questions\n *\n * Every row carries a PROVIDER rate and a BILLED rate (AGL-3015):\n *\n * - `providerRates` is what the vendor charges us. It is money leaving the\n * company, so cost accounting, the margin surfaces and every staff spend\n * meter price usage through this one and no other.\n * - `billedRates` is what a customer's credits are charged at. The credit\n * meter, the band, the overage ladder and the model selector price usage\n * through this one, because it is the figure a customer's balance moves by.\n *\n * They are equal on nearly every row, and a helper writes the pair from one\n * figure so an unmarked row cannot drift apart by a typo. A row may be\n * billed ABOVE its provider rate deliberately — the markup is carried in the\n * rate rather than in the credit conversion, which keeps a credit a fixed\n * quantity of billed spend on every model and leaves the band, the ladder\n * and the invoice on the one arithmetic they were sized with. A row billed\n * BELOW its provider rate would sell tokens at a loss, and\n * `billed-rate-is-not-provider-cost.spec.ts` refuses one.\n *\n * Both are keyed by model rather than fixed at one model's rates because the\n * default is an env override: a one-line incident swap to a dearer model\n * would otherwise keep reporting the cheaper money, and per-org cost would\n * read as roughly right — the failure mode this whole meter exists to\n * prevent. An unknown id falls back to the most EXPENSIVE known tier on\n * purpose, on both rates: an estimate that errs low is worse than one that\n * errs high, whether the figure is our bill or the customer's.\n */\n\nexport interface AiTokenRates {\n inputPerToken: number\n outputPerToken: number\n cacheReadPerToken: number\n cacheWritePerToken: number\n}\n\n/** List price per MTok → per-token rates, cache read at 0.1x, write 1.25x. */\nexport function aiRatesPerMTok(inputPerMTok: number, outputPerMTok: number): AiTokenRates {\n return {\n inputPerToken: inputPerMTok / 1_000_000,\n outputPerToken: outputPerMTok / 1_000_000,\n cacheReadPerToken: (inputPerMTok * 0.1) / 1_000_000,\n cacheWritePerToken: (inputPerMTok * 1.25) / 1_000_000,\n }\n}\n\n/**\n * The two rate tables a catalog row carries. Separate fields rather than one\n * table and a multiplier: a reader that wants our cost has to name it, and\n * a reader that wants the customer's has to name that, so neither can be\n * reached by accident.\n */\nexport interface AiCatalogRates {\n /** What the provider bills us for this model. */\n providerRates: AiTokenRates\n /** What a customer's credits are charged at for this model. */\n billedRates: AiTokenRates\n}\n\n/**\n * A row billed at exactly the provider's list rate — the usual case, written\n * once so the pair cannot drift.\n */\nexport function aiRatesAtList(\n inputPerMTok: number,\n outputPerMTok: number,\n): AiCatalogRates {\n return {\n providerRates: aiRatesPerMTok(inputPerMTok, outputPerMTok),\n billedRates: aiRatesPerMTok(inputPerMTok, outputPerMTok),\n }\n}\n\nexport interface AiCatalogEntry extends AiModelDescriptor, AiCatalogRates {\n /**\n * The tier a routing decision reads: `fast` for short, constrained\n * answers; `balanced` for the assistant and briefs; `deep` for the\n * dearest, slowest class.\n */\n tier: 'fast' | 'balanced' | 'deep'\n /**\n * The shortest prefix this model's provider will cache (AGL-2937). A\n * request whose cached span falls under it is served with the markers\n * honored and NOTHING cached: no error, no warning, and a usage report\n * that reads as a permanent cache miss.\n *\n * It is a property of the model rather than of the tier or the vendor,\n * and it does not fall as a generation advances — so the number has to be\n * written beside the model, and a door that means to cache has to be held\n * to it (`aiCachedPrefixCaches` in the runtime, and the door table in\n * `runtime/ai-prompt-cache.spec.ts`). Without it, \"this block carries a\n * breakpoint\" is a claim nothing checks, and a prompt can be designed\n * around a cache it never had.\n */\n cacheMinTokens: number\n}\n\n/**\n * The sentinels a docs-only answer is metered under (AGL-2486): not\n * models, but ids the meter sees, priced at zero so a deflected turn lands\n * in the same rollup as a served one and a keyless deployment does not\n * read as the most efficient one on the platform.\n */\nexport const AI_METER_SENTINELS = {\n docsRetrieval: 'docs-retrieval',\n docsLinks: 'docs-links',\n answerCache: 'assist-cache',\n} as const\n\nconst ANTHROPIC = 'anthropic'\nconst OPENAI_COMPATIBLE = 'openai-compatible'\n\n/**\n * A provider's row on the published subprocessor list: every field but the\n * host, which the registered adapter reports as its `endpointHost`.\n * `{apiKeyEnv}` anywhere in the wording stands for the adapter's\n * `apiKeyEnv`, so a credential's name is written in its adapter alone and\n * never in this module, which the browser bundle carries.\n */\nexport type AiProviderSubprocessorWording = Omit<PluginSubprocessorDeclaration, 'host'>\n\n/** A provider the catalog carries models for. */\nexport interface AiCatalogProvider {\n id: string\n /** The name a settings form shows. */\n label: string\n /**\n * The vendor's row on the published subprocessor list, written here and\n * nowhere else: `aiSubprocessors` derives the plugin's declarations from\n * it. Absent for a provider whose endpoint an operator names, which has\n * no fixed recipient to publish.\n */\n subprocessor?: AiProviderSubprocessorWording\n}\n\n/**\n * The Anthropic row's data cell up to its CRM sentence: every door but CRM\n * assistance, worded once for both of the CRM sentences below.\n */\nconst ANTHROPIC_DATA_RECEIVED_LEAD =\n \"What the user submits — a question, instruction or brief, with the earlier messages of the same Assist conversation — and the content of the element, post, section or page being worked on, with the generated response. On Pro and above, the organization's name and the console route and host travel with an Assist question. For an edit the assistant proposes in the besigner, an outline of the open page, component or layout: element and component ids, layer names, shortened setting values and the selected element's styles. For a generation job, the site inventory: the names and addresses of its pages and collections, the names of its components, layouts, templates, forms and datasets with their prop and field names, and the theme's summary, colors and fonts. For a theme change, the site's current theme settings and brand colors as hex values, from the organization's brand settings, the site logo in the media library or a public page the brief links to. For features that review or write search information, the text and structure of the pages concerned. For product copy, the store's name, the product's name, type, description, tags, options and search listing, the store's category names, and the product's first media-library photo as a copy at most 768 px on its longer edge with its metadata stripped; never another media file, a price, stock, an order or a customer. For products, categories and discounts proposed from a brief, the store's name and its existing category names. For an automation drafted from a brief, which of CRM, webhooks and bookings the plan includes; for an explanation, the automation's outline (trigger, conditions, each step's text with the names of the lists, campaigns, workflows, webhooks and datasets it uses and whether each exists, and a workflow's function names and expressions) and, for a failed run, its time, steps and recorded errors, with email addresses removed and never the triggering event's data. For an insight, the figure reports available and aggregate tables: traffic with top page paths, referrers and campaign tags; form views and submissions; revenue, orders and best-selling product names; bookings by service; campaign subjects with delivery, open and click rates; A/B test and variant conversions; and, for a dataset the member can see, field names and types, record and fill counts, number ranges and totals grouped by a value at least three records share. Email addresses and phone numbers are removed, and no individual record is sent. \"\n\n/** What every CRM door adds after its record sentence: drafts and imports. */\nconst ANTHROPIC_DATA_RECEIVED_CRM_DRAFTS_AND_IMPORTS =\n \"An email draft adds the request and the record's merge field names; an import sends the field names and types and each column's header and value kind, never a row.\"\n\n/** How the data cell words CRM assistance: what a record sends, and what never leaves. */\nexport interface AnthropicCrmAssistanceWording {\n record: string\n never: string\n}\n\n/**\n * CRM assistance as `/legal/subprocessors` published it until October 5,\n * 2026: the disclosed fields of a record, with addresses and numbers in its\n * text replaced. It is still what production sends while\n * `release_crm_assist_whole_record` is off, which the published wording\n * covers: the row describes what the CRM can send, not who has it on.\n */\nexport const ANTHROPIC_CRM_ASSISTANCE_DISCLOSED: AnthropicCrmAssistanceWording = {\n record:\n \"For CRM assistance, the opened contact, company, deal or lead as the CRM shows it: its name and, by kind, job title, company, lifecycle stage, tags, capture history and counts, domain, industry, headcount, pipeline stages, status, amount, dates, lost reason, parties and lead status, with its notes, newest timeline entries and open tasks and deals. Email addresses and phone numbers in that text are replaced, and no email, phone or postal field, marketing consent, custom field value, team member or record id is sent.\",\n never: 'No account identifiers, email addresses or authentication tokens.',\n}\n\n/**\n * CRM assistance once the whole record is sent (AGL-3520): the open-ended\n * wording the Subprocessors row and Privacy Policy section 2 were\n * republished with on October 5, 2026 (legal v10), before\n * `release_crm_assist_whole_record` may be turned on. The catalog row below\n * carries it, dated to that change-log entry.\n */\nexport const ANTHROPIC_CRM_ASSISTANCE_WHOLE_RECORD: AnthropicCrmAssistanceWording = {\n record:\n 'For CRM assistance, the opened record as the CRM shows it, with all of its standard and custom fields, including contact details, notes, timeline and related records.',\n never: 'No account identifiers or authentication tokens, and no email address outside an opened CRM record.',\n}\n\n/** The Anthropic row's data cell, with CRM assistance worded as `crm` words it. */\nexport function anthropicDataReceived(crm: AnthropicCrmAssistanceWording): string {\n return `${ANTHROPIC_DATA_RECEIVED_LEAD}${crm.record} ${ANTHROPIC_DATA_RECEIVED_CRM_DRAFTS_AND_IMPORTS} ${crm.never}`\n}\n\n/**\n * The providers the catalog carries models for, with the name a settings\n * form shows. A form offers providers from here for the reason it offers\n * models from here: nothing outside this folder names a vendor. For the\n * same reason a vendor's subprocessor row is written here too.\n */\nexport const AI_CATALOG_PROVIDERS: readonly AiCatalogProvider[] = [\n {\n id: ANTHROPIC,\n label: 'Anthropic',\n subprocessor: {\n entity: 'Anthropic, PBC',\n region: 'United States',\n purpose:\n \"AI-assisted features in the console and the site editor: the Aglyn Assist helper, including changes it proposes to a page, component, or layout open in the editor; editor assistance (rewriting element copy, drafting blog bodies, generating a section layout); and AI generation, which creates drafts and proposals for a customer's site from a brief or from what the site already holds, such as copy, layouts, templates, automations, search titles and descriptions, theme changes, product descriptions and tags, draft products, and store categories and discounts; explanations of a site's automations, including why an automation's run failed; AI insights, which answer a customer's questions about its own figures, such as site traffic, sales, bookings, forms, campaigns, A/B tests and datasets, and write a weekly summary of them for members who ask for one; and AI assistance in the CRM, which summarizes a contact, company, deal or lead for a user and suggests a next step, drafts a one-to-one email for the user to review and send, and suggests how the columns of a spreadsheet the user imports match CRM fields\",\n publishedOn: '2026-10-05',\n reason:\n \"Reached through the AI plugin's Anthropic adapter (`libs/plugins/ai/src/lib/providers/anthropic.ts`) by the doors that call the AI runtime. `libs/plugins/ai/src/lib/server/assist-chat.ts` is gated by `release_assist` AND the key, and a generation job's text step by `release_ai_generative`; `libs/plugins/ai/src/lib/server/ai-assist.ts` carries NO release flag, so setting `{apiKeyEnv}` in production is by itself what starts this flow. `assist-anthropic-subprocessor-gate.spec.ts` holds the per-door detail and is the deeper guard for this one vendor.\",\n dataReceived: anthropicDataReceived(ANTHROPIC_CRM_ASSISTANCE_WHOLE_RECORD),\n },\n },\n { id: OPENAI_COMPATIBLE, label: 'OpenAI-compatible endpoint' },\n]\n\n/**\n * `vision` is written on every row rather than assumed (AGL-2916): each model\n * below reads a picture in a user turn, in the four formats\n * `AI_IMAGE_MEDIA_TYPES` names, as its vendor documents. A row added without\n * it reads as a model that does not, and is never sent one.\n */\nconst anthropicCapabilities = {\n streaming: true,\n tools: true,\n thinking: true,\n promptCache: true,\n vision: true,\n}\n\nexport const AI_MODEL_CATALOG: readonly AiCatalogEntry[] = [\n {\n id: 'claude-sonnet-5',\n provider: ANTHROPIC,\n label: 'Claude Sonnet 5',\n capabilities: anthropicCapabilities,\n /*\n * THE ONE ROW BILLED ABOVE ITS PROVIDER RATE (AGL-3015).\n *\n * The balanced tier prices most requests, so this markup is most of the\n * platform's AI margin. It is carried in the rate and not in the credit\n * conversion for the reason the module head gives: a credit stays a\n * fixed quantity of billed spend on every model, so the band, the\n * overage ladder and the invoice keep the arithmetic they were sized\n * with, and nothing below has to know which model served a request.\n *\n * Everything that reasons about what we PAY — the margin surfaces, the\n * staff spend meters, the discount guardrail's cost of goods — reads\n * `providerRates`, which is the vendor's published list.\n */\n providerRates: aiRatesPerMTok(2, 10),\n billedRates: aiRatesPerMTok(3, 15),\n tier: 'balanced',\n cacheMinTokens: 1_024,\n },\n {\n id: 'claude-sonnet-4-6',\n provider: ANTHROPIC,\n label: 'Claude Sonnet 4.6',\n capabilities: anthropicCapabilities,\n ...aiRatesAtList(3, 15),\n tier: 'balanced',\n cacheMinTokens: 1_024,\n },\n {\n // Rejects an explicit `thinking` setting; the runtime omits it.\n id: 'claude-haiku-4-5',\n provider: ANTHROPIC,\n label: 'Claude Haiku 4.5',\n capabilities: { ...anthropicCapabilities, thinking: false },\n ...aiRatesAtList(1, 5),\n tier: 'fast',\n // Four times the balanced tier's minimum: the cheapest model per token\n // is the hardest one to cache for, which is why a short prompt moved\n // here can cost more per request than it saved per token.\n cacheMinTokens: 4_096,\n },\n {\n id: 'claude-opus-5',\n provider: ANTHROPIC,\n label: 'Claude Opus 5',\n capabilities: anthropicCapabilities,\n ...aiRatesAtList(5, 25),\n tier: 'deep',\n cacheMinTokens: 512,\n },\n {\n id: 'claude-opus-4-8',\n provider: ANTHROPIC,\n label: 'Claude Opus 4.8',\n capabilities: anthropicCapabilities,\n ...aiRatesAtList(5, 25),\n tier: 'deep',\n cacheMinTokens: 1_024,\n },\n /**\n * The OpenAI-compatible adapter serves whatever the endpoint behind\n * `AI_OPENAI_COMPAT_BASE_URL` serves; these are the ids the routing table\n * may name on it. The figures are the vendor's public list, billed at\n * list: a deployment that reaches its own gateway at a negotiated price\n * is paying less than this says, so its margin reads low rather than\n * high — the direction a cost figure may be wrong in.\n */\n {\n id: 'gpt-5',\n provider: OPENAI_COMPATIBLE,\n label: 'GPT-5',\n capabilities: { streaming: true, tools: true, thinking: false, promptCache: true, vision: true },\n ...aiRatesAtList(1.25, 10),\n tier: 'balanced',\n cacheMinTokens: 1_024,\n },\n {\n id: 'gpt-5-mini',\n provider: OPENAI_COMPATIBLE,\n label: 'GPT-5 mini',\n capabilities: { streaming: true, tools: true, thinking: false, promptCache: true, vision: true },\n ...aiRatesAtList(0.25, 2),\n tier: 'fast',\n cacheMinTokens: 1_024,\n },\n]\n\n/** The dearest known tier, used when a model id is not in the catalog. */\nexport const AI_FALLBACK_RATES: AiTokenRates = aiRatesPerMTok(10, 50)\n\n/**\n * The minimum an unknown model id is assumed to have: the largest one the\n * catalog knows. It errs the way the rate fallback errs — toward the answer\n * that costs more — so an unrecognized id reads as \"this prompt does not\n * cache\" rather than promising a saving nobody measured.\n */\nexport const AI_FALLBACK_CACHE_MIN_TOKENS = 4_096\n\n/** The shortest prefix a model's provider will cache; the dearest assumption for an unknown id. */\nexport function aiModelCacheMinTokens(modelId: string): number {\n return aiCatalogEntry(modelId)?.cacheMinTokens ?? AI_FALLBACK_CACHE_MIN_TOKENS\n}\n\nconst ZERO_RATES = aiRatesPerMTok(0, 0)\n\n/** The catalog row for a model id, or `undefined` for an unknown id. */\nexport function aiCatalogEntry(modelId: string): AiCatalogEntry | undefined {\n return AI_MODEL_CATALOG.find((entry) => entry.id === modelId)\n}\n\nconst isSentinel = (modelId: string): boolean =>\n (Object.values(AI_METER_SENTINELS) as string[]).includes(modelId)\n\n/**\n * What the PROVIDER charges for a model id — our bill. A meter sentinel is\n * free; an unknown id is priced at the dearest tier.\n *\n * The figure every cost and margin surface prices usage at. Answering the\n * customer's question from here would report a margin the platform does not\n * earn, and answering ours from `aiBilledRatesForModel` reports one it does\n * not pay; the two are separate functions so neither is reachable by\n * forgetting which one was meant.\n */\nexport function aiProviderRatesForModel(modelId: string): AiTokenRates {\n if (isSentinel(modelId)) return ZERO_RATES\n return aiCatalogEntry(modelId)?.providerRates ?? AI_FALLBACK_RATES\n}\n\n/**\n * What a CUSTOMER'S CREDITS are charged at for a model id. A meter sentinel\n * is free; an unknown id is priced at the dearest tier.\n *\n * The figure the credit meter, the band and the model selector price usage\n * at. It is at or above `aiProviderRatesForModel` on every row.\n */\nexport function aiBilledRatesForModel(modelId: string): AiTokenRates {\n if (isSentinel(modelId)) return ZERO_RATES\n return aiCatalogEntry(modelId)?.billedRates ?? AI_FALLBACK_RATES\n}\n\nfunction priceUsage(usage: AiUsage, rate: AiTokenRates): number {\n const raw =\n usage.inputTokens * rate.inputPerToken +\n usage.outputTokens * rate.outputPerToken +\n usage.cacheReadTokens * rate.cacheReadPerToken +\n usage.cacheWriteTokens * rate.cacheWritePerToken\n return Math.round(raw * 1_000_000) / 1_000_000\n}\n\n/**\n * What one exchange COST US, at the serving model's provider rates, rounded\n * to 6dp. Real money, and the only figure a margin may be taken against.\n */\nexport function estimateAiProviderCostUsd(usage: AiUsage, modelId: string): number {\n return priceUsage(usage, aiProviderRatesForModel(modelId))\n}\n\n/**\n * What one exchange DRAWS FROM A CUSTOMER, at the serving model's billed\n * rates, rounded to 6dp — the figure `assistCreditsFromUsd` turns into\n * credits. At or above `estimateAiProviderCostUsd` for the same exchange.\n */\nexport function estimateAiBilledUsd(usage: AiUsage, modelId: string): number {\n return priceUsage(usage, aiBilledRatesForModel(modelId))\n}\n\n/** Every catalog id a provider serves. */\nexport function aiModelIdsForProvider(providerId: string): string[] {\n return AI_MODEL_CATALOG.filter((entry) => entry.provider === providerId).map(\n (entry) => entry.id,\n )\n}\n\n/**\n * What a step asks for, and what the routing table answers with. Every\n * door names its kind here rather than a model, so a model swap — an\n * incident override, an org's own setting, a cheaper model for a cheap\n * step — is one row rather than a search through the doors (AGL-2937).\n */\nexport type AiStepKind =\n | 'assist.chat'\n | 'copy.element'\n | 'copy.section'\n | 'copy.blog'\n | 'generate.section'\n | 'job.component'\n | 'job.crm'\n | 'job.experiment'\n | 'job.form'\n | 'job.insight'\n | 'job.layout'\n | 'job.template'\n | 'job.page'\n | 'job.products'\n | 'job.seo'\n | 'job.text'\n | 'job.theme'\n | 'job.plan'\n | 'job.email'\n | 'job.campaign'\n | 'job.workflow'\n\n/** The tier each step kind is served from when no setting overrides it. */\nexport const AI_STEP_TIERS: Record<AiStepKind, AiCatalogEntry['tier']> = {\n 'assist.chat': 'balanced',\n 'copy.element': 'fast',\n 'copy.section': 'balanced',\n 'copy.blog': 'balanced',\n 'generate.section': 'balanced',\n // A reusable component is one structured tree with the typed props it\n // declares, and choosing what becomes a prop is the judgment the step sells.\n 'job.component': 'balanced',\n // CRM by AI (AGL-2917): a record's summary and next step, a deal's stage, a\n // lead's standing, a one-to-one email draft and an import's column matches.\n // Short answers through a strict tool, held to the facts the CRM reports\n // about the record the member opened.\n 'job.crm': 'fast',\n // A/B tests by AI (AGL-2914): variants of one piece of copy, and a\n // result put into words for a verdict code already reached. Short answers\n // through a strict tool, but the judgment a variant sells — which ONE idea\n // to vary so the result says something — is the balanced tier's.\n 'job.experiment': 'balanced',\n // A form is one small tree held to the building rules and to the contract\n // its submissions are read by; a re-ask costs more than the tier saves.\n 'job.form': 'balanced',\n // An insight (AGL-2915) chooses which figures answer a question and says\n // what they show; the trace holds every number, and the judgment of what is\n // worth saying is what the fast tier does worse.\n 'job.insight': 'balanced',\n // A layout or a page template is one structured tree held to every\n // building rule; the fast tier re-asks more than it saves.\n 'job.layout': 'balanced',\n 'job.template': 'balanced',\n // A page section (AGL-2907) is held to the whole page's building rules on\n // every pass, for the same reason.\n 'job.page': 'balanced',\n // A product's copy, a store's first products, or its categories and\n // discounts (AGL-2916): judgment about what a photo shows and what a shopper\n // needs, held to storefront claim rules a re-ask costs more to meet than the\n // tier saves.\n 'job.products': 'balanced',\n // SEO fields, alt text and an audit's fixes (AGL-2910): short answers\n // through a strict tool, held to a length and to the page's own text.\n 'job.seo': 'fast',\n 'job.text': 'balanced',\n // A theme is one structured answer over a small, fixed control set; the\n // judgment is in the color choices, which the fast tier makes worse.\n 'job.theme': 'balanced',\n 'job.plan': 'balanced',\n // An email is one tree held to every building rule, with subject lines\n // whose judgment is the point of asking; the fast tier re-asks more than it\n // saves. A campaign is the same email, drafted into a campaign.\n 'job.email': 'balanced',\n 'job.campaign': 'balanced',\n // An automation is a small structured answer whose judgment is choosing the\n // trigger and steps a description means, and an explanation reads one\n // closely; the fast tier re-asks more than it saves on both (AGL-2919).\n 'job.workflow': 'balanced',\n}\n\n/** The first catalog model of a tier on a provider, or the provider's first model. */\nexport function aiDefaultModelFor(providerId: string, tier: AiCatalogEntry['tier']): string | undefined {\n const onProvider = AI_MODEL_CATALOG.filter((entry) => entry.provider === providerId)\n return (onProvider.find((entry) => entry.tier === tier) ?? onProvider[0])?.id\n}\n"],"names":["aiRatesPerMTok","inputPerMTok","outputPerMTok","inputPerToken","outputPerToken","cacheReadPerToken","cacheWritePerToken","aiRatesAtList","providerRates","billedRates","AI_METER_SENTINELS","docsRetrieval","docsLinks","answerCache","ANTHROPIC","OPENAI_COMPATIBLE","ANTHROPIC_DATA_RECEIVED_LEAD","ANTHROPIC_DATA_RECEIVED_CRM_DRAFTS_AND_IMPORTS","ANTHROPIC_CRM_ASSISTANCE_DISCLOSED","record","never","ANTHROPIC_CRM_ASSISTANCE_WHOLE_RECORD","anthropicDataReceived","crm","AI_CATALOG_PROVIDERS","id","label","subprocessor","entity","region","purpose","publishedOn","reason","dataReceived","anthropicCapabilities","streaming","tools","thinking","promptCache","vision","AI_MODEL_CATALOG","provider","capabilities","tier","cacheMinTokens","AI_FALLBACK_RATES","AI_FALLBACK_CACHE_MIN_TOKENS","aiModelCacheMinTokens","modelId","aiCatalogEntry","ZERO_RATES","find","entry","isSentinel","Object","values","includes","aiProviderRatesForModel","aiBilledRatesForModel","priceUsage","usage","rate","raw","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","Math","round","estimateAiProviderCostUsd","estimateAiBilledUsd","aiModelIdsForProvider","providerId","filter","map","AI_STEP_TIERS","aiDefaultModelFor","onProvider"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/catalog.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 type { PluginSubprocessorDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-subprocessors'\nimport type { AiModelDescriptor, AiUsage } from './contract'\n\n/**\n * The model catalog (AGL-2939): every model id the plugin will route to,\n * with its provider, its display name, its capabilities and its list rates.\n * The ONE place a model id is written outside a provider adapter — the\n * routing table maps a step kind to an id through here, the meter prices\n * usage by id through here, and a settings form offers ids from here.\n *\n * ## Two rates, because one figure was answering two questions\n *\n * Every row carries a PROVIDER rate and a BILLED rate (AGL-3015):\n *\n * - `providerRates` is what the vendor charges us. It is money leaving the\n * company, so cost accounting, the margin surfaces and every staff spend\n * meter price usage through this one and no other.\n * - `billedRates` is what a customer's credits are charged at. The credit\n * meter, the band, the overage ladder and the model selector price usage\n * through this one, because it is the figure a customer's balance moves by.\n *\n * They are equal on nearly every row, and a helper writes the pair from one\n * figure so an unmarked row cannot drift apart by a typo. A row may be\n * billed ABOVE its provider rate deliberately — the markup is carried in the\n * rate rather than in the credit conversion, which keeps a credit a fixed\n * quantity of billed spend on every model and leaves the band, the ladder\n * and the invoice on the one arithmetic they were sized with. A row billed\n * BELOW its provider rate would sell tokens at a loss, and\n * `billed-rate-is-not-provider-cost.spec.ts` refuses one.\n *\n * Both are keyed by model rather than fixed at one model's rates because the\n * default is an env override: a one-line incident swap to a dearer model\n * would otherwise keep reporting the cheaper money, and per-org cost would\n * read as roughly right — the failure mode this whole meter exists to\n * prevent. An unknown id falls back to the most EXPENSIVE known tier on\n * purpose, on both rates: an estimate that errs low is worse than one that\n * errs high, whether the figure is our bill or the customer's.\n */\n\nexport interface AiTokenRates {\n inputPerToken: number\n outputPerToken: number\n cacheReadPerToken: number\n cacheWritePerToken: number\n}\n\n/** List price per MTok → per-token rates, cache read at 0.1x, write 1.25x. */\nexport function aiRatesPerMTok(inputPerMTok: number, outputPerMTok: number): AiTokenRates {\n return {\n inputPerToken: inputPerMTok / 1_000_000,\n outputPerToken: outputPerMTok / 1_000_000,\n cacheReadPerToken: (inputPerMTok * 0.1) / 1_000_000,\n cacheWritePerToken: (inputPerMTok * 1.25) / 1_000_000,\n }\n}\n\n/**\n * The two rate tables a catalog row carries. Separate fields rather than one\n * table and a multiplier: a reader that wants our cost has to name it, and\n * a reader that wants the customer's has to name that, so neither can be\n * reached by accident.\n */\nexport interface AiCatalogRates {\n /** What the provider bills us for this model. */\n providerRates: AiTokenRates\n /** What a customer's credits are charged at for this model. */\n billedRates: AiTokenRates\n}\n\n/**\n * A row billed at exactly the provider's list rate — the usual case, written\n * once so the pair cannot drift.\n */\nexport function aiRatesAtList(\n inputPerMTok: number,\n outputPerMTok: number,\n): AiCatalogRates {\n return {\n providerRates: aiRatesPerMTok(inputPerMTok, outputPerMTok),\n billedRates: aiRatesPerMTok(inputPerMTok, outputPerMTok),\n }\n}\n\nexport interface AiCatalogEntry extends AiModelDescriptor, AiCatalogRates {\n /**\n * The tier a routing decision reads: `fast` for short, constrained\n * answers; `balanced` for the assistant and briefs; `deep` for the\n * dearest, slowest class.\n */\n tier: 'fast' | 'balanced' | 'deep'\n /**\n * The shortest prefix this model's provider will cache (AGL-2937). A\n * request whose cached span falls under it is served with the markers\n * honored and NOTHING cached: no error, no warning, and a usage report\n * that reads as a permanent cache miss.\n *\n * It is a property of the model rather than of the tier or the vendor,\n * and it does not fall as a generation advances — so the number has to be\n * written beside the model, and a door that means to cache has to be held\n * to it (`aiCachedPrefixCaches` in the runtime, and the door table in\n * `runtime/ai-prompt-cache.spec.ts`). Without it, \"this block carries a\n * breakpoint\" is a claim nothing checks, and a prompt can be designed\n * around a cache it never had.\n */\n cacheMinTokens: number\n}\n\n/**\n * The sentinels a docs-only answer is metered under (AGL-2486): not\n * models, but ids the meter sees, priced at zero so a deflected turn lands\n * in the same rollup as a served one and a keyless deployment does not\n * read as the most efficient one on the platform.\n */\nexport const AI_METER_SENTINELS = {\n docsRetrieval: 'docs-retrieval',\n docsLinks: 'docs-links',\n answerCache: 'assist-cache',\n} as const\n\nconst ANTHROPIC = 'anthropic'\nconst OPENAI_COMPATIBLE = 'openai-compatible'\n\n/**\n * A provider's row on the published subprocessor list: every field but the\n * host, which the registered adapter reports as its `endpointHost`.\n * `{apiKeyEnv}` anywhere in the wording stands for the adapter's\n * `apiKeyEnv`, so a credential's name is written in its adapter alone and\n * never in this module, which the browser bundle carries.\n */\nexport type AiProviderSubprocessorWording = Omit<PluginSubprocessorDeclaration, 'host'>\n\n/** A provider the catalog carries models for. */\nexport interface AiCatalogProvider {\n id: string\n /** The name a settings form shows. */\n label: string\n /**\n * The vendor's row on the published subprocessor list, written here and\n * nowhere else: `aiSubprocessors` derives the plugin's declarations from\n * it. Absent for a provider whose endpoint an operator names, which has\n * no fixed recipient to publish.\n */\n subprocessor?: AiProviderSubprocessorWording\n}\n\n/**\n * The Anthropic row's data cell up to its CRM sentence: every door but CRM\n * assistance, worded once for both of the CRM sentences below.\n */\nconst ANTHROPIC_DATA_RECEIVED_LEAD =\n \"What the user submits — a question, instruction or brief, with the earlier messages of the same Assist conversation — and the content of the element, post, section or page being worked on, with the generated response. On Pro and above, the organization's name and the console route and host travel with an Assist question. For an edit the assistant proposes in the besigner, an outline of the open page, component or layout: element and component ids, layer names, shortened setting values and the selected element's styles. For a generation job, the site inventory: the names and addresses of its pages and collections, the names of its components, layouts, templates, forms and datasets with their prop and field names, and the theme's summary, colors and fonts. For a theme change, the site's current theme settings and brand colors as hex values, from the organization's brand settings, the site logo in the media library or a public page the brief links to. For features that review or write search information, the text and structure of the pages concerned. For product copy, the store's name, the product's name, type, description, tags, options and search listing, the store's category names, and the product's first media-library photo as a copy at most 768 px on its longer edge with its metadata stripped; never another media file, a price, stock, an order or a customer. For products, categories and discounts proposed from a brief, the store's name and its existing category names. For an automation drafted from a brief, which of CRM, webhooks and bookings the plan includes; for an explanation, the automation's outline (trigger, conditions, each step's text with the names of the lists, campaigns, workflows, webhooks and datasets it uses and whether each exists, and a workflow's function names and expressions) and, for a failed run, its time, steps and recorded errors, with email addresses removed and never the triggering event's data. For an insight, the figure reports available and aggregate tables: traffic with top page paths, referrers and campaign tags; form views and submissions; revenue, orders and best-selling product names; bookings by service; campaign subjects with delivery, open and click rates; A/B test and variant conversions; and, for a dataset the member can see, field names and types, record and fill counts, number ranges and totals grouped by a value at least three records share. Email addresses and phone numbers are removed, and no individual record is sent. \"\n\n/** What every CRM door adds after its record sentence: drafts and imports. */\nconst ANTHROPIC_DATA_RECEIVED_CRM_DRAFTS_AND_IMPORTS =\n \"An email draft adds the request and the record's merge field names; an import sends the field names and types and each column's header and value kind, never a row.\"\n\n/** How the data cell words CRM assistance: what a record sends, and what never leaves. */\nexport interface AnthropicCrmAssistanceWording {\n record: string\n never: string\n}\n\n/**\n * CRM assistance as `/legal/subprocessors` published it until October 5,\n * 2026: the disclosed fields of a record, with addresses and numbers in its\n * text replaced. It is still what production sends while\n * `release_crm_assist_whole_record` is off, which the published wording\n * covers: the row describes what the CRM can send, not who has it on.\n */\nexport const ANTHROPIC_CRM_ASSISTANCE_DISCLOSED: AnthropicCrmAssistanceWording = {\n record:\n \"For CRM assistance, the opened contact, company, deal or lead as the CRM shows it: its name and, by kind, job title, company, lifecycle stage, tags, capture history and counts, domain, industry, headcount, pipeline stages, status, amount, dates, lost reason, parties and lead status, with its notes, newest timeline entries and open tasks and deals. Email addresses and phone numbers in that text are replaced, and no email, phone or postal field, marketing consent, custom field value, team member or record id is sent.\",\n never: 'No account identifiers, email addresses or authentication tokens.',\n}\n\n/**\n * CRM assistance once the whole record is sent (AGL-3520): the open-ended\n * wording the Subprocessors row and Privacy Policy section 2 were\n * republished with on October 5, 2026 (legal v10), before\n * `release_crm_assist_whole_record` may be turned on. The catalog row below\n * carries it in `dataReceived`.\n */\nexport const ANTHROPIC_CRM_ASSISTANCE_WHOLE_RECORD: AnthropicCrmAssistanceWording = {\n record:\n 'For CRM assistance, the opened record as the CRM shows it, with all of its standard and custom fields, including contact details, notes, timeline and related records.',\n never: 'No account identifiers or authentication tokens, and no email address outside an opened CRM record.',\n}\n\n/** The Anthropic row's data cell, with CRM assistance worded as `crm` words it. */\nexport function anthropicDataReceived(crm: AnthropicCrmAssistanceWording): string {\n return `${ANTHROPIC_DATA_RECEIVED_LEAD}${crm.record} ${ANTHROPIC_DATA_RECEIVED_CRM_DRAFTS_AND_IMPORTS} ${crm.never}`\n}\n\n/**\n * The providers the catalog carries models for, with the name a settings\n * form shows. A form offers providers from here for the reason it offers\n * models from here: nothing outside this folder names a vendor. For the\n * same reason a vendor's subprocessor row is written here too.\n */\nexport const AI_CATALOG_PROVIDERS: readonly AiCatalogProvider[] = [\n {\n id: ANTHROPIC,\n label: 'Anthropic',\n subprocessor: {\n entity: 'Anthropic, PBC',\n region: 'United States',\n purpose:\n \"AI features of the Services, including the Aglyn Assist helper: creating, editing and reviewing content, drafts and settings for a customer’s site, store and CRM, and explaining and summarizing the customer’s own setup and data\",\n publishedOn: '2026-10-07',\n reason:\n \"Reached through the AI plugin's Anthropic adapter (`libs/plugins/ai/src/lib/providers/anthropic.ts`) by the doors that call the AI runtime. `libs/plugins/ai/src/lib/server/assist-chat.ts` is gated by `release_assist` AND the key, and a generation job's text step by `release_ai_generative`; `libs/plugins/ai/src/lib/server/ai-assist.ts` carries NO release flag, so setting `{apiKeyEnv}` in production is by itself what starts this flow. `assist-anthropic-subprocessor-gate.spec.ts` holds the per-door detail and is the deeper guard for this one vendor.\",\n dataReceived: anthropicDataReceived(ANTHROPIC_CRM_ASSISTANCE_WHOLE_RECORD),\n },\n },\n { id: OPENAI_COMPATIBLE, label: 'OpenAI-compatible endpoint' },\n]\n\n/**\n * `vision` is written on every row rather than assumed (AGL-2916): each model\n * below reads a picture in a user turn, in the four formats\n * `AI_IMAGE_MEDIA_TYPES` names, as its vendor documents. A row added without\n * it reads as a model that does not, and is never sent one.\n */\nconst anthropicCapabilities = {\n streaming: true,\n tools: true,\n thinking: true,\n promptCache: true,\n vision: true,\n}\n\nexport const AI_MODEL_CATALOG: readonly AiCatalogEntry[] = [\n {\n id: 'claude-sonnet-5',\n provider: ANTHROPIC,\n label: 'Claude Sonnet 5',\n capabilities: anthropicCapabilities,\n /*\n * THE ONE ROW BILLED ABOVE ITS PROVIDER RATE (AGL-3015).\n *\n * The balanced tier prices most requests, so this markup is most of the\n * platform's AI margin. It is carried in the rate and not in the credit\n * conversion for the reason the module head gives: a credit stays a\n * fixed quantity of billed spend on every model, so the band, the\n * overage ladder and the invoice keep the arithmetic they were sized\n * with, and nothing below has to know which model served a request.\n *\n * Everything that reasons about what we PAY — the margin surfaces, the\n * staff spend meters, the discount guardrail's cost of goods — reads\n * `providerRates`, which is the vendor's published list.\n */\n providerRates: aiRatesPerMTok(2, 10),\n billedRates: aiRatesPerMTok(3, 15),\n tier: 'balanced',\n cacheMinTokens: 1_024,\n },\n {\n id: 'claude-sonnet-4-6',\n provider: ANTHROPIC,\n label: 'Claude Sonnet 4.6',\n capabilities: anthropicCapabilities,\n ...aiRatesAtList(3, 15),\n tier: 'balanced',\n cacheMinTokens: 1_024,\n },\n {\n // Rejects an explicit `thinking` setting; the runtime omits it.\n id: 'claude-haiku-4-5',\n provider: ANTHROPIC,\n label: 'Claude Haiku 4.5',\n capabilities: { ...anthropicCapabilities, thinking: false },\n ...aiRatesAtList(1, 5),\n tier: 'fast',\n // Four times the balanced tier's minimum: the cheapest model per token\n // is the hardest one to cache for, which is why a short prompt moved\n // here can cost more per request than it saved per token.\n cacheMinTokens: 4_096,\n },\n {\n id: 'claude-opus-5',\n provider: ANTHROPIC,\n label: 'Claude Opus 5',\n capabilities: anthropicCapabilities,\n ...aiRatesAtList(5, 25),\n tier: 'deep',\n cacheMinTokens: 512,\n },\n {\n id: 'claude-opus-4-8',\n provider: ANTHROPIC,\n label: 'Claude Opus 4.8',\n capabilities: anthropicCapabilities,\n ...aiRatesAtList(5, 25),\n tier: 'deep',\n cacheMinTokens: 1_024,\n },\n /**\n * The OpenAI-compatible adapter serves whatever the endpoint behind\n * `AI_OPENAI_COMPAT_BASE_URL` serves; these are the ids the routing table\n * may name on it. The figures are the vendor's public list, billed at\n * list: a deployment that reaches its own gateway at a negotiated price\n * is paying less than this says, so its margin reads low rather than\n * high — the direction a cost figure may be wrong in.\n */\n {\n id: 'gpt-5',\n provider: OPENAI_COMPATIBLE,\n label: 'GPT-5',\n capabilities: { streaming: true, tools: true, thinking: false, promptCache: true, vision: true },\n ...aiRatesAtList(1.25, 10),\n tier: 'balanced',\n cacheMinTokens: 1_024,\n },\n {\n id: 'gpt-5-mini',\n provider: OPENAI_COMPATIBLE,\n label: 'GPT-5 mini',\n capabilities: { streaming: true, tools: true, thinking: false, promptCache: true, vision: true },\n ...aiRatesAtList(0.25, 2),\n tier: 'fast',\n cacheMinTokens: 1_024,\n },\n]\n\n/**\n * The IMAGE models (AGL-3602): every model id the Media library's \"Create\n * with AI\" photo mode may route to — Google's Gemini image models on Vertex\n * AI — with a per-picture provider rate and a per-picture billed rate, and the\n * token rates the same request's prompt and thinking are billed at. Kept\n * apart from `AI_MODEL_CATALOG` because nothing that lists text models — the\n * model selector, the routing table, the allotments' allowlists — may ever\n * offer one of these for a conversation.\n *\n * ## The rates\n *\n * Google prices an output picture as image-output tokens: Gemini 3.1 Flash\n * Image writes a 1K picture as 1,120 tokens at $60 per million on the global\n * endpoint, so $0.0672 a picture. Its prompt is input tokens ($0.50 per\n * million) and its thinking is text output ($3.00 per million). Every figure\n * here is the global endpoint's list price, read from Google's Vertex AI\n * pricing page on 2026-10-06; a regional endpoint lists 10% higher, so a\n * deployment that moves off `global` reads its cost low by that much.\n *\n * Every billed figure is the provider figure at `AI_IMAGE_BILLED_MARKUP`:\n * the same ratio the balanced text tier is billed at over its provider rate\n * (Claude Sonnet 5, $3/$15 billed over $2/$10), so a credit buys the same\n * share of margin whether it is spent on words or on pictures. A picture is\n * NEVER billed at provider cost; `billed-rate-is-not-provider-cost.spec.ts`\n * refuses a row that is.\n *\n * A credit is $0.001 of billed spend (`ASSIST_CREDIT_COST_USD`), so the\n * default model's picture is 101 credits before its prompt and thinking.\n */\nexport interface AiImageCatalogEntry extends AiCatalogRates {\n id: string\n /** The image provider the model is served by. */\n provider: string\n label: string\n /** What the provider bills us for one generated picture. */\n providerUsdPerImage: number\n /** What one picture draws from a customer's credits. */\n billedUsdPerImage: number\n}\n\n/**\n * The billed rate over the provider rate: the balanced text tier's own markup\n * (`claude-sonnet-5`, 3 / 2), restated as a number so an image row cannot be\n * written at list by accident.\n */\nexport const AI_IMAGE_BILLED_MARKUP = 1.5\n\n/**\n * An image row's four figures from Google's list prices — USD per output\n * picture, and USD per million input and text-output tokens — each billed at\n * `AI_IMAGE_BILLED_MARKUP`.\n */\nexport function aiImageRatesAtMarkup(\n providerUsdPerImage: number,\n inputPerMTok: number,\n outputPerMTok: number,\n): Pick<\n AiImageCatalogEntry,\n 'providerUsdPerImage' | 'billedUsdPerImage' | 'providerRates' | 'billedRates'\n> {\n return {\n providerUsdPerImage,\n billedUsdPerImage: roundUsd(providerUsdPerImage * AI_IMAGE_BILLED_MARKUP),\n providerRates: aiRatesPerMTok(inputPerMTok, outputPerMTok),\n billedRates: aiRatesPerMTok(\n inputPerMTok * AI_IMAGE_BILLED_MARKUP,\n outputPerMTok * AI_IMAGE_BILLED_MARKUP,\n ),\n }\n}\n\n/** The image model a photo is made with when the operator names none. */\nexport const AI_IMAGE_DEFAULT_MODEL = 'gemini-3.1-flash-image'\n\n/** The image provider's id: Google's models on Vertex AI. */\nexport const AI_IMAGE_VERTEX_PROVIDER_ID = 'google-vertex'\n\nexport const AI_IMAGE_MODEL_CATALOG: readonly AiImageCatalogEntry[] = [\n {\n // The default: Google's own replacement for Imagen 4, which Vertex AI\n // discontinued in 2026.\n id: 'gemini-3.1-flash-image',\n provider: AI_IMAGE_VERTEX_PROVIDER_ID,\n label: 'Gemini 3.1 Flash Image',\n ...aiImageRatesAtMarkup(0.0672, 0.5, 3),\n },\n {\n id: 'gemini-3.1-flash-lite-image',\n provider: AI_IMAGE_VERTEX_PROVIDER_ID,\n label: 'Gemini 3.1 Flash-Lite Image',\n ...aiImageRatesAtMarkup(0.0336, 0.25, 1.5),\n },\n {\n id: 'gemini-3-pro-image',\n provider: AI_IMAGE_VERTEX_PROVIDER_ID,\n label: 'Gemini 3 Pro Image',\n ...aiImageRatesAtMarkup(0.1344, 2, 12),\n },\n]\n\n/**\n * The per-picture rates an unknown image model id is priced at: the dearest\n * row, on both rates, for the reason `AI_FALLBACK_RATES` gives — an estimate\n * that errs low is worse than one that errs high.\n */\nexport const AI_IMAGE_FALLBACK_RATES: Pick<\n AiImageCatalogEntry,\n 'providerUsdPerImage' | 'billedUsdPerImage'\n> = {\n providerUsdPerImage: Math.max(...AI_IMAGE_MODEL_CATALOG.map((row) => row.providerUsdPerImage)),\n billedUsdPerImage: Math.max(...AI_IMAGE_MODEL_CATALOG.map((row) => row.billedUsdPerImage)),\n}\n\n/** The image catalog row for a model id, or `undefined` for an unknown id. */\nexport function aiImageCatalogEntry(modelId: string): AiImageCatalogEntry | undefined {\n return AI_IMAGE_MODEL_CATALOG.find((entry) => entry.id === modelId)\n}\n\n/** What the provider charges us for one picture from `modelId`. */\nexport function aiImageProviderUsdPerImage(modelId: string): number {\n return (aiImageCatalogEntry(modelId) ?? AI_IMAGE_FALLBACK_RATES).providerUsdPerImage\n}\n\n/** What one picture from `modelId` draws from a customer's credits. */\nexport function aiImageBilledUsdPerImage(modelId: string): number {\n return (aiImageCatalogEntry(modelId) ?? AI_IMAGE_FALLBACK_RATES).billedUsdPerImage\n}\n\n/**\n * Usage as the meter prices it: a text exchange's tokens, and, for a door\n * that makes pictures, how many it delivered. `images` is absent on every\n * text exchange, which prices exactly as it always did.\n */\nexport type AiMeteredUsage = AiUsage & { images?: number }\n\n/** Pictures on a usage record: whole, finite, non-negative, else 0. */\nfunction aiImageCount(usage: AiMeteredUsage): number {\n const value = Number(usage.images ?? 0)\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0\n}\n\nfunction roundUsd(value: number): number {\n return Math.round(value * 1_000_000) / 1_000_000\n}\n\n/** The dearest known tier, used when a model id is not in the catalog. */\nexport const AI_FALLBACK_RATES: AiTokenRates = aiRatesPerMTok(10, 50)\n\n/**\n * The minimum an unknown model id is assumed to have: the largest one the\n * catalog knows. It errs the way the rate fallback errs — toward the answer\n * that costs more — so an unrecognized id reads as \"this prompt does not\n * cache\" rather than promising a saving nobody measured.\n */\nexport const AI_FALLBACK_CACHE_MIN_TOKENS = 4_096\n\n/** The shortest prefix a model's provider will cache; the dearest assumption for an unknown id. */\nexport function aiModelCacheMinTokens(modelId: string): number {\n return aiCatalogEntry(modelId)?.cacheMinTokens ?? AI_FALLBACK_CACHE_MIN_TOKENS\n}\n\nconst ZERO_RATES = aiRatesPerMTok(0, 0)\n\n/** The catalog row for a model id, or `undefined` for an unknown id. */\nexport function aiCatalogEntry(modelId: string): AiCatalogEntry | undefined {\n return AI_MODEL_CATALOG.find((entry) => entry.id === modelId)\n}\n\nconst isSentinel = (modelId: string): boolean =>\n (Object.values(AI_METER_SENTINELS) as string[]).includes(modelId)\n\n/**\n * What the PROVIDER charges for a model id — our bill. A meter sentinel is\n * free; an image model's prompt and thinking are priced at its image row's\n * token rates (AGL-3602); an unknown id is priced at the dearest tier.\n *\n * The figure every cost and margin surface prices usage at. Answering the\n * customer's question from here would report a margin the platform does not\n * earn, and answering ours from `aiBilledRatesForModel` reports one it does\n * not pay; the two are separate functions so neither is reachable by\n * forgetting which one was meant.\n */\nexport function aiProviderRatesForModel(modelId: string): AiTokenRates {\n if (isSentinel(modelId)) return ZERO_RATES\n return (\n aiCatalogEntry(modelId)?.providerRates ??\n aiImageCatalogEntry(modelId)?.providerRates ??\n AI_FALLBACK_RATES\n )\n}\n\n/**\n * What a CUSTOMER'S CREDITS are charged at for a model id. A meter sentinel\n * is free; an unknown id is priced at the dearest tier.\n *\n * The figure the credit meter, the band and the model selector price usage\n * at. It is at or above `aiProviderRatesForModel` on every row.\n */\nexport function aiBilledRatesForModel(modelId: string): AiTokenRates {\n if (isSentinel(modelId)) return ZERO_RATES\n return (\n aiCatalogEntry(modelId)?.billedRates ??\n aiImageCatalogEntry(modelId)?.billedRates ??\n AI_FALLBACK_RATES\n )\n}\n\nfunction priceUsage(usage: AiUsage, rate: AiTokenRates): number {\n const raw =\n usage.inputTokens * rate.inputPerToken +\n usage.outputTokens * rate.outputPerToken +\n usage.cacheReadTokens * rate.cacheReadPerToken +\n usage.cacheWriteTokens * rate.cacheWritePerToken\n return Math.round(raw * 1_000_000) / 1_000_000\n}\n\n/**\n * What one exchange COST US, at the serving model's provider rates, rounded\n * to 6dp. Real money, and the only figure a margin may be taken against.\n */\nexport function estimateAiProviderCostUsd(usage: AiMeteredUsage, modelId: string): number {\n return roundUsd(\n priceUsage(usage, aiProviderRatesForModel(modelId)) +\n aiImageCount(usage) * aiImageProviderUsdPerImage(modelId),\n )\n}\n\n/**\n * What one exchange DRAWS FROM A CUSTOMER, at the serving model's billed\n * rates, rounded to 6dp — the figure `assistCreditsFromUsd` turns into\n * credits. At or above `estimateAiProviderCostUsd` for the same exchange.\n */\nexport function estimateAiBilledUsd(usage: AiMeteredUsage, modelId: string): number {\n return roundUsd(\n priceUsage(usage, aiBilledRatesForModel(modelId)) +\n aiImageCount(usage) * aiImageBilledUsdPerImage(modelId),\n )\n}\n\n/** Every catalog id a provider serves. */\nexport function aiModelIdsForProvider(providerId: string): string[] {\n return AI_MODEL_CATALOG.filter((entry) => entry.provider === providerId).map(\n (entry) => entry.id,\n )\n}\n\n/**\n * What a step asks for, and what the routing table answers with. Every\n * door names its kind here rather than a model, so a model swap — an\n * incident override, an org's own setting, a cheaper model for a cheap\n * step — is one row rather than a search through the doors (AGL-2937).\n */\nexport type AiStepKind =\n | 'assist.chat'\n | 'copy.element'\n | 'copy.section'\n | 'copy.blog'\n | 'generate.section'\n | 'job.component'\n | 'job.crm'\n | 'job.experiment'\n | 'job.form'\n | 'job.insight'\n | 'job.layout'\n | 'job.logic'\n | 'job.template'\n | 'job.page'\n | 'job.products'\n | 'job.seo'\n | 'job.text'\n | 'job.theme'\n | 'job.plan'\n | 'job.email'\n | 'job.campaign'\n | 'job.workflow'\n\n/** The tier each step kind is served from when no setting overrides it. */\nexport const AI_STEP_TIERS: Record<AiStepKind, AiCatalogEntry['tier']> = {\n 'assist.chat': 'balanced',\n 'copy.element': 'fast',\n 'copy.section': 'balanced',\n 'copy.blog': 'balanced',\n 'generate.section': 'balanced',\n // A reusable component is one structured tree with the typed props it\n // declares, and choosing what becomes a prop is the judgment the step sells.\n 'job.component': 'balanced',\n // CRM by AI (AGL-2917): a record's summary and next step, a deal's stage, a\n // lead's standing, a one-to-one email draft and an import's column matches.\n // Short answers through a strict tool, held to the facts the CRM reports\n // about the record the member opened.\n 'job.crm': 'fast',\n // A/B tests by AI (AGL-2914): variants of one piece of copy, and a\n // result put into words for a verdict code already reached. Short answers\n // through a strict tool, but the judgment a variant sells — which ONE idea\n // to vary so the result says something — is the balanced tier's.\n 'job.experiment': 'balanced',\n // A form is one small tree held to the building rules and to the contract\n // its submissions are read by; a re-ask costs more than the tier saves.\n 'job.form': 'balanced',\n // An insight (AGL-2915) chooses which figures answer a question and says\n // what they show; the trace holds every number, and the judgment of what is\n // worth saying is what the fast tier does worse.\n 'job.insight': 'balanced',\n // A layout or a page template is one structured tree held to every\n // building rule; the fast tier re-asks more than it saves.\n 'job.layout': 'balanced',\n // A site function or variable (AGL-3603): a small structured answer held\n // to the evaluator's grammar, the names it may read and a first run, whose\n // judgment is turning a description into conditions and arithmetic; the\n // fast tier re-asks more than it saves.\n 'job.logic': 'balanced',\n 'job.template': 'balanced',\n // A page section (AGL-2907) is held to the whole page's building rules on\n // every pass, for the same reason.\n 'job.page': 'balanced',\n // A product's copy, a store's first products, or its categories and\n // discounts (AGL-2916): judgment about what a photo shows and what a shopper\n // needs, held to storefront claim rules a re-ask costs more to meet than the\n // tier saves.\n 'job.products': 'balanced',\n // SEO fields, alt text and an audit's fixes (AGL-2910): short answers\n // through a strict tool, held to a length and to the page's own text.\n 'job.seo': 'fast',\n 'job.text': 'balanced',\n // A theme is one structured answer over a small, fixed control set; the\n // judgment is in the color choices, which the fast tier makes worse.\n 'job.theme': 'balanced',\n 'job.plan': 'balanced',\n // An email is one tree held to every building rule, with subject lines\n // whose judgment is the point of asking; the fast tier re-asks more than it\n // saves. A campaign is the same email, drafted into a campaign.\n 'job.email': 'balanced',\n 'job.campaign': 'balanced',\n // An automation is a small structured answer whose judgment is choosing the\n // trigger and steps a description means, and an explanation reads one\n // closely; the fast tier re-asks more than it saves on both (AGL-2919).\n 'job.workflow': 'balanced',\n}\n\n/** The first catalog model of a tier on a provider, or the provider's first model. */\nexport function aiDefaultModelFor(providerId: string, tier: AiCatalogEntry['tier']): string | undefined {\n const onProvider = AI_MODEL_CATALOG.filter((entry) => entry.provider === providerId)\n return (onProvider.find((entry) => entry.tier === tier) ?? onProvider[0])?.id\n}\n"],"names":["aiRatesPerMTok","inputPerMTok","outputPerMTok","inputPerToken","outputPerToken","cacheReadPerToken","cacheWritePerToken","aiRatesAtList","providerRates","billedRates","AI_METER_SENTINELS","docsRetrieval","docsLinks","answerCache","ANTHROPIC","OPENAI_COMPATIBLE","ANTHROPIC_DATA_RECEIVED_LEAD","ANTHROPIC_DATA_RECEIVED_CRM_DRAFTS_AND_IMPORTS","ANTHROPIC_CRM_ASSISTANCE_DISCLOSED","record","never","ANTHROPIC_CRM_ASSISTANCE_WHOLE_RECORD","anthropicDataReceived","crm","AI_CATALOG_PROVIDERS","id","label","subprocessor","entity","region","purpose","publishedOn","reason","dataReceived","anthropicCapabilities","streaming","tools","thinking","promptCache","vision","AI_MODEL_CATALOG","provider","capabilities","tier","cacheMinTokens","AI_IMAGE_BILLED_MARKUP","aiImageRatesAtMarkup","providerUsdPerImage","billedUsdPerImage","roundUsd","AI_IMAGE_DEFAULT_MODEL","AI_IMAGE_VERTEX_PROVIDER_ID","AI_IMAGE_MODEL_CATALOG","AI_IMAGE_FALLBACK_RATES","Math","max","map","row","aiImageCatalogEntry","modelId","find","entry","aiImageProviderUsdPerImage","aiImageBilledUsdPerImage","aiImageCount","usage","value","Number","images","isFinite","floor","round","AI_FALLBACK_RATES","AI_FALLBACK_CACHE_MIN_TOKENS","aiModelCacheMinTokens","aiCatalogEntry","ZERO_RATES","isSentinel","Object","values","includes","aiProviderRatesForModel","aiBilledRatesForModel","priceUsage","rate","raw","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","estimateAiProviderCostUsd","estimateAiBilledUsd","aiModelIdsForProvider","providerId","filter","AI_STEP_TIERS","aiDefaultModelFor","onProvider"],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiUsage } from './contract';
|
|
18
|
+
/**
|
|
19
|
+
* The image-generation contract (AGL-3602): the one shape a door that makes
|
|
20
|
+
* pictures speaks, beside the text contract in `contract.ts` rather than
|
|
21
|
+
* inside it. A text provider answers tokens and is priced per token; an image
|
|
22
|
+
* provider answers pictures and is priced per picture, and folding the two
|
|
23
|
+
* into one interface would hand every text adapter a method it cannot
|
|
24
|
+
* implement and every image adapter four token counts it does not have.
|
|
25
|
+
*
|
|
26
|
+
* Browser-safe: the constants and types here are what the Media dialog reads
|
|
27
|
+
* too. The adapter that speaks a vendor's wire lives in its own module.
|
|
28
|
+
*/
|
|
29
|
+
/** The shapes a picture may be asked for, as the dialog offers them. */
|
|
30
|
+
export declare const AI_IMAGE_ASPECT_RATIOS: readonly ["1:1", "4:3", "3:4", "16:9", "9:16"];
|
|
31
|
+
export type AiImageAspectRatio = (typeof AI_IMAGE_ASPECT_RATIOS)[number];
|
|
32
|
+
/** The most pictures one request makes. */
|
|
33
|
+
export declare const AI_IMAGE_MAX_COUNT = 4;
|
|
34
|
+
/** The longest description a request carries, in characters. */
|
|
35
|
+
export declare const AI_IMAGE_PROMPT_MAX_CHARS = 1000;
|
|
36
|
+
/**
|
|
37
|
+
* The two ways the Media dialog makes a picture (AGL-3602): a `photo` from
|
|
38
|
+
* the image provider, and an `illustration` drawn as SVG by the text
|
|
39
|
+
* provider every other AI door already uses.
|
|
40
|
+
*/
|
|
41
|
+
export declare const AI_IMAGE_MODES: readonly ["photo", "illustration"];
|
|
42
|
+
export type AiImageMode = (typeof AI_IMAGE_MODES)[number];
|
|
43
|
+
/** What an illustration is: a scene, an icon, a tileable pattern or a simple mark. */
|
|
44
|
+
export declare const AI_SVG_STYLES: readonly ["illustration", "icon", "pattern", "logo"];
|
|
45
|
+
export type AiSvgStyle = (typeof AI_SVG_STYLES)[number];
|
|
46
|
+
/** The most colors a person may name for an illustration. */
|
|
47
|
+
export declare const AI_SVG_MAX_COLORS = 6;
|
|
48
|
+
/**
|
|
49
|
+
* Whether this deployment offers the photo mode, as its build says: a
|
|
50
|
+
* deployment that configured an image provider sets
|
|
51
|
+
* `NEXT_PUBLIC_AI_IMAGE_PHOTOS=on` beside it. Read where the dialog renders,
|
|
52
|
+
* so no request is made to decide what to draw; the door still refuses a
|
|
53
|
+
* photo on a deployment with no provider.
|
|
54
|
+
*/
|
|
55
|
+
export declare function aiImagePhotosOffered(): boolean;
|
|
56
|
+
export declare function isAiImageAspectRatio(value: unknown): value is AiImageAspectRatio;
|
|
57
|
+
/** One request to an image provider. */
|
|
58
|
+
export interface AiImageRequest {
|
|
59
|
+
/** The model id, from the image catalog. */
|
|
60
|
+
model: string;
|
|
61
|
+
prompt: string;
|
|
62
|
+
aspectRatio: AiImageAspectRatio;
|
|
63
|
+
/** 1 to `AI_IMAGE_MAX_COUNT`. */
|
|
64
|
+
count: number;
|
|
65
|
+
}
|
|
66
|
+
/** One picture the provider returned. */
|
|
67
|
+
export interface AiGeneratedImage {
|
|
68
|
+
/** The bytes, base64-encoded, as the provider sent them. */
|
|
69
|
+
base64: string;
|
|
70
|
+
/** The type the bytes are in: `image/jpeg` or `image/png`. */
|
|
71
|
+
mimeType: string;
|
|
72
|
+
}
|
|
73
|
+
/** What one request made. */
|
|
74
|
+
export interface AiImageResult {
|
|
75
|
+
model: string;
|
|
76
|
+
images: AiGeneratedImage[];
|
|
77
|
+
/**
|
|
78
|
+
* The tokens the requests spent beside the pictures themselves — the
|
|
79
|
+
* prompt, and any thinking — which the meter prices at the image row's
|
|
80
|
+
* token rates. The pictures are priced per picture, never as tokens here.
|
|
81
|
+
*/
|
|
82
|
+
usage: AiUsage;
|
|
83
|
+
/**
|
|
84
|
+
* How many of the requested pictures the provider's safety filter held
|
|
85
|
+
* back. A picture held back is not returned and is never billed.
|
|
86
|
+
*/
|
|
87
|
+
filtered: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The provider's safety filter declined the whole request: the description,
|
|
91
|
+
* or every picture made from it. Nothing came back, so nothing is billed and
|
|
92
|
+
* the reservation is handed back. `message` is the sentence a person reads.
|
|
93
|
+
*/
|
|
94
|
+
export declare class AiImageSafetyRefusal extends Error {
|
|
95
|
+
readonly name = "AiImageSafetyRefusal";
|
|
96
|
+
/** The provider's own reason codes, for the log and never for a reader. */
|
|
97
|
+
readonly reasons: readonly string[];
|
|
98
|
+
constructor(reasons?: readonly string[]);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* An image provider: one vendor's wire behind the contract. It reports
|
|
102
|
+
* whether this deployment configured it, so a door can answer "not here"
|
|
103
|
+
* before it reserves anything, and it maps its vendor's failures onto
|
|
104
|
+
* `AiUpstreamError` and its declines onto `AiImageSafetyRefusal`.
|
|
105
|
+
*/
|
|
106
|
+
export interface AiImageProvider {
|
|
107
|
+
readonly id: string;
|
|
108
|
+
readonly label: string;
|
|
109
|
+
/** The model a request runs on when nothing else names one. */
|
|
110
|
+
defaultModel(): string;
|
|
111
|
+
/** True when this deployment's environment turns the provider on. */
|
|
112
|
+
configured(): boolean;
|
|
113
|
+
/** The host requests go to, for the operator and the subprocessor record. */
|
|
114
|
+
endpointHost(): string;
|
|
115
|
+
generate(request: AiImageRequest): Promise<AiImageResult>;
|
|
116
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The image-generation contract (AGL-3602): the one shape a door that makes
|
|
18
|
+
* pictures speaks, beside the text contract in `contract.ts` rather than
|
|
19
|
+
* inside it. A text provider answers tokens and is priced per token; an image
|
|
20
|
+
* provider answers pictures and is priced per picture, and folding the two
|
|
21
|
+
* into one interface would hand every text adapter a method it cannot
|
|
22
|
+
* implement and every image adapter four token counts it does not have.
|
|
23
|
+
*
|
|
24
|
+
* Browser-safe: the constants and types here are what the Media dialog reads
|
|
25
|
+
* too. The adapter that speaks a vendor's wire lives in its own module.
|
|
26
|
+
*/ /** The shapes a picture may be asked for, as the dialog offers them. */ export const AI_IMAGE_ASPECT_RATIOS = [
|
|
27
|
+
'1:1',
|
|
28
|
+
'4:3',
|
|
29
|
+
'3:4',
|
|
30
|
+
'16:9',
|
|
31
|
+
'9:16'
|
|
32
|
+
];
|
|
33
|
+
/** The most pictures one request makes. */ export const AI_IMAGE_MAX_COUNT = 4;
|
|
34
|
+
/** The longest description a request carries, in characters. */ export const AI_IMAGE_PROMPT_MAX_CHARS = 1000;
|
|
35
|
+
/**
|
|
36
|
+
* The two ways the Media dialog makes a picture (AGL-3602): a `photo` from
|
|
37
|
+
* the image provider, and an `illustration` drawn as SVG by the text
|
|
38
|
+
* provider every other AI door already uses.
|
|
39
|
+
*/ export const AI_IMAGE_MODES = [
|
|
40
|
+
'photo',
|
|
41
|
+
'illustration'
|
|
42
|
+
];
|
|
43
|
+
/** What an illustration is: a scene, an icon, a tileable pattern or a simple mark. */ export const AI_SVG_STYLES = [
|
|
44
|
+
'illustration',
|
|
45
|
+
'icon',
|
|
46
|
+
'pattern',
|
|
47
|
+
'logo'
|
|
48
|
+
];
|
|
49
|
+
/** The most colors a person may name for an illustration. */ export const AI_SVG_MAX_COLORS = 6;
|
|
50
|
+
/**
|
|
51
|
+
* Whether this deployment offers the photo mode, as its build says: a
|
|
52
|
+
* deployment that configured an image provider sets
|
|
53
|
+
* `NEXT_PUBLIC_AI_IMAGE_PHOTOS=on` beside it. Read where the dialog renders,
|
|
54
|
+
* so no request is made to decide what to draw; the door still refuses a
|
|
55
|
+
* photo on a deployment with no provider.
|
|
56
|
+
*/ export function aiImagePhotosOffered() {
|
|
57
|
+
return process.env.NEXT_PUBLIC_AI_IMAGE_PHOTOS === 'on';
|
|
58
|
+
}
|
|
59
|
+
export function isAiImageAspectRatio(value) {
|
|
60
|
+
return AI_IMAGE_ASPECT_RATIOS.includes(value);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The provider's safety filter declined the whole request: the description,
|
|
64
|
+
* or every picture made from it. Nothing came back, so nothing is billed and
|
|
65
|
+
* the reservation is handed back. `message` is the sentence a person reads.
|
|
66
|
+
*/ export class AiImageSafetyRefusal extends Error {
|
|
67
|
+
constructor(reasons = []){
|
|
68
|
+
super('The image service declined this description. Try describing the scene differently, without real people, brands or anything unsafe.'), this.name = 'AiImageSafetyRefusal';
|
|
69
|
+
this.reasons = reasons;
|
|
70
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=image-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/image-contract.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 type { AiUsage } from './contract'\n\n/**\n * The image-generation contract (AGL-3602): the one shape a door that makes\n * pictures speaks, beside the text contract in `contract.ts` rather than\n * inside it. A text provider answers tokens and is priced per token; an image\n * provider answers pictures and is priced per picture, and folding the two\n * into one interface would hand every text adapter a method it cannot\n * implement and every image adapter four token counts it does not have.\n *\n * Browser-safe: the constants and types here are what the Media dialog reads\n * too. The adapter that speaks a vendor's wire lives in its own module.\n */\n\n/** The shapes a picture may be asked for, as the dialog offers them. */\nexport const AI_IMAGE_ASPECT_RATIOS = ['1:1', '4:3', '3:4', '16:9', '9:16'] as const\nexport type AiImageAspectRatio = (typeof AI_IMAGE_ASPECT_RATIOS)[number]\n\n/** The most pictures one request makes. */\nexport const AI_IMAGE_MAX_COUNT = 4\n\n/** The longest description a request carries, in characters. */\nexport const AI_IMAGE_PROMPT_MAX_CHARS = 1_000\n\n/**\n * The two ways the Media dialog makes a picture (AGL-3602): a `photo` from\n * the image provider, and an `illustration` drawn as SVG by the text\n * provider every other AI door already uses.\n */\nexport const AI_IMAGE_MODES = ['photo', 'illustration'] as const\nexport type AiImageMode = (typeof AI_IMAGE_MODES)[number]\n\n/** What an illustration is: a scene, an icon, a tileable pattern or a simple mark. */\nexport const AI_SVG_STYLES = ['illustration', 'icon', 'pattern', 'logo'] as const\nexport type AiSvgStyle = (typeof AI_SVG_STYLES)[number]\n\n/** The most colors a person may name for an illustration. */\nexport const AI_SVG_MAX_COLORS = 6\n\n/**\n * Whether this deployment offers the photo mode, as its build says: a\n * deployment that configured an image provider sets\n * `NEXT_PUBLIC_AI_IMAGE_PHOTOS=on` beside it. Read where the dialog renders,\n * so no request is made to decide what to draw; the door still refuses a\n * photo on a deployment with no provider.\n */\nexport function aiImagePhotosOffered(): boolean {\n return process.env.NEXT_PUBLIC_AI_IMAGE_PHOTOS === 'on'\n}\n\nexport function isAiImageAspectRatio(value: unknown): value is AiImageAspectRatio {\n return (AI_IMAGE_ASPECT_RATIOS as readonly unknown[]).includes(value)\n}\n\n/** One request to an image provider. */\nexport interface AiImageRequest {\n /** The model id, from the image catalog. */\n model: string\n prompt: string\n aspectRatio: AiImageAspectRatio\n /** 1 to `AI_IMAGE_MAX_COUNT`. */\n count: number\n}\n\n/** One picture the provider returned. */\nexport interface AiGeneratedImage {\n /** The bytes, base64-encoded, as the provider sent them. */\n base64: string\n /** The type the bytes are in: `image/jpeg` or `image/png`. */\n mimeType: string\n}\n\n/** What one request made. */\nexport interface AiImageResult {\n model: string\n images: AiGeneratedImage[]\n /**\n * The tokens the requests spent beside the pictures themselves — the\n * prompt, and any thinking — which the meter prices at the image row's\n * token rates. The pictures are priced per picture, never as tokens here.\n */\n usage: AiUsage\n /**\n * How many of the requested pictures the provider's safety filter held\n * back. A picture held back is not returned and is never billed.\n */\n filtered: number\n}\n\n/**\n * The provider's safety filter declined the whole request: the description,\n * or every picture made from it. Nothing came back, so nothing is billed and\n * the reservation is handed back. `message` is the sentence a person reads.\n */\nexport class AiImageSafetyRefusal extends Error {\n override readonly name = 'AiImageSafetyRefusal'\n /** The provider's own reason codes, for the log and never for a reader. */\n readonly reasons: readonly string[]\n\n constructor(reasons: readonly string[] = []) {\n super(\n 'The image service declined this description. Try describing the scene differently, without real people, brands or anything unsafe.',\n )\n this.reasons = reasons\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/**\n * An image provider: one vendor's wire behind the contract. It reports\n * whether this deployment configured it, so a door can answer \"not here\"\n * before it reserves anything, and it maps its vendor's failures onto\n * `AiUpstreamError` and its declines onto `AiImageSafetyRefusal`.\n */\nexport interface AiImageProvider {\n readonly id: string\n readonly label: string\n /** The model a request runs on when nothing else names one. */\n defaultModel(): string\n /** True when this deployment's environment turns the provider on. */\n configured(): boolean\n /** The host requests go to, for the operator and the subprocessor record. */\n endpointHost(): string\n generate(request: AiImageRequest): Promise<AiImageResult>\n}\n"],"names":["AI_IMAGE_ASPECT_RATIOS","AI_IMAGE_MAX_COUNT","AI_IMAGE_PROMPT_MAX_CHARS","AI_IMAGE_MODES","AI_SVG_STYLES","AI_SVG_MAX_COLORS","aiImagePhotosOffered","process","env","NEXT_PUBLIC_AI_IMAGE_PHOTOS","isAiImageAspectRatio","value","includes","AiImageSafetyRefusal","Error","reasons","name","Object","setPrototypeOf","prototype"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;CAUC,GAED,sEAAsE,GACtE,OAAO,MAAMA,yBAAyB;IAAC;IAAO;IAAO;IAAO;IAAQ;CAAO,CAAS;AAGpF,yCAAyC,GACzC,OAAO,MAAMC,qBAAqB,EAAC;AAEnC,8DAA8D,GAC9D,OAAO,MAAMC,4BAA4B,KAAK;AAE9C;;;;CAIC,GACD,OAAO,MAAMC,iBAAiB;IAAC;IAAS;CAAe,CAAS;AAGhE,oFAAoF,GACpF,OAAO,MAAMC,gBAAgB;IAAC;IAAgB;IAAQ;IAAW;CAAO,CAAS;AAGjF,2DAA2D,GAC3D,OAAO,MAAMC,oBAAoB,EAAC;AAElC;;;;;;CAMC,GACD,OAAO,SAASC;IACd,OAAOC,QAAQC,GAAG,CAACC,2BAA2B,KAAK;AACrD;AAEA,OAAO,SAASC,qBAAqBC,KAAc;IACjD,OAAO,AAACX,uBAA8CY,QAAQ,CAACD;AACjE;AAqCA;;;;CAIC,GACD,OAAO,MAAME,6BAA6BC;IAKxC,YAAYC,UAA6B,EAAE,CAAE;QAC3C,KAAK,CACH,4IANcC,OAAO;QAQvB,IAAI,CAACD,OAAO,GAAGA;QACfE,OAAOC,cAAc,CAAC,IAAI,EAAE,WAAWC,SAAS;IAClD;AACF"}
|
|
@@ -296,6 +296,17 @@ import { resolveAiRoute } from "./routing.js";
|
|
|
296
296
|
outputTokens: 1000,
|
|
297
297
|
cacheReadTokens: 4500,
|
|
298
298
|
cacheWriteTokens: 0
|
|
299
|
+
},
|
|
300
|
+
// The logic step (AGL-3603): the fields doctrine, one rules block and the
|
|
301
|
+
// tool's schema are the cached prefix (1,556 tokens for a function, as
|
|
302
|
+
// `runtime/ai-prompt-cache.spec.ts` measures it); the site's variables and
|
|
303
|
+
// the request ride uncached, and a function of a few operations is about
|
|
304
|
+
// 1,200 characters of JSON with as much again to think in.
|
|
305
|
+
'job.logic': {
|
|
306
|
+
inputTokens: 700,
|
|
307
|
+
outputTokens: 900,
|
|
308
|
+
cacheReadTokens: 1500,
|
|
309
|
+
cacheWriteTokens: 0
|
|
299
310
|
}
|
|
300
311
|
};
|
|
301
312
|
/** The fewest measured exchanges a median is taken over. */ export const AI_MEASURED_MEDIAN_MIN_SAMPLES = 3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/model-choice.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 { assistCreditsFromUsd } from '../usage/assist-credits'\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_MODEL_CATALOG,\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiBilledRatesForModel,\n estimateAiBilledUsd,\n type AiCatalogEntry,\n type AiStepKind,\n} from './catalog'\nimport type { AiUsage } from './contract'\nimport { resolveAiRoute, type AiPluginSettings } from './routing'\n\n/**\n * THE MODEL SWITCH (AGL-2942): what a person may pick, what \"Auto\" means,\n * and what a pick costs.\n *\n * ## Auto is the routing table\n *\n * A request that names no model — or names one it may not have — runs on\n * `resolveAiRoute(kind)`, the table that maps a step kind to a catalog model\n * (AGL-2937). Picking a model is an override of that one row for one request,\n * never a second table.\n *\n * ## Three bounds, one intersection\n *\n * A manual pick must be served by the active provider, in a tier the org's\n * plan offers (`AI_PLAN_MODEL_TIERS`), on the org's own restriction when it\n * set one, and on the allotment allowlists that apply to the request. The\n * two allowlists bound Auto too: when the routing table's answer is not on\n * them, Auto runs on the allowed model of the same tier, else the cheapest\n * allowed one. The plan bounds only a pick — Auto is the platform's choice,\n * made for the step, and a plan never makes it worse.\n *\n * An allowlist that leaves nothing this deployment serves is not a way to\n * switch AI off by mistake: the choice falls back to the routing table, and\n * the permission keys are how AI is switched off on purpose.\n *\n * ## The price beside each option\n *\n * Credits per typical request at each model's list rates, over the step's\n * measured median exchange when the workspace has enough of them, and over\n * `AI_STEP_NOMINAL_USAGE` until it does — with the multiple of what Auto\n * would cost for the same request. Every figure is credits, never dollars.\n */\n\n/** The value a request sends for \"let the platform route it\". */\nexport const AI_MODEL_AUTO = 'auto'\n\n/**\n * The catalog tiers a MANUAL pick may name, per plan. Free runs on Auto only:\n * its taste is a wall, and a pick there would spend the account's credits at\n * a rate nobody chose on purpose. The dearest tier opens from Scale up, where\n * the band is large enough that a slower, deeper model is a real choice\n * rather than a fast way to end the month.\n */\nexport const AI_PLAN_MODEL_TIERS: Record<OrgPlan, readonly AiCatalogEntry['tier'][]> = {\n free: [],\n starter: ['fast', 'balanced'],\n pro: ['fast', 'balanced'],\n business: ['fast', 'balanced'],\n scale: ['fast', 'balanced', 'deep'],\n advanced: ['fast', 'balanced', 'deep'],\n agency: ['fast', 'balanced', 'deep'],\n enterprise: ['fast', 'balanced', 'deep'],\n}\n\n/** The plans whose admins may restrict the model set org-wide. */\nexport const AI_MODEL_RESTRICTION_PLANS: readonly OrgPlan[] = ['agency', 'enterprise']\n\n/**\n * A typical exchange per step kind, before a workspace has measured one: the\n * doors' own prompt sizes and output ceilings, read conservatively. Replaced\n * by the workspace's measured median as soon as `aiMedianUsage` has enough\n * samples, and only ever used for the price beside an option.\n */\nexport const AI_STEP_NOMINAL_USAGE: Record<AiStepKind, AiUsage> = {\n 'assist.chat': { inputTokens: 900, outputTokens: 450, cacheReadTokens: 1_050, cacheWriteTokens: 0 },\n 'copy.element': { inputTokens: 350, outputTokens: 120, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.blog': { inputTokens: 600, outputTokens: 1_100, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'generate.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The text step's rules are about 500 characters — far under the balanced\n // tier's cacheable minimum, so they are billed as input every time rather\n // than read from a cache (`runtime/ai-prompt-cache.spec.ts` measures it).\n 'job.text': { inputTokens: 450, outputTokens: 500, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The theme step: the tool's schema and both system blocks (about 7,500\n // characters) are the cached prefix, the site's inventory and the brief ride\n // uncached, and a full-palette answer is about 1,300 characters of JSON with\n // as much again to think in.\n 'job.theme': { inputTokens: 500, outputTokens: 850, cacheReadTokens: 2_500, cacheWriteTokens: 0 },\n // The plan step: the tool's schema, the doctrine and the plan instructions\n // (about 10,000 characters) are the cached prefix, the site inventory and the\n // brief ride uncached, and a plan answer is about 600 characters of JSON with\n // as much again to think in.\n 'job.plan': { inputTokens: 500, outputTokens: 400, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The component step: the doctrine, the component instructions, the\n // component surface's catalog and the tool are the cached prefix, the site\n // inventory, the brief and the confirmed plan ride uncached, and a card-sized\n // answer — its tree and declared props as JSON — is about 1,700 characters.\n // The component step's spec measures the prefix and the answer against this.\n 'job.component': { inputTokens: 700, outputTokens: 600, cacheReadTokens: 5_800, cacheWriteTokens: 0 },\n // The CRM step's exchange (AGL-2917). NOTHING is cached: the step runs on\n // the fast tier, whose cacheable minimum is far above what its prompt\n // reaches — the doctrine's field rules, the CRM rules and the tool come to\n // 650 to 913 tokens, as `runtime/ai-prompt-cache.spec.ts` measures — so\n // every byte is billed as input. A record's facts ride with them, a\n // timeline cut to its newest twelve entries, and the typical answer is a\n // summary and a next step of a few sentences, with no thinking.\n 'job.crm': { inputTokens: 1_800, outputTokens: 250, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'job.layout': { inputTokens: 700, outputTokens: 1_000, cacheReadTokens: 5_600, cacheWriteTokens: 0 },\n 'job.template': { inputTokens: 1_200, outputTokens: 700, cacheReadTokens: 6_400, cacheWriteTokens: 0 },\n // One page section's exchange (AGL-2907), from the modules' own text: the\n // doctrine (6,281 characters), the page instructions (1,524), the screen\n // palette catalog (8,353) and the section tool (484) are the cached prefix;\n // the site inventory (711 on the median golden site), the brief, the plan\n // and the section line ride uncached; and a golden section answer is 901\n // characters at the median, asked for with no thinking.\n 'job.page': { inputTokens: 600, outputTokens: 300, cacheReadTokens: 4_400, cacheWriteTokens: 0 },\n // One product's copy (AGL-2916), the exchange a bulk job makes once a\n // product: the doctrine, the copy rules and the tool (about 9,500 characters)\n // are the cached prefix; the product's facts, the site's categories and a\n // photo at most 768 pixels on its long edge (about 790 tokens) ride\n // uncached; and the answer is a description with its search listing, tags,\n // categories and option names, about 1,300 characters of JSON.\n 'job.products': { inputTokens: 1_500, outputTokens: 450, cacheReadTokens: 2_400, cacheWriteTokens: 0 },\n // The SEO step's listing exchange, which a page's or a product's listing is\n // exactly one of. NOTHING is cached here, whatever the breakpoints say: the\n // step runs on the fast tier, whose cacheable minimum is four times what\n // this prompt reaches, so every static byte is billed as input on every\n // attempt — the doctrine's block in its field scope, the listing rules and\n // the tool's schema, about 760 tokens together. The page text, capped at\n // 2,000 characters, rides with the current listing and a dozen other titles\n // for about another 750. The largest answer the tool accepts is about 600\n // characters of JSON, and the request asks for no thinking. An audit pass\n // is heavier: eight pages' findings and text, about 9,000 characters, with\n // up to about 8,000 back.\n 'job.seo': { inputTokens: 1_500, outputTokens: 250, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The experiment step (AGL-2914). NOTHING is cached: the A/B test rules\n // and the acceptable-use block come to about 2,300 characters, under the\n // balanced tier's cacheable minimum, so they are billed as input every\n // time. The copy under test, or a test's arms with its verdict, rides with\n // them, and a four-variant answer is about 800 characters of JSON.\n 'job.experiment': { inputTokens: 900, outputTokens: 300, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The form step: the doctrine, the form instructions, the form surface's\n // catalog (about 150 tokens) and the tool's schema are the cached prefix, the\n // site inventory and the brief ride uncached, and a form's answer is 200 to\n // 400 tokens of JSON with no extended thinking.\n 'job.form': { inputTokens: 700, outputTokens: 450, cacheReadTokens: 3_900, cacheWriteTokens: 0 },\n // An insight (AGL-2915): two exchanges, and NOTHING cached — the rules and\n // the acceptable-use block are under the balanced tier's minimum, so they\n // are billed as input on both. The read call carries the question and the\n // readers' catalog (about 2,000 characters) and answers a few reads; the\n // answer call carries four tables of about ten rows each (about 4,000\n // characters) and answers three to five insights as JSON.\n 'job.insight': { inputTokens: 2_800, outputTokens: 650, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The email step, and the campaign step that shares its generation: the\n // doctrine, the email instructions, the email palette catalog and the\n // tool's schema (about 10,300 characters) are the cached prefix, the site\n // inventory and the brief ride uncached, and the answer is one email's\n // node map with three subject lines and three preheaders.\n 'job.email': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n 'job.campaign': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The workflow step's draft (AGL-2919): the doctrine, the automation\n // vocabulary and the tool's schema are the cached prefix (4,564 tokens, as\n // `runtime/ai-prompt-cache.spec.ts` measures it); what the workspace can\n // run, the site's forms and datasets and the description ride uncached; and\n // an automation of a few steps is about 1,300 characters of JSON with as\n // much again to think in. An explanation caches half as much.\n 'job.workflow': { inputTokens: 500, outputTokens: 1_000, cacheReadTokens: 4_500, cacheWriteTokens: 0 },\n}\n\n/** The fewest measured exchanges a median is taken over. */\nexport const AI_MEASURED_MEDIAN_MIN_SAMPLES = 3\n\nexport interface AiModelBounds {\n plan: OrgPlan\n /** The allotment allowlists that apply, intersected; `null` for none. */\n allotmentModels?: readonly string[] | null\n /** The org-wide restriction; `null` for none. */\n orgModels?: readonly string[] | null\n}\n\nfunction onLists(entry: AiCatalogEntry, bounds: AiModelBounds): boolean {\n if (bounds.orgModels?.length && !bounds.orgModels.includes(entry.id)) return false\n if (bounds.allotmentModels && !bounds.allotmentModels.includes(entry.id)) return false\n return true\n}\n\n/** The catalog models a manual pick may name on a provider under these bounds. */\nexport function aiModelsSelectable(\n providerId: string,\n bounds: AiModelBounds,\n): AiCatalogEntry[] {\n const tiers = AI_PLAN_MODEL_TIERS[bounds.plan] ?? []\n return AI_MODEL_CATALOG.filter(\n (entry) =>\n entry.provider === providerId && tiers.includes(entry.tier) && onLists(entry, bounds),\n )\n}\n\n/**\n * A model's blended BILLED rate, for \"the cheapest allowed\" and nothing\n * else.\n *\n * Billed and not provider (AGL-3015): the substitution below happens when a\n * workspace asked for a model its bounds do not allow, and the party it\n * falls back for is the one whose credits are drawn. \"Cheapest\" has to mean\n * the same thing here as in the selector this substitution stands in for,\n * which sorts on `creditsPerRequest`.\n */\nconst blendedRate = (id: string): number => {\n const rates = aiBilledRatesForModel(id)\n return rates.inputPerToken + rates.outputPerToken\n}\n\nexport interface AiModelChoice {\n /** The catalog id the request runs, and is metered, on. */\n model: string\n /** True when the routing table decided: Auto, or a pick that was declined. */\n auto: boolean\n /** A pick that was asked for and is not allowed, for the envelope. */\n declined: string | null\n}\n\n/**\n * The model one request runs on, or `null` when no provider is registered —\n * which every door has refused before it asks.\n */\nexport function resolveAiModelChoice(\n kind: AiStepKind,\n requested: unknown,\n bounds: AiModelBounds,\n settings?: AiPluginSettings,\n): AiModelChoice | null {\n const route = resolveAiRoute(kind, settings)\n if (!route) return null\n const wanted =\n typeof requested === 'string' && requested.trim() && requested.trim() !== AI_MODEL_AUTO\n ? requested.trim()\n : null\n if (wanted && aiModelsSelectable(route.provider.id, bounds).some((entry) => entry.id === wanted)) {\n return { model: wanted, auto: false, declined: null }\n }\n const restricted = Boolean(bounds.orgModels?.length) || Boolean(bounds.allotmentModels)\n let model = route.model\n if (restricted) {\n const allowed = AI_MODEL_CATALOG.filter(\n (entry) => entry.provider === route.provider.id && onLists(entry, bounds),\n )\n if (allowed.length && !allowed.some((entry) => entry.id === model)) {\n const tier = aiCatalogEntry(model)?.tier ?? AI_STEP_TIERS[kind]\n const sameTier = allowed.find((entry) => entry.tier === tier)\n const cheapest = [...allowed].sort((a, b) => blendedRate(a.id) - blendedRate(b.id))[0]\n model = (sameTier ?? cheapest).id\n }\n }\n return { model, auto: true, declined: wanted }\n}\n\n/** The median exchange over measured samples, or `null` below the minimum. */\nexport function aiMedianUsage(samples: readonly AiUsage[]): AiUsage | null {\n if (samples.length < AI_MEASURED_MEDIAN_MIN_SAMPLES) return null\n const median = (field: keyof AiUsage): number => {\n const values = samples\n .map((sample) => Number(sample[field] ?? 0))\n .filter((value) => Number.isFinite(value) && value >= 0)\n .sort((a, b) => a - b)\n if (!values.length) return 0\n const middle = Math.floor(values.length / 2)\n return values.length % 2\n ? values[middle]\n : Math.round((values[middle - 1] + values[middle]) / 2)\n }\n return {\n inputTokens: median('inputTokens'),\n outputTokens: median('outputTokens'),\n cacheReadTokens: median('cacheReadTokens'),\n cacheWriteTokens: median('cacheWriteTokens'),\n }\n}\n\nexport interface AiModelOption {\n /** A catalog id, or `AI_MODEL_AUTO`. */\n id: string\n label: string\n tier: AiCatalogEntry['tier']\n /** Credits a typical request of this kind costs on the option. */\n creditsPerRequest: number\n /** That cost as a multiple of Auto's, to one decimal. */\n multiplier: number\n}\n\nexport interface AiModelOptions {\n /** Auto, labeled with the model it routes this kind to under these bounds. */\n auto: AiModelOption & { model: string }\n /** The manual picks, cheapest first; empty where the plan offers none. */\n options: AiModelOption[]\n /** Whether the price came from the workspace's own measured exchanges. */\n measured: boolean\n}\n\n/** What the selector lists for one step kind, or `null` without a provider. */\nexport function aiModelOptions(\n kind: AiStepKind,\n bounds: AiModelBounds,\n measured: AiUsage | null,\n settings?: AiPluginSettings,\n): AiModelOptions | null {\n const route = resolveAiRoute(kind, settings)\n const auto = resolveAiModelChoice(kind, null, bounds, settings)\n if (!route || !auto) return null\n const typical = measured ?? AI_STEP_NOMINAL_USAGE[kind]\n // Billed rates throughout (AGL-3015): every figure below is what the\n // workspace will be charged for picking this model — credits per request\n // and the multiple of Auto those credits come to. Our own cost for the\n // same request is not this selector's subject and would misstate both.\n const autoCost = estimateAiBilledUsd(typical, auto.model)\n const priced = (entry: AiCatalogEntry): AiModelOption => {\n const cost = estimateAiBilledUsd(typical, entry.id)\n return {\n id: entry.id,\n label: entry.label,\n tier: entry.tier,\n creditsPerRequest: assistCreditsFromUsd(cost),\n multiplier: autoCost > 0 ? Math.round((cost / autoCost) * 10) / 10 : 1,\n }\n }\n const autoEntry = aiCatalogEntry(auto.model)\n return {\n auto: {\n id: AI_MODEL_AUTO,\n label: autoEntry ? `Auto (${autoEntry.label})` : 'Auto',\n tier: autoEntry?.tier ?? AI_STEP_TIERS[kind],\n creditsPerRequest: assistCreditsFromUsd(autoCost),\n multiplier: 1,\n model: auto.model,\n },\n options: aiModelsSelectable(route.provider.id, bounds)\n .map(priced)\n .sort((a, b) => a.creditsPerRequest - b.creditsPerRequest || a.label.localeCompare(b.label)),\n measured: measured !== null,\n }\n}\n"],"names":["assistCreditsFromUsd","AI_MODEL_CATALOG","AI_STEP_TIERS","aiCatalogEntry","aiBilledRatesForModel","estimateAiBilledUsd","resolveAiRoute","AI_MODEL_AUTO","AI_PLAN_MODEL_TIERS","free","starter","pro","business","scale","advanced","agency","enterprise","AI_MODEL_RESTRICTION_PLANS","AI_STEP_NOMINAL_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","AI_MEASURED_MEDIAN_MIN_SAMPLES","onLists","entry","bounds","orgModels","length","includes","id","allotmentModels","aiModelsSelectable","providerId","tiers","plan","filter","provider","tier","blendedRate","rates","inputPerToken","outputPerToken","resolveAiModelChoice","kind","requested","settings","route","wanted","trim","some","model","auto","declined","restricted","Boolean","allowed","sameTier","find","cheapest","sort","a","b","aiMedianUsage","samples","median","field","values","map","sample","Number","value","isFinite","middle","Math","floor","round","aiModelOptions","measured","typical","autoCost","priced","cost","label","creditsPerRequest","multiplier","autoEntry","options","localeCompare"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/model-choice.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 { assistCreditsFromUsd } from '../usage/assist-credits'\nimport type { OrgPlan } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport {\n AI_MODEL_CATALOG,\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiBilledRatesForModel,\n estimateAiBilledUsd,\n type AiCatalogEntry,\n type AiStepKind,\n} from './catalog'\nimport type { AiUsage } from './contract'\nimport { resolveAiRoute, type AiPluginSettings } from './routing'\n\n/**\n * THE MODEL SWITCH (AGL-2942): what a person may pick, what \"Auto\" means,\n * and what a pick costs.\n *\n * ## Auto is the routing table\n *\n * A request that names no model — or names one it may not have — runs on\n * `resolveAiRoute(kind)`, the table that maps a step kind to a catalog model\n * (AGL-2937). Picking a model is an override of that one row for one request,\n * never a second table.\n *\n * ## Three bounds, one intersection\n *\n * A manual pick must be served by the active provider, in a tier the org's\n * plan offers (`AI_PLAN_MODEL_TIERS`), on the org's own restriction when it\n * set one, and on the allotment allowlists that apply to the request. The\n * two allowlists bound Auto too: when the routing table's answer is not on\n * them, Auto runs on the allowed model of the same tier, else the cheapest\n * allowed one. The plan bounds only a pick — Auto is the platform's choice,\n * made for the step, and a plan never makes it worse.\n *\n * An allowlist that leaves nothing this deployment serves is not a way to\n * switch AI off by mistake: the choice falls back to the routing table, and\n * the permission keys are how AI is switched off on purpose.\n *\n * ## The price beside each option\n *\n * Credits per typical request at each model's list rates, over the step's\n * measured median exchange when the workspace has enough of them, and over\n * `AI_STEP_NOMINAL_USAGE` until it does — with the multiple of what Auto\n * would cost for the same request. Every figure is credits, never dollars.\n */\n\n/** The value a request sends for \"let the platform route it\". */\nexport const AI_MODEL_AUTO = 'auto'\n\n/**\n * The catalog tiers a MANUAL pick may name, per plan. Free runs on Auto only:\n * its taste is a wall, and a pick there would spend the account's credits at\n * a rate nobody chose on purpose. The dearest tier opens from Scale up, where\n * the band is large enough that a slower, deeper model is a real choice\n * rather than a fast way to end the month.\n */\nexport const AI_PLAN_MODEL_TIERS: Record<OrgPlan, readonly AiCatalogEntry['tier'][]> = {\n free: [],\n starter: ['fast', 'balanced'],\n pro: ['fast', 'balanced'],\n business: ['fast', 'balanced'],\n scale: ['fast', 'balanced', 'deep'],\n advanced: ['fast', 'balanced', 'deep'],\n agency: ['fast', 'balanced', 'deep'],\n enterprise: ['fast', 'balanced', 'deep'],\n}\n\n/** The plans whose admins may restrict the model set org-wide. */\nexport const AI_MODEL_RESTRICTION_PLANS: readonly OrgPlan[] = ['agency', 'enterprise']\n\n/**\n * A typical exchange per step kind, before a workspace has measured one: the\n * doors' own prompt sizes and output ceilings, read conservatively. Replaced\n * by the workspace's measured median as soon as `aiMedianUsage` has enough\n * samples, and only ever used for the price beside an option.\n */\nexport const AI_STEP_NOMINAL_USAGE: Record<AiStepKind, AiUsage> = {\n 'assist.chat': { inputTokens: 900, outputTokens: 450, cacheReadTokens: 1_050, cacheWriteTokens: 0 },\n 'copy.element': { inputTokens: 350, outputTokens: 120, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'copy.blog': { inputTokens: 600, outputTokens: 1_100, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'generate.section': { inputTokens: 700, outputTokens: 1_400, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The text step's rules are about 500 characters — far under the balanced\n // tier's cacheable minimum, so they are billed as input every time rather\n // than read from a cache (`runtime/ai-prompt-cache.spec.ts` measures it).\n 'job.text': { inputTokens: 450, outputTokens: 500, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The theme step: the tool's schema and both system blocks (about 7,500\n // characters) are the cached prefix, the site's inventory and the brief ride\n // uncached, and a full-palette answer is about 1,300 characters of JSON with\n // as much again to think in.\n 'job.theme': { inputTokens: 500, outputTokens: 850, cacheReadTokens: 2_500, cacheWriteTokens: 0 },\n // The plan step: the tool's schema, the doctrine and the plan instructions\n // (about 10,000 characters) are the cached prefix, the site inventory and the\n // brief ride uncached, and a plan answer is about 600 characters of JSON with\n // as much again to think in.\n 'job.plan': { inputTokens: 500, outputTokens: 400, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The component step: the doctrine, the component instructions, the\n // component surface's catalog and the tool are the cached prefix, the site\n // inventory, the brief and the confirmed plan ride uncached, and a card-sized\n // answer — its tree and declared props as JSON — is about 1,700 characters.\n // The component step's spec measures the prefix and the answer against this.\n 'job.component': { inputTokens: 700, outputTokens: 600, cacheReadTokens: 5_800, cacheWriteTokens: 0 },\n // The CRM step's exchange (AGL-2917). NOTHING is cached: the step runs on\n // the fast tier, whose cacheable minimum is far above what its prompt\n // reaches — the doctrine's field rules, the CRM rules and the tool come to\n // 650 to 913 tokens, as `runtime/ai-prompt-cache.spec.ts` measures — so\n // every byte is billed as input. A record's facts ride with them, a\n // timeline cut to its newest twelve entries, and the typical answer is a\n // summary and a next step of a few sentences, with no thinking.\n 'job.crm': { inputTokens: 1_800, outputTokens: 250, cacheReadTokens: 0, cacheWriteTokens: 0 },\n 'job.layout': { inputTokens: 700, outputTokens: 1_000, cacheReadTokens: 5_600, cacheWriteTokens: 0 },\n 'job.template': { inputTokens: 1_200, outputTokens: 700, cacheReadTokens: 6_400, cacheWriteTokens: 0 },\n // One page section's exchange (AGL-2907), from the modules' own text: the\n // doctrine (6,281 characters), the page instructions (1,524), the screen\n // palette catalog (8,353) and the section tool (484) are the cached prefix;\n // the site inventory (711 on the median golden site), the brief, the plan\n // and the section line ride uncached; and a golden section answer is 901\n // characters at the median, asked for with no thinking.\n 'job.page': { inputTokens: 600, outputTokens: 300, cacheReadTokens: 4_400, cacheWriteTokens: 0 },\n // One product's copy (AGL-2916), the exchange a bulk job makes once a\n // product: the doctrine, the copy rules and the tool (about 9,500 characters)\n // are the cached prefix; the product's facts, the site's categories and a\n // photo at most 768 pixels on its long edge (about 790 tokens) ride\n // uncached; and the answer is a description with its search listing, tags,\n // categories and option names, about 1,300 characters of JSON.\n 'job.products': { inputTokens: 1_500, outputTokens: 450, cacheReadTokens: 2_400, cacheWriteTokens: 0 },\n // The SEO step's listing exchange, which a page's or a product's listing is\n // exactly one of. NOTHING is cached here, whatever the breakpoints say: the\n // step runs on the fast tier, whose cacheable minimum is four times what\n // this prompt reaches, so every static byte is billed as input on every\n // attempt — the doctrine's block in its field scope, the listing rules and\n // the tool's schema, about 760 tokens together. The page text, capped at\n // 2,000 characters, rides with the current listing and a dozen other titles\n // for about another 750. The largest answer the tool accepts is about 600\n // characters of JSON, and the request asks for no thinking. An audit pass\n // is heavier: eight pages' findings and text, about 9,000 characters, with\n // up to about 8,000 back.\n 'job.seo': { inputTokens: 1_500, outputTokens: 250, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The experiment step (AGL-2914). NOTHING is cached: the A/B test rules\n // and the acceptable-use block come to about 2,300 characters, under the\n // balanced tier's cacheable minimum, so they are billed as input every\n // time. The copy under test, or a test's arms with its verdict, rides with\n // them, and a four-variant answer is about 800 characters of JSON.\n 'job.experiment': { inputTokens: 900, outputTokens: 300, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The form step: the doctrine, the form instructions, the form surface's\n // catalog (about 150 tokens) and the tool's schema are the cached prefix, the\n // site inventory and the brief ride uncached, and a form's answer is 200 to\n // 400 tokens of JSON with no extended thinking.\n 'job.form': { inputTokens: 700, outputTokens: 450, cacheReadTokens: 3_900, cacheWriteTokens: 0 },\n // An insight (AGL-2915): two exchanges, and NOTHING cached — the rules and\n // the acceptable-use block are under the balanced tier's minimum, so they\n // are billed as input on both. The read call carries the question and the\n // readers' catalog (about 2,000 characters) and answers a few reads; the\n // answer call carries four tables of about ten rows each (about 4,000\n // characters) and answers three to five insights as JSON.\n 'job.insight': { inputTokens: 2_800, outputTokens: 650, cacheReadTokens: 0, cacheWriteTokens: 0 },\n // The email step, and the campaign step that shares its generation: the\n // doctrine, the email instructions, the email palette catalog and the\n // tool's schema (about 10,300 characters) are the cached prefix, the site\n // inventory and the brief ride uncached, and the answer is one email's\n // node map with three subject lines and three preheaders.\n 'job.email': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n 'job.campaign': { inputTokens: 600, outputTokens: 1_500, cacheReadTokens: 3_600, cacheWriteTokens: 0 },\n // The workflow step's draft (AGL-2919): the doctrine, the automation\n // vocabulary and the tool's schema are the cached prefix (4,564 tokens, as\n // `runtime/ai-prompt-cache.spec.ts` measures it); what the workspace can\n // run, the site's forms and datasets and the description ride uncached; and\n // an automation of a few steps is about 1,300 characters of JSON with as\n // much again to think in. An explanation caches half as much.\n 'job.workflow': { inputTokens: 500, outputTokens: 1_000, cacheReadTokens: 4_500, cacheWriteTokens: 0 },\n // The logic step (AGL-3603): the fields doctrine, one rules block and the\n // tool's schema are the cached prefix (1,556 tokens for a function, as\n // `runtime/ai-prompt-cache.spec.ts` measures it); the site's variables and\n // the request ride uncached, and a function of a few operations is about\n // 1,200 characters of JSON with as much again to think in.\n 'job.logic': { inputTokens: 700, outputTokens: 900, cacheReadTokens: 1_500, cacheWriteTokens: 0 },\n}\n\n/** The fewest measured exchanges a median is taken over. */\nexport const AI_MEASURED_MEDIAN_MIN_SAMPLES = 3\n\nexport interface AiModelBounds {\n plan: OrgPlan\n /** The allotment allowlists that apply, intersected; `null` for none. */\n allotmentModels?: readonly string[] | null\n /** The org-wide restriction; `null` for none. */\n orgModels?: readonly string[] | null\n}\n\nfunction onLists(entry: AiCatalogEntry, bounds: AiModelBounds): boolean {\n if (bounds.orgModels?.length && !bounds.orgModels.includes(entry.id)) return false\n if (bounds.allotmentModels && !bounds.allotmentModels.includes(entry.id)) return false\n return true\n}\n\n/** The catalog models a manual pick may name on a provider under these bounds. */\nexport function aiModelsSelectable(\n providerId: string,\n bounds: AiModelBounds,\n): AiCatalogEntry[] {\n const tiers = AI_PLAN_MODEL_TIERS[bounds.plan] ?? []\n return AI_MODEL_CATALOG.filter(\n (entry) =>\n entry.provider === providerId && tiers.includes(entry.tier) && onLists(entry, bounds),\n )\n}\n\n/**\n * A model's blended BILLED rate, for \"the cheapest allowed\" and nothing\n * else.\n *\n * Billed and not provider (AGL-3015): the substitution below happens when a\n * workspace asked for a model its bounds do not allow, and the party it\n * falls back for is the one whose credits are drawn. \"Cheapest\" has to mean\n * the same thing here as in the selector this substitution stands in for,\n * which sorts on `creditsPerRequest`.\n */\nconst blendedRate = (id: string): number => {\n const rates = aiBilledRatesForModel(id)\n return rates.inputPerToken + rates.outputPerToken\n}\n\nexport interface AiModelChoice {\n /** The catalog id the request runs, and is metered, on. */\n model: string\n /** True when the routing table decided: Auto, or a pick that was declined. */\n auto: boolean\n /** A pick that was asked for and is not allowed, for the envelope. */\n declined: string | null\n}\n\n/**\n * The model one request runs on, or `null` when no provider is registered —\n * which every door has refused before it asks.\n */\nexport function resolveAiModelChoice(\n kind: AiStepKind,\n requested: unknown,\n bounds: AiModelBounds,\n settings?: AiPluginSettings,\n): AiModelChoice | null {\n const route = resolveAiRoute(kind, settings)\n if (!route) return null\n const wanted =\n typeof requested === 'string' && requested.trim() && requested.trim() !== AI_MODEL_AUTO\n ? requested.trim()\n : null\n if (wanted && aiModelsSelectable(route.provider.id, bounds).some((entry) => entry.id === wanted)) {\n return { model: wanted, auto: false, declined: null }\n }\n const restricted = Boolean(bounds.orgModels?.length) || Boolean(bounds.allotmentModels)\n let model = route.model\n if (restricted) {\n const allowed = AI_MODEL_CATALOG.filter(\n (entry) => entry.provider === route.provider.id && onLists(entry, bounds),\n )\n if (allowed.length && !allowed.some((entry) => entry.id === model)) {\n const tier = aiCatalogEntry(model)?.tier ?? AI_STEP_TIERS[kind]\n const sameTier = allowed.find((entry) => entry.tier === tier)\n const cheapest = [...allowed].sort((a, b) => blendedRate(a.id) - blendedRate(b.id))[0]\n model = (sameTier ?? cheapest).id\n }\n }\n return { model, auto: true, declined: wanted }\n}\n\n/** The median exchange over measured samples, or `null` below the minimum. */\nexport function aiMedianUsage(samples: readonly AiUsage[]): AiUsage | null {\n if (samples.length < AI_MEASURED_MEDIAN_MIN_SAMPLES) return null\n const median = (field: keyof AiUsage): number => {\n const values = samples\n .map((sample) => Number(sample[field] ?? 0))\n .filter((value) => Number.isFinite(value) && value >= 0)\n .sort((a, b) => a - b)\n if (!values.length) return 0\n const middle = Math.floor(values.length / 2)\n return values.length % 2\n ? values[middle]\n : Math.round((values[middle - 1] + values[middle]) / 2)\n }\n return {\n inputTokens: median('inputTokens'),\n outputTokens: median('outputTokens'),\n cacheReadTokens: median('cacheReadTokens'),\n cacheWriteTokens: median('cacheWriteTokens'),\n }\n}\n\nexport interface AiModelOption {\n /** A catalog id, or `AI_MODEL_AUTO`. */\n id: string\n label: string\n tier: AiCatalogEntry['tier']\n /** Credits a typical request of this kind costs on the option. */\n creditsPerRequest: number\n /** That cost as a multiple of Auto's, to one decimal. */\n multiplier: number\n}\n\nexport interface AiModelOptions {\n /** Auto, labeled with the model it routes this kind to under these bounds. */\n auto: AiModelOption & { model: string }\n /** The manual picks, cheapest first; empty where the plan offers none. */\n options: AiModelOption[]\n /** Whether the price came from the workspace's own measured exchanges. */\n measured: boolean\n}\n\n/** What the selector lists for one step kind, or `null` without a provider. */\nexport function aiModelOptions(\n kind: AiStepKind,\n bounds: AiModelBounds,\n measured: AiUsage | null,\n settings?: AiPluginSettings,\n): AiModelOptions | null {\n const route = resolveAiRoute(kind, settings)\n const auto = resolveAiModelChoice(kind, null, bounds, settings)\n if (!route || !auto) return null\n const typical = measured ?? AI_STEP_NOMINAL_USAGE[kind]\n // Billed rates throughout (AGL-3015): every figure below is what the\n // workspace will be charged for picking this model — credits per request\n // and the multiple of Auto those credits come to. Our own cost for the\n // same request is not this selector's subject and would misstate both.\n const autoCost = estimateAiBilledUsd(typical, auto.model)\n const priced = (entry: AiCatalogEntry): AiModelOption => {\n const cost = estimateAiBilledUsd(typical, entry.id)\n return {\n id: entry.id,\n label: entry.label,\n tier: entry.tier,\n creditsPerRequest: assistCreditsFromUsd(cost),\n multiplier: autoCost > 0 ? Math.round((cost / autoCost) * 10) / 10 : 1,\n }\n }\n const autoEntry = aiCatalogEntry(auto.model)\n return {\n auto: {\n id: AI_MODEL_AUTO,\n label: autoEntry ? `Auto (${autoEntry.label})` : 'Auto',\n tier: autoEntry?.tier ?? AI_STEP_TIERS[kind],\n creditsPerRequest: assistCreditsFromUsd(autoCost),\n multiplier: 1,\n model: auto.model,\n },\n options: aiModelsSelectable(route.provider.id, bounds)\n .map(priced)\n .sort((a, b) => a.creditsPerRequest - b.creditsPerRequest || a.label.localeCompare(b.label)),\n measured: measured !== null,\n }\n}\n"],"names":["assistCreditsFromUsd","AI_MODEL_CATALOG","AI_STEP_TIERS","aiCatalogEntry","aiBilledRatesForModel","estimateAiBilledUsd","resolveAiRoute","AI_MODEL_AUTO","AI_PLAN_MODEL_TIERS","free","starter","pro","business","scale","advanced","agency","enterprise","AI_MODEL_RESTRICTION_PLANS","AI_STEP_NOMINAL_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","AI_MEASURED_MEDIAN_MIN_SAMPLES","onLists","entry","bounds","orgModels","length","includes","id","allotmentModels","aiModelsSelectable","providerId","tiers","plan","filter","provider","tier","blendedRate","rates","inputPerToken","outputPerToken","resolveAiModelChoice","kind","requested","settings","route","wanted","trim","some","model","auto","declined","restricted","Boolean","allowed","sameTier","find","cheapest","sort","a","b","aiMedianUsage","samples","median","field","values","map","sample","Number","value","isFinite","middle","Math","floor","round","aiModelOptions","measured","typical","autoCost","priced","cost","label","creditsPerRequest","multiplier","autoEntry","options","localeCompare"],"mappings":"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"}
|
|
@@ -372,6 +372,24 @@ import { aiProviderById, defaultAiProvider } from "./registry.js";
|
|
|
372
372
|
meanScore: 0.9688
|
|
373
373
|
}
|
|
374
374
|
},
|
|
375
|
+
'job.logic': {
|
|
376
|
+
// A site function or variable proposed, or a function explained
|
|
377
|
+
// (AGL-3603): which conditions and arithmetic a description means is the
|
|
378
|
+
// judgment the step sells, so it thinks before it answers.
|
|
379
|
+
thinking: 'adaptive',
|
|
380
|
+
effort: null,
|
|
381
|
+
maxTokens: 3000,
|
|
382
|
+
maxTokensBasis: 'the largest function the tool accepts — twelve parameters, twenty-four operations — is far past any description; a function of eight operations is about 2,400 characters of JSON, at three characters a token with as much again to think in, as ai-job-logic-step.spec.ts measures it',
|
|
383
|
+
eval: {
|
|
384
|
+
kinds: [
|
|
385
|
+
'logic'
|
|
386
|
+
],
|
|
387
|
+
scores: 'answers',
|
|
388
|
+
source: 'authored',
|
|
389
|
+
passRate: 1,
|
|
390
|
+
meanScore: 1
|
|
391
|
+
}
|
|
392
|
+
},
|
|
375
393
|
'job.seo': {
|
|
376
394
|
thinking: null,
|
|
377
395
|
effort: null,
|