@aglyn/plugins-ai 1.0.0-beta.228 → 1.0.0-beta.230
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-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 +35 -5
- 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-jobs-list-page.component.js +12 -1
- package/src/lib/components/ai-jobs-list-page.component.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-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.d.ts +1 -1
- package/src/lib/components/ai-site-build-page.component.js +177 -20
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +10 -1
- package/src/lib/components/ai-site-start-card.component.js +7 -4
- package/src/lib/components/ai-site-start-card.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.d.ts +11 -0
- package/src/lib/components/assist-jobs-drawer.component.js +85 -4
- 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 +138 -0
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
- package/src/lib/jobs/ai-job-build-step.js +657 -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-layout-site-pages.d.ts +70 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js +216 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-step.d.ts +2 -2
- package/src/lib/jobs/ai-job-layout-step.js +19 -1
- 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-step.js +6 -6
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
- package/src/lib/jobs/ai-job-plan-step.js +79 -11
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
- package/src/lib/jobs/ai-job-site-step.js +207 -58
- 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 +318 -13
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +90 -2
- package/src/lib/jobs/ai-jobs.js +495 -61
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- 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/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 +471 -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 +56 -0
- package/src/lib/model/ai-build-progress.js +128 -0
- package/src/lib/model/ai-build-progress.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.js +3 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +20 -1
- package/src/lib/model/ai-job-failure-copy.js +28 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -0
- package/src/lib/model/ai-job-notice.js +11 -2
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +87 -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 +28 -3
- package/src/lib/model/ai-site-build-progress.js +231 -17
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +16 -0
- package/src/lib/model/ai-site-start-seo.js +54 -4
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +14 -0
- package/src/lib/model/ai-site-start.js +43 -0
- 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 +240 -11
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +81 -4
- package/src/lib/providers/catalog.js +80 -9
- 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 +19 -0
- package/src/lib/runtime/ai-doctrine-validators.js +89 -0
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -0
- package/src/lib/runtime/ai-doctrine.js +16 -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/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 +11 -0
- package/src/lib/tools/ai-workflow-tool.js +31 -0
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +31 -3
- package/src/lib/usage/assist-job-refund.js +46 -10
- 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/routing.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_SETTING_PLATFORM, AI_STEP_MODEL_SETTING } from '../plugin-config'\nimport {\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiDefaultModelFor,\n type AiStepKind,\n} from './catalog'\nimport type { AiEvalKind } from '../runtime/ai-eval'\nimport type { AiEffort, AiProvider, AiThinking } from './contract'\nimport { aiProviderById, defaultAiProvider } from './registry'\n\n/**\n * The routing table (AGL-2937, AGL-2939): step kind → provider and model,\n * through the catalog and never a vendor literal.\n *\n * Precedence, dearest override first:\n *\n * 1. the org's `pluginSettings/ai` — a provider, and a model per step kind\n * that must be one the provider serves;\n * 2. the deployment's environment — `AI_PROVIDER` for the provider,\n * `AI_DEFAULT_MODEL` for every step, and `ASSIST_MODEL` for the\n * assistant alone (the incident-response override the chat door has\n * always honored);\n * 3. the catalog's tier for the step kind on the resolved provider.\n */\n\n/** The environment override for every step kind. */\nexport const AI_DEFAULT_MODEL_ENV = 'AI_DEFAULT_MODEL'\n/** The assistant's own override, honored before the general one. */\nexport const ASSIST_MODEL_ENV = 'ASSIST_MODEL'\n\n/** The org's resolved `pluginSettings/ai` values, as `getPluginConfig` merges them. */\nexport type AiPluginSettings = Record<string, unknown>\n\nexport interface AiRoute {\n provider: AiProvider\n model: string\n}\n\n/** A settings value that names a real choice, or `undefined` for the platform default. */\nfunction chosen(settings: AiPluginSettings | undefined, key: string): string | undefined {\n const value = settings?.[key]\n return typeof value === 'string' && value && value !== AI_SETTING_PLATFORM\n ? value\n : undefined\n}\n\n/** The provider a workspace runs on: its own choice when registered, else the platform's. */\nexport function resolveAiProvider(settings?: AiPluginSettings): AiProvider | undefined {\n const wanted = chosen(settings, 'provider')\n if (wanted) {\n const named = aiProviderById(wanted)\n if (named) return named\n }\n return defaultAiProvider()\n}\n\n/**\n * The model a step kind runs on for a workspace, on the resolved provider.\n * A chosen model on another provider is not served — the provider decides\n * what it can run — and falls back to the provider's default for the tier.\n */\nexport function resolveAiRoute(\n kind: AiStepKind,\n settings?: AiPluginSettings,\n): AiRoute | undefined {\n const provider = resolveAiProvider(settings)\n if (!provider) return undefined\n const servedBy = (modelId: string | undefined): string | undefined =>\n modelId && aiCatalogEntry(modelId)?.provider === provider.id ? modelId : undefined\n const model =\n servedBy(chosen(settings, AI_STEP_MODEL_SETTING[kind])) ??\n (kind === 'assist.chat' ? servedBy(process.env[ASSIST_MODEL_ENV]?.trim()) : undefined) ??\n servedBy(process.env[AI_DEFAULT_MODEL_ENV]?.trim()) ??\n aiDefaultModelFor(provider.id, AI_STEP_TIERS[kind])\n return model ? { provider, model } : undefined\n}\n\n/**\n * What one step kind asks of its model, beside the tier it is served from\n * (`AI_STEP_TIERS` in the catalog): whether it thinks and how hard, its\n * answer ceiling, and the eval score that tier holds on the step's golden\n * briefs (AGL-2937).\n */\nexport interface AiRoutingRow {\n /** `null` sends no setting: the model's own default, and all a fast-tier model accepts. */\n thinking: AiThinking | null\n effort: AiEffort | null\n /** The answer ceiling, thinking included. */\n maxTokens: number\n /** What the ceiling is sized from. */\n maxTokensBasis: string\n eval: AiRoutingEval\n}\n\nexport interface AiRoutingEval {\n /** The golden-brief kinds the step answers. */\n kinds: readonly AiEvalKind[]\n /** `answers` scores the step's answers; `plans` scores the plans those kinds' answers carry. */\n scores: 'answers' | 'plans'\n /** `authored` while the score is the hand-written references'; `recorded` once a live run records answers. */\n source: 'authored' | 'recorded'\n passRate: number\n /** The mean answer score; `null` for a plan score, which passes or fails. */\n meanScore: number | null\n}\n\n/**\n * THE ROUTING TABLE (AGL-2937): what each step kind asks of the model, in one\n * place, with the evidence beside it.\n *\n * A step runs on the smallest tier that holds its eval floor. A row moves to a\n * cheaper tier, or a lower effort, only when a recorded eval\n * (`npm run eval:ai-live`) holds the floor there; while a row's score is the\n * authored references', it says so, and the row stays where production has\n * run it. `runtime/ai-eval.spec.ts` recomputes every row's score from the\n * harness, so a row cannot claim a score its briefs do not hold.\n *\n * A ceiling is sized from the p95 output the staff signals page reports for\n * the kind once production has served it; until then `maxTokensBasis` names\n * what it is sized from. The same spec holds every reference answer under its\n * row's ceiling at three characters a token, a reading that errs high for a\n * tokenizer that splits finer than four.\n */\nexport const AI_ROUTING_TABLE: Readonly<Record<AiStepKind, AiRoutingRow>> = {\n 'assist.chat': {\n // A scoped, latency-sensitive chat turn: no thinking, the low effort rung.\n thinking: 'off',\n effort: 'low',\n maxTokens: 1024,\n maxTokensBasis:\n 'a docs-grounded answer of a few short paragraphs; the edit rung raises it for its tool call',\n eval: { kinds: ['chat'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.element': {\n thinking: null,\n effort: null,\n maxTokens: 1024,\n maxTokensBasis: 'one element of copy, about the length of the text it replaces',\n eval: { kinds: ['element'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.blog': {\n thinking: null,\n effort: null,\n maxTokens: 2048,\n maxTokensBasis: 'one blog post body in markdown-lite',\n eval: { kinds: ['blog'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'generate.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.layout': {\n // The plan was reasoned out and confirmed before the step, so it writes without thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a layout, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['layout'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.template': {\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a template, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['template'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.component': {\n // A component's tree and the properties it declares, in one answer, from a\n // plan reasoned out and confirmed before the step (AGL-2908).\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the ceiling the layout and template steps keep, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['component'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.crm': {\n // CRM by AI (AGL-2917): short answers through a strict tool, written from\n // the facts the CRM reports about the record the member opened, with no\n // thinking. The ceiling fits an inline door, so a member is answered in\n // the request that asked.\n thinking: null,\n effort: null,\n maxTokens: 700,\n maxTokensBasis:\n \"the largest answer a CRM tool accepts: sixty columns matched to fields by number, or an email draft at its length limits, at three characters a token with room, as ai-job-crm-step.spec.ts measures it\",\n eval: { kinds: ['crm'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9938 },\n },\n 'job.experiment': {\n // Variants of one piece of copy, or a result explained against a verdict\n // code reached before the request was made (AGL-2914). The judgment is\n // already made either way, so it writes without thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n \"four variants at the tool's own lengths — a name, a headline, a body and a rationale each — written as JSON, about 3,000 characters at three characters a token with room; an explanation is a headline, four sentences and a next step, well under it, as ai-job-experiment-step.spec.ts measures both\",\n eval: { kinds: ['experiment'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.form': {\n // A form's two halves in one answer: the field declaration and the design\n // that renders it, as JSON, with no extended thinking (AGL-2913). The step\n // asks this ceiling, which is the doctrine's own for a form, and registers\n // the time it takes (AGL-3035).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"the largest form the doctrine's output budget admits, a form's fields, its consent and routing declaration and the design that renders them written as JSON at the wordiest golden's characters a stored byte, under it with room; ai-job-form-step.spec.ts measures it\",\n eval: { kinds: ['form'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.insight': {\n // Readers chosen, then their tables phrased (AGL-2915): short sentences\n // over figures code computed, each held to the rows it cites, so no\n // extended thinking. The read call before the answer asks for at most\n // `AI_INSIGHT_READ_MAX_TOKENS` of the same allowance.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n 'five insights at their 280-character bound, each citing three tables of ten rows, and the gap sentence, written as JSON: about 2,700 characters, under 1,000 tokens at three characters a token, with room',\n eval: { kinds: ['insight'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.page': {\n // One section of a page, written against a plan a member already\n // confirmed, so it writes without thinking (AGL-2907). The ceiling here\n // is the MOST a pass may ask for; each pass sizes its own down from it to\n // what fits the time the job beat can give the step, which on the\n // balanced tier is about half of this.\n thinking: 'off',\n effort: null,\n maxTokens: 2000,\n maxTokensBasis:\n \"a section of a page written as JSON, about forty elements at the step's measured 45 tokens an element; a pass lowers it to the worst case that fits AI_JOB_PAGE_STEP_MINIMUM_MS on the model it runs\",\n eval: { kinds: ['page'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.products': {\n // A product's copy from its name, text and photo, or a store's first\n // products, categories and discounts from a brief (AGL-2916): answers held\n // to the storefront claim rules, written with no extended thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the largest catalog the tool accepts, twelve proposed products at every bound written as JSON, which the balanced tier asks as much of as fits a beat; one product's copy asks 1,500 and categories with discounts 2,000, as ai-job-products-step.spec.ts measures them\",\n eval: {\n kinds: ['product', 'catalog', 'categories'],\n scores: 'answers',\n source: 'authored',\n passRate: 1,\n meanScore: 0.9922,\n },\n },\n 'job.email': {\n // One email design from the email palette, written against the brief with\n // no extended thinking (AGL-2912). A campaign job runs the same\n // generation, so it carries the same ceiling.\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.campaign': {\n // The campaign job generates its email through the email step, so it is\n // routed identically; the draft campaign it also writes asks nothing of\n // the model (AGL-2912).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.workflow': {\n // An automation drafted from a description, or one explained (AGL-2919):\n // which trigger and which steps a description means is the judgment the\n // step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 4000,\n maxTokensBasis:\n 'the largest automation the tool accepts, 6,000 characters written out as JSON, at three characters a token with as much again to think in, as ai-job-workflow-step.spec.ts measures it',\n eval: { kinds: ['workflow'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.seo': {\n thinking: null,\n effort: null,\n maxTokens: 1024,\n maxTokensBasis:\n \"the largest listing the tool accepts, at three characters a token with a quarter again, as ai-job-seo-step.spec.ts measures it; an audit's site proposal and fix batches keep ceilings of their own, measured the same way\",\n eval: { kinds: ['seo'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.text': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 1024,\n maxTokensBasis: 'a few paragraphs of copy; a brief asking for more gets a draft to extend',\n eval: { kinds: ['text'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.theme': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n 'the largest answer the theme tool accepts, about 2,500 tokens of JSON as ai-job-theme-step.spec.ts measures it, with as much again to think in',\n eval: { kinds: ['theme'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.plan': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis: 'a plan of up to twelve screens with sixteen sections each, with room to think',\n eval: {\n kinds: ['page', 'template', 'component', 'layout', 'form'],\n scores: 'plans',\n source: 'authored',\n passRate: 1,\n meanScore: null,\n },\n },\n}\n\n/** The model id alone — what a door records on the meter and the job. */\nexport function aiModelForStep(kind: AiStepKind, settings?: AiPluginSettings): string {\n const route = resolveAiRoute(kind, settings)\n if (!route) {\n // Every door gates on the provider being configured before it asks\n // for a model, so this is a wiring fault rather than a customer path.\n throw new Error('no AI provider is registered')\n }\n return route.model\n}\n"],"names":["AI_SETTING_PLATFORM","AI_STEP_MODEL_SETTING","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","aiProviderById","defaultAiProvider","AI_DEFAULT_MODEL_ENV","ASSIST_MODEL_ENV","chosen","settings","key","value","undefined","resolveAiProvider","wanted","named","resolveAiRoute","kind","servedBy","process","provider","modelId","id","model","env","trim","AI_ROUTING_TABLE","thinking","effort","maxTokens","maxTokensBasis","eval","kinds","scores","source","passRate","meanScore","aiModelForStep","route","Error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/routing.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_SETTING_PLATFORM, AI_STEP_MODEL_SETTING } from '../plugin-config'\nimport {\n AI_STEP_TIERS,\n aiCatalogEntry,\n aiDefaultModelFor,\n type AiStepKind,\n} from './catalog'\nimport type { AiEvalKind } from '../runtime/ai-eval'\nimport type { AiEffort, AiProvider, AiThinking } from './contract'\nimport { aiProviderById, defaultAiProvider } from './registry'\n\n/**\n * The routing table (AGL-2937, AGL-2939): step kind → provider and model,\n * through the catalog and never a vendor literal.\n *\n * Precedence, dearest override first:\n *\n * 1. the org's `pluginSettings/ai` — a provider, and a model per step kind\n * that must be one the provider serves;\n * 2. the deployment's environment — `AI_PROVIDER` for the provider,\n * `AI_DEFAULT_MODEL` for every step, and `ASSIST_MODEL` for the\n * assistant alone (the incident-response override the chat door has\n * always honored);\n * 3. the catalog's tier for the step kind on the resolved provider.\n */\n\n/** The environment override for every step kind. */\nexport const AI_DEFAULT_MODEL_ENV = 'AI_DEFAULT_MODEL'\n/** The assistant's own override, honored before the general one. */\nexport const ASSIST_MODEL_ENV = 'ASSIST_MODEL'\n\n/** The org's resolved `pluginSettings/ai` values, as `getPluginConfig` merges them. */\nexport type AiPluginSettings = Record<string, unknown>\n\nexport interface AiRoute {\n provider: AiProvider\n model: string\n}\n\n/** A settings value that names a real choice, or `undefined` for the platform default. */\nfunction chosen(settings: AiPluginSettings | undefined, key: string): string | undefined {\n const value = settings?.[key]\n return typeof value === 'string' && value && value !== AI_SETTING_PLATFORM\n ? value\n : undefined\n}\n\n/** The provider a workspace runs on: its own choice when registered, else the platform's. */\nexport function resolveAiProvider(settings?: AiPluginSettings): AiProvider | undefined {\n const wanted = chosen(settings, 'provider')\n if (wanted) {\n const named = aiProviderById(wanted)\n if (named) return named\n }\n return defaultAiProvider()\n}\n\n/**\n * The model a step kind runs on for a workspace, on the resolved provider.\n * A chosen model on another provider is not served — the provider decides\n * what it can run — and falls back to the provider's default for the tier.\n */\nexport function resolveAiRoute(\n kind: AiStepKind,\n settings?: AiPluginSettings,\n): AiRoute | undefined {\n const provider = resolveAiProvider(settings)\n if (!provider) return undefined\n const servedBy = (modelId: string | undefined): string | undefined =>\n modelId && aiCatalogEntry(modelId)?.provider === provider.id ? modelId : undefined\n const model =\n servedBy(chosen(settings, AI_STEP_MODEL_SETTING[kind])) ??\n (kind === 'assist.chat' ? servedBy(process.env[ASSIST_MODEL_ENV]?.trim()) : undefined) ??\n servedBy(process.env[AI_DEFAULT_MODEL_ENV]?.trim()) ??\n aiDefaultModelFor(provider.id, AI_STEP_TIERS[kind])\n return model ? { provider, model } : undefined\n}\n\n/**\n * What one step kind asks of its model, beside the tier it is served from\n * (`AI_STEP_TIERS` in the catalog): whether it thinks and how hard, its\n * answer ceiling, and the eval score that tier holds on the step's golden\n * briefs (AGL-2937).\n */\nexport interface AiRoutingRow {\n /** `null` sends no setting: the model's own default, and all a fast-tier model accepts. */\n thinking: AiThinking | null\n effort: AiEffort | null\n /** The answer ceiling, thinking included. */\n maxTokens: number\n /** What the ceiling is sized from. */\n maxTokensBasis: string\n eval: AiRoutingEval\n}\n\nexport interface AiRoutingEval {\n /** The golden-brief kinds the step answers. */\n kinds: readonly AiEvalKind[]\n /** `answers` scores the step's answers; `plans` scores the plans those kinds' answers carry. */\n scores: 'answers' | 'plans'\n /** `authored` while the score is the hand-written references'; `recorded` once a live run records answers. */\n source: 'authored' | 'recorded'\n passRate: number\n /** The mean answer score; `null` for a plan score, which passes or fails. */\n meanScore: number | null\n}\n\n/**\n * THE ROUTING TABLE (AGL-2937): what each step kind asks of the model, in one\n * place, with the evidence beside it.\n *\n * A step runs on the smallest tier that holds its eval floor. A row moves to a\n * cheaper tier, or a lower effort, only when a recorded eval\n * (`npm run eval:ai-live`) holds the floor there; while a row's score is the\n * authored references', it says so, and the row stays where production has\n * run it. `runtime/ai-eval.spec.ts` recomputes every row's score from the\n * harness, so a row cannot claim a score its briefs do not hold.\n *\n * A ceiling is sized from the p95 output the staff signals page reports for\n * the kind once production has served it; until then `maxTokensBasis` names\n * what it is sized from. The same spec holds every reference answer under its\n * row's ceiling at three characters a token, a reading that errs high for a\n * tokenizer that splits finer than four.\n */\nexport const AI_ROUTING_TABLE: Readonly<Record<AiStepKind, AiRoutingRow>> = {\n 'assist.chat': {\n // A scoped, latency-sensitive chat turn: no thinking, the low effort rung.\n thinking: 'off',\n effort: 'low',\n maxTokens: 1024,\n maxTokensBasis:\n 'a docs-grounded answer of a few short paragraphs; the edit rung raises it for its tool call',\n eval: { kinds: ['chat'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.element': {\n thinking: null,\n effort: null,\n maxTokens: 1024,\n maxTokensBasis: 'one element of copy, about the length of the text it replaces',\n eval: { kinds: ['element'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'copy.blog': {\n thinking: null,\n effort: null,\n maxTokens: 2048,\n maxTokensBasis: 'one blog post body in markdown-lite',\n eval: { kinds: ['blog'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'generate.section': {\n thinking: null,\n effort: null,\n maxTokens: 3000,\n maxTokensBasis: 'a section of four to twelve nodes written as JSON',\n eval: { kinds: ['section'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.layout': {\n // The plan was reasoned out and confirmed before the step, so it writes without thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a layout, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['layout'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.template': {\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"tighter than the doctrine's default for a template, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['template'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.component': {\n // A component's tree and the properties it declares, in one answer, from a\n // plan reasoned out and confirmed before the step (AGL-2908).\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the ceiling the layout and template steps keep, so one step and its re-ask fit the job beat's budget; the tree is held to its rule 17 budget either way\",\n eval: { kinds: ['component'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.crm': {\n // CRM by AI (AGL-2917): short answers through a strict tool, written from\n // the facts the CRM reports about the record the member opened, with no\n // thinking. The ceiling fits an inline door, so a member is answered in\n // the request that asked.\n thinking: null,\n effort: null,\n maxTokens: 700,\n maxTokensBasis:\n \"the largest answer a CRM tool accepts: sixty columns matched to fields by number, or an email draft at its length limits, at three characters a token with room, as ai-job-crm-step.spec.ts measures it\",\n eval: { kinds: ['crm'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9938 },\n },\n 'job.experiment': {\n // Variants of one piece of copy, or a result explained against a verdict\n // code reached before the request was made (AGL-2914). The judgment is\n // already made either way, so it writes without thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n \"four variants at the tool's own lengths — a name, a headline, a body and a rationale each — written as JSON, about 3,000 characters at three characters a token with room; an explanation is a headline, four sentences and a next step, well under it, as ai-job-experiment-step.spec.ts measures both\",\n eval: { kinds: ['experiment'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.form': {\n // A form's two halves in one answer: the field declaration and the design\n // that renders it, as JSON, with no extended thinking (AGL-2913). The step\n // asks this ceiling, which is the doctrine's own for a form, and registers\n // the time it takes (AGL-3035).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"the largest form the doctrine's output budget admits, a form's fields, its consent and routing declaration and the design that renders them written as JSON at the wordiest golden's characters a stored byte, under it with room; ai-job-form-step.spec.ts measures it\",\n eval: { kinds: ['form'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.insight': {\n // Readers chosen, then their tables phrased (AGL-2915): short sentences\n // over figures code computed, each held to the rows it cites, so no\n // extended thinking. The read call before the answer asks for at most\n // `AI_INSIGHT_READ_MAX_TOKENS` of the same allowance.\n thinking: 'off',\n effort: null,\n maxTokens: 1500,\n maxTokensBasis:\n 'five insights at their 280-character bound, each citing three tables of ten rows, and the gap sentence, written as JSON: about 2,700 characters, under 1,000 tokens at three characters a token, with room',\n eval: { kinds: ['insight'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.page': {\n // One section of a page, written against a plan a member already\n // confirmed, so it writes without thinking (AGL-2907). The ceiling here\n // is the MOST a pass may ask for; each pass sizes its own down from it to\n // what fits the time the job beat can give the step, which on the\n // balanced tier is about half of this.\n thinking: 'off',\n effort: null,\n maxTokens: 2000,\n maxTokensBasis:\n \"a section of a page written as JSON, about forty elements at the step's measured 45 tokens an element; a pass lowers it to the worst case that fits AI_JOB_PAGE_STEP_MINIMUM_MS on the model it runs\",\n eval: { kinds: ['page'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9833 },\n },\n 'job.products': {\n // A product's copy from its name, text and photo, or a store's first\n // products, categories and discounts from a brief (AGL-2916): answers held\n // to the storefront claim rules, written with no extended thinking.\n thinking: 'off',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n \"the largest catalog the tool accepts, twelve proposed products at every bound written as JSON, which the balanced tier asks as much of as fits a beat; one product's copy asks 1,500 and categories with discounts 2,000, as ai-job-products-step.spec.ts measures them\",\n eval: {\n kinds: ['product', 'catalog', 'categories'],\n scores: 'answers',\n source: 'authored',\n passRate: 1,\n meanScore: 0.9922,\n },\n },\n 'job.email': {\n // One email design from the email palette, written against the brief with\n // no extended thinking (AGL-2912). A campaign job runs the same\n // generation, so it carries the same ceiling.\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.campaign': {\n // The campaign job generates its email through the email step, so it is\n // routed identically; the draft campaign it also writes asks nothing of\n // the model (AGL-2912).\n thinking: 'off',\n effort: null,\n maxTokens: 6000,\n maxTokensBasis:\n \"an email's node map written as JSON, with three subject lines and three preheaders beside it\",\n eval: { kinds: ['email'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.workflow': {\n // An automation drafted from a description, or one explained (AGL-2919):\n // which trigger and which steps a description means is the judgment the\n // step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 4000,\n maxTokensBasis:\n 'the largest automation the tool accepts, 6,000 characters written out as JSON, at three characters a token with as much again to think in, as ai-job-workflow-step.spec.ts measures it',\n eval: { kinds: ['workflow'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.logic': {\n // A site function or variable proposed, or a function explained\n // (AGL-3603): which conditions and arithmetic a description means is the\n // judgment the step sells, so it thinks before it answers.\n thinking: 'adaptive',\n effort: null,\n maxTokens: 3000,\n maxTokensBasis:\n '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',\n eval: { kinds: ['logic'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.seo': {\n thinking: null,\n effort: null,\n maxTokens: 1024,\n maxTokensBasis:\n \"the largest listing the tool accepts, at three characters a token with a quarter again, as ai-job-seo-step.spec.ts measures it; an audit's site proposal and fix batches keep ceilings of their own, measured the same way\",\n eval: { kinds: ['seo'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.text': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 1024,\n maxTokensBasis: 'a few paragraphs of copy; a brief asking for more gets a draft to extend',\n eval: { kinds: ['text'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 1 },\n },\n 'job.theme': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis:\n 'the largest answer the theme tool accepts, about 2,500 tokens of JSON as ai-job-theme-step.spec.ts measures it, with as much again to think in',\n eval: { kinds: ['theme'], scores: 'answers', source: 'authored', passRate: 1, meanScore: 0.9688 },\n },\n 'job.plan': {\n thinking: 'adaptive',\n effort: null,\n maxTokens: 8000,\n maxTokensBasis: 'a plan of up to twelve screens with sixteen sections each, with room to think',\n eval: {\n kinds: ['page', 'template', 'component', 'layout', 'form'],\n scores: 'plans',\n source: 'authored',\n passRate: 1,\n meanScore: null,\n },\n },\n}\n\n/** The model id alone — what a door records on the meter and the job. */\nexport function aiModelForStep(kind: AiStepKind, settings?: AiPluginSettings): string {\n const route = resolveAiRoute(kind, settings)\n if (!route) {\n // Every door gates on the provider being configured before it asks\n // for a model, so this is a wiring fault rather than a customer path.\n throw new Error('no AI provider is registered')\n }\n return route.model\n}\n"],"names":["AI_SETTING_PLATFORM","AI_STEP_MODEL_SETTING","AI_STEP_TIERS","aiCatalogEntry","aiDefaultModelFor","aiProviderById","defaultAiProvider","AI_DEFAULT_MODEL_ENV","ASSIST_MODEL_ENV","chosen","settings","key","value","undefined","resolveAiProvider","wanted","named","resolveAiRoute","kind","servedBy","process","provider","modelId","id","model","env","trim","AI_ROUTING_TABLE","thinking","effort","maxTokens","maxTokensBasis","eval","kinds","scores","source","passRate","meanScore","aiModelForStep","route","Error"],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
import { type AiGeneratedImage, type AiImageProvider, type AiImageRequest } from './image-contract';
|
|
19
|
+
/**
|
|
20
|
+
* Google's Gemini image models on Vertex AI (AGL-3602), behind the image
|
|
21
|
+
* contract: `POST https://aiplatform.googleapis.com/v1/projects/{project}/
|
|
22
|
+
* locations/global/publishers/google/models/{model}:generateContent`.
|
|
23
|
+
*
|
|
24
|
+
* Gemini, not Imagen: Google discontinued the Imagen endpoints on Vertex AI
|
|
25
|
+
* in 2026 and names `gemini-3.1-flash-image` as Imagen 4's replacement, at
|
|
26
|
+
* the HIGH thinking level for the standard Imagen 4 model.
|
|
27
|
+
*
|
|
28
|
+
* ## No key of its own
|
|
29
|
+
*
|
|
30
|
+
* It authenticates as the service account firebase-admin already runs as,
|
|
31
|
+
* through that credential's `getAccessToken()` — whose scopes include
|
|
32
|
+
* `cloud-platform`, the scope Vertex AI asks for — the way the Web Risk and
|
|
33
|
+
* reCAPTCHA admin calls do. The account needs the Vertex AI User role
|
|
34
|
+
* (`roles/aiplatform.user`) on the project named below.
|
|
35
|
+
*
|
|
36
|
+
* ## Off unless an operator turns it on
|
|
37
|
+
*
|
|
38
|
+
* `AI_IMAGE_VERTEX_PROJECT` names the Google Cloud project the requests are
|
|
39
|
+
* billed to, and is the switch: unset, `configured()` is false and the Media
|
|
40
|
+
* door answers that photos are not made here. The console offers the photo
|
|
41
|
+
* mode only when `NEXT_PUBLIC_AI_IMAGE_PHOTOS` is `on`, which a deployment
|
|
42
|
+
* sets beside it. A self-hosted deployment is therefore off until its
|
|
43
|
+
* operator chooses a project.
|
|
44
|
+
*
|
|
45
|
+
* ⛔ THE ORDER FOR AGLYN'S OWN PRODUCTION, and no check can see it, so this
|
|
46
|
+
* paragraph is the gate: a description reaching Vertex AI makes Google LLC a
|
|
47
|
+
* recipient of customer content for a purpose `/legal/subprocessors` does not
|
|
48
|
+
* name today (its Google rows say database, storage, authentication, backups
|
|
49
|
+
* and logging). Publish that row first — the master, then the page, with a
|
|
50
|
+
* change-log line; the wording is drafted in
|
|
51
|
+
* `docs/legal-drafts/AGL-3602-subprocessors.md` — then declare the host in
|
|
52
|
+
* the AI catalog with that date as `publishedOn`, and only then set the two
|
|
53
|
+
* variables in production.
|
|
54
|
+
*
|
|
55
|
+
* ## What is sent, and nothing else
|
|
56
|
+
*
|
|
57
|
+
* The description the member typed and the shape. No account, site,
|
|
58
|
+
* organization or member identifier, no other content of the site and no
|
|
59
|
+
* image. One request per picture (`candidateCount` 1), each asking for:
|
|
60
|
+
*
|
|
61
|
+
* - `responseModalities: ['IMAGE']` — a picture and no prose, which is also
|
|
62
|
+
* what lets the model render at the size asked for;
|
|
63
|
+
* - `imageConfig: { aspectRatio, imageSize: '1K' }` — the shape, at the
|
|
64
|
+
* 1K size the per-picture rate is priced at;
|
|
65
|
+
* - `thinkingConfig: { thinkingLevel: 'HIGH' }` — Google's mapping for the
|
|
66
|
+
* Imagen 4 model this replaces;
|
|
67
|
+
* - `safetySettings` at `BLOCK_MEDIUM_AND_ABOVE` on harassment, hate,
|
|
68
|
+
* sexually explicit and dangerous content — the "block some" Imagen
|
|
69
|
+
* defaulted to.
|
|
70
|
+
*
|
|
71
|
+
* Google marks every Gemini image with its invisible SynthID watermark, and
|
|
72
|
+
* its own filters refuse what its policies do not allow — among them
|
|
73
|
+
* photorealistic depictions of children or celebrities that violate them —
|
|
74
|
+
* whatever is asked. Neither is a setting here.
|
|
75
|
+
*/
|
|
76
|
+
export declare const VERTEX_IMAGE_PROJECT_ENV = "AI_IMAGE_VERTEX_PROJECT";
|
|
77
|
+
export declare const VERTEX_IMAGE_LOCATION_ENV = "AI_IMAGE_VERTEX_LOCATION";
|
|
78
|
+
export declare const VERTEX_IMAGE_MODEL_ENV = "AI_IMAGE_MODEL";
|
|
79
|
+
/** The location requests go to when the operator names none: Google's global endpoint. */
|
|
80
|
+
export declare const VERTEX_IMAGE_DEFAULT_LOCATION = "global";
|
|
81
|
+
/** The model a request runs on when the operator names none. */
|
|
82
|
+
export declare const VERTEX_IMAGE_DEFAULT_MODEL = "gemini-3.1-flash-image";
|
|
83
|
+
/** How long one picture may take, thinking included. */
|
|
84
|
+
export declare const VERTEX_IMAGE_TIMEOUT_MS = 50000;
|
|
85
|
+
/** The harm categories every request filters at "block some". */
|
|
86
|
+
export declare const VERTEX_IMAGE_SAFETY_CATEGORIES: readonly ["HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"];
|
|
87
|
+
/** The configured project, or `null` when the provider is off. */
|
|
88
|
+
export declare function vertexImageProject(): string | null;
|
|
89
|
+
/** The configured location; the global endpoint when unset or malformed. */
|
|
90
|
+
export declare function vertexImageLocation(): string;
|
|
91
|
+
/** The configured model; the default when unset or malformed. */
|
|
92
|
+
export declare function vertexImageModel(): string;
|
|
93
|
+
/** The host a request goes to: the global endpoint's, or a region's. */
|
|
94
|
+
export declare function vertexImageHost(location?: string): string;
|
|
95
|
+
/** The `:generateContent` address for a model. */
|
|
96
|
+
export declare function vertexImageUrl(project: string, location: string, model: string): string;
|
|
97
|
+
/** The request body for one picture, exactly as it is sent. */
|
|
98
|
+
export declare function vertexImageRequestBody(request: Pick<AiImageRequest, 'prompt' | 'aspectRatio'>): Record<string, unknown>;
|
|
99
|
+
/** One answer, read: its picture, why it was withheld, and what it spent. */
|
|
100
|
+
export interface VertexImageAnswer {
|
|
101
|
+
image: AiGeneratedImage | null;
|
|
102
|
+
/** The finish or block reason, when no picture came back. */
|
|
103
|
+
withheld: string | null;
|
|
104
|
+
usage: AiUsage;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The picture in a `:generateContent` answer. A prompt the filter blocked
|
|
108
|
+
* carries `promptFeedback.blockReason`; a picture the filter withheld carries
|
|
109
|
+
* a finish reason such as `IMAGE_SAFETY`; a model that declined answers in
|
|
110
|
+
* text with `STOP` and no picture. All three are a withheld picture.
|
|
111
|
+
*/
|
|
112
|
+
export declare function parseVertexImageAnswer(body: unknown): VertexImageAnswer;
|
|
113
|
+
export interface VertexImageProviderOptions {
|
|
114
|
+
fetch?: typeof fetch;
|
|
115
|
+
accessToken?: () => Promise<string | null>;
|
|
116
|
+
timeoutMs?: number;
|
|
117
|
+
}
|
|
118
|
+
/** The adapter, with its network and its credential injectable for a spec. */
|
|
119
|
+
export declare function createVertexImageProvider(options?: VertexImageProviderOptions): AiImageProvider;
|
|
120
|
+
/** The adapter the Media door uses. */
|
|
121
|
+
export declare const vertexImageProvider: AiImageProvider;
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
|
|
17
|
+
import { getApp } from "firebase-admin/app";
|
|
18
|
+
import { AI_IMAGE_DEFAULT_MODEL, AI_IMAGE_VERTEX_PROVIDER_ID } from "./catalog.js";
|
|
19
|
+
import { aiTokenCount, AiUpstreamError } from "./contract.js";
|
|
20
|
+
import { AI_IMAGE_MAX_COUNT, AiImageSafetyRefusal } from "./image-contract.js";
|
|
21
|
+
/**
|
|
22
|
+
* Google's Gemini image models on Vertex AI (AGL-3602), behind the image
|
|
23
|
+
* contract: `POST https://aiplatform.googleapis.com/v1/projects/{project}/
|
|
24
|
+
* locations/global/publishers/google/models/{model}:generateContent`.
|
|
25
|
+
*
|
|
26
|
+
* Gemini, not Imagen: Google discontinued the Imagen endpoints on Vertex AI
|
|
27
|
+
* in 2026 and names `gemini-3.1-flash-image` as Imagen 4's replacement, at
|
|
28
|
+
* the HIGH thinking level for the standard Imagen 4 model.
|
|
29
|
+
*
|
|
30
|
+
* ## No key of its own
|
|
31
|
+
*
|
|
32
|
+
* It authenticates as the service account firebase-admin already runs as,
|
|
33
|
+
* through that credential's `getAccessToken()` — whose scopes include
|
|
34
|
+
* `cloud-platform`, the scope Vertex AI asks for — the way the Web Risk and
|
|
35
|
+
* reCAPTCHA admin calls do. The account needs the Vertex AI User role
|
|
36
|
+
* (`roles/aiplatform.user`) on the project named below.
|
|
37
|
+
*
|
|
38
|
+
* ## Off unless an operator turns it on
|
|
39
|
+
*
|
|
40
|
+
* `AI_IMAGE_VERTEX_PROJECT` names the Google Cloud project the requests are
|
|
41
|
+
* billed to, and is the switch: unset, `configured()` is false and the Media
|
|
42
|
+
* door answers that photos are not made here. The console offers the photo
|
|
43
|
+
* mode only when `NEXT_PUBLIC_AI_IMAGE_PHOTOS` is `on`, which a deployment
|
|
44
|
+
* sets beside it. A self-hosted deployment is therefore off until its
|
|
45
|
+
* operator chooses a project.
|
|
46
|
+
*
|
|
47
|
+
* ⛔ THE ORDER FOR AGLYN'S OWN PRODUCTION, and no check can see it, so this
|
|
48
|
+
* paragraph is the gate: a description reaching Vertex AI makes Google LLC a
|
|
49
|
+
* recipient of customer content for a purpose `/legal/subprocessors` does not
|
|
50
|
+
* name today (its Google rows say database, storage, authentication, backups
|
|
51
|
+
* and logging). Publish that row first — the master, then the page, with a
|
|
52
|
+
* change-log line; the wording is drafted in
|
|
53
|
+
* `docs/legal-drafts/AGL-3602-subprocessors.md` — then declare the host in
|
|
54
|
+
* the AI catalog with that date as `publishedOn`, and only then set the two
|
|
55
|
+
* variables in production.
|
|
56
|
+
*
|
|
57
|
+
* ## What is sent, and nothing else
|
|
58
|
+
*
|
|
59
|
+
* The description the member typed and the shape. No account, site,
|
|
60
|
+
* organization or member identifier, no other content of the site and no
|
|
61
|
+
* image. One request per picture (`candidateCount` 1), each asking for:
|
|
62
|
+
*
|
|
63
|
+
* - `responseModalities: ['IMAGE']` — a picture and no prose, which is also
|
|
64
|
+
* what lets the model render at the size asked for;
|
|
65
|
+
* - `imageConfig: { aspectRatio, imageSize: '1K' }` — the shape, at the
|
|
66
|
+
* 1K size the per-picture rate is priced at;
|
|
67
|
+
* - `thinkingConfig: { thinkingLevel: 'HIGH' }` — Google's mapping for the
|
|
68
|
+
* Imagen 4 model this replaces;
|
|
69
|
+
* - `safetySettings` at `BLOCK_MEDIUM_AND_ABOVE` on harassment, hate,
|
|
70
|
+
* sexually explicit and dangerous content — the "block some" Imagen
|
|
71
|
+
* defaulted to.
|
|
72
|
+
*
|
|
73
|
+
* Google marks every Gemini image with its invisible SynthID watermark, and
|
|
74
|
+
* its own filters refuse what its policies do not allow — among them
|
|
75
|
+
* photorealistic depictions of children or celebrities that violate them —
|
|
76
|
+
* whatever is asked. Neither is a setting here.
|
|
77
|
+
*/ export const VERTEX_IMAGE_PROJECT_ENV = 'AI_IMAGE_VERTEX_PROJECT';
|
|
78
|
+
export const VERTEX_IMAGE_LOCATION_ENV = 'AI_IMAGE_VERTEX_LOCATION';
|
|
79
|
+
export const VERTEX_IMAGE_MODEL_ENV = 'AI_IMAGE_MODEL';
|
|
80
|
+
/** The location requests go to when the operator names none: Google's global endpoint. */ export const VERTEX_IMAGE_DEFAULT_LOCATION = 'global';
|
|
81
|
+
/** The model a request runs on when the operator names none. */ export const VERTEX_IMAGE_DEFAULT_MODEL = AI_IMAGE_DEFAULT_MODEL;
|
|
82
|
+
/** How long one picture may take, thinking included. */ export const VERTEX_IMAGE_TIMEOUT_MS = 50000;
|
|
83
|
+
/** The harm categories every request filters at "block some". */ export const VERTEX_IMAGE_SAFETY_CATEGORIES = [
|
|
84
|
+
'HARM_CATEGORY_HARASSMENT',
|
|
85
|
+
'HARM_CATEGORY_HATE_SPEECH',
|
|
86
|
+
'HARM_CATEGORY_SEXUALLY_EXPLICIT',
|
|
87
|
+
'HARM_CATEGORY_DANGEROUS_CONTENT'
|
|
88
|
+
];
|
|
89
|
+
/** A Google Cloud project id: 6–30 lowercase letters, digits and hyphens. */ const PROJECT_ID = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
90
|
+
/** `global`, or a Vertex AI region such as `us-central1`. */ const LOCATION = /^(global|[a-z]+-[a-z]+[0-9]+)$/;
|
|
91
|
+
/** A publisher model id, `gemini-3.1-flash-image`. */ const MODEL_ID = /^[a-z0-9][a-z0-9.-]{1,62}$/;
|
|
92
|
+
/** The finish reasons that mean the picture was withheld rather than failed. */ const WITHHELD_FINISH = new Set([
|
|
93
|
+
'SAFETY',
|
|
94
|
+
'IMAGE_SAFETY',
|
|
95
|
+
'PROHIBITED_CONTENT',
|
|
96
|
+
'IMAGE_PROHIBITED_CONTENT',
|
|
97
|
+
'BLOCKLIST',
|
|
98
|
+
'SPII',
|
|
99
|
+
'RECITATION',
|
|
100
|
+
'IMAGE_RECITATION',
|
|
101
|
+
'IMAGE_OTHER',
|
|
102
|
+
'NO_IMAGE'
|
|
103
|
+
]);
|
|
104
|
+
function envValue(name) {
|
|
105
|
+
var _ref;
|
|
106
|
+
var _process_env_name;
|
|
107
|
+
return (_ref = (_process_env_name = process.env[name]) == null ? void 0 : _process_env_name.trim()) != null ? _ref : '';
|
|
108
|
+
}
|
|
109
|
+
/** The configured project, or `null` when the provider is off. */ export function vertexImageProject() {
|
|
110
|
+
const project = envValue(VERTEX_IMAGE_PROJECT_ENV);
|
|
111
|
+
return PROJECT_ID.test(project) ? project : null;
|
|
112
|
+
}
|
|
113
|
+
/** The configured location; the global endpoint when unset or malformed. */ export function vertexImageLocation() {
|
|
114
|
+
const location = envValue(VERTEX_IMAGE_LOCATION_ENV);
|
|
115
|
+
return LOCATION.test(location) ? location : VERTEX_IMAGE_DEFAULT_LOCATION;
|
|
116
|
+
}
|
|
117
|
+
/** The configured model; the default when unset or malformed. */ export function vertexImageModel() {
|
|
118
|
+
const model = envValue(VERTEX_IMAGE_MODEL_ENV);
|
|
119
|
+
return MODEL_ID.test(model) ? model : VERTEX_IMAGE_DEFAULT_MODEL;
|
|
120
|
+
}
|
|
121
|
+
/** The host a request goes to: the global endpoint's, or a region's. */ export function vertexImageHost(location = vertexImageLocation()) {
|
|
122
|
+
return location === 'global' ? 'aiplatform.googleapis.com' : `${location}-aiplatform.googleapis.com`;
|
|
123
|
+
}
|
|
124
|
+
/** The `:generateContent` address for a model. */ export function vertexImageUrl(project, location, model) {
|
|
125
|
+
return `https://${vertexImageHost(location)}/v1/projects/${project}/locations/${location}` + `/publishers/google/models/${model}:generateContent`;
|
|
126
|
+
}
|
|
127
|
+
/** The request body for one picture, exactly as it is sent. */ export function vertexImageRequestBody(request) {
|
|
128
|
+
return {
|
|
129
|
+
contents: [
|
|
130
|
+
{
|
|
131
|
+
role: 'USER',
|
|
132
|
+
parts: [
|
|
133
|
+
{
|
|
134
|
+
text: request.prompt
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
generationConfig: {
|
|
140
|
+
responseModalities: [
|
|
141
|
+
'IMAGE'
|
|
142
|
+
],
|
|
143
|
+
candidateCount: 1,
|
|
144
|
+
imageConfig: {
|
|
145
|
+
aspectRatio: request.aspectRatio,
|
|
146
|
+
imageSize: '1K'
|
|
147
|
+
},
|
|
148
|
+
thinkingConfig: {
|
|
149
|
+
thinkingLevel: 'HIGH'
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
safetySettings: VERTEX_IMAGE_SAFETY_CATEGORIES.map((category)=>({
|
|
153
|
+
category,
|
|
154
|
+
threshold: 'BLOCK_MEDIUM_AND_ABOVE'
|
|
155
|
+
}))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const IMAGE_TYPES = new Set([
|
|
159
|
+
'image/jpeg',
|
|
160
|
+
'image/png',
|
|
161
|
+
'image/webp'
|
|
162
|
+
]);
|
|
163
|
+
/**
|
|
164
|
+
* The picture in a `:generateContent` answer. A prompt the filter blocked
|
|
165
|
+
* carries `promptFeedback.blockReason`; a picture the filter withheld carries
|
|
166
|
+
* a finish reason such as `IMAGE_SAFETY`; a model that declined answers in
|
|
167
|
+
* text with `STOP` and no picture. All three are a withheld picture.
|
|
168
|
+
*/ export function parseVertexImageAnswer(body) {
|
|
169
|
+
var _answer_usageMetadata;
|
|
170
|
+
var _answer_promptFeedback, _candidate_content;
|
|
171
|
+
const answer = body != null ? body : {};
|
|
172
|
+
const meta = (_answer_usageMetadata = answer['usageMetadata']) != null ? _answer_usageMetadata : {};
|
|
173
|
+
const usage = {
|
|
174
|
+
inputTokens: aiTokenCount(meta['promptTokenCount']),
|
|
175
|
+
// The thinking only: the picture's own tokens are priced per picture.
|
|
176
|
+
outputTokens: aiTokenCount(meta['thoughtsTokenCount']),
|
|
177
|
+
cacheReadTokens: 0,
|
|
178
|
+
cacheWriteTokens: 0
|
|
179
|
+
};
|
|
180
|
+
const blocked = (_answer_promptFeedback = answer['promptFeedback']) == null ? void 0 : _answer_promptFeedback['blockReason'];
|
|
181
|
+
if (typeof blocked === 'string' && blocked) {
|
|
182
|
+
return {
|
|
183
|
+
image: null,
|
|
184
|
+
withheld: blocked,
|
|
185
|
+
usage
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const candidate = Array.isArray(answer['candidates']) ? answer['candidates'][0] : null;
|
|
189
|
+
const parts = Array.isArray(candidate == null ? void 0 : (_candidate_content = candidate.content) == null ? void 0 : _candidate_content.parts) ? candidate.content.parts : [];
|
|
190
|
+
for (const part of parts){
|
|
191
|
+
const inline = part == null ? void 0 : part.inlineData;
|
|
192
|
+
const data = typeof (inline == null ? void 0 : inline.data) === 'string' ? inline.data : '';
|
|
193
|
+
const mimeType = typeof (inline == null ? void 0 : inline.mimeType) === 'string' ? inline.mimeType : '';
|
|
194
|
+
if (data && IMAGE_TYPES.has(mimeType)) {
|
|
195
|
+
return {
|
|
196
|
+
image: {
|
|
197
|
+
base64: data,
|
|
198
|
+
mimeType
|
|
199
|
+
},
|
|
200
|
+
withheld: null,
|
|
201
|
+
usage
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const finish = typeof (candidate == null ? void 0 : candidate.finishReason) === 'string' ? candidate.finishReason : '';
|
|
206
|
+
return {
|
|
207
|
+
image: null,
|
|
208
|
+
withheld: WITHHELD_FINISH.has(finish) ? finish : finish ? `${finish}_NO_IMAGE` : 'NO_IMAGE',
|
|
209
|
+
usage
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/** An access token for the platform's own service account, or `null`. */ async function serviceAccountAccessToken() {
|
|
213
|
+
var _ref;
|
|
214
|
+
// The default app, initialized by the platform's firebase-admin module on
|
|
215
|
+
// first use; its credential is the one Web Risk's token comes from.
|
|
216
|
+
firebaseAdmin.app();
|
|
217
|
+
const credential = getApp().options.credential;
|
|
218
|
+
if (!credential) return null;
|
|
219
|
+
const token = await credential.getAccessToken();
|
|
220
|
+
return (_ref = token == null ? void 0 : token.access_token) != null ? _ref : null;
|
|
221
|
+
}
|
|
222
|
+
const addUsage = (a, b)=>({
|
|
223
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
224
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
225
|
+
cacheReadTokens: 0,
|
|
226
|
+
cacheWriteTokens: 0
|
|
227
|
+
});
|
|
228
|
+
/** The adapter, with its network and its credential injectable for a spec. */ export function createVertexImageProvider(options = {}) {
|
|
229
|
+
async function one(url, token, request) {
|
|
230
|
+
var _response_headers_get;
|
|
231
|
+
let response;
|
|
232
|
+
try {
|
|
233
|
+
var _options_fetch, _options_timeoutMs;
|
|
234
|
+
response = await ((_options_fetch = options.fetch) != null ? _options_fetch : fetch)(url, {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: {
|
|
237
|
+
Authorization: `Bearer ${token}`,
|
|
238
|
+
'Content-Type': 'application/json; charset=utf-8'
|
|
239
|
+
},
|
|
240
|
+
body: JSON.stringify(vertexImageRequestBody(request)),
|
|
241
|
+
signal: AbortSignal.timeout((_options_timeoutMs = options.timeoutMs) != null ? _options_timeoutMs : VERTEX_IMAGE_TIMEOUT_MS)
|
|
242
|
+
});
|
|
243
|
+
} catch (unused) {
|
|
244
|
+
// No answer at all — a timeout or a network fault — is ours to retry.
|
|
245
|
+
throw new AiUpstreamError(null, true, null);
|
|
246
|
+
}
|
|
247
|
+
const requestId = (_response_headers_get = response.headers.get('x-goog-request-id')) != null ? _response_headers_get : null;
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
const status = response.status;
|
|
250
|
+
const detail = await response.text().catch(()=>'');
|
|
251
|
+
console.error('vertex image request failed', {
|
|
252
|
+
status,
|
|
253
|
+
requestId,
|
|
254
|
+
detail: detail.slice(0, 500)
|
|
255
|
+
});
|
|
256
|
+
throw new AiUpstreamError(status, status === 429 || status >= 500, requestId, status === 401 || status === 403 ? 'credentials' : null);
|
|
257
|
+
}
|
|
258
|
+
return parseVertexImageAnswer(await response.json().catch(()=>null));
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
id: AI_IMAGE_VERTEX_PROVIDER_ID,
|
|
262
|
+
label: 'Google Vertex AI',
|
|
263
|
+
defaultModel: vertexImageModel,
|
|
264
|
+
configured: ()=>vertexImageProject() !== null,
|
|
265
|
+
endpointHost: ()=>vertexImageHost(),
|
|
266
|
+
async generate (request) {
|
|
267
|
+
var _options_accessToken;
|
|
268
|
+
const project = vertexImageProject();
|
|
269
|
+
if (!project) throw new AiUpstreamError(null, false, null, 'credentials');
|
|
270
|
+
const token = await ((_options_accessToken = options.accessToken) != null ? _options_accessToken : serviceAccountAccessToken)();
|
|
271
|
+
if (!token) throw new AiUpstreamError(null, false, null, 'credentials');
|
|
272
|
+
const url = vertexImageUrl(project, vertexImageLocation(), request.model);
|
|
273
|
+
const count = Math.min(AI_IMAGE_MAX_COUNT, Math.max(1, Math.floor(request.count)));
|
|
274
|
+
// One request per picture, side by side: a Gemini image request answers
|
|
275
|
+
// with one picture, and four in a row would outlast the door.
|
|
276
|
+
const settled = await Promise.allSettled(Array.from({
|
|
277
|
+
length: count
|
|
278
|
+
}, ()=>one(url, token, request)));
|
|
279
|
+
const answers = settled.flatMap((entry)=>entry.status === 'fulfilled' ? [
|
|
280
|
+
entry.value
|
|
281
|
+
] : []);
|
|
282
|
+
const failure = settled.find((entry)=>entry.status === 'rejected');
|
|
283
|
+
const images = answers.flatMap((answer)=>answer.image ? [
|
|
284
|
+
answer.image
|
|
285
|
+
] : []);
|
|
286
|
+
const withheld = answers.flatMap((answer)=>answer.withheld ? [
|
|
287
|
+
answer.withheld
|
|
288
|
+
] : []);
|
|
289
|
+
const usage = answers.reduce((sum, answer)=>addUsage(sum, answer.usage), {
|
|
290
|
+
inputTokens: 0,
|
|
291
|
+
outputTokens: 0,
|
|
292
|
+
cacheReadTokens: 0,
|
|
293
|
+
cacheWriteTokens: 0
|
|
294
|
+
});
|
|
295
|
+
if (!images.length) {
|
|
296
|
+
// Every picture failed for us: the fault is the answer. Every picture
|
|
297
|
+
// was withheld: the filter's decline is.
|
|
298
|
+
if (failure && !withheld.length) throw failure.reason;
|
|
299
|
+
throw new AiImageSafetyRefusal(withheld);
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
model: request.model,
|
|
303
|
+
images,
|
|
304
|
+
filtered: count - images.length,
|
|
305
|
+
usage
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** The adapter the Media door uses. */ export const vertexImageProvider = createVertexImageProvider();
|
|
311
|
+
|
|
312
|
+
//# sourceMappingURL=vertex-image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/providers/vertex-image.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 { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { getApp } from 'firebase-admin/app'\nimport { AI_IMAGE_DEFAULT_MODEL, AI_IMAGE_VERTEX_PROVIDER_ID } from './catalog'\nimport { aiTokenCount, AiUpstreamError, type AiUsage } from './contract'\nimport {\n AI_IMAGE_MAX_COUNT,\n AiImageSafetyRefusal,\n type AiGeneratedImage,\n type AiImageProvider,\n type AiImageRequest,\n type AiImageResult,\n} from './image-contract'\n\n/**\n * Google's Gemini image models on Vertex AI (AGL-3602), behind the image\n * contract: `POST https://aiplatform.googleapis.com/v1/projects/{project}/\n * locations/global/publishers/google/models/{model}:generateContent`.\n *\n * Gemini, not Imagen: Google discontinued the Imagen endpoints on Vertex AI\n * in 2026 and names `gemini-3.1-flash-image` as Imagen 4's replacement, at\n * the HIGH thinking level for the standard Imagen 4 model.\n *\n * ## No key of its own\n *\n * It authenticates as the service account firebase-admin already runs as,\n * through that credential's `getAccessToken()` — whose scopes include\n * `cloud-platform`, the scope Vertex AI asks for — the way the Web Risk and\n * reCAPTCHA admin calls do. The account needs the Vertex AI User role\n * (`roles/aiplatform.user`) on the project named below.\n *\n * ## Off unless an operator turns it on\n *\n * `AI_IMAGE_VERTEX_PROJECT` names the Google Cloud project the requests are\n * billed to, and is the switch: unset, `configured()` is false and the Media\n * door answers that photos are not made here. The console offers the photo\n * mode only when `NEXT_PUBLIC_AI_IMAGE_PHOTOS` is `on`, which a deployment\n * sets beside it. A self-hosted deployment is therefore off until its\n * operator chooses a project.\n *\n * ⛔ THE ORDER FOR AGLYN'S OWN PRODUCTION, and no check can see it, so this\n * paragraph is the gate: a description reaching Vertex AI makes Google LLC a\n * recipient of customer content for a purpose `/legal/subprocessors` does not\n * name today (its Google rows say database, storage, authentication, backups\n * and logging). Publish that row first — the master, then the page, with a\n * change-log line; the wording is drafted in\n * `docs/legal-drafts/AGL-3602-subprocessors.md` — then declare the host in\n * the AI catalog with that date as `publishedOn`, and only then set the two\n * variables in production.\n *\n * ## What is sent, and nothing else\n *\n * The description the member typed and the shape. No account, site,\n * organization or member identifier, no other content of the site and no\n * image. One request per picture (`candidateCount` 1), each asking for:\n *\n * - `responseModalities: ['IMAGE']` — a picture and no prose, which is also\n * what lets the model render at the size asked for;\n * - `imageConfig: { aspectRatio, imageSize: '1K' }` — the shape, at the\n * 1K size the per-picture rate is priced at;\n * - `thinkingConfig: { thinkingLevel: 'HIGH' }` — Google's mapping for the\n * Imagen 4 model this replaces;\n * - `safetySettings` at `BLOCK_MEDIUM_AND_ABOVE` on harassment, hate,\n * sexually explicit and dangerous content — the \"block some\" Imagen\n * defaulted to.\n *\n * Google marks every Gemini image with its invisible SynthID watermark, and\n * its own filters refuse what its policies do not allow — among them\n * photorealistic depictions of children or celebrities that violate them —\n * whatever is asked. Neither is a setting here.\n */\n\nexport const VERTEX_IMAGE_PROJECT_ENV = 'AI_IMAGE_VERTEX_PROJECT'\nexport const VERTEX_IMAGE_LOCATION_ENV = 'AI_IMAGE_VERTEX_LOCATION'\nexport const VERTEX_IMAGE_MODEL_ENV = 'AI_IMAGE_MODEL'\n\n/** The location requests go to when the operator names none: Google's global endpoint. */\nexport const VERTEX_IMAGE_DEFAULT_LOCATION = 'global'\n\n/** The model a request runs on when the operator names none. */\nexport const VERTEX_IMAGE_DEFAULT_MODEL = AI_IMAGE_DEFAULT_MODEL\n\n/** How long one picture may take, thinking included. */\nexport const VERTEX_IMAGE_TIMEOUT_MS = 50_000\n\n/** The harm categories every request filters at \"block some\". */\nexport const VERTEX_IMAGE_SAFETY_CATEGORIES = [\n 'HARM_CATEGORY_HARASSMENT',\n 'HARM_CATEGORY_HATE_SPEECH',\n 'HARM_CATEGORY_SEXUALLY_EXPLICIT',\n 'HARM_CATEGORY_DANGEROUS_CONTENT',\n] as const\n\n/** A Google Cloud project id: 6–30 lowercase letters, digits and hyphens. */\nconst PROJECT_ID = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/\n/** `global`, or a Vertex AI region such as `us-central1`. */\nconst LOCATION = /^(global|[a-z]+-[a-z]+[0-9]+)$/\n/** A publisher model id, `gemini-3.1-flash-image`. */\nconst MODEL_ID = /^[a-z0-9][a-z0-9.-]{1,62}$/\n\n/** The finish reasons that mean the picture was withheld rather than failed. */\nconst WITHHELD_FINISH = new Set([\n 'SAFETY',\n 'IMAGE_SAFETY',\n 'PROHIBITED_CONTENT',\n 'IMAGE_PROHIBITED_CONTENT',\n 'BLOCKLIST',\n 'SPII',\n 'RECITATION',\n 'IMAGE_RECITATION',\n 'IMAGE_OTHER',\n 'NO_IMAGE',\n])\n\nfunction envValue(name: string): string {\n return process.env[name]?.trim() ?? ''\n}\n\n/** The configured project, or `null` when the provider is off. */\nexport function vertexImageProject(): string | null {\n const project = envValue(VERTEX_IMAGE_PROJECT_ENV)\n return PROJECT_ID.test(project) ? project : null\n}\n\n/** The configured location; the global endpoint when unset or malformed. */\nexport function vertexImageLocation(): string {\n const location = envValue(VERTEX_IMAGE_LOCATION_ENV)\n return LOCATION.test(location) ? location : VERTEX_IMAGE_DEFAULT_LOCATION\n}\n\n/** The configured model; the default when unset or malformed. */\nexport function vertexImageModel(): string {\n const model = envValue(VERTEX_IMAGE_MODEL_ENV)\n return MODEL_ID.test(model) ? model : VERTEX_IMAGE_DEFAULT_MODEL\n}\n\n/** The host a request goes to: the global endpoint's, or a region's. */\nexport function vertexImageHost(location = vertexImageLocation()): string {\n return location === 'global' ? 'aiplatform.googleapis.com' : `${location}-aiplatform.googleapis.com`\n}\n\n/** The `:generateContent` address for a model. */\nexport function vertexImageUrl(project: string, location: string, model: string): string {\n return (\n `https://${vertexImageHost(location)}/v1/projects/${project}/locations/${location}` +\n `/publishers/google/models/${model}:generateContent`\n )\n}\n\n/** The request body for one picture, exactly as it is sent. */\nexport function vertexImageRequestBody(\n request: Pick<AiImageRequest, 'prompt' | 'aspectRatio'>,\n): Record<string, unknown> {\n return {\n contents: [{ role: 'USER', parts: [{ text: request.prompt }] }],\n generationConfig: {\n responseModalities: ['IMAGE'],\n candidateCount: 1,\n imageConfig: { aspectRatio: request.aspectRatio, imageSize: '1K' },\n thinkingConfig: { thinkingLevel: 'HIGH' },\n },\n safetySettings: VERTEX_IMAGE_SAFETY_CATEGORIES.map((category) => ({\n category,\n threshold: 'BLOCK_MEDIUM_AND_ABOVE',\n })),\n }\n}\n\nconst IMAGE_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp'])\n\n/** One answer, read: its picture, why it was withheld, and what it spent. */\nexport interface VertexImageAnswer {\n image: AiGeneratedImage | null\n /** The finish or block reason, when no picture came back. */\n withheld: string | null\n usage: AiUsage\n}\n\n/**\n * The picture in a `:generateContent` answer. A prompt the filter blocked\n * carries `promptFeedback.blockReason`; a picture the filter withheld carries\n * a finish reason such as `IMAGE_SAFETY`; a model that declined answers in\n * text with `STOP` and no picture. All three are a withheld picture.\n */\nexport function parseVertexImageAnswer(body: unknown): VertexImageAnswer {\n const answer = (body ?? {}) as Record<string, any>\n const meta = (answer['usageMetadata'] ?? {}) as Record<string, unknown>\n const usage: AiUsage = {\n inputTokens: aiTokenCount(meta['promptTokenCount']),\n // The thinking only: the picture's own tokens are priced per picture.\n outputTokens: aiTokenCount(meta['thoughtsTokenCount']),\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n const blocked = answer['promptFeedback']?.['blockReason']\n if (typeof blocked === 'string' && blocked) {\n return { image: null, withheld: blocked, usage }\n }\n const candidate = Array.isArray(answer['candidates']) ? answer['candidates'][0] : null\n const parts = Array.isArray(candidate?.content?.parts) ? candidate.content.parts : []\n for (const part of parts) {\n const inline = part?.inlineData\n const data = typeof inline?.data === 'string' ? inline.data : ''\n const mimeType = typeof inline?.mimeType === 'string' ? inline.mimeType : ''\n if (data && IMAGE_TYPES.has(mimeType)) {\n return { image: { base64: data, mimeType }, withheld: null, usage }\n }\n }\n const finish = typeof candidate?.finishReason === 'string' ? candidate.finishReason : ''\n return {\n image: null,\n withheld: WITHHELD_FINISH.has(finish) ? finish : finish ? `${finish}_NO_IMAGE` : 'NO_IMAGE',\n usage,\n }\n}\n\n/** An access token for the platform's own service account, or `null`. */\nasync function serviceAccountAccessToken(): Promise<string | null> {\n // The default app, initialized by the platform's firebase-admin module on\n // first use; its credential is the one Web Risk's token comes from.\n firebaseAdmin.app()\n const credential = getApp().options.credential\n if (!credential) return null\n const token = await credential.getAccessToken()\n return token?.access_token ?? null\n}\n\nexport interface VertexImageProviderOptions {\n fetch?: typeof fetch\n accessToken?: () => Promise<string | null>\n timeoutMs?: number\n}\n\nconst addUsage = (a: AiUsage, b: AiUsage): AiUsage => ({\n inputTokens: a.inputTokens + b.inputTokens,\n outputTokens: a.outputTokens + b.outputTokens,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n})\n\n/** The adapter, with its network and its credential injectable for a spec. */\nexport function createVertexImageProvider(\n options: VertexImageProviderOptions = {},\n): AiImageProvider {\n async function one(\n url: string,\n token: string,\n request: AiImageRequest,\n ): Promise<VertexImageAnswer> {\n let response: Response\n try {\n response = await (options.fetch ?? fetch)(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json; charset=utf-8',\n },\n body: JSON.stringify(vertexImageRequestBody(request)),\n signal: AbortSignal.timeout(options.timeoutMs ?? VERTEX_IMAGE_TIMEOUT_MS),\n })\n } catch {\n // No answer at all — a timeout or a network fault — is ours to retry.\n throw new AiUpstreamError(null, true, null)\n }\n const requestId = response.headers.get('x-goog-request-id') ?? null\n if (!response.ok) {\n const status = response.status\n const detail = await response.text().catch(() => '')\n console.error('vertex image request failed', {\n status,\n requestId,\n detail: detail.slice(0, 500),\n })\n throw new AiUpstreamError(\n status,\n status === 429 || status >= 500,\n requestId,\n status === 401 || status === 403 ? 'credentials' : null,\n )\n }\n return parseVertexImageAnswer(await response.json().catch(() => null))\n }\n\n return {\n id: AI_IMAGE_VERTEX_PROVIDER_ID,\n label: 'Google Vertex AI',\n defaultModel: vertexImageModel,\n configured: () => vertexImageProject() !== null,\n endpointHost: () => vertexImageHost(),\n async generate(request: AiImageRequest): Promise<AiImageResult> {\n const project = vertexImageProject()\n if (!project) throw new AiUpstreamError(null, false, null, 'credentials')\n const token = await (options.accessToken ?? serviceAccountAccessToken)()\n if (!token) throw new AiUpstreamError(null, false, null, 'credentials')\n const url = vertexImageUrl(project, vertexImageLocation(), request.model)\n const count = Math.min(AI_IMAGE_MAX_COUNT, Math.max(1, Math.floor(request.count)))\n // One request per picture, side by side: a Gemini image request answers\n // with one picture, and four in a row would outlast the door.\n const settled = await Promise.allSettled(\n Array.from({ length: count }, () => one(url, token, request)),\n )\n const answers = settled.flatMap((entry) => (entry.status === 'fulfilled' ? [entry.value] : []))\n const failure = settled.find((entry) => entry.status === 'rejected') as\n | PromiseRejectedResult\n | undefined\n const images = answers.flatMap((answer) => (answer.image ? [answer.image] : []))\n const withheld = answers.flatMap((answer) => (answer.withheld ? [answer.withheld] : []))\n const usage = answers.reduce<AiUsage>((sum, answer) => addUsage(sum, answer.usage), {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n })\n if (!images.length) {\n // Every picture failed for us: the fault is the answer. Every picture\n // was withheld: the filter's decline is.\n if (failure && !withheld.length) throw failure.reason\n throw new AiImageSafetyRefusal(withheld)\n }\n return {\n model: request.model,\n images,\n filtered: count - images.length,\n usage,\n }\n },\n }\n}\n\n/** The adapter the Media door uses. */\nexport const vertexImageProvider: AiImageProvider = createVertexImageProvider()\n"],"names":["firebaseAdmin","getApp","AI_IMAGE_DEFAULT_MODEL","AI_IMAGE_VERTEX_PROVIDER_ID","aiTokenCount","AiUpstreamError","AI_IMAGE_MAX_COUNT","AiImageSafetyRefusal","VERTEX_IMAGE_PROJECT_ENV","VERTEX_IMAGE_LOCATION_ENV","VERTEX_IMAGE_MODEL_ENV","VERTEX_IMAGE_DEFAULT_LOCATION","VERTEX_IMAGE_DEFAULT_MODEL","VERTEX_IMAGE_TIMEOUT_MS","VERTEX_IMAGE_SAFETY_CATEGORIES","PROJECT_ID","LOCATION","MODEL_ID","WITHHELD_FINISH","Set","envValue","name","process","env","trim","vertexImageProject","project","test","vertexImageLocation","location","vertexImageModel","model","vertexImageHost","vertexImageUrl","vertexImageRequestBody","request","contents","role","parts","text","prompt","generationConfig","responseModalities","candidateCount","imageConfig","aspectRatio","imageSize","thinkingConfig","thinkingLevel","safetySettings","map","category","threshold","IMAGE_TYPES","parseVertexImageAnswer","body","answer","candidate","meta","usage","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","blocked","image","withheld","Array","isArray","content","part","inline","inlineData","data","mimeType","has","base64","finish","finishReason","serviceAccountAccessToken","app","credential","options","token","getAccessToken","access_token","addUsage","a","b","createVertexImageProvider","one","url","response","fetch","method","headers","Authorization","JSON","stringify","signal","AbortSignal","timeout","timeoutMs","requestId","get","ok","status","detail","catch","console","error","slice","json","id","label","defaultModel","configured","endpointHost","generate","accessToken","count","Math","min","max","floor","settled","Promise","allSettled","from","length","answers","flatMap","entry","value","failure","find","images","reduce","sum","reason","filtered","vertexImageProvider"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,aAAa,QAAQ,iDAAgD;AAC9E,SAASC,MAAM,QAAQ,qBAAoB;AAC3C,SAASC,sBAAsB,EAAEC,2BAA2B,QAAQ,eAAW;AAC/E,SAASC,YAAY,EAAEC,eAAe,QAAsB,gBAAY;AACxE,SACEC,kBAAkB,EAClBC,oBAAoB,QAKf,sBAAkB;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDC,GAED,OAAO,MAAMC,2BAA2B,0BAAyB;AACjE,OAAO,MAAMC,4BAA4B,2BAA0B;AACnE,OAAO,MAAMC,yBAAyB,iBAAgB;AAEtD,wFAAwF,GACxF,OAAO,MAAMC,gCAAgC,SAAQ;AAErD,8DAA8D,GAC9D,OAAO,MAAMC,6BAA6BV,uBAAsB;AAEhE,sDAAsD,GACtD,OAAO,MAAMW,0BAA0B,MAAM;AAE7C,+DAA+D,GAC/D,OAAO,MAAMC,iCAAiC;IAC5C;IACA;IACA;IACA;CACD,CAAS;AAEV,2EAA2E,GAC3E,MAAMC,aAAa;AACnB,2DAA2D,GAC3D,MAAMC,WAAW;AACjB,oDAAoD,GACpD,MAAMC,WAAW;AAEjB,8EAA8E,GAC9E,MAAMC,kBAAkB,IAAIC,IAAI;IAC9B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,SAASC,SAASC,IAAY;;QACrBC;IAAP,gBAAOA,oBAAAA,QAAQC,GAAG,CAACF,KAAK,qBAAjBC,kBAAmBE,IAAI,qBAAM;AACtC;AAEA,gEAAgE,GAChE,OAAO,SAASC;IACd,MAAMC,UAAUN,SAASZ;IACzB,OAAOO,WAAWY,IAAI,CAACD,WAAWA,UAAU;AAC9C;AAEA,0EAA0E,GAC1E,OAAO,SAASE;IACd,MAAMC,WAAWT,SAASX;IAC1B,OAAOO,SAASW,IAAI,CAACE,YAAYA,WAAWlB;AAC9C;AAEA,+DAA+D,GAC/D,OAAO,SAASmB;IACd,MAAMC,QAAQX,SAASV;IACvB,OAAOO,SAASU,IAAI,CAACI,SAASA,QAAQnB;AACxC;AAEA,sEAAsE,GACtE,OAAO,SAASoB,gBAAgBH,WAAWD,qBAAqB;IAC9D,OAAOC,aAAa,WAAW,8BAA8B,GAAGA,SAAS,0BAA0B,CAAC;AACtG;AAEA,gDAAgD,GAChD,OAAO,SAASI,eAAeP,OAAe,EAAEG,QAAgB,EAAEE,KAAa;IAC7E,OACE,CAAC,QAAQ,EAAEC,gBAAgBH,UAAU,aAAa,EAAEH,QAAQ,WAAW,EAAEG,UAAU,GACnF,CAAC,0BAA0B,EAAEE,MAAM,gBAAgB,CAAC;AAExD;AAEA,6DAA6D,GAC7D,OAAO,SAASG,uBACdC,OAAuD;IAEvD,OAAO;QACLC,UAAU;YAAC;gBAAEC,MAAM;gBAAQC,OAAO;oBAAC;wBAAEC,MAAMJ,QAAQK,MAAM;oBAAC;iBAAE;YAAC;SAAE;QAC/DC,kBAAkB;YAChBC,oBAAoB;gBAAC;aAAQ;YAC7BC,gBAAgB;YAChBC,aAAa;gBAAEC,aAAaV,QAAQU,WAAW;gBAAEC,WAAW;YAAK;YACjEC,gBAAgB;gBAAEC,eAAe;YAAO;QAC1C;QACAC,gBAAgBnC,+BAA+BoC,GAAG,CAAC,CAACC,WAAc,CAAA;gBAChEA;gBACAC,WAAW;YACb,CAAA;IACF;AACF;AAEA,MAAMC,cAAc,IAAIlC,IAAI;IAAC;IAAc;IAAa;CAAa;AAUrE;;;;;CAKC,GACD,OAAO,SAASmC,uBAAuBC,IAAa;QAEpCC;QAQEA,wBAKYC;IAd5B,MAAMD,SAAUD,eAAAA,OAAQ,CAAC;IACzB,MAAMG,QAAQF,wBAAAA,MAAM,CAAC,gBAAgB,YAAvBA,wBAA2B,CAAC;IAC1C,MAAMG,QAAiB;QACrBC,aAAaxD,aAAasD,IAAI,CAAC,mBAAmB;QAClD,sEAAsE;QACtEG,cAAczD,aAAasD,IAAI,CAAC,qBAAqB;QACrDI,iBAAiB;QACjBC,kBAAkB;IACpB;IACA,MAAMC,WAAUR,yBAAAA,MAAM,CAAC,iBAAiB,qBAAxBA,sBAA0B,CAAC,cAAc;IACzD,IAAI,OAAOQ,YAAY,YAAYA,SAAS;QAC1C,OAAO;YAAEC,OAAO;YAAMC,UAAUF;YAASL;QAAM;IACjD;IACA,MAAMF,YAAYU,MAAMC,OAAO,CAACZ,MAAM,CAAC,aAAa,IAAIA,MAAM,CAAC,aAAa,CAAC,EAAE,GAAG;IAClF,MAAMlB,QAAQ6B,MAAMC,OAAO,CAACX,8BAAAA,qBAAAA,UAAWY,OAAO,qBAAlBZ,mBAAoBnB,KAAK,IAAImB,UAAUY,OAAO,CAAC/B,KAAK,GAAG,EAAE;IACrF,KAAK,MAAMgC,QAAQhC,MAAO;QACxB,MAAMiC,SAASD,wBAAAA,KAAME,UAAU;QAC/B,MAAMC,OAAO,QAAOF,0BAAAA,OAAQE,IAAI,MAAK,WAAWF,OAAOE,IAAI,GAAG;QAC9D,MAAMC,WAAW,QAAOH,0BAAAA,OAAQG,QAAQ,MAAK,WAAWH,OAAOG,QAAQ,GAAG;QAC1E,IAAID,QAAQpB,YAAYsB,GAAG,CAACD,WAAW;YACrC,OAAO;gBAAET,OAAO;oBAAEW,QAAQH;oBAAMC;gBAAS;gBAAGR,UAAU;gBAAMP;YAAM;QACpE;IACF;IACA,MAAMkB,SAAS,QAAOpB,6BAAAA,UAAWqB,YAAY,MAAK,WAAWrB,UAAUqB,YAAY,GAAG;IACtF,OAAO;QACLb,OAAO;QACPC,UAAUhD,gBAAgByD,GAAG,CAACE,UAAUA,SAASA,SAAS,GAAGA,OAAO,SAAS,CAAC,GAAG;QACjFlB;IACF;AACF;AAEA,uEAAuE,GACvE,eAAeoB;;IACb,0EAA0E;IAC1E,oEAAoE;IACpE/E,cAAcgF,GAAG;IACjB,MAAMC,aAAahF,SAASiF,OAAO,CAACD,UAAU;IAC9C,IAAI,CAACA,YAAY,OAAO;IACxB,MAAME,QAAQ,MAAMF,WAAWG,cAAc;IAC7C,eAAOD,yBAAAA,MAAOE,YAAY,mBAAI;AAChC;AAQA,MAAMC,WAAW,CAACC,GAAYC,IAAyB,CAAA;QACrD5B,aAAa2B,EAAE3B,WAAW,GAAG4B,EAAE5B,WAAW;QAC1CC,cAAc0B,EAAE1B,YAAY,GAAG2B,EAAE3B,YAAY;QAC7CC,iBAAiB;QACjBC,kBAAkB;IACpB,CAAA;AAEA,4EAA4E,GAC5E,OAAO,SAAS0B,0BACdP,UAAsC,CAAC,CAAC;IAExC,eAAeQ,IACbC,GAAW,EACXR,KAAa,EACbhD,OAAuB;YAiBLyD;QAflB,IAAIA;QACJ,IAAI;gBACgBV,gBAOYA;YAP9BU,WAAW,MAAM,EAACV,iBAAAA,QAAQW,KAAK,YAAbX,iBAAiBW,OAAOF,KAAK;gBAC7CG,QAAQ;gBACRC,SAAS;oBACPC,eAAe,CAAC,OAAO,EAAEb,OAAO;oBAChC,gBAAgB;gBAClB;gBACA5B,MAAM0C,KAAKC,SAAS,CAAChE,uBAAuBC;gBAC5CgE,QAAQC,YAAYC,OAAO,EAACnB,qBAAAA,QAAQoB,SAAS,YAAjBpB,qBAAqBrE;YACnD;QACF,EAAE,eAAM;YACN,sEAAsE;YACtE,MAAM,IAAIR,gBAAgB,MAAM,MAAM;QACxC;QACA,MAAMkG,aAAYX,wBAAAA,SAASG,OAAO,CAACS,GAAG,CAAC,gCAArBZ,wBAA6C;QAC/D,IAAI,CAACA,SAASa,EAAE,EAAE;YAChB,MAAMC,SAASd,SAASc,MAAM;YAC9B,MAAMC,SAAS,MAAMf,SAASrD,IAAI,GAAGqE,KAAK,CAAC,IAAM;YACjDC,QAAQC,KAAK,CAAC,+BAA+B;gBAC3CJ;gBACAH;gBACAI,QAAQA,OAAOI,KAAK,CAAC,GAAG;YAC1B;YACA,MAAM,IAAI1G,gBACRqG,QACAA,WAAW,OAAOA,UAAU,KAC5BH,WACAG,WAAW,OAAOA,WAAW,MAAM,gBAAgB;QAEvD;QACA,OAAOpD,uBAAuB,MAAMsC,SAASoB,IAAI,GAAGJ,KAAK,CAAC,IAAM;IAClE;IAEA,OAAO;QACLK,IAAI9G;QACJ+G,OAAO;QACPC,cAAcrF;QACdsF,YAAY,IAAM3F,yBAAyB;QAC3C4F,cAAc,IAAMrF;QACpB,MAAMsF,UAASnF,OAAuB;gBAGf+C;YAFrB,MAAMxD,UAAUD;YAChB,IAAI,CAACC,SAAS,MAAM,IAAIrB,gBAAgB,MAAM,OAAO,MAAM;YAC3D,MAAM8E,QAAQ,MAAM,EAACD,uBAAAA,QAAQqC,WAAW,YAAnBrC,uBAAuBH;YAC5C,IAAI,CAACI,OAAO,MAAM,IAAI9E,gBAAgB,MAAM,OAAO,MAAM;YACzD,MAAMsF,MAAM1D,eAAeP,SAASE,uBAAuBO,QAAQJ,KAAK;YACxE,MAAMyF,QAAQC,KAAKC,GAAG,CAACpH,oBAAoBmH,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACzF,QAAQqF,KAAK;YAC/E,wEAAwE;YACxE,8DAA8D;YAC9D,MAAMK,UAAU,MAAMC,QAAQC,UAAU,CACtC5D,MAAM6D,IAAI,CAAC;gBAAEC,QAAQT;YAAM,GAAG,IAAM9B,IAAIC,KAAKR,OAAOhD;YAEtD,MAAM+F,UAAUL,QAAQM,OAAO,CAAC,CAACC,QAAWA,MAAM1B,MAAM,KAAK,cAAc;oBAAC0B,MAAMC,KAAK;iBAAC,GAAG,EAAE;YAC7F,MAAMC,UAAUT,QAAQU,IAAI,CAAC,CAACH,QAAUA,MAAM1B,MAAM,KAAK;YAGzD,MAAM8B,SAASN,QAAQC,OAAO,CAAC,CAAC3E,SAAYA,OAAOS,KAAK,GAAG;oBAACT,OAAOS,KAAK;iBAAC,GAAG,EAAE;YAC9E,MAAMC,WAAWgE,QAAQC,OAAO,CAAC,CAAC3E,SAAYA,OAAOU,QAAQ,GAAG;oBAACV,OAAOU,QAAQ;iBAAC,GAAG,EAAE;YACtF,MAAMP,QAAQuE,QAAQO,MAAM,CAAU,CAACC,KAAKlF,SAAW8B,SAASoD,KAAKlF,OAAOG,KAAK,GAAG;gBAClFC,aAAa;gBACbC,cAAc;gBACdC,iBAAiB;gBACjBC,kBAAkB;YACpB;YACA,IAAI,CAACyE,OAAOP,MAAM,EAAE;gBAClB,sEAAsE;gBACtE,yCAAyC;gBACzC,IAAIK,WAAW,CAACpE,SAAS+D,MAAM,EAAE,MAAMK,QAAQK,MAAM;gBACrD,MAAM,IAAIpI,qBAAqB2D;YACjC;YACA,OAAO;gBACLnC,OAAOI,QAAQJ,KAAK;gBACpByG;gBACAI,UAAUpB,QAAQgB,OAAOP,MAAM;gBAC/BtE;YACF;QACF;IACF;AACF;AAEA,qCAAqC,GACrC,OAAO,MAAMkF,sBAAuCpD,4BAA2B"}
|
|
@@ -621,6 +621,25 @@ export declare const AI_SPLIT_LIST_MIN_SECTIONS = 2;
|
|
|
621
621
|
* are two lists, and rule 8 counts each within its own section (AGL-3061).
|
|
622
622
|
*/
|
|
623
623
|
export declare function detectPlanSplitLists(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiDoctrineViolation[];
|
|
624
|
+
/**
|
|
625
|
+
* A `new:` reference that spells a creation differently — `new:cake-order-form`
|
|
626
|
+
* for "Cake order form" (a live bakery plan, 2026-10-07) — is pointed at that
|
|
627
|
+
* creation before the rules read the plan, when exactly one creation matches
|
|
628
|
+
* it with case, spaces and punctuation ignored. Two that match, or none, are
|
|
629
|
+
* left for the rules to ask about.
|
|
630
|
+
*/
|
|
631
|
+
export declare function aiSettlePlanRefs(plan: AiBuildPlan): AiBuildPlan;
|
|
632
|
+
/**
|
|
633
|
+
* A page that names no layout, on a site where only one layout can frame it,
|
|
634
|
+
* is put in that layout before the rules read the plan (2026-10-07). The
|
|
635
|
+
* model sometimes leaves one page's layout empty — a live run's bookkeeper
|
|
636
|
+
* plan did, beside a layout it created — and rule 2 refused the whole plan
|
|
637
|
+
* for a choice with one answer. That answer exists only on a site with no
|
|
638
|
+
* layout whose plan creates exactly one, which is every guided start. Where
|
|
639
|
+
* the site has a layout of its own, a page that names none may mean another,
|
|
640
|
+
* so the rule still asks.
|
|
641
|
+
*/
|
|
642
|
+
export declare function aiSettlePlanLayouts(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiBuildPlan;
|
|
624
643
|
/**
|
|
625
644
|
* Rule 2 (plan): every screen declares a layout, no section is a copy of a
|
|
626
645
|
* layout region, and no section places a layout, which frames a whole screen
|