@aglyn/plugins-ai 1.0.0-beta.250 → 1.0.0-beta.252
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/package.json +17 -17
- package/src/lib/components/ai-brief-dialog.component.js +15 -1
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +1 -1
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +20 -2
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-store.d.ts +11 -0
- package/src/lib/components/ai-jobs-store.js +22 -0
- package/src/lib/components/ai-jobs-store.js.map +1 -1
- package/src/lib/jobs/ai-job-crm-step.js +7 -4
- package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +24 -0
- package/src/lib/jobs/ai-job-drafts.js +46 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +6 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-reuse.d.ts +78 -0
- package/src/lib/jobs/ai-job-form-reuse.js +136 -0
- package/src/lib/jobs/ai-job-form-reuse.js.map +1 -0
- package/src/lib/jobs/ai-job-form-step.d.ts +17 -0
- package/src/lib/jobs/ai-job-form-step.js +63 -11
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +6 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +19 -0
- package/src/lib/jobs/ai-job-plan-step.js +39 -1
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +5 -3
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +7 -5
- package/src/lib/jobs/ai-job-site-content.js +7 -5
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.js +6 -3
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +11 -7
- package/src/lib/jobs/ai-layout-stock-photos.js +25 -10
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-stock-business-nouns.d.ts +62 -0
- package/src/lib/jobs/ai-stock-business-nouns.js +264 -0
- package/src/lib/jobs/ai-stock-business-nouns.js.map +1 -0
- package/src/lib/model/ai-build-plan.d.ts +7 -3
- package/src/lib/model/ai-build-plan.js +11 -1
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +32 -18
- package/src/lib/model/ai-credit-estimate.js +36 -18
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +30 -0
- package/src/lib/model/ai-job-activity.js +35 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +5 -3
- package/src/lib/model/ai-media-credits.js +7 -5
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-site-job.js +6 -3
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.d.ts +5 -0
- package/src/lib/model/ai-site-store-pages.js +2 -0
- package/src/lib/model/ai-site-store-pages.js.map +1 -1
- package/src/lib/plugin-config.js +2 -2
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/providers/anthropic-crm-wording-superseded.d.ts +29 -0
- package/src/lib/providers/anthropic-crm-wording-superseded.js +31 -0
- package/src/lib/providers/anthropic-crm-wording-superseded.js.map +1 -0
- package/src/lib/providers/catalog.d.ts +24 -17
- package/src/lib/providers/catalog.js +63 -30
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/model-choice.d.ts +1 -1
- package/src/lib/providers/model-choice.js +39 -30
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.d.ts +20 -1
- package/src/lib/providers/routing.js +47 -8
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dev-env.d.ts +37 -1
- package/src/lib/runtime/ai-dev-env.js +59 -6
- package/src/lib/runtime/ai-dev-env.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +8 -4
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.d.ts +8 -0
- package/src/lib/runtime/ai-eval-live.js +34 -8
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +4 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-runtime.js +17 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/seo-fields.js +4 -3
- package/src/lib/runtime/seo-fields.js.map +1 -1
- package/src/lib/server/ai-assist.js +3 -2
- package/src/lib/server/ai-assist.js.map +1 -1
- package/src/lib/server/ai-media-svg.js +4 -0
- package/src/lib/server/ai-media-svg.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.js +2 -5
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
|
@@ -293,6 +293,52 @@ function subdomainOf(host) {
|
|
|
293
293
|
]);
|
|
294
294
|
return aiDraftBandRefusal(input.kind, siblingRows(snapshot), input.org, host == null ? void 0 : host.get('screens'));
|
|
295
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* A stored instant in milliseconds, whatever wrote it: a Firestore
|
|
298
|
+
* `Timestamp`, a `Date`, a number or an ISO string. `null` when unreadable.
|
|
299
|
+
*/ export function aiDraftInstantMs(value) {
|
|
300
|
+
if (value == null) return null;
|
|
301
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null;
|
|
302
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
303
|
+
if (typeof value === 'string') {
|
|
304
|
+
const parsed = Date.parse(value);
|
|
305
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
306
|
+
}
|
|
307
|
+
const toMillis = value.toMillis;
|
|
308
|
+
return typeof toMillis === 'function' ? toMillis.call(value) : null;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Whether the band refuses the kind only because of drafts made after
|
|
312
|
+
* `sinceMs` (AGL-3765): the rows refuse now, and the rows made at or before
|
|
313
|
+
* then would not. A job whose plan was confirmed while the site had room, and
|
|
314
|
+
* that another build or a member filled before the job's own write, stopped
|
|
315
|
+
* on something it could not have known when it was confirmed. A row whose
|
|
316
|
+
* `createdAt` cannot be read counts as made before, so a refusal this cannot
|
|
317
|
+
* date stays the workspace's own limit.
|
|
318
|
+
*/ export function aiDraftBandTakenSince(kind, rows, org, sinceMs, routingMap) {
|
|
319
|
+
if (sinceMs === null || !aiDraftBandRefusal(kind, rows, org, routingMap)) return false;
|
|
320
|
+
const before = rows.filter((row)=>{
|
|
321
|
+
const at = aiDraftInstantMs(row.createdAt);
|
|
322
|
+
return at === null || at <= sinceMs;
|
|
323
|
+
});
|
|
324
|
+
return aiDraftBandRefusal(kind, before, org, routingMap) === null;
|
|
325
|
+
}
|
|
326
|
+
/** {@link aiDraftBandTakenSince}, over the site's rows of the kind as they are now. */ export async function readAiDraftBandTakenSince(firestore, input) {
|
|
327
|
+
const sinceMs = aiDraftInstantMs(input.since);
|
|
328
|
+
if (sinceMs === null) return false;
|
|
329
|
+
const [snapshot, host] = await Promise.all([
|
|
330
|
+
draftCollection(firestore, input.hostId, input.kind).select('kind', 'source.type', 'deletedAt', 'createdAt').get(),
|
|
331
|
+
input.kind === 'screen' ? firestore.collection('hosts').doc(input.hostId).get() : Promise.resolve(null)
|
|
332
|
+
]);
|
|
333
|
+
const rows = snapshot.docs.map((doc)=>({
|
|
334
|
+
id: doc.id,
|
|
335
|
+
kind: doc.get('kind'),
|
|
336
|
+
sourceType: doc.get('source.type'),
|
|
337
|
+
deletedAt: doc.get('deletedAt'),
|
|
338
|
+
createdAt: doc.get('createdAt')
|
|
339
|
+
}));
|
|
340
|
+
return aiDraftBandTakenSince(input.kind, rows, input.org, sinceMs, host == null ? void 0 : host.get('screens'));
|
|
341
|
+
}
|
|
296
342
|
/**
|
|
297
343
|
* A draft screen's one-segment address: the one asked for, else one from its
|
|
298
344
|
* name, never a reserved segment, and never an address a live screen keeps
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport {\n FORM_COMPONENT_ID,\n newFormListFields,\n normalizeFormSlug,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n checkDatasetQuota,\n checkEntitlement,\n checkQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n billableScreenIds,\n normalizeScreenSlug,\n reservedScreenRouteSegment,\n SCREEN_ROOT_PATH,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport type {\n AglynOrgBilling,\n OrgFeatureFlags,\n} from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { HostScreenVisibility, type ReusableComponentProp } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiUnrestrictedPlanCapabilities,\n type AiPlanCapabilities,\n type AiPlanCreation,\n} from '../model/ai-plan-capabilities'\nimport type { AiJobAdmissionRefusal } from './ai-job-admission'\n\n/**\n * Where a generation job's layout, page template, form, reusable component\n * or page lands (AGL-2909, AGL-2913, AGL-2908, AGL-2907): a new draft, made\n * as the console's host resources route makes one.\n *\n * ── The document the create route makes ─────────────────────────────────\n *\n * `AI_DRAFT_FIELDS` is that route's allow-list for each kind, and a spec\n * reads the route's source to hold the two together. Nothing outside the\n * list is written except the route's own stamps: `createdAt`, `updatedAt`,\n * `createdBy`, the list keys every artifact create carries\n * (`artifactCreateListKeys`: the name keys, a component's or template's stored\n * kind, a template's library row), and a template's `source`, which is\n * `authored` — a template a member's job generated is theirs, never a starter\n * or a listing. A layout's or a screen's first version carries the keys the\n * versions route seeds one with. A form is the document the Forms page's\n * Create sends: its design canvas-shaped under `rootId` with the form node\n * bound to the form's id and captioned with its name, a slug read from that\n * name, and no version — the form's page mints the first one when someone\n * opens it. Where Create sends an empty `fields`, a generated form sends the\n * declaration read off its own design, so the two agree from the start. A\n * component carries its tree and the props it declares on its own document,\n * as Use template writes one, and gets its first version where every\n * component does: when a member opens it.\n *\n * ── Counted like a create ────────────────────────────────────────────────\n *\n * The band is met INSIDE the transaction that writes (AGL-2231), with the\n * route's arithmetic: every layout document counts against\n * `sharedLayoutsPerHost`; a template counts against `templatesPerHost` when it\n * records a source other than a platform starter — the route's `!=` query,\n * which never matches a document with no `source.type` at all; a screen counts\n * against `screensPerHost` by `billableScreenIds`, over the screens and the\n * host's routing map, which counts an unrouted screen too (AGL-1445); and a\n * form needs the plan to include the route's `entitlement` first, refused in\n * the route's own words, after which every form document counts against\n * `formsPerHost`. A reusable component counts against `componentsPerHost`\n * (AGL-3615) — Free 1, every paid plan unlimited — over its LIVE documents,\n * since a deleted component keeps its document with `deletedAt` stamped; on\n * an unlimited plan nothing is counted.\n *\n * ── Applied to nothing ───────────────────────────────────────────────────\n *\n * A layout renders only around a screen or layout that names it, or as the\n * built-in page layout the host document names; a template is inert until a\n * member uses it; a form renders only where a page places it; a component\n * renders only where an instance places it, and a new one has no instance;\n * and a screen serves nothing until the host's routing map names it, which is\n * what publishing writes (`publishScreenRoute`). So the writer touches the new\n * documents and nothing else — no host document, no routing map, no\n * collection, store setting, other layout or component — and never a\n * `publishedAt` or a publish schedule. What the job hands back is a link to\n * the draft, never a binding.\n *\n * ── One draft per recorded id ────────────────────────────────────────────\n *\n * The caller names the document id: the console resource id the job recorded\n * for the draft before its first write (`ai-job-draft-ids.ts`). A step run\n * again after its draft was written — a process cut off before the machine\n * recorded the step — reads the same id, finds that draft and reports it,\n * rather than writing a second.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport type AiDraftKind = 'layout' | 'template' | 'form' | 'component' | 'screen'\n\n/** The draft kinds that keep their tree in versions, as the besigner opens them. */\nexport type AiVersionedDraftKind = Extract<AiDraftKind, 'layout' | 'screen'>\n\n/** The console host resources route's allow-list for each kind. */\nexport const AI_DRAFT_FIELDS: Readonly<Record<AiDraftKind, readonly string[]>> = {\n layout: ['displayName', 'description', 'versionId'],\n template: [\n 'kind',\n 'displayName',\n 'description',\n 'placeholders',\n 'nodes',\n 'rootId',\n 'props',\n 'slug',\n 'seo',\n // A component template's own kind (AGL-3287); a job never sends one.\n 'componentKind',\n ],\n form: [\n 'displayName',\n 'slug',\n 'fields',\n 'consentFieldName',\n // A sign-up form's opt-in is the merchant's to declare; a job never sends one.\n 'optInOnSubmit',\n 'routing',\n 'legacyMatch',\n 'rootId',\n 'nodes',\n ],\n // `kind` marks a reusable email block (AGL-3287); a job makes page\n // components and never sends one.\n component: ['displayName', 'description', 'rootId', 'nodes', 'props', 'kind'],\n // `kind` is on the route's list for the email composer; a page has none, and\n // the writer never sends one.\n screen: ['displayName', 'description', 'slug', 'seo', 'kind', 'versionId'],\n}\n\n/** The keys a layout's first version is seeded with, all on the versions route's list. */\nexport const AI_DRAFT_VERSION_FIELDS: readonly string[] = ['layoutId', 'hostId', 'displayName', 'nodes']\n\n/**\n * The keys a screen's first version is seeded with, all on the versions\n * route's list: the screen it belongs to, the site, the label, the tree, and\n * the layout it renders inside when the plan names one the site has.\n */\nexport const AI_DRAFT_SCREEN_VERSION_FIELDS: readonly string[] = [\n 'screenId',\n 'hostId',\n 'displayName',\n 'nodes',\n 'layoutId',\n]\n\n/** The label on a generated draft's first version, as on one Use template makes. */\nexport const AI_DRAFT_VERSION_NAME = 'Initial version'\n\n/** The route's refusal when the site's plan does not include the kind at all. */\nexport const AI_DRAFT_ENTITLEMENT_REFUSAL = 'This feature is not included in your plan — see Billing'\n\nexport interface AiDraftBand {\n collection: 'layouts' | 'templates' | 'forms' | 'components' | 'screens'\n /** The allowance the route counts the kind against, where it counts one. */\n quotaKey?:\n | 'sharedLayoutsPerHost'\n | 'templatesPerHost'\n | 'formsPerHost'\n | 'componentsPerHost'\n | 'screensPerHost'\n /** The feature the plan must include before any document of the kind counts. */\n entitlement?: keyof OrgFeatureFlags\n /** The route's plural label, in the refusal it gives. */\n label: string\n}\n\n/** Each kind's collection, counter, feature and label, as the create route declares them. */\nexport const AI_DRAFT_BANDS: Readonly<Record<AiDraftKind, AiDraftBand>> = {\n layout: { collection: 'layouts', quotaKey: 'sharedLayoutsPerHost', label: 'shared layouts' },\n template: { collection: 'templates', quotaKey: 'templatesPerHost', label: 'templates' },\n // A saved form is counted against `formsPerHost` alone — Free 1 through\n // the ceiling — and is not behind `reusableComponents` (AGL-3597).\n form: { collection: 'forms', quotaKey: 'formsPerHost', label: 'forms' },\n // A reusable component is counted against `componentsPerHost` alone — Free\n // 1, every paid plan unlimited — and is not behind `reusableComponents`\n // (AGL-3615), the way a saved form left it.\n component: { collection: 'components', quotaKey: 'componentsPerHost', label: 'reusable components' },\n screen: { collection: 'screens', quotaKey: 'screensPerHost', label: 'pages' },\n}\n\ninterface SiblingRow {\n id: string\n name: string\n slug: string\n kind: unknown\n sourceType: unknown\n deletedAt: unknown\n deleted: boolean\n}\n\n/** A host's routing map: screen id → the path it is published at. */\ntype RoutingMap = Record<string, unknown> | null | undefined\n\nfunction draftCollection(firestore: Firestore, hostId: string, kind: AiDraftKind) {\n return firestore.collection('hosts').doc(hostId).collection(AI_DRAFT_BANDS[kind].collection)\n}\n\n/** The fields the band, the name, the address and a deletion are read from. */\nfunction siblingsOf(collection: FirebaseFirestore.CollectionReference) {\n return collection.select('displayName', 'slug', 'kind', 'source.type', 'deletedAt')\n}\n\nfunction siblingRows(snapshot: FirebaseFirestore.QuerySnapshot): SiblingRow[] {\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get('displayName') ?? ''),\n slug: String(doc.get('slug') ?? ''),\n kind: doc.get('kind'),\n sourceType: doc.get('source.type'),\n deletedAt: doc.get('deletedAt'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\nfunction subdomainOf(host: FirebaseFirestore.DocumentSnapshot): string | null {\n const subdomain = host.get('subdomain')\n return typeof subdomain === 'string' && subdomain ? subdomain : null\n}\n\n/**\n * The route's refusal when the site's plan does not include the kind, or\n * holds no more of it; `null` while it does. The feature is asked first, as\n * the route asks it before it counts.\n */\nexport function aiDraftBandRefusal(\n kind: AiDraftKind,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>,\n org: Partial<AglynOrgBilling> | null,\n routingMap?: RoutingMap,\n): string | null {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return AI_DRAFT_ENTITLEMENT_REFUSAL\n }\n if (!band.quotaKey) return null\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : kind === 'screen'\n ? billableScreenIds(\n rows.map((row) => ({ id: row.id, kind: row.kind, deletedAt: row.deletedAt })),\n (routingMap ?? undefined) as never,\n ).size\n : liveRows(kind, rows).length\n const quota = checkQuota(org, band.quotaKey, used)\n return quota.allowed\n ? null\n : `Your plan includes ${quota.limit} ${band.label} — upgrade in Billing for more`\n}\n\n/**\n * The rows a kind's allowance counts: every one, except a component's, where\n * a deletion stamps `deletedAt` and keeps the document — the route counts the\n * live ones, so a Free site that deleted its one component may make another\n * (AGL-3615).\n */\nfunction liveRows<Row extends { deletedAt?: unknown }>(kind: AiDraftKind, rows: ReadonlyArray<Row>): ReadonlyArray<Row> {\n return kind === 'component' ? rows.filter((row) => row.deletedAt == null) : rows\n}\n\n/**\n * Whether a job builds a page's repeats as reusable components (AGL-3071,\n * AGL-3615): only where the site's component allowance is unlimited. A\n * finite one — Free's 1 — cannot hold every repeat a page or a site plan\n * draws, and the doctrine's rule 1 would then demand components the plan may\n * not create; so there the repeats are drawn inline and the allowance is the\n * member's to spend, as it was while Free had no components at all.\n */\nexport function aiBuildsWithComponents(org: Partial<AglynOrgBilling> | null): boolean {\n return !Number.isFinite(resolveOrgEntitlements(org).componentsPerHost)\n}\n\n/** The draft kinds a plan's creations land as, whose band a plan is told about (AGL-3030). */\nconst AI_PLAN_DRAFT_KINDS = ['component', 'form', 'layout', 'template'] as const\n\ntype AiPlanDraftKind = (typeof AI_PLAN_DRAFT_KINDS)[number]\n\n/** Every draft kind's plural label made singular, for a band of one. */\nfunction bandLabel(label: string, count: number): string {\n return count === 1 ? label.replace(/s$/, '') : label\n}\n\n/**\n * Whether the site may take `used + 1` of a kind, in the create route's\n * arithmetic: the feature first, then the allowance. `null` for a kind whose\n * allowance is unlimited or counts nothing, which needs no rows read.\n */\nfunction planCreation(\n kind: AiPlanDraftKind,\n org: Partial<AglynOrgBilling> | null,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>> | null,\n): AiPlanCreation {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return {\n allowed: false,\n left: 0,\n reason: `this workspace's plan does not include ${kind === 'form' ? 'saved forms' : band.label}`,\n }\n }\n const limit = band.quotaKey ? resolveOrgEntitlements(org)[band.quotaKey] : Number.POSITIVE_INFINITY\n if (!rows || !Number.isFinite(limit)) return { allowed: true, left: null, reason: null }\n // The route's own arithmetic decides; the count beside it is what is left.\n const refusal = aiDraftBandRefusal(kind, rows, org)\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : liveRows(kind, rows).length\n return refusal\n ? {\n allowed: false,\n left: 0,\n reason: `this site already holds the ${limit} ${bandLabel(band.label, limit)} its plan includes`,\n }\n : { allowed: true, left: Math.max(0, limit - used), reason: null }\n}\n\n/** Whether a kind's rows are counted at all on this plan: included, and under a finite allowance. */\nfunction countsRows(kind: AiPlanDraftKind, org: Partial<AglynOrgBilling> | null): boolean {\n const band = AI_DRAFT_BANDS[kind]\n if (!band.quotaKey) return false\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) return false\n return Number.isFinite(resolveOrgEntitlements(org)[band.quotaKey])\n}\n\n/**\n * What a plan may create on this site (AGL-3030), from the workspace's plan\n * and the rows each counted kind already holds — pure, so the specs and the\n * eval harness hold the arithmetic without a Firestore. A kind whose rows\n * are not given is counted as having room, which is what an unlimited\n * allowance means.\n *\n * A theme change is a proposal a member applies, never a draft, so it counts\n * against nothing. A dataset is org data that no job writes; a plan may name\n * one only where the workspace's plan includes datasets at all. An email\n * design is written by the email plugin, whose own gate decides.\n *\n * A workspace on the Free plan spends the Free taste, a wall of monthly\n * credits, so its plan is held to the sections that wall pays for (AGL-3070).\n * The effective plan decides, as it decides the taste's own allowance: a\n * workspace whose subscription died is Free again.\n */\nexport function aiPlanCapabilitiesFrom(\n org: Partial<AglynOrgBilling> | null,\n rows: Partial<Record<AiPlanDraftKind, ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>>> = {},\n): AiPlanCapabilities {\n const unrestricted = aiUnrestrictedPlanCapabilities()\n const datasets = checkDatasetQuota(org, 0).limit > 0\n return {\n reusableComponents: aiBuildsWithComponents(org),\n create: {\n ...unrestricted.create,\n component: planCreation('component', org, rows.component ?? null),\n form: planCreation('form', org, rows.form ?? null),\n layout: planCreation('layout', org, rows.layout ?? null),\n template: planCreation('template', org, rows.template ?? null),\n dataset: datasets\n ? unrestricted.create.dataset\n : { allowed: false, left: 0, reason: \"this workspace's plan does not include datasets\" },\n },\n ...(resolveEffectivePlan(org) === 'free' ? { freeTaste: true } : {}),\n }\n}\n\n/**\n * What a plan may create on this site, read: the rows of each kind whose\n * allowance the workspace's plan counts, as the draft writer reads them, and\n * nothing for a kind the plan leaves out or leaves unlimited.\n */\nexport async function readAiPlanCapabilities(\n firestore: Firestore,\n input: { hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<AiPlanCapabilities> {\n const counted = AI_PLAN_DRAFT_KINDS.filter((kind) => countsRows(kind, input.org))\n const snapshots = await Promise.all(\n counted.map((kind) => siblingsOf(draftCollection(firestore, input.hostId, kind)).get()),\n )\n return aiPlanCapabilitiesFrom(\n input.org,\n Object.fromEntries(counted.map((kind, index) => [kind, siblingRows(snapshots[index])])),\n )\n}\n\n/**\n * Whether the site can take one more draft of the kind: a read, for a door\n * that asks before it spends. The write asks again inside its transaction.\n */\nexport async function aiDraftAllowanceRefusal(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<string | null> {\n const [snapshot, host] = await Promise.all([\n siblingsOf(draftCollection(firestore, input.hostId, input.kind)).get(),\n input.kind === 'screen'\n ? firestore.collection('hosts').doc(input.hostId).get()\n : Promise.resolve(null),\n ])\n return aiDraftBandRefusal(\n input.kind,\n siblingRows(snapshot),\n input.org,\n host?.get('screens') as RoutingMap,\n )\n}\n\n/**\n * A draft screen's one-segment address: the one asked for, else one from its\n * name, never a reserved segment, and never an address a live screen keeps\n * or the routing map already serves — so the member's publish at it later\n * takes nothing from a live page. The root is asked for only while no screen\n * holds it.\n *\n * Except the placeholder home page a site is created with (AGL-3408), which\n * holds `/` only until a real home page is published there: the guided start\n * is offered to exactly the sites that still have it, and the home page it\n * drafts has to be able to ask for the root. Publishing it replaces the\n * placeholder in the same write.\n */\nexport function aiDraftScreenSlug(\n requested: string | null | undefined,\n name: string,\n rows: ReadonlyArray<Pick<SiblingRow, 'slug' | 'deleted'> & { id?: string }>,\n routingMap: RoutingMap,\n defaultHomeScreenId?: string | null,\n): string {\n const placeholder = (id: string | undefined, path: unknown) =>\n Boolean(defaultHomeScreenId) && id === defaultHomeScreenId && path === SCREEN_ROOT_PATH\n const taken = new Set<string>([\n ...rows\n .filter((row) => !row.deleted && row.slug && !placeholder(row.id, row.slug))\n .map((row) => row.slug),\n ...Object.entries(routingMap ?? {})\n .filter(([id, path]) => typeof path === 'string' && !placeholder(id, path))\n .map(([, path]) => path as string),\n ])\n const usable = (value: string | undefined) =>\n value && !reservedScreenRouteSegment(value) && !(value === SCREEN_ROOT_PATH && taken.has(value))\n ? value\n : undefined\n const base = usable(normalizeScreenSlug(requested ?? undefined)) ?? usable(normalizeScreenSlug(name)) ?? 'page'\n if (base === SCREEN_ROOT_PATH) return base\n let slug = base\n let attempt = 2\n while (taken.has(slug)) slug = `${base}-${attempt++}`\n return slug\n}\n\nexport interface AiDraftRecord {\n id: string\n /** A layout's or a screen's first version; `null` for every other kind, which the writer gives none. */\n versionId: string | null\n name: string\n hostSubdomain: string | null\n /** A screen's search listing as the draft holds it now; absent on other kinds. */\n seo?: { title?: string; description?: string } | null\n /** Whether a member deleted the draft since it was written. */\n deleted?: boolean\n}\n\nfunction recordOf(\n id: string,\n draft: FirebaseFirestore.DocumentSnapshot,\n host: FirebaseFirestore.DocumentSnapshot,\n): AiDraftRecord {\n const versionId = draft.get('versionId')\n const seo = draft.get('seo')\n return {\n id,\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n name: String(draft.get('displayName') ?? ''),\n hostSubdomain: subdomainOf(host),\n ...(seo && typeof seo === 'object' ? { seo: seo as AiDraftRecord['seo'] } : {}),\n ...(draft.get('deletedAt') != null ? { deleted: true } : {}),\n }\n}\n\n/** The draft a job already wrote under `id`, when a run of its step got that far. */\nexport async function readAiDraft(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; id: string },\n): Promise<AiDraftRecord | null> {\n const draft = await draftCollection(firestore, input.hostId, input.kind).doc(input.id).get()\n if (!draft.exists) return null\n return recordOf(input.id, draft, await firestore.collection('hosts').doc(input.hostId).get())\n}\n\n/**\n * The tree a template draft holds now, decoded; `null` when there is none.\n *\n * A template is not a versioned draft — its nodes live on the document\n * itself, which is why `readAiDraftNodes` refuses its kind.\n */\nexport async function readAiTemplateDraftNodes(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<NodesMap | null> {\n const draft = await draftCollection(firestore, input.hostId, 'template').doc(input.id).get()\n if (!draft.exists) return null\n return decodeStoredNodes<NodesMap>(draft.get('nodes')) ?? null\n}\n\n/**\n * The property names a component draft declares now; `null` when there is no\n * such draft. Read from the draft document rather than its version, because a\n * component's props live beside its tree and a copy carries its source's.\n */\nexport async function readAiDraftComponentProps(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<string[] | null> {\n const draft = await draftCollection(firestore, input.hostId, 'component').doc(input.id).get()\n if (!draft.exists) return null\n const props = draft.get('props')\n if (!Array.isArray(props)) return []\n return props\n .map((prop) => (prop && typeof prop === 'object' ? (prop as Record<string, unknown>)['name'] : null))\n .filter((name): name is string => typeof name === 'string' && name.length > 0)\n}\n\n/** The tree a versioned draft's first version holds now, decoded; `null` when there is none. */\nexport async function readAiDraftNodes(\n firestore: Firestore,\n input: { kind: AiVersionedDraftKind; hostId: string; id: string },\n): Promise<{ versionId: string; nodes: NodesMap } | null> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n const draft = await draftRef.get()\n const versionId = draft.exists ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) return null\n const version = await draftRef.collection('versions').doc(versionId).get()\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n return nodes ? { versionId, nodes } : null\n}\n\nexport interface AiDraftInput {\n kind: AiDraftKind\n hostId: string\n /** The draft's document id: the id the job recorded for this draft. */\n id: string\n /** Whose work the draft is: the job's creator, whose brief it built. */\n uid: string\n org: Partial<AglynOrgBilling> | null\n /** The name asked for; a live sibling already called that gets a number. */\n name: string\n nodes: NodesMap\n /** A page template's suggested address, or a screen's one-segment address. */\n slug?: string | null\n /** A form's declaration and the ids its design hangs from; required for a form. */\n form?: AiFormDraftDeclaration\n /** A component's root, which the node map holds; required for a component. */\n rootId?: string\n /** The properties a component declares, which its tree binds. */\n props?: readonly ReusableComponentProp[]\n /** A screen's layout, for its first version to render inside; a layout the site has. */\n layoutId?: string | null\n /**\n * A screen's access, as the page's Access setting in the console writes it\n * (`screen.visibility`): only ever `UNLISTED` here, a store's account and\n * cart (AGL-3676), which the tenant then serves `noindex` and leaves out of\n * the sitemap. Absent, the page is public and indexable, as every content\n * and landing page stays.\n */\n visibility?: HostScreenVisibility.UNLISTED\n \n /**\n * The job the member started that wrote this draft (AGL-3596), stamped as\n * `aiJobId` on a page's or a layout's document and first version, so staff\n * can tell a generated page from a copy someone made. A scaffold's unit\n * names the site job, not the unit's derived id.\n */\n aiJobId?: string | null\n now: Date\n}\n\n/** What a form draft declares beside its design, read off that design. */\nexport interface AiFormDraftDeclaration {\n /** The canvas root `nodes` is stored under, as the Forms page's Create stores it. */\n rootId: string\n /** The form node, which carries the form's id and is captioned with the draft's name. */\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n}\n\nexport type AiDraftWrite =\n | (AiDraftRecord & { ok: true; replayed: boolean })\n | { ok: false; status: 403 | 404; error: string }\n\n/** Keep only the keys the create route admits for the kind. */\nfunction allowListed(kind: AiDraftKind, data: Record<string, unknown>): Record<string, unknown> {\n const allowed = new Set(AI_DRAFT_FIELDS[kind])\n return Object.fromEntries(\n Object.entries(data).filter(([key, value]) => allowed.has(key) && value !== undefined),\n )\n}\n\n/** The node map with its form node captioned with `name`, as Create captions a new form. */\nfunction withFormCaption(nodes: NodesMap, formNodeId: string, name: string): NodesMap {\n const node = nodes[formNodeId]\n if (!node || node.componentId !== FORM_COMPONENT_ID) return nodes\n return { ...nodes, [formNodeId]: { ...node, props: { ...(node.props ?? {}), formName: name } } }\n}\n\n/**\n * Write one draft: the band met, the name made unique among the live\n * siblings, the node map stored as msgpack, all in one transaction. A\n * refusal is returned, never thrown; a Firestore failure propagates, so the\n * machine's retry applies.\n */\nexport async function writeAiDraft(firestore: Firestore, input: AiDraftInput): Promise<AiDraftWrite> {\n const packed = encodeStoredNodes(input.nodes)\n if (!packed) throw new Error('an AI draft needs a node map')\n if (input.kind === 'form' && !input.form) throw new Error('an AI form draft needs its declaration')\n if (input.kind === 'component' && !(input.rootId && input.nodes[input.rootId])) {\n throw new Error('a component draft needs a root its node map holds')\n }\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const collection = draftCollection(firestore, input.hostId, input.kind)\n const draftRef = collection.doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, siblings] = await Promise.all([\n tx.get(hostRef),\n tx.get(draftRef),\n tx.get(siblingsOf(collection)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return { ok: true, replayed: true, ...recordOf(input.id, existing, host) }\n }\n const rows = siblingRows(siblings)\n // Read, never written: the routing map decides which screens count.\n const routingMap = host.get('screens') as RoutingMap\n const refusal = aiDraftBandRefusal(input.kind, rows, input.org, routingMap)\n if (refusal) return { ok: false, status: 403, error: refusal }\n const defaultHomeScreenId = host.get('defaultHomeScreenId') as string | null | undefined\n // A screen draft that takes the root the placeholder home page holds\n // replaces that page when it is published (AGL-3408), so it is named as\n // the page it replaces, \"Home\", rather than as a copy of it (AGL-3596).\n const slug =\n input.kind === 'screen'\n ? aiDraftScreenSlug(input.slug, input.name, rows, routingMap, defaultHomeScreenId)\n : null\n const replacesPlaceholder = (row: SiblingRow) =>\n slug === SCREEN_ROOT_PATH &&\n Boolean(defaultHomeScreenId) &&\n row.id === defaultHomeScreenId &&\n (routingMap ?? {})[row.id] === SCREEN_ROOT_PATH\n const name = uniqueDuplicateName(\n input.name,\n rows.filter((row) => !row.deleted && !replacesPlaceholder(row)).map((row) => row.name),\n )\n const stamps = { createdAt: input.now, updatedAt: input.now, createdBy: input.uid }\n const marker = input.aiJobId ? { aiJobId: input.aiJobId } : {}\n const nodes = Buffer.from(packed)\n let versionId: string | null = null\n if (input.kind === 'layout') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('layout', { displayName: name, versionId }),\n ...artifactCreateListKeys('layouts', { displayName: name }),\n ...stamps,\n ...marker,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n layoutId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...stamps,\n ...marker,\n })\n } else if (input.kind === 'screen') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('screen', { displayName: name, slug, versionId }),\n ...artifactCreateListKeys('screens', { displayName: name }),\n // Not on the create route's list: the console sets it after, with the\n // page's Access setting, and this writer sets the same field at once.\n ...(input.visibility === HostScreenVisibility.UNLISTED ? { visibility: HostScreenVisibility.UNLISTED } : {}),\n ...stamps,\n ...marker,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n screenId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...(input.layoutId ? { layoutId: input.layoutId } : {}),\n ...stamps,\n ...marker,\n })\n } else if (input.kind === 'template') {\n tx.create(draftRef, {\n ...allowListed('template', {\n kind: 'page',\n displayName: name,\n nodes,\n slug: input.slug || undefined,\n }),\n source: { type: 'authored' },\n ...artifactCreateListKeys('templates', {\n kind: 'page',\n displayName: name,\n source: { type: 'authored' },\n }),\n ...stamps,\n })\n } else if (input.kind === 'component') {\n tx.create(draftRef, {\n ...allowListed('component', {\n displayName: name,\n rootId: input.rootId,\n nodes,\n props: input.props?.length ? [...input.props] : undefined,\n }),\n ...artifactCreateListKeys('components', { displayName: name }),\n ...stamps,\n })\n } else {\n const form = input.form as AiFormDraftDeclaration\n const captioned = encodeStoredNodes(withFormCaption(input.nodes, form.formNodeId, name)) ?? packed\n const slug = normalizeFormSlug(name) || input.id\n tx.create(draftRef, {\n ...allowListed('form', {\n displayName: name,\n slug,\n fields: form.fields,\n consentFieldName: form.consentFieldName || undefined,\n routing: form.routing ?? undefined,\n rootId: form.rootId,\n nodes: Buffer.from(captioned),\n }),\n // What the Forms list queries (AGL-3330), as a console create writes it.\n ...newFormListFields({ id: input.id, displayName: name, slug, routing: form.routing }),\n ...stamps,\n })\n }\n return {\n ok: true,\n replayed: false,\n id: input.id,\n versionId,\n name,\n hostSubdomain: subdomainOf(host),\n }\n })\n}\n\nexport type AiDraftNodesUpdate =\n | { ok: true; changed: boolean }\n | { ok: false; status: 404; error: string }\n\n/**\n * Change the tree of a versioned draft the job wrote, in one transaction:\n * read the first version as it is stored NOW — a member may have opened and\n * saved it — hand it to `update`, and store what comes back with a fresh\n * `updatedAt`, which is what the besigner's save guard compares. `update`\n * answering `null` changes nothing: a pass run again after its write.\n */\nexport async function updateAiDraftNodes(\n firestore: Firestore,\n input: {\n kind: AiVersionedDraftKind\n hostId: string\n id: string\n now: Date\n update: (nodes: NodesMap) => NodesMap | null\n },\n): Promise<AiDraftNodesUpdate> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftNodesUpdate> => {\n const draft = await tx.get(draftRef)\n const versionId = draft.exists && draft.get('deletedAt') == null ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) {\n return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n }\n const versionRef = draftRef.collection('versions').doc(versionId)\n const version = await tx.get(versionRef)\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n if (!nodes) return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n const next = input.update(nodes)\n if (!next) return { ok: true, changed: false }\n const packed = encodeStoredNodes(next)\n if (!packed) throw new Error('an AI draft needs a node map')\n tx.update(versionRef, { nodes: Buffer.from(packed), updatedAt: input.now })\n return { ok: true, changed: true }\n })\n}\n\n/**\n * Put a search title and description on a draft screen the job wrote, each\n * only where the draft has none: a value a member typed since is theirs.\n */\nexport async function writeAiDraftScreenSeo(\n firestore: Firestore,\n input: {\n hostId: string\n id: string\n seo: { title?: string | null; description?: string | null }\n now: Date\n },\n): Promise<void> {\n const ref = draftCollection(firestore, input.hostId, 'screen').doc(input.id)\n await firestore.runTransaction(async (tx) => {\n const screen = await tx.get(ref)\n if (!screen.exists || screen.get('deletedAt') != null) return\n const patch: Record<string, unknown> = {}\n for (const key of ['title', 'description'] as const) {\n const value = String(input.seo[key] ?? '').trim()\n if (value && !String(screen.get(`seo.${key}`) ?? '').trim()) patch[`seo.${key}`] = value\n }\n if (Object.keys(patch).length) tx.update(ref, { ...patch, updatedAt: input.now })\n })\n}\n\n/** The site's subdomain, which a console link names the site by. */\nexport async function aiSiteSubdomain(firestore: Firestore, hostId: string): Promise<string | null> {\n return subdomainOf(await firestore.collection('hosts').doc(hostId).get())\n}\n\n/**\n * What a door says before a job that writes a draft of this kind exists: a\n * site is named, and it is the job's own org's; whatever the kind checks of\n * its own; then the allowance. `null` admits the job.\n */\nexport async function aiDraftAdmissionRefusal(\n firestore: Firestore,\n input: {\n orgId: string\n hostId: string | null\n kind: AiDraftKind\n /** What the refusal calls the job's output; the kind when absent. */\n noun?: string\n org: object | null\n /** The kind's own check, asked once the site is known to be the org's. */\n ownCheck?: (hostId: string) => Promise<AiJobAdmissionRefusal | null>\n },\n): Promise<AiJobAdmissionRefusal | null> {\n if (!input.hostId) {\n return {\n status: 400,\n error: `Open the site the ${input.noun ?? input.kind} is for before starting the job`,\n }\n }\n const owner = await resolveOrgIdForHost(input.hostId)\n if (!owner || owner !== input.orgId) return { status: 404, error: 'Unknown site' }\n const own = await input.ownCheck?.(input.hostId)\n if (own) return own\n const refusal = await aiDraftAllowanceRefusal(firestore, {\n kind: input.kind,\n hostId: input.hostId,\n org: input.org as Partial<AglynOrgBilling> | null,\n })\n return refusal ? { status: 403, error: refusal } : null\n}\n"],"names":["createResourceUid","uniqueDuplicateName","FORM_COMPONENT_ID","newFormListFields","normalizeFormSlug","artifactCreateListKeys","checkDatasetQuota","checkEntitlement","checkQuota","resolveEffectivePlan","resolveOrgEntitlements","billableScreenIds","normalizeScreenSlug","reservedScreenRouteSegment","SCREEN_ROOT_PATH","decodeStoredNodes","encodeStoredNodes","HostScreenVisibility","resolveOrgIdForHost","aiUnrestrictedPlanCapabilities","AI_DRAFT_FIELDS","layout","template","form","component","screen","AI_DRAFT_VERSION_FIELDS","AI_DRAFT_SCREEN_VERSION_FIELDS","AI_DRAFT_VERSION_NAME","AI_DRAFT_ENTITLEMENT_REFUSAL","AI_DRAFT_BANDS","collection","quotaKey","label","draftCollection","firestore","hostId","kind","doc","siblingsOf","select","siblingRows","snapshot","docs","map","id","name","String","get","slug","sourceType","deletedAt","deleted","subdomainOf","host","subdomain","aiDraftBandRefusal","rows","org","routingMap","band","entitlement","used","filter","row","undefined","length","size","liveRows","quota","allowed","limit","aiBuildsWithComponents","Number","isFinite","componentsPerHost","AI_PLAN_DRAFT_KINDS","bandLabel","count","replace","planCreation","left","reason","POSITIVE_INFINITY","refusal","Math","max","countsRows","aiPlanCapabilitiesFrom","unrestricted","datasets","reusableComponents","create","dataset","freeTaste","readAiPlanCapabilities","input","counted","snapshots","Promise","all","Object","fromEntries","index","aiDraftAllowanceRefusal","resolve","aiDraftScreenSlug","requested","defaultHomeScreenId","usable","placeholder","path","Boolean","taken","Set","entries","value","has","base","attempt","recordOf","draft","versionId","seo","hostSubdomain","readAiDraft","exists","readAiTemplateDraftNodes","readAiDraftComponentProps","props","Array","isArray","prop","readAiDraftNodes","draftRef","version","nodes","allowListed","data","key","withFormCaption","formNodeId","node","componentId","formName","writeAiDraft","packed","Error","rootId","hostRef","runTransaction","tx","existing","siblings","ok","status","error","replayed","replacesPlaceholder","stamps","createdAt","now","updatedAt","createdBy","uid","marker","aiJobId","Buffer","from","displayName","layoutId","visibility","UNLISTED","screenId","source","type","captioned","fields","consentFieldName","routing","updateAiDraftNodes","versionRef","next","update","changed","writeAiDraftScreenSeo","ref","patch","trim","keys","aiSiteSubdomain","aiDraftAdmissionRefusal","noun","owner","orgId","own","ownCheck"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-drafts.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport {\n FORM_COMPONENT_ID,\n newFormListFields,\n normalizeFormSlug,\n type FormFieldDecl,\n type FormRouting,\n} from '@aglyn/aglyn/app-utils/forms'\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport {\n checkDatasetQuota,\n checkEntitlement,\n checkQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport {\n billableScreenIds,\n normalizeScreenSlug,\n reservedScreenRouteSegment,\n SCREEN_ROOT_PATH,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport type {\n AglynOrgBilling,\n OrgFeatureFlags,\n} from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { HostScreenVisibility, type ReusableComponentProp } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport {\n aiUnrestrictedPlanCapabilities,\n type AiPlanCapabilities,\n type AiPlanCreation,\n} from '../model/ai-plan-capabilities'\nimport type { AiJobAdmissionRefusal } from './ai-job-admission'\n\n/**\n * Where a generation job's layout, page template, form, reusable component\n * or page lands (AGL-2909, AGL-2913, AGL-2908, AGL-2907): a new draft, made\n * as the console's host resources route makes one.\n *\n * ── The document the create route makes ─────────────────────────────────\n *\n * `AI_DRAFT_FIELDS` is that route's allow-list for each kind, and a spec\n * reads the route's source to hold the two together. Nothing outside the\n * list is written except the route's own stamps: `createdAt`, `updatedAt`,\n * `createdBy`, the list keys every artifact create carries\n * (`artifactCreateListKeys`: the name keys, a component's or template's stored\n * kind, a template's library row), and a template's `source`, which is\n * `authored` — a template a member's job generated is theirs, never a starter\n * or a listing. A layout's or a screen's first version carries the keys the\n * versions route seeds one with. A form is the document the Forms page's\n * Create sends: its design canvas-shaped under `rootId` with the form node\n * bound to the form's id and captioned with its name, a slug read from that\n * name, and no version — the form's page mints the first one when someone\n * opens it. Where Create sends an empty `fields`, a generated form sends the\n * declaration read off its own design, so the two agree from the start. A\n * component carries its tree and the props it declares on its own document,\n * as Use template writes one, and gets its first version where every\n * component does: when a member opens it.\n *\n * ── Counted like a create ────────────────────────────────────────────────\n *\n * The band is met INSIDE the transaction that writes (AGL-2231), with the\n * route's arithmetic: every layout document counts against\n * `sharedLayoutsPerHost`; a template counts against `templatesPerHost` when it\n * records a source other than a platform starter — the route's `!=` query,\n * which never matches a document with no `source.type` at all; a screen counts\n * against `screensPerHost` by `billableScreenIds`, over the screens and the\n * host's routing map, which counts an unrouted screen too (AGL-1445); and a\n * form needs the plan to include the route's `entitlement` first, refused in\n * the route's own words, after which every form document counts against\n * `formsPerHost`. A reusable component counts against `componentsPerHost`\n * (AGL-3615) — Free 1, every paid plan unlimited — over its LIVE documents,\n * since a deleted component keeps its document with `deletedAt` stamped; on\n * an unlimited plan nothing is counted.\n *\n * ── Applied to nothing ───────────────────────────────────────────────────\n *\n * A layout renders only around a screen or layout that names it, or as the\n * built-in page layout the host document names; a template is inert until a\n * member uses it; a form renders only where a page places it; a component\n * renders only where an instance places it, and a new one has no instance;\n * and a screen serves nothing until the host's routing map names it, which is\n * what publishing writes (`publishScreenRoute`). So the writer touches the new\n * documents and nothing else — no host document, no routing map, no\n * collection, store setting, other layout or component — and never a\n * `publishedAt` or a publish schedule. What the job hands back is a link to\n * the draft, never a binding.\n *\n * ── One draft per recorded id ────────────────────────────────────────────\n *\n * The caller names the document id: the console resource id the job recorded\n * for the draft before its first write (`ai-job-draft-ids.ts`). A step run\n * again after its draft was written — a process cut off before the machine\n * recorded the step — reads the same id, finds that draft and reports it,\n * rather than writing a second.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport type AiDraftKind = 'layout' | 'template' | 'form' | 'component' | 'screen'\n\n/** The draft kinds that keep their tree in versions, as the besigner opens them. */\nexport type AiVersionedDraftKind = Extract<AiDraftKind, 'layout' | 'screen'>\n\n/** The console host resources route's allow-list for each kind. */\nexport const AI_DRAFT_FIELDS: Readonly<Record<AiDraftKind, readonly string[]>> = {\n layout: ['displayName', 'description', 'versionId'],\n template: [\n 'kind',\n 'displayName',\n 'description',\n 'placeholders',\n 'nodes',\n 'rootId',\n 'props',\n 'slug',\n 'seo',\n // A component template's own kind (AGL-3287); a job never sends one.\n 'componentKind',\n ],\n form: [\n 'displayName',\n 'slug',\n 'fields',\n 'consentFieldName',\n // A sign-up form's opt-in is the merchant's to declare; a job never sends one.\n 'optInOnSubmit',\n 'routing',\n 'legacyMatch',\n 'rootId',\n 'nodes',\n ],\n // `kind` marks a reusable email block (AGL-3287); a job makes page\n // components and never sends one.\n component: ['displayName', 'description', 'rootId', 'nodes', 'props', 'kind'],\n // `kind` is on the route's list for the email composer; a page has none, and\n // the writer never sends one.\n screen: ['displayName', 'description', 'slug', 'seo', 'kind', 'versionId'],\n}\n\n/** The keys a layout's first version is seeded with, all on the versions route's list. */\nexport const AI_DRAFT_VERSION_FIELDS: readonly string[] = ['layoutId', 'hostId', 'displayName', 'nodes']\n\n/**\n * The keys a screen's first version is seeded with, all on the versions\n * route's list: the screen it belongs to, the site, the label, the tree, and\n * the layout it renders inside when the plan names one the site has.\n */\nexport const AI_DRAFT_SCREEN_VERSION_FIELDS: readonly string[] = [\n 'screenId',\n 'hostId',\n 'displayName',\n 'nodes',\n 'layoutId',\n]\n\n/** The label on a generated draft's first version, as on one Use template makes. */\nexport const AI_DRAFT_VERSION_NAME = 'Initial version'\n\n/** The route's refusal when the site's plan does not include the kind at all. */\nexport const AI_DRAFT_ENTITLEMENT_REFUSAL = 'This feature is not included in your plan — see Billing'\n\nexport interface AiDraftBand {\n collection: 'layouts' | 'templates' | 'forms' | 'components' | 'screens'\n /** The allowance the route counts the kind against, where it counts one. */\n quotaKey?:\n | 'sharedLayoutsPerHost'\n | 'templatesPerHost'\n | 'formsPerHost'\n | 'componentsPerHost'\n | 'screensPerHost'\n /** The feature the plan must include before any document of the kind counts. */\n entitlement?: keyof OrgFeatureFlags\n /** The route's plural label, in the refusal it gives. */\n label: string\n}\n\n/** Each kind's collection, counter, feature and label, as the create route declares them. */\nexport const AI_DRAFT_BANDS: Readonly<Record<AiDraftKind, AiDraftBand>> = {\n layout: { collection: 'layouts', quotaKey: 'sharedLayoutsPerHost', label: 'shared layouts' },\n template: { collection: 'templates', quotaKey: 'templatesPerHost', label: 'templates' },\n // A saved form is counted against `formsPerHost` alone — Free 1 through\n // the ceiling — and is not behind `reusableComponents` (AGL-3597).\n form: { collection: 'forms', quotaKey: 'formsPerHost', label: 'forms' },\n // A reusable component is counted against `componentsPerHost` alone — Free\n // 1, every paid plan unlimited — and is not behind `reusableComponents`\n // (AGL-3615), the way a saved form left it.\n component: { collection: 'components', quotaKey: 'componentsPerHost', label: 'reusable components' },\n screen: { collection: 'screens', quotaKey: 'screensPerHost', label: 'pages' },\n}\n\ninterface SiblingRow {\n id: string\n name: string\n slug: string\n kind: unknown\n sourceType: unknown\n deletedAt: unknown\n deleted: boolean\n}\n\n/** A host's routing map: screen id → the path it is published at. */\ntype RoutingMap = Record<string, unknown> | null | undefined\n\nfunction draftCollection(firestore: Firestore, hostId: string, kind: AiDraftKind) {\n return firestore.collection('hosts').doc(hostId).collection(AI_DRAFT_BANDS[kind].collection)\n}\n\n/** The fields the band, the name, the address and a deletion are read from. */\nfunction siblingsOf(collection: FirebaseFirestore.CollectionReference) {\n return collection.select('displayName', 'slug', 'kind', 'source.type', 'deletedAt')\n}\n\nfunction siblingRows(snapshot: FirebaseFirestore.QuerySnapshot): SiblingRow[] {\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n name: String(doc.get('displayName') ?? ''),\n slug: String(doc.get('slug') ?? ''),\n kind: doc.get('kind'),\n sourceType: doc.get('source.type'),\n deletedAt: doc.get('deletedAt'),\n deleted: doc.get('deletedAt') != null,\n }))\n}\n\nfunction subdomainOf(host: FirebaseFirestore.DocumentSnapshot): string | null {\n const subdomain = host.get('subdomain')\n return typeof subdomain === 'string' && subdomain ? subdomain : null\n}\n\n/**\n * The route's refusal when the site's plan does not include the kind, or\n * holds no more of it; `null` while it does. The feature is asked first, as\n * the route asks it before it counts.\n */\nexport function aiDraftBandRefusal(\n kind: AiDraftKind,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>,\n org: Partial<AglynOrgBilling> | null,\n routingMap?: RoutingMap,\n): string | null {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return AI_DRAFT_ENTITLEMENT_REFUSAL\n }\n if (!band.quotaKey) return null\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : kind === 'screen'\n ? billableScreenIds(\n rows.map((row) => ({ id: row.id, kind: row.kind, deletedAt: row.deletedAt })),\n (routingMap ?? undefined) as never,\n ).size\n : liveRows(kind, rows).length\n const quota = checkQuota(org, band.quotaKey, used)\n return quota.allowed\n ? null\n : `Your plan includes ${quota.limit} ${band.label} — upgrade in Billing for more`\n}\n\n/**\n * The rows a kind's allowance counts: every one, except a component's, where\n * a deletion stamps `deletedAt` and keeps the document — the route counts the\n * live ones, so a Free site that deleted its one component may make another\n * (AGL-3615).\n */\nfunction liveRows<Row extends { deletedAt?: unknown }>(kind: AiDraftKind, rows: ReadonlyArray<Row>): ReadonlyArray<Row> {\n return kind === 'component' ? rows.filter((row) => row.deletedAt == null) : rows\n}\n\n/**\n * Whether a job builds a page's repeats as reusable components (AGL-3071,\n * AGL-3615): only where the site's component allowance is unlimited. A\n * finite one — Free's 1 — cannot hold every repeat a page or a site plan\n * draws, and the doctrine's rule 1 would then demand components the plan may\n * not create; so there the repeats are drawn inline and the allowance is the\n * member's to spend, as it was while Free had no components at all.\n */\nexport function aiBuildsWithComponents(org: Partial<AglynOrgBilling> | null): boolean {\n return !Number.isFinite(resolveOrgEntitlements(org).componentsPerHost)\n}\n\n/** The draft kinds a plan's creations land as, whose band a plan is told about (AGL-3030). */\nconst AI_PLAN_DRAFT_KINDS = ['component', 'form', 'layout', 'template'] as const\n\ntype AiPlanDraftKind = (typeof AI_PLAN_DRAFT_KINDS)[number]\n\n/** Every draft kind's plural label made singular, for a band of one. */\nfunction bandLabel(label: string, count: number): string {\n return count === 1 ? label.replace(/s$/, '') : label\n}\n\n/**\n * Whether the site may take `used + 1` of a kind, in the create route's\n * arithmetic: the feature first, then the allowance. `null` for a kind whose\n * allowance is unlimited or counts nothing, which needs no rows read.\n */\nfunction planCreation(\n kind: AiPlanDraftKind,\n org: Partial<AglynOrgBilling> | null,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>> | null,\n): AiPlanCreation {\n const band = AI_DRAFT_BANDS[kind]\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) {\n return {\n allowed: false,\n left: 0,\n reason: `this workspace's plan does not include ${kind === 'form' ? 'saved forms' : band.label}`,\n }\n }\n const limit = band.quotaKey ? resolveOrgEntitlements(org)[band.quotaKey] : Number.POSITIVE_INFINITY\n if (!rows || !Number.isFinite(limit)) return { allowed: true, left: null, reason: null }\n // The route's own arithmetic decides; the count beside it is what is left.\n const refusal = aiDraftBandRefusal(kind, rows, org)\n const used =\n kind === 'template'\n ? rows.filter((row) => row.sourceType !== undefined && row.sourceType !== 'starter').length\n : liveRows(kind, rows).length\n return refusal\n ? {\n allowed: false,\n left: 0,\n reason: `this site already holds the ${limit} ${bandLabel(band.label, limit)} its plan includes`,\n }\n : { allowed: true, left: Math.max(0, limit - used), reason: null }\n}\n\n/** Whether a kind's rows are counted at all on this plan: included, and under a finite allowance. */\nfunction countsRows(kind: AiPlanDraftKind, org: Partial<AglynOrgBilling> | null): boolean {\n const band = AI_DRAFT_BANDS[kind]\n if (!band.quotaKey) return false\n if (band.entitlement && !checkEntitlement(org, band.entitlement)) return false\n return Number.isFinite(resolveOrgEntitlements(org)[band.quotaKey])\n}\n\n/**\n * What a plan may create on this site (AGL-3030), from the workspace's plan\n * and the rows each counted kind already holds — pure, so the specs and the\n * eval harness hold the arithmetic without a Firestore. A kind whose rows\n * are not given is counted as having room, which is what an unlimited\n * allowance means.\n *\n * A theme change is a proposal a member applies, never a draft, so it counts\n * against nothing. A dataset is org data that no job writes; a plan may name\n * one only where the workspace's plan includes datasets at all. An email\n * design is written by the email plugin, whose own gate decides.\n *\n * A workspace on the Free plan spends the Free taste, a wall of monthly\n * credits, so its plan is held to the sections that wall pays for (AGL-3070).\n * The effective plan decides, as it decides the taste's own allowance: a\n * workspace whose subscription died is Free again.\n */\nexport function aiPlanCapabilitiesFrom(\n org: Partial<AglynOrgBilling> | null,\n rows: Partial<Record<AiPlanDraftKind, ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'>>>> = {},\n): AiPlanCapabilities {\n const unrestricted = aiUnrestrictedPlanCapabilities()\n const datasets = checkDatasetQuota(org, 0).limit > 0\n return {\n reusableComponents: aiBuildsWithComponents(org),\n create: {\n ...unrestricted.create,\n component: planCreation('component', org, rows.component ?? null),\n form: planCreation('form', org, rows.form ?? null),\n layout: planCreation('layout', org, rows.layout ?? null),\n template: planCreation('template', org, rows.template ?? null),\n dataset: datasets\n ? unrestricted.create.dataset\n : { allowed: false, left: 0, reason: \"this workspace's plan does not include datasets\" },\n },\n ...(resolveEffectivePlan(org) === 'free' ? { freeTaste: true } : {}),\n }\n}\n\n/**\n * What a plan may create on this site, read: the rows of each kind whose\n * allowance the workspace's plan counts, as the draft writer reads them, and\n * nothing for a kind the plan leaves out or leaves unlimited.\n */\nexport async function readAiPlanCapabilities(\n firestore: Firestore,\n input: { hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<AiPlanCapabilities> {\n const counted = AI_PLAN_DRAFT_KINDS.filter((kind) => countsRows(kind, input.org))\n const snapshots = await Promise.all(\n counted.map((kind) => siblingsOf(draftCollection(firestore, input.hostId, kind)).get()),\n )\n return aiPlanCapabilitiesFrom(\n input.org,\n Object.fromEntries(counted.map((kind, index) => [kind, siblingRows(snapshots[index])])),\n )\n}\n\n/**\n * Whether the site can take one more draft of the kind: a read, for a door\n * that asks before it spends. The write asks again inside its transaction.\n */\nexport async function aiDraftAllowanceRefusal(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; org: Partial<AglynOrgBilling> | null },\n): Promise<string | null> {\n const [snapshot, host] = await Promise.all([\n siblingsOf(draftCollection(firestore, input.hostId, input.kind)).get(),\n input.kind === 'screen'\n ? firestore.collection('hosts').doc(input.hostId).get()\n : Promise.resolve(null),\n ])\n return aiDraftBandRefusal(\n input.kind,\n siblingRows(snapshot),\n input.org,\n host?.get('screens') as RoutingMap,\n )\n}\n\n/**\n * A stored instant in milliseconds, whatever wrote it: a Firestore\n * `Timestamp`, a `Date`, a number or an ISO string. `null` when unreadable.\n */\nexport function aiDraftInstantMs(value: unknown): number | null {\n if (value == null) return null\n if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string') {\n const parsed = Date.parse(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n const toMillis = (value as { toMillis?: unknown }).toMillis\n return typeof toMillis === 'function' ? (toMillis.call(value) as number) : null\n}\n\n/**\n * Whether the band refuses the kind only because of drafts made after\n * `sinceMs` (AGL-3765): the rows refuse now, and the rows made at or before\n * then would not. A job whose plan was confirmed while the site had room, and\n * that another build or a member filled before the job's own write, stopped\n * on something it could not have known when it was confirmed. A row whose\n * `createdAt` cannot be read counts as made before, so a refusal this cannot\n * date stays the workspace's own limit.\n */\nexport function aiDraftBandTakenSince(\n kind: AiDraftKind,\n rows: ReadonlyArray<Pick<SiblingRow, 'id' | 'kind' | 'sourceType' | 'deletedAt'> & { createdAt?: unknown }>,\n org: Partial<AglynOrgBilling> | null,\n sinceMs: number | null,\n routingMap?: RoutingMap,\n): boolean {\n if (sinceMs === null || !aiDraftBandRefusal(kind, rows, org, routingMap)) return false\n const before = rows.filter((row) => {\n const at = aiDraftInstantMs(row.createdAt)\n return at === null || at <= sinceMs\n })\n return aiDraftBandRefusal(kind, before, org, routingMap) === null\n}\n\n/** {@link aiDraftBandTakenSince}, over the site's rows of the kind as they are now. */\nexport async function readAiDraftBandTakenSince(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; org: Partial<AglynOrgBilling> | null; since: unknown },\n): Promise<boolean> {\n const sinceMs = aiDraftInstantMs(input.since)\n if (sinceMs === null) return false\n const [snapshot, host] = await Promise.all([\n draftCollection(firestore, input.hostId, input.kind).select('kind', 'source.type', 'deletedAt', 'createdAt').get(),\n input.kind === 'screen' ? firestore.collection('hosts').doc(input.hostId).get() : Promise.resolve(null),\n ])\n const rows = snapshot.docs.map((doc) => ({\n id: doc.id,\n kind: doc.get('kind'),\n sourceType: doc.get('source.type'),\n deletedAt: doc.get('deletedAt'),\n createdAt: doc.get('createdAt'),\n }))\n return aiDraftBandTakenSince(input.kind, rows, input.org, sinceMs, host?.get('screens') as RoutingMap)\n}\n\n/**\n * A draft screen's one-segment address: the one asked for, else one from its\n * name, never a reserved segment, and never an address a live screen keeps\n * or the routing map already serves — so the member's publish at it later\n * takes nothing from a live page. The root is asked for only while no screen\n * holds it.\n *\n * Except the placeholder home page a site is created with (AGL-3408), which\n * holds `/` only until a real home page is published there: the guided start\n * is offered to exactly the sites that still have it, and the home page it\n * drafts has to be able to ask for the root. Publishing it replaces the\n * placeholder in the same write.\n */\nexport function aiDraftScreenSlug(\n requested: string | null | undefined,\n name: string,\n rows: ReadonlyArray<Pick<SiblingRow, 'slug' | 'deleted'> & { id?: string }>,\n routingMap: RoutingMap,\n defaultHomeScreenId?: string | null,\n): string {\n const placeholder = (id: string | undefined, path: unknown) =>\n Boolean(defaultHomeScreenId) && id === defaultHomeScreenId && path === SCREEN_ROOT_PATH\n const taken = new Set<string>([\n ...rows\n .filter((row) => !row.deleted && row.slug && !placeholder(row.id, row.slug))\n .map((row) => row.slug),\n ...Object.entries(routingMap ?? {})\n .filter(([id, path]) => typeof path === 'string' && !placeholder(id, path))\n .map(([, path]) => path as string),\n ])\n const usable = (value: string | undefined) =>\n value && !reservedScreenRouteSegment(value) && !(value === SCREEN_ROOT_PATH && taken.has(value))\n ? value\n : undefined\n const base = usable(normalizeScreenSlug(requested ?? undefined)) ?? usable(normalizeScreenSlug(name)) ?? 'page'\n if (base === SCREEN_ROOT_PATH) return base\n let slug = base\n let attempt = 2\n while (taken.has(slug)) slug = `${base}-${attempt++}`\n return slug\n}\n\nexport interface AiDraftRecord {\n id: string\n /** A layout's or a screen's first version; `null` for every other kind, which the writer gives none. */\n versionId: string | null\n name: string\n hostSubdomain: string | null\n /** A screen's search listing as the draft holds it now; absent on other kinds. */\n seo?: { title?: string; description?: string } | null\n /** Whether a member deleted the draft since it was written. */\n deleted?: boolean\n}\n\nfunction recordOf(\n id: string,\n draft: FirebaseFirestore.DocumentSnapshot,\n host: FirebaseFirestore.DocumentSnapshot,\n): AiDraftRecord {\n const versionId = draft.get('versionId')\n const seo = draft.get('seo')\n return {\n id,\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n name: String(draft.get('displayName') ?? ''),\n hostSubdomain: subdomainOf(host),\n ...(seo && typeof seo === 'object' ? { seo: seo as AiDraftRecord['seo'] } : {}),\n ...(draft.get('deletedAt') != null ? { deleted: true } : {}),\n }\n}\n\n/** The draft a job already wrote under `id`, when a run of its step got that far. */\nexport async function readAiDraft(\n firestore: Firestore,\n input: { kind: AiDraftKind; hostId: string; id: string },\n): Promise<AiDraftRecord | null> {\n const draft = await draftCollection(firestore, input.hostId, input.kind).doc(input.id).get()\n if (!draft.exists) return null\n return recordOf(input.id, draft, await firestore.collection('hosts').doc(input.hostId).get())\n}\n\n/**\n * The tree a template draft holds now, decoded; `null` when there is none.\n *\n * A template is not a versioned draft — its nodes live on the document\n * itself, which is why `readAiDraftNodes` refuses its kind.\n */\nexport async function readAiTemplateDraftNodes(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<NodesMap | null> {\n const draft = await draftCollection(firestore, input.hostId, 'template').doc(input.id).get()\n if (!draft.exists) return null\n return decodeStoredNodes<NodesMap>(draft.get('nodes')) ?? null\n}\n\n/**\n * The property names a component draft declares now; `null` when there is no\n * such draft. Read from the draft document rather than its version, because a\n * component's props live beside its tree and a copy carries its source's.\n */\nexport async function readAiDraftComponentProps(\n firestore: Firestore,\n input: { hostId: string; id: string },\n): Promise<string[] | null> {\n const draft = await draftCollection(firestore, input.hostId, 'component').doc(input.id).get()\n if (!draft.exists) return null\n const props = draft.get('props')\n if (!Array.isArray(props)) return []\n return props\n .map((prop) => (prop && typeof prop === 'object' ? (prop as Record<string, unknown>)['name'] : null))\n .filter((name): name is string => typeof name === 'string' && name.length > 0)\n}\n\n/** The tree a versioned draft's first version holds now, decoded; `null` when there is none. */\nexport async function readAiDraftNodes(\n firestore: Firestore,\n input: { kind: AiVersionedDraftKind; hostId: string; id: string },\n): Promise<{ versionId: string; nodes: NodesMap } | null> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n const draft = await draftRef.get()\n const versionId = draft.exists ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) return null\n const version = await draftRef.collection('versions').doc(versionId).get()\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n return nodes ? { versionId, nodes } : null\n}\n\nexport interface AiDraftInput {\n kind: AiDraftKind\n hostId: string\n /** The draft's document id: the id the job recorded for this draft. */\n id: string\n /** Whose work the draft is: the job's creator, whose brief it built. */\n uid: string\n org: Partial<AglynOrgBilling> | null\n /** The name asked for; a live sibling already called that gets a number. */\n name: string\n nodes: NodesMap\n /** A page template's suggested address, or a screen's one-segment address. */\n slug?: string | null\n /** A form's declaration and the ids its design hangs from; required for a form. */\n form?: AiFormDraftDeclaration\n /** A component's root, which the node map holds; required for a component. */\n rootId?: string\n /** The properties a component declares, which its tree binds. */\n props?: readonly ReusableComponentProp[]\n /** A screen's layout, for its first version to render inside; a layout the site has. */\n layoutId?: string | null\n /**\n * A screen's access, as the page's Access setting in the console writes it\n * (`screen.visibility`): only ever `UNLISTED` here, a store's account and\n * cart (AGL-3676), which the tenant then serves `noindex` and leaves out of\n * the sitemap. Absent, the page is public and indexable, as every content\n * and landing page stays.\n */\n visibility?: HostScreenVisibility.UNLISTED\n \n /**\n * The job the member started that wrote this draft (AGL-3596), stamped as\n * `aiJobId` on a page's or a layout's document and first version, so staff\n * can tell a generated page from a copy someone made. A scaffold's unit\n * names the site job, not the unit's derived id.\n */\n aiJobId?: string | null\n now: Date\n}\n\n/** What a form draft declares beside its design, read off that design. */\nexport interface AiFormDraftDeclaration {\n /** The canvas root `nodes` is stored under, as the Forms page's Create stores it. */\n rootId: string\n /** The form node, which carries the form's id and is captioned with the draft's name. */\n formNodeId: string\n fields: FormFieldDecl[]\n consentFieldName: string | null\n routing: FormRouting | null\n}\n\nexport type AiDraftWrite =\n | (AiDraftRecord & { ok: true; replayed: boolean })\n | { ok: false; status: 403 | 404; error: string }\n\n/** Keep only the keys the create route admits for the kind. */\nfunction allowListed(kind: AiDraftKind, data: Record<string, unknown>): Record<string, unknown> {\n const allowed = new Set(AI_DRAFT_FIELDS[kind])\n return Object.fromEntries(\n Object.entries(data).filter(([key, value]) => allowed.has(key) && value !== undefined),\n )\n}\n\n/** The node map with its form node captioned with `name`, as Create captions a new form. */\nfunction withFormCaption(nodes: NodesMap, formNodeId: string, name: string): NodesMap {\n const node = nodes[formNodeId]\n if (!node || node.componentId !== FORM_COMPONENT_ID) return nodes\n return { ...nodes, [formNodeId]: { ...node, props: { ...(node.props ?? {}), formName: name } } }\n}\n\n/**\n * Write one draft: the band met, the name made unique among the live\n * siblings, the node map stored as msgpack, all in one transaction. A\n * refusal is returned, never thrown; a Firestore failure propagates, so the\n * machine's retry applies.\n */\nexport async function writeAiDraft(firestore: Firestore, input: AiDraftInput): Promise<AiDraftWrite> {\n const packed = encodeStoredNodes(input.nodes)\n if (!packed) throw new Error('an AI draft needs a node map')\n if (input.kind === 'form' && !input.form) throw new Error('an AI form draft needs its declaration')\n if (input.kind === 'component' && !(input.rootId && input.nodes[input.rootId])) {\n throw new Error('a component draft needs a root its node map holds')\n }\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const collection = draftCollection(firestore, input.hostId, input.kind)\n const draftRef = collection.doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, siblings] = await Promise.all([\n tx.get(hostRef),\n tx.get(draftRef),\n tx.get(siblingsOf(collection)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return { ok: true, replayed: true, ...recordOf(input.id, existing, host) }\n }\n const rows = siblingRows(siblings)\n // Read, never written: the routing map decides which screens count.\n const routingMap = host.get('screens') as RoutingMap\n const refusal = aiDraftBandRefusal(input.kind, rows, input.org, routingMap)\n if (refusal) return { ok: false, status: 403, error: refusal }\n const defaultHomeScreenId = host.get('defaultHomeScreenId') as string | null | undefined\n // A screen draft that takes the root the placeholder home page holds\n // replaces that page when it is published (AGL-3408), so it is named as\n // the page it replaces, \"Home\", rather than as a copy of it (AGL-3596).\n const slug =\n input.kind === 'screen'\n ? aiDraftScreenSlug(input.slug, input.name, rows, routingMap, defaultHomeScreenId)\n : null\n const replacesPlaceholder = (row: SiblingRow) =>\n slug === SCREEN_ROOT_PATH &&\n Boolean(defaultHomeScreenId) &&\n row.id === defaultHomeScreenId &&\n (routingMap ?? {})[row.id] === SCREEN_ROOT_PATH\n const name = uniqueDuplicateName(\n input.name,\n rows.filter((row) => !row.deleted && !replacesPlaceholder(row)).map((row) => row.name),\n )\n const stamps = { createdAt: input.now, updatedAt: input.now, createdBy: input.uid }\n const marker = input.aiJobId ? { aiJobId: input.aiJobId } : {}\n const nodes = Buffer.from(packed)\n let versionId: string | null = null\n if (input.kind === 'layout') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('layout', { displayName: name, versionId }),\n ...artifactCreateListKeys('layouts', { displayName: name }),\n ...stamps,\n ...marker,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n layoutId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...stamps,\n ...marker,\n })\n } else if (input.kind === 'screen') {\n versionId = createResourceUid()\n tx.create(draftRef, {\n ...allowListed('screen', { displayName: name, slug, versionId }),\n ...artifactCreateListKeys('screens', { displayName: name }),\n // Not on the create route's list: the console sets it after, with the\n // page's Access setting, and this writer sets the same field at once.\n ...(input.visibility === HostScreenVisibility.UNLISTED ? { visibility: HostScreenVisibility.UNLISTED } : {}),\n ...stamps,\n ...marker,\n })\n tx.create(draftRef.collection('versions').doc(versionId), {\n screenId: input.id,\n hostId: input.hostId,\n displayName: AI_DRAFT_VERSION_NAME,\n nodes,\n ...(input.layoutId ? { layoutId: input.layoutId } : {}),\n ...stamps,\n ...marker,\n })\n } else if (input.kind === 'template') {\n tx.create(draftRef, {\n ...allowListed('template', {\n kind: 'page',\n displayName: name,\n nodes,\n slug: input.slug || undefined,\n }),\n source: { type: 'authored' },\n ...artifactCreateListKeys('templates', {\n kind: 'page',\n displayName: name,\n source: { type: 'authored' },\n }),\n ...stamps,\n })\n } else if (input.kind === 'component') {\n tx.create(draftRef, {\n ...allowListed('component', {\n displayName: name,\n rootId: input.rootId,\n nodes,\n props: input.props?.length ? [...input.props] : undefined,\n }),\n ...artifactCreateListKeys('components', { displayName: name }),\n ...stamps,\n })\n } else {\n const form = input.form as AiFormDraftDeclaration\n const captioned = encodeStoredNodes(withFormCaption(input.nodes, form.formNodeId, name)) ?? packed\n const slug = normalizeFormSlug(name) || input.id\n tx.create(draftRef, {\n ...allowListed('form', {\n displayName: name,\n slug,\n fields: form.fields,\n consentFieldName: form.consentFieldName || undefined,\n routing: form.routing ?? undefined,\n rootId: form.rootId,\n nodes: Buffer.from(captioned),\n }),\n // What the Forms list queries (AGL-3330), as a console create writes it.\n ...newFormListFields({ id: input.id, displayName: name, slug, routing: form.routing }),\n ...stamps,\n })\n }\n return {\n ok: true,\n replayed: false,\n id: input.id,\n versionId,\n name,\n hostSubdomain: subdomainOf(host),\n }\n })\n}\n\nexport type AiDraftNodesUpdate =\n | { ok: true; changed: boolean }\n | { ok: false; status: 404; error: string }\n\n/**\n * Change the tree of a versioned draft the job wrote, in one transaction:\n * read the first version as it is stored NOW — a member may have opened and\n * saved it — hand it to `update`, and store what comes back with a fresh\n * `updatedAt`, which is what the besigner's save guard compares. `update`\n * answering `null` changes nothing: a pass run again after its write.\n */\nexport async function updateAiDraftNodes(\n firestore: Firestore,\n input: {\n kind: AiVersionedDraftKind\n hostId: string\n id: string\n now: Date\n update: (nodes: NodesMap) => NodesMap | null\n },\n): Promise<AiDraftNodesUpdate> {\n const draftRef = draftCollection(firestore, input.hostId, input.kind).doc(input.id)\n return firestore.runTransaction(async (tx): Promise<AiDraftNodesUpdate> => {\n const draft = await tx.get(draftRef)\n const versionId = draft.exists && draft.get('deletedAt') == null ? draft.get('versionId') : null\n if (typeof versionId !== 'string' || !versionId) {\n return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n }\n const versionRef = draftRef.collection('versions').doc(versionId)\n const version = await tx.get(versionRef)\n const nodes = version.exists ? decodeStoredNodes<NodesMap>(version.get('nodes')) : null\n if (!nodes) return { ok: false, status: 404, error: 'The draft is no longer on the site' }\n const next = input.update(nodes)\n if (!next) return { ok: true, changed: false }\n const packed = encodeStoredNodes(next)\n if (!packed) throw new Error('an AI draft needs a node map')\n tx.update(versionRef, { nodes: Buffer.from(packed), updatedAt: input.now })\n return { ok: true, changed: true }\n })\n}\n\n/**\n * Put a search title and description on a draft screen the job wrote, each\n * only where the draft has none: a value a member typed since is theirs.\n */\nexport async function writeAiDraftScreenSeo(\n firestore: Firestore,\n input: {\n hostId: string\n id: string\n seo: { title?: string | null; description?: string | null }\n now: Date\n },\n): Promise<void> {\n const ref = draftCollection(firestore, input.hostId, 'screen').doc(input.id)\n await firestore.runTransaction(async (tx) => {\n const screen = await tx.get(ref)\n if (!screen.exists || screen.get('deletedAt') != null) return\n const patch: Record<string, unknown> = {}\n for (const key of ['title', 'description'] as const) {\n const value = String(input.seo[key] ?? '').trim()\n if (value && !String(screen.get(`seo.${key}`) ?? '').trim()) patch[`seo.${key}`] = value\n }\n if (Object.keys(patch).length) tx.update(ref, { ...patch, updatedAt: input.now })\n })\n}\n\n/** The site's subdomain, which a console link names the site by. */\nexport async function aiSiteSubdomain(firestore: Firestore, hostId: string): Promise<string | null> {\n return subdomainOf(await firestore.collection('hosts').doc(hostId).get())\n}\n\n/**\n * What a door says before a job that writes a draft of this kind exists: a\n * site is named, and it is the job's own org's; whatever the kind checks of\n * its own; then the allowance. `null` admits the job.\n */\nexport async function aiDraftAdmissionRefusal(\n firestore: Firestore,\n input: {\n orgId: string\n hostId: string | null\n kind: AiDraftKind\n /** What the refusal calls the job's output; the kind when absent. */\n noun?: string\n org: object | null\n /** The kind's own check, asked once the site is known to be the org's. */\n ownCheck?: (hostId: string) => Promise<AiJobAdmissionRefusal | null>\n },\n): Promise<AiJobAdmissionRefusal | null> {\n if (!input.hostId) {\n return {\n status: 400,\n error: `Open the site the ${input.noun ?? input.kind} is for before starting the job`,\n }\n }\n const owner = await resolveOrgIdForHost(input.hostId)\n if (!owner || owner !== input.orgId) return { status: 404, error: 'Unknown site' }\n const own = await input.ownCheck?.(input.hostId)\n if (own) return own\n const refusal = await aiDraftAllowanceRefusal(firestore, {\n kind: input.kind,\n hostId: input.hostId,\n org: input.org as Partial<AglynOrgBilling> | null,\n })\n return refusal ? { status: 403, error: refusal } : null\n}\n"],"names":["createResourceUid","uniqueDuplicateName","FORM_COMPONENT_ID","newFormListFields","normalizeFormSlug","artifactCreateListKeys","checkDatasetQuota","checkEntitlement","checkQuota","resolveEffectivePlan","resolveOrgEntitlements","billableScreenIds","normalizeScreenSlug","reservedScreenRouteSegment","SCREEN_ROOT_PATH","decodeStoredNodes","encodeStoredNodes","HostScreenVisibility","resolveOrgIdForHost","aiUnrestrictedPlanCapabilities","AI_DRAFT_FIELDS","layout","template","form","component","screen","AI_DRAFT_VERSION_FIELDS","AI_DRAFT_SCREEN_VERSION_FIELDS","AI_DRAFT_VERSION_NAME","AI_DRAFT_ENTITLEMENT_REFUSAL","AI_DRAFT_BANDS","collection","quotaKey","label","draftCollection","firestore","hostId","kind","doc","siblingsOf","select","siblingRows","snapshot","docs","map","id","name","String","get","slug","sourceType","deletedAt","deleted","subdomainOf","host","subdomain","aiDraftBandRefusal","rows","org","routingMap","band","entitlement","used","filter","row","undefined","length","size","liveRows","quota","allowed","limit","aiBuildsWithComponents","Number","isFinite","componentsPerHost","AI_PLAN_DRAFT_KINDS","bandLabel","count","replace","planCreation","left","reason","POSITIVE_INFINITY","refusal","Math","max","countsRows","aiPlanCapabilitiesFrom","unrestricted","datasets","reusableComponents","create","dataset","freeTaste","readAiPlanCapabilities","input","counted","snapshots","Promise","all","Object","fromEntries","index","aiDraftAllowanceRefusal","resolve","aiDraftInstantMs","value","Date","getTime","parsed","parse","toMillis","call","aiDraftBandTakenSince","sinceMs","before","at","createdAt","readAiDraftBandTakenSince","since","aiDraftScreenSlug","requested","defaultHomeScreenId","usable","placeholder","path","Boolean","taken","Set","entries","has","base","attempt","recordOf","draft","versionId","seo","hostSubdomain","readAiDraft","exists","readAiTemplateDraftNodes","readAiDraftComponentProps","props","Array","isArray","prop","readAiDraftNodes","draftRef","version","nodes","allowListed","data","key","withFormCaption","formNodeId","node","componentId","formName","writeAiDraft","packed","Error","rootId","hostRef","runTransaction","tx","existing","siblings","ok","status","error","replayed","replacesPlaceholder","stamps","now","updatedAt","createdBy","uid","marker","aiJobId","Buffer","from","displayName","layoutId","visibility","UNLISTED","screenId","source","type","captioned","fields","consentFieldName","routing","updateAiDraftNodes","versionRef","next","update","changed","writeAiDraftScreenSeo","ref","patch","trim","keys","aiSiteSubdomain","aiDraftAdmissionRefusal","noun","owner","orgId","own","ownCheck"],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
17
17
|
*/ import { AI_EXPERIMENT_FIGURE_READER, AI_EXPERIMENT_MAX_VARIANTS, AI_EXPERIMENT_MIN_VARIANTS, AI_EXPERIMENT_SUBJECT_MAX_CHARS, aiExperimentNextStep, aiExperimentTarget, aiExperimentTask, aiExperimentVerdictNamesWinner, checkAiExperimentExplanation, checkAiExperimentVariants, readAiExperimentArms, readAiExperimentResult } from "../model/ai-experiment.js";
|
|
18
18
|
import { AI_STEP_TIERS } from "../providers/catalog.js";
|
|
19
19
|
import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
|
|
20
|
+
import { aiDevEffortFor } from "../runtime/ai-dev-env.js";
|
|
20
21
|
import { AI_ACCEPTABLE_USE_BLOCK, runAiRequest } from "../runtime/ai-runtime.js";
|
|
21
22
|
import { AI_GENERATION_MAX_ATTEMPTS } from "../runtime/ai-generation-bounds.js";
|
|
22
23
|
import { aiInsightReaders, readAiInsightTables } from "../insights/ai-insight-readers.js";
|
|
@@ -208,7 +209,7 @@ const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
|
208
209
|
};
|
|
209
210
|
}
|
|
210
211
|
export const runAiJobExperimentStep = async (context)=>{
|
|
211
|
-
var _ref;
|
|
212
|
+
var _ref, _aiDevEffortFor;
|
|
212
213
|
var _job_inputs, _result_toolUse_find;
|
|
213
214
|
const { job, signal } = context;
|
|
214
215
|
const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, 'job.experiment')) != null ? _ref : aiModelForStep('job.experiment');
|
|
@@ -236,6 +237,8 @@ export const runAiJobExperimentStep = async (context)=>{
|
|
|
236
237
|
});
|
|
237
238
|
if (!task) return stop(AI_EXPERIMENT_NO_TASK_COPY);
|
|
238
239
|
if (!job.hostId) return stop(AI_EXPERIMENT_NO_SITE_COPY);
|
|
240
|
+
// A development run's effort A/B arm (AGL-3781), else the row's.
|
|
241
|
+
const effort = (_aiDevEffortFor = aiDevEffortFor('job.experiment')) != null ? _aiDevEffortFor : AI_ROUTING_TABLE['job.experiment'].effort;
|
|
239
242
|
const ask = (messages, tool)=>runAiRequest(_extends({
|
|
240
243
|
model,
|
|
241
244
|
system: AI_JOB_EXPERIMENT_SYSTEM,
|
|
@@ -246,6 +249,8 @@ export const runAiJobExperimentStep = async (context)=>{
|
|
|
246
249
|
maxTokens
|
|
247
250
|
}, AI_ROUTING_TABLE['job.experiment'].thinking ? {
|
|
248
251
|
thinking: AI_ROUTING_TABLE['job.experiment'].thinking
|
|
252
|
+
} : {}, effort ? {
|
|
253
|
+
effort
|
|
249
254
|
} : {}, {
|
|
250
255
|
stream: false
|
|
251
256
|
}, signal ? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-experiment-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_EXPERIMENT_FIGURE_READER,\n AI_EXPERIMENT_MAX_VARIANTS,\n AI_EXPERIMENT_MIN_VARIANTS,\n AI_EXPERIMENT_SUBJECT_MAX_CHARS,\n aiExperimentNextStep,\n aiExperimentTarget,\n aiExperimentTask,\n aiExperimentVerdictNamesWinner,\n checkAiExperimentExplanation,\n checkAiExperimentVariants,\n readAiExperimentArms,\n readAiExperimentResult,\n type AiExperimentArm,\n type AiExperimentExplanation,\n type AiExperimentReading,\n type AiExperimentTarget,\n type AiExperimentVariantsProposal,\n} from '../model/ai-experiment'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiSystemBlock,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { aiInsightReaders, readAiInsightTables } from '../insights/ai-insight-readers'\nimport {\n registerAiJobAdmission,\n type AiJobAdmissionContext,\n type AiJobAdmissionRefusal,\n} from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\nimport {\n AI_EXPERIMENT_EXPLAIN_TOOL_NAME,\n AI_EXPERIMENT_VARIANTS_TOOL_NAME,\n aiExperimentExplainTool,\n aiExperimentVariantsTool,\n parseAiExperimentExplanation,\n parseAiExperimentVariants,\n} from '../tools/ai-experiment-tool'\n\n/**\n * The `experiment` step (AGL-2914): A/B tests by AI.\n *\n * A/B testing already exists — the marketing plugin models the experiment,\n * buckets visitors, counts exposures and conversions and compares the arms.\n * This step is the AI layer over it, and it does two things:\n *\n * 1. **Variants.** From the copy under test, it proposes between two and four\n * variants of it. It proposes COPY and nothing else: no weight, no traffic\n * split, no goal, no schedule and no winner. A person puts them into the\n * A/B testing card, which is the only thing that creates or starts a test.\n * 2. **The result.** Code reads the test's figures through the reader\n * marketing registers for them, decides what they support, and only then\n * asks the model for the words. The verdict is never the model's, and an\n * answer that claims more than the verdict is cut back to it.\n *\n * ## Nothing here writes a test\n *\n * Both answers are proposals, like a theme's or a listing's. The experiment\n * document, its variants' screen versions and its traffic are marketing's and\n * the besigner's, and a job that could write them could start an experiment on\n * live visitors without anyone deciding to.\n *\n * ## What reaches the model\n *\n * For variants: the brief, what the test varies, and the copy under test as\n * the surface handed it over. For a result: the test's name, its arms' counts\n * and rates as the results reader published them, and the verdict code\n * reached. No visitor, no session and no assignment is read, because the\n * reader publishes none — it answers in aggregates, which is the whole reason\n * a figure reader is what this reads.\n */\n\n/** The reader whose figures a result is explained from, and the plugin that owns it. */\nexport const AI_EXPERIMENT_READER_ID = AI_EXPERIMENT_FIGURE_READER\n\n/** The window a result is read over: a test's figures are totals since it started. */\nexport const AI_EXPERIMENT_READ_DAYS = 0\n\nexport const AI_EXPERIMENT_NO_TASK_COPY =\n 'Start this from the A/B testing card, which says whether you want variants or an explanation.'\nexport const AI_EXPERIMENT_NO_TARGET_COPY =\n 'Say whether the test varies a page, a section or an email.'\nexport const AI_EXPERIMENT_NO_SITE_COPY = 'Open the site whose test this is.'\nexport const AI_EXPERIMENT_NO_READER_COPY =\n 'A/B testing is not available on this workspace, so there is no test to work on.'\nexport const AI_EXPERIMENT_NO_SUBJECT_COPY =\n 'Give the copy the test varies, so there is something to write variants of.'\nexport const AI_EXPERIMENT_NO_TEST_COPY =\n 'Name the test to explain, as A/B testing lists it.'\nexport const AI_EXPERIMENT_NO_FIGURES_COPY =\n 'That test’s figures could not be read. Try again in a moment.'\nexport const AI_EXPERIMENT_UNREADABLE_RESULT_COPY =\n 'That test has no first variant and something to compare against it yet, so there is nothing to explain.'\nexport const AI_EXPERIMENT_NO_VARIANTS_COPY =\n 'The AI did not write variants that could be used. Try the brief in different words.'\nexport const AI_EXPERIMENT_NO_EXPLANATION_COPY =\n 'The AI did not explain the result in words that match its figures. Try again.'\n\n/** The step's answer ceiling, from the routing table. */\nexport const AI_JOB_EXPERIMENT_MAX_TOKENS = AI_ROUTING_TABLE['job.experiment'].maxTokens\n\n/**\n * The step's time: at most one re-ask at the routing ceiling, plus the one\n * figure read an explanation makes. Variants read nothing.\n */\nexport const AI_JOB_EXPERIMENT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.experiment'],\n maxTokens: AI_JOB_EXPERIMENT_MAX_TOKENS,\n attempts: AI_GENERATION_MAX_ATTEMPTS,\n lookups: 1,\n})\n\nexport const AI_JOB_EXPERIMENT_STEP_MINIMUM_MS = AI_JOB_EXPERIMENT_STEP_BUDGET.minimumMs\n\n/** The rules both tasks are sent: static, byte-identical for every workspace. */\nexport const AI_EXPERIMENT_RULES = [\n 'You work on A/B tests for a website builder. A test shows visitors one of several variants of the same thing and counts which converts best. You never start, stop or change a test: you write variants for a person to put into the A/B testing card, and you put a result someone else measured into plain words.',\n '',\n `When you are given copy under test, call ${AI_EXPERIMENT_VARIANTS_TOOL_NAME} once:`,\n `- Answer with between ${AI_EXPERIMENT_MIN_VARIANTS} and ${AI_EXPERIMENT_MAX_VARIANTS} variants. The first is the copy as it stands, unchanged, so the test has something to measure against.`,\n '- Each later variant changes ONE idea — the promise, the specificity, the length, the call to action — so a result says which idea won.',\n '- No two variants say the same thing in different words.',\n '- Write plain text: no HTML, no markdown, no emoji, no fences.',\n '- Never invent a fact, a price, a name, a statistic or a guarantee the copy under test did not give you.',\n '- Write in the language of the copy under test.',\n '',\n `When you are given a result, call ${AI_EXPERIMENT_EXPLAIN_TOOL_NAME} once:`,\n '- The request states the verdict. It was decided from the counts before you were asked, and it is not yours to revise.',\n '- Where the verdict names no winner, write nothing that says or implies one: no variant is ahead, none is better, nothing is significant, and you recommend nothing. Say what the figures show and why they do not settle it.',\n '- Where the verdict names a winner, name that variant and no other.',\n '- Every number you write appears in the figures you were given, as they show it or rounded. Never compute one.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- Write in the language of the brief.',\n].join('\\n')\n\nexport const AI_JOB_EXPERIMENT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_EXPERIMENT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the test varies, in the words the prompt names it by. */\nconst TARGET_NOUN: Record<AiExperimentTarget, string> = {\n screen: 'a page',\n section: 'one section of a page',\n email: 'an email',\n}\n\n/** The user turn for variants: the brief, what varies, and the copy as it stands. */\nexport function aiExperimentVariantsPrompt(input: {\n brief: string\n target: AiExperimentTarget\n subject: string\n goal: string\n}): string {\n return [\n `Brief: ${input.brief}`,\n `The test varies ${TARGET_NOUN[input.target]}.`,\n ...(input.goal ? [`It is trying to move: ${input.goal}`] : []),\n '',\n 'The copy under test, as it stands:',\n input.subject,\n ].join('\\n')\n}\n\n/** One arm as the explanation prompt shows it. */\nfunction armText(arm: AiExperimentArm, index: number): string {\n const rate = arm.rate === null ? 'no rate' : `${arm.rate}%`\n const against =\n index === 0\n ? 'the first variant, which the others are compared against'\n : [\n arm.lift === null ? 'lift not measurable' : `lift ${arm.lift}%`,\n arm.confidence === null\n ? 'confidence not measurable'\n : `confidence ${arm.confidence}%`,\n ].join(', ')\n return `- ${arm.id}: shown ${arm.exposures}, converted ${arm.conversions}, rate ${rate} (${against})`\n}\n\n/** What the verdict tells the model it may say. */\nexport function aiExperimentVerdictText(reading: AiExperimentReading): string {\n if (reading.verdict === 'winner') {\n return `VERDICT: ${reading.winnerId} is ahead of the first variant by more than chance explains, at the ${reading.threshold}% confidence this test is called at. Name ${reading.winnerId} and no other variant.`\n }\n if (reading.verdict === 'control') {\n return `VERDICT: every other variant is behind the first one, ${reading.winnerId}, by more than chance explains. Name ${reading.winnerId} and no other variant.`\n }\n return [\n `VERDICT: THERE IS NO WINNER — ${reading.reasons.join('; ')}.`,\n 'Say what the figures show and why they do not settle it. Name no winner, say no variant is ahead, better, winning or significant, and recommend no variant.',\n `Leave winner and next empty; what to do next is already decided: ${aiExperimentNextStep(reading.verdict)}`,\n ].join('\\n')\n}\n\n/** The user turn for an explanation: the test, its arms, and the verdict. */\nexport function aiExperimentExplainPrompt(input: {\n brief: string\n reading: AiExperimentReading\n}): string {\n return [\n ...(input.brief ? [`Brief: ${input.brief}`] : []),\n `Test: ${input.reading.test}`,\n 'Figures, totals since the test started:',\n ...input.reading.arms.map(armText),\n '',\n aiExperimentVerdictText(input.reading),\n ].join('\\n')\n}\n\n/** The proposal an answer becomes, as the job's output carries it. */\nfunction experimentOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n label: string,\n proposal: Record<string, unknown>,\n): AiJobOutput {\n return {\n resource: 'experiment',\n // The test the surface named, so a person opens the one they asked about;\n // a variants job for a test that does not exist yet names the job itself.\n id: str(job.inputs?.['experimentId']) || job.$id,\n versionId: null,\n hostId: job.hostId ?? null,\n hostSubdomain: str(job.inputs?.['hostSubdomain']) || null,\n label,\n proposal,\n }\n}\n\n/**\n * The A/B test results for this job's site, or `null` when the workspace has\n * no such reader. The insight path's own resolver is what decides whether a\n * reader may be read here — the plan's entitlement, the owning plugin's\n * release flag, and whether that plugin runs on this workspace and this site\n * — so this asks it rather than restating any of it.\n */\nasync function readExperimentFigures(\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'>,\n place: { orgId: string; hostId: string | null; uid: string | null },\n): Promise<{ table: Parameters<typeof readAiExperimentArms>[0]; refusals: string[] } | null> {\n if (!place.hostId) return null\n const snapshot = await context.firestore.collection('hosts').doc(place.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== place.orgId) return null\n const readers = await aiInsightReaders({\n orgId: place.orgId,\n hostId: place.hostId,\n org: context.org ?? null,\n host,\n surface: 'analytics',\n })\n const experiments = readers.filter(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n if (!experiments.length) return null\n const read = await readAiInsightTables(\n experiments,\n [{ reader: AI_EXPERIMENT_READER_ID, days: AI_EXPERIMENT_READ_DAYS, params: {} }],\n { orgId: place.orgId, hostId: place.hostId, uid: place.uid, now: context.now },\n 1,\n )\n return { table: read.tables[0] ?? null, refusals: read.refusals }\n}\n\nexport const runAiJobExperimentStep: AiJobStepRunner = async (\n context: AiJobStepContext,\n) => {\n const { job, signal } = context\n const model = context.modelFor?.('job.experiment') ?? aiModelForStep('job.experiment')\n const maxTokens = AI_JOB_EXPERIMENT_STEP_BUDGET.maxTokens(model)\n const task = aiExperimentTask(job.inputs)\n const spent = (result: { usage: unknown; estCostUsd: number; stopReason: string | null }) => ({\n usage: result.usage as AiJobStepOutcome['usage'],\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n effort: null,\n })\n const stop = (failure: string): AiJobStepOutcome => ({\n outputs: [],\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0,\n model,\n stopReason: null,\n failure,\n })\n\n if (!task) return stop(AI_EXPERIMENT_NO_TASK_COPY)\n if (!job.hostId) return stop(AI_EXPERIMENT_NO_SITE_COPY)\n\n const ask = (messages: AiMessage[], tool: ReturnType<typeof aiExperimentExplainTool>) =>\n runAiRequest({\n model,\n system: AI_JOB_EXPERIMENT_SYSTEM,\n messages,\n tools: [tool],\n maxTokens,\n ...(AI_ROUTING_TABLE['job.experiment'].thinking\n ? { thinking: AI_ROUTING_TABLE['job.experiment'].thinking }\n : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n if (task === 'variants') {\n const target = aiExperimentTarget(job.inputs)\n if (!target) return stop(AI_EXPERIMENT_NO_TARGET_COPY)\n const subject = str(job.inputs?.['subject']).slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS)\n if (!subject) return stop(AI_EXPERIMENT_NO_SUBJECT_COPY)\n const tool = aiExperimentVariantsTool(target)\n let messages: AiMessage[] = [\n {\n role: 'user',\n content: aiExperimentVariantsPrompt({\n brief: job.brief,\n target,\n subject,\n goal: str(job.inputs?.['goal']),\n }),\n },\n ]\n let last: AiJobStepOutcome | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS; attempt += 1) {\n const result = await ask(messages, tool)\n if (result.kind === 'refusal') {\n return { outputs: [], ...spent(result), refused: true }\n }\n const parsed = parseAiExperimentVariants(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_VARIANTS_TOOL_NAME)?.input,\n )\n const checked = checkAiExperimentVariants(parsed, target)\n last = { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_VARIANTS_COPY }\n if (checked) {\n if (checked.findings.length) {\n console.info('ai experiment variants dropped', {\n jobId: job.$id,\n findings: checked.findings,\n })\n }\n return {\n outputs: [variantsOutput(job, target, checked.proposal)],\n ...spent(result),\n }\n }\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}.` },\n {\n role: 'user',\n content:\n `Those variants could not be used. Answer again with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}: ` +\n `at least ${AI_EXPERIMENT_MIN_VARIANTS} variants, each with copy of its own, none repeating ` +\n 'another, and plain text throughout.',\n },\n ]\n }\n return last ?? stop(AI_EXPERIMENT_NO_VARIANTS_COPY)\n }\n\n const test = str(job.inputs?.['test'])\n if (!test) return stop(AI_EXPERIMENT_NO_TEST_COPY)\n const figures = await readExperimentFigures(context, {\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n })\n if (!figures) return stop(AI_EXPERIMENT_NO_READER_COPY)\n if (!figures.table) {\n if (figures.refusals.length) {\n console.warn('ai experiment figures refused', { jobId: job.$id, refusals: figures.refusals })\n }\n return stop(AI_EXPERIMENT_NO_FIGURES_COPY)\n }\n const reading = readAiExperimentResult(test, readAiExperimentArms(figures.table, test))\n // Nothing to explain, and nothing spent finding that out: the verdict was\n // reached from the figures alone.\n if (reading.verdict === 'unreadable') return stop(AI_EXPERIMENT_UNREADABLE_RESULT_COPY)\n\n const result = await ask(\n [{ role: 'user', content: aiExperimentExplainPrompt({ brief: job.brief, reading }) }],\n aiExperimentExplainTool(),\n )\n if (result.kind === 'refusal') return { outputs: [], ...spent(result), refused: true }\n const explained = checkAiExperimentExplanation(\n parseAiExperimentExplanation(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_EXPLAIN_TOOL_NAME)?.input,\n ),\n reading,\n )\n if (!explained) {\n return { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_EXPLANATION_COPY }\n }\n if (explained.findings.length) {\n console.info('ai experiment explanation cut back to its reading', {\n jobId: job.$id,\n verdict: reading.verdict,\n findings: explained.findings,\n })\n }\n return { outputs: [explanationOutput(job, reading, explained)], ...spent(result) }\n}\n\n/** A variants answer as the job's output. */\nfunction variantsOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n target: AiExperimentTarget,\n proposal: AiExperimentVariantsProposal,\n): AiJobOutput {\n return {\n ...experimentOutput(job, `${proposal.variants.length} variants to test`, {\n task: 'variants',\n target,\n goal: proposal.goal,\n variants: proposal.variants,\n }),\n note: 'Put these into A/B testing to start the test; nothing has been changed on the site.',\n }\n}\n\n/** An explanation as the job's output, carrying the verdict code reached. */\nfunction explanationOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n reading: AiExperimentReading,\n explained: AiExperimentExplanation,\n): AiJobOutput {\n return experimentOutput(job, explained.test || 'A/B test result', {\n task: 'explain',\n verdict: explained.verdict,\n headline: explained.headline,\n points: explained.points,\n // Only ever the reading's arm: an explanation carries no winner the\n // figures did not support, whatever the answer said.\n winnerId: aiExperimentVerdictNamesWinner(reading.verdict) ? explained.winnerId : null,\n next: explained.next,\n threshold: reading.threshold,\n arms: reading.arms,\n })\n}\n\n/**\n * Whether an experiment job may run here: a site of the job's own org, a task\n * the step runs, and A/B testing available on it — which is the results\n * reader being one this workspace may read, asked of the owning plugin rather\n * than restated.\n */\nexport async function aiExperimentAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n if (!aiExperimentTask(context.inputs)) {\n return { status: 400, error: AI_EXPERIMENT_NO_TASK_COPY }\n }\n if (!context.hostId) return { status: 400, error: AI_EXPERIMENT_NO_SITE_COPY }\n const snapshot = await context.firestore.collection('hosts').doc(context.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== context.orgId) {\n return { status: 404, error: 'Unknown site' }\n }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host,\n surface: 'analytics',\n })\n return readers.some(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n ? null\n : { status: 403, error: AI_EXPERIMENT_NO_READER_COPY }\n}\n\n/**\n * The experiment kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiExperimentJob(): void {\n registerAiJobStep('experiment', runAiJobExperimentStep, {\n minimumMs: AI_JOB_EXPERIMENT_STEP_MINIMUM_MS,\n })\n registerAiJobAdmission('experiment', aiExperimentAdmissionRefusal)\n}\n"],"names":["AI_EXPERIMENT_FIGURE_READER","AI_EXPERIMENT_MAX_VARIANTS","AI_EXPERIMENT_MIN_VARIANTS","AI_EXPERIMENT_SUBJECT_MAX_CHARS","aiExperimentNextStep","aiExperimentTarget","aiExperimentTask","aiExperimentVerdictNamesWinner","checkAiExperimentExplanation","checkAiExperimentVariants","readAiExperimentArms","readAiExperimentResult","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","aiInsightReaders","readAiInsightTables","registerAiJobAdmission","aiJobStepBudget","registerAiJobStep","AI_EXPERIMENT_EXPLAIN_TOOL_NAME","AI_EXPERIMENT_VARIANTS_TOOL_NAME","aiExperimentExplainTool","aiExperimentVariantsTool","parseAiExperimentExplanation","parseAiExperimentVariants","AI_EXPERIMENT_READER_ID","AI_EXPERIMENT_READ_DAYS","AI_EXPERIMENT_NO_TASK_COPY","AI_EXPERIMENT_NO_TARGET_COPY","AI_EXPERIMENT_NO_SITE_COPY","AI_EXPERIMENT_NO_READER_COPY","AI_EXPERIMENT_NO_SUBJECT_COPY","AI_EXPERIMENT_NO_TEST_COPY","AI_EXPERIMENT_NO_FIGURES_COPY","AI_EXPERIMENT_UNREADABLE_RESULT_COPY","AI_EXPERIMENT_NO_VARIANTS_COPY","AI_EXPERIMENT_NO_EXPLANATION_COPY","AI_JOB_EXPERIMENT_MAX_TOKENS","maxTokens","AI_JOB_EXPERIMENT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_EXPERIMENT_STEP_MINIMUM_MS","minimumMs","AI_EXPERIMENT_RULES","join","AI_JOB_EXPERIMENT_SYSTEM","text","cacheBreakpoint","str","value","trim","TARGET_NOUN","screen","section","email","aiExperimentVariantsPrompt","input","brief","target","goal","subject","armText","arm","index","rate","against","lift","confidence","id","exposures","conversions","aiExperimentVerdictText","reading","verdict","winnerId","threshold","reasons","aiExperimentExplainPrompt","test","arms","map","experimentOutput","job","label","proposal","resource","inputs","$id","versionId","hostId","hostSubdomain","readExperimentFigures","context","place","snapshot","read","firestore","collection","doc","get","host","exists","data","orgId","readers","org","surface","experiments","filter","reader","length","days","params","uid","now","table","tables","refusals","runAiJobExperimentStep","result","signal","model","modelFor","task","spent","usage","estCostUsd","stopReason","effort","stop","failure","outputs","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","ask","messages","tool","system","tools","thinking","stream","slice","role","content","last","attempt","kind","refused","parsed","toolUse","find","use","name","checked","findings","console","info","jobId","variantsOutput","figures","createdBy","warn","explained","explanationOutput","variants","note","headline","points","next","aiExperimentAdmissionRefusal","status","error","some","registerAiExperimentJob"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-experiment-step.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_EXPERIMENT_FIGURE_READER,\n AI_EXPERIMENT_MAX_VARIANTS,\n AI_EXPERIMENT_MIN_VARIANTS,\n AI_EXPERIMENT_SUBJECT_MAX_CHARS,\n aiExperimentNextStep,\n aiExperimentTarget,\n aiExperimentTask,\n aiExperimentVerdictNamesWinner,\n checkAiExperimentExplanation,\n checkAiExperimentVariants,\n readAiExperimentArms,\n readAiExperimentResult,\n type AiExperimentArm,\n type AiExperimentExplanation,\n type AiExperimentReading,\n type AiExperimentTarget,\n type AiExperimentVariantsProposal,\n} from '../model/ai-experiment'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport { aiDevEffortFor } from '../runtime/ai-dev-env'\nimport {\n AI_ACCEPTABLE_USE_BLOCK,\n runAiRequest,\n type AiMessage,\n type AiSystemBlock,\n} from '../runtime/ai-runtime'\nimport { AI_GENERATION_MAX_ATTEMPTS } from '../runtime/ai-generation-bounds'\nimport { aiInsightReaders, readAiInsightTables } from '../insights/ai-insight-readers'\nimport {\n registerAiJobAdmission,\n type AiJobAdmissionContext,\n type AiJobAdmissionRefusal,\n} from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\nimport {\n AI_EXPERIMENT_EXPLAIN_TOOL_NAME,\n AI_EXPERIMENT_VARIANTS_TOOL_NAME,\n aiExperimentExplainTool,\n aiExperimentVariantsTool,\n parseAiExperimentExplanation,\n parseAiExperimentVariants,\n} from '../tools/ai-experiment-tool'\n\n/**\n * The `experiment` step (AGL-2914): A/B tests by AI.\n *\n * A/B testing already exists — the marketing plugin models the experiment,\n * buckets visitors, counts exposures and conversions and compares the arms.\n * This step is the AI layer over it, and it does two things:\n *\n * 1. **Variants.** From the copy under test, it proposes between two and four\n * variants of it. It proposes COPY and nothing else: no weight, no traffic\n * split, no goal, no schedule and no winner. A person puts them into the\n * A/B testing card, which is the only thing that creates or starts a test.\n * 2. **The result.** Code reads the test's figures through the reader\n * marketing registers for them, decides what they support, and only then\n * asks the model for the words. The verdict is never the model's, and an\n * answer that claims more than the verdict is cut back to it.\n *\n * ## Nothing here writes a test\n *\n * Both answers are proposals, like a theme's or a listing's. The experiment\n * document, its variants' screen versions and its traffic are marketing's and\n * the besigner's, and a job that could write them could start an experiment on\n * live visitors without anyone deciding to.\n *\n * ## What reaches the model\n *\n * For variants: the brief, what the test varies, and the copy under test as\n * the surface handed it over. For a result: the test's name, its arms' counts\n * and rates as the results reader published them, and the verdict code\n * reached. No visitor, no session and no assignment is read, because the\n * reader publishes none — it answers in aggregates, which is the whole reason\n * a figure reader is what this reads.\n */\n\n/** The reader whose figures a result is explained from, and the plugin that owns it. */\nexport const AI_EXPERIMENT_READER_ID = AI_EXPERIMENT_FIGURE_READER\n\n/** The window a result is read over: a test's figures are totals since it started. */\nexport const AI_EXPERIMENT_READ_DAYS = 0\n\nexport const AI_EXPERIMENT_NO_TASK_COPY =\n 'Start this from the A/B testing card, which says whether you want variants or an explanation.'\nexport const AI_EXPERIMENT_NO_TARGET_COPY =\n 'Say whether the test varies a page, a section or an email.'\nexport const AI_EXPERIMENT_NO_SITE_COPY = 'Open the site whose test this is.'\nexport const AI_EXPERIMENT_NO_READER_COPY =\n 'A/B testing is not available on this workspace, so there is no test to work on.'\nexport const AI_EXPERIMENT_NO_SUBJECT_COPY =\n 'Give the copy the test varies, so there is something to write variants of.'\nexport const AI_EXPERIMENT_NO_TEST_COPY =\n 'Name the test to explain, as A/B testing lists it.'\nexport const AI_EXPERIMENT_NO_FIGURES_COPY =\n 'That test’s figures could not be read. Try again in a moment.'\nexport const AI_EXPERIMENT_UNREADABLE_RESULT_COPY =\n 'That test has no first variant and something to compare against it yet, so there is nothing to explain.'\nexport const AI_EXPERIMENT_NO_VARIANTS_COPY =\n 'The AI did not write variants that could be used. Try the brief in different words.'\nexport const AI_EXPERIMENT_NO_EXPLANATION_COPY =\n 'The AI did not explain the result in words that match its figures. Try again.'\n\n/** The step's answer ceiling, from the routing table. */\nexport const AI_JOB_EXPERIMENT_MAX_TOKENS = AI_ROUTING_TABLE['job.experiment'].maxTokens\n\n/**\n * The step's time: at most one re-ask at the routing ceiling, plus the one\n * figure read an explanation makes. Variants read nothing.\n */\nexport const AI_JOB_EXPERIMENT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.experiment'],\n maxTokens: AI_JOB_EXPERIMENT_MAX_TOKENS,\n attempts: AI_GENERATION_MAX_ATTEMPTS,\n lookups: 1,\n})\n\nexport const AI_JOB_EXPERIMENT_STEP_MINIMUM_MS = AI_JOB_EXPERIMENT_STEP_BUDGET.minimumMs\n\n/** The rules both tasks are sent: static, byte-identical for every workspace. */\nexport const AI_EXPERIMENT_RULES = [\n 'You work on A/B tests for a website builder. A test shows visitors one of several variants of the same thing and counts which converts best. You never start, stop or change a test: you write variants for a person to put into the A/B testing card, and you put a result someone else measured into plain words.',\n '',\n `When you are given copy under test, call ${AI_EXPERIMENT_VARIANTS_TOOL_NAME} once:`,\n `- Answer with between ${AI_EXPERIMENT_MIN_VARIANTS} and ${AI_EXPERIMENT_MAX_VARIANTS} variants. The first is the copy as it stands, unchanged, so the test has something to measure against.`,\n '- Each later variant changes ONE idea — the promise, the specificity, the length, the call to action — so a result says which idea won.',\n '- No two variants say the same thing in different words.',\n '- Write plain text: no HTML, no markdown, no emoji, no fences.',\n '- Never invent a fact, a price, a name, a statistic or a guarantee the copy under test did not give you.',\n '- Write in the language of the copy under test.',\n '',\n `When you are given a result, call ${AI_EXPERIMENT_EXPLAIN_TOOL_NAME} once:`,\n '- The request states the verdict. It was decided from the counts before you were asked, and it is not yours to revise.',\n '- Where the verdict names no winner, write nothing that says or implies one: no variant is ahead, none is better, nothing is significant, and you recommend nothing. Say what the figures show and why they do not settle it.',\n '- Where the verdict names a winner, name that variant and no other.',\n '- Every number you write appears in the figures you were given, as they show it or rounded. Never compute one.',\n '- Plain sentences: no markdown, no headings, no lists.',\n '- Write in the language of the brief.',\n].join('\\n')\n\nexport const AI_JOB_EXPERIMENT_SYSTEM: AiSystemBlock[] = [\n { text: `${AI_EXPERIMENT_RULES}\\n\\n${AI_ACCEPTABLE_USE_BLOCK}`, cacheBreakpoint: true },\n]\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** What the test varies, in the words the prompt names it by. */\nconst TARGET_NOUN: Record<AiExperimentTarget, string> = {\n screen: 'a page',\n section: 'one section of a page',\n email: 'an email',\n}\n\n/** The user turn for variants: the brief, what varies, and the copy as it stands. */\nexport function aiExperimentVariantsPrompt(input: {\n brief: string\n target: AiExperimentTarget\n subject: string\n goal: string\n}): string {\n return [\n `Brief: ${input.brief}`,\n `The test varies ${TARGET_NOUN[input.target]}.`,\n ...(input.goal ? [`It is trying to move: ${input.goal}`] : []),\n '',\n 'The copy under test, as it stands:',\n input.subject,\n ].join('\\n')\n}\n\n/** One arm as the explanation prompt shows it. */\nfunction armText(arm: AiExperimentArm, index: number): string {\n const rate = arm.rate === null ? 'no rate' : `${arm.rate}%`\n const against =\n index === 0\n ? 'the first variant, which the others are compared against'\n : [\n arm.lift === null ? 'lift not measurable' : `lift ${arm.lift}%`,\n arm.confidence === null\n ? 'confidence not measurable'\n : `confidence ${arm.confidence}%`,\n ].join(', ')\n return `- ${arm.id}: shown ${arm.exposures}, converted ${arm.conversions}, rate ${rate} (${against})`\n}\n\n/** What the verdict tells the model it may say. */\nexport function aiExperimentVerdictText(reading: AiExperimentReading): string {\n if (reading.verdict === 'winner') {\n return `VERDICT: ${reading.winnerId} is ahead of the first variant by more than chance explains, at the ${reading.threshold}% confidence this test is called at. Name ${reading.winnerId} and no other variant.`\n }\n if (reading.verdict === 'control') {\n return `VERDICT: every other variant is behind the first one, ${reading.winnerId}, by more than chance explains. Name ${reading.winnerId} and no other variant.`\n }\n return [\n `VERDICT: THERE IS NO WINNER — ${reading.reasons.join('; ')}.`,\n 'Say what the figures show and why they do not settle it. Name no winner, say no variant is ahead, better, winning or significant, and recommend no variant.',\n `Leave winner and next empty; what to do next is already decided: ${aiExperimentNextStep(reading.verdict)}`,\n ].join('\\n')\n}\n\n/** The user turn for an explanation: the test, its arms, and the verdict. */\nexport function aiExperimentExplainPrompt(input: {\n brief: string\n reading: AiExperimentReading\n}): string {\n return [\n ...(input.brief ? [`Brief: ${input.brief}`] : []),\n `Test: ${input.reading.test}`,\n 'Figures, totals since the test started:',\n ...input.reading.arms.map(armText),\n '',\n aiExperimentVerdictText(input.reading),\n ].join('\\n')\n}\n\n/** The proposal an answer becomes, as the job's output carries it. */\nfunction experimentOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n label: string,\n proposal: Record<string, unknown>,\n): AiJobOutput {\n return {\n resource: 'experiment',\n // The test the surface named, so a person opens the one they asked about;\n // a variants job for a test that does not exist yet names the job itself.\n id: str(job.inputs?.['experimentId']) || job.$id,\n versionId: null,\n hostId: job.hostId ?? null,\n hostSubdomain: str(job.inputs?.['hostSubdomain']) || null,\n label,\n proposal,\n }\n}\n\n/**\n * The A/B test results for this job's site, or `null` when the workspace has\n * no such reader. The insight path's own resolver is what decides whether a\n * reader may be read here — the plan's entitlement, the owning plugin's\n * release flag, and whether that plugin runs on this workspace and this site\n * — so this asks it rather than restating any of it.\n */\nasync function readExperimentFigures(\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'>,\n place: { orgId: string; hostId: string | null; uid: string | null },\n): Promise<{ table: Parameters<typeof readAiExperimentArms>[0]; refusals: string[] } | null> {\n if (!place.hostId) return null\n const snapshot = await context.firestore.collection('hosts').doc(place.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== place.orgId) return null\n const readers = await aiInsightReaders({\n orgId: place.orgId,\n hostId: place.hostId,\n org: context.org ?? null,\n host,\n surface: 'analytics',\n })\n const experiments = readers.filter(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n if (!experiments.length) return null\n const read = await readAiInsightTables(\n experiments,\n [{ reader: AI_EXPERIMENT_READER_ID, days: AI_EXPERIMENT_READ_DAYS, params: {} }],\n { orgId: place.orgId, hostId: place.hostId, uid: place.uid, now: context.now },\n 1,\n )\n return { table: read.tables[0] ?? null, refusals: read.refusals }\n}\n\nexport const runAiJobExperimentStep: AiJobStepRunner = async (\n context: AiJobStepContext,\n) => {\n const { job, signal } = context\n const model = context.modelFor?.('job.experiment') ?? aiModelForStep('job.experiment')\n const maxTokens = AI_JOB_EXPERIMENT_STEP_BUDGET.maxTokens(model)\n const task = aiExperimentTask(job.inputs)\n const spent = (result: { usage: unknown; estCostUsd: number; stopReason: string | null }) => ({\n usage: result.usage as AiJobStepOutcome['usage'],\n estCostUsd: result.estCostUsd,\n model,\n stopReason: result.stopReason,\n effort: null,\n })\n const stop = (failure: string): AiJobStepOutcome => ({\n outputs: [],\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n estCostUsd: 0,\n model,\n stopReason: null,\n failure,\n })\n\n if (!task) return stop(AI_EXPERIMENT_NO_TASK_COPY)\n if (!job.hostId) return stop(AI_EXPERIMENT_NO_SITE_COPY)\n\n // A development run's effort A/B arm (AGL-3781), else the row's.\n const effort = aiDevEffortFor('job.experiment') ?? AI_ROUTING_TABLE['job.experiment'].effort\n const ask = (messages: AiMessage[], tool: ReturnType<typeof aiExperimentExplainTool>) =>\n runAiRequest({\n model,\n system: AI_JOB_EXPERIMENT_SYSTEM,\n messages,\n tools: [tool],\n maxTokens,\n ...(AI_ROUTING_TABLE['job.experiment'].thinking\n ? { thinking: AI_ROUTING_TABLE['job.experiment'].thinking }\n : {}),\n ...(effort ? { effort } : {}),\n stream: false,\n ...(signal ? { signal } : {}),\n })\n\n if (task === 'variants') {\n const target = aiExperimentTarget(job.inputs)\n if (!target) return stop(AI_EXPERIMENT_NO_TARGET_COPY)\n const subject = str(job.inputs?.['subject']).slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS)\n if (!subject) return stop(AI_EXPERIMENT_NO_SUBJECT_COPY)\n const tool = aiExperimentVariantsTool(target)\n let messages: AiMessage[] = [\n {\n role: 'user',\n content: aiExperimentVariantsPrompt({\n brief: job.brief,\n target,\n subject,\n goal: str(job.inputs?.['goal']),\n }),\n },\n ]\n let last: AiJobStepOutcome | null = null\n for (let attempt = 0; attempt < AI_GENERATION_MAX_ATTEMPTS; attempt += 1) {\n const result = await ask(messages, tool)\n if (result.kind === 'refusal') {\n return { outputs: [], ...spent(result), refused: true }\n }\n const parsed = parseAiExperimentVariants(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_VARIANTS_TOOL_NAME)?.input,\n )\n const checked = checkAiExperimentVariants(parsed, target)\n last = { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_VARIANTS_COPY }\n if (checked) {\n if (checked.findings.length) {\n console.info('ai experiment variants dropped', {\n jobId: job.$id,\n findings: checked.findings,\n })\n }\n return {\n outputs: [variantsOutput(job, target, checked.proposal)],\n ...spent(result),\n }\n }\n messages = [\n ...messages,\n { role: 'assistant', content: `Answered with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}.` },\n {\n role: 'user',\n content:\n `Those variants could not be used. Answer again with ${AI_EXPERIMENT_VARIANTS_TOOL_NAME}: ` +\n `at least ${AI_EXPERIMENT_MIN_VARIANTS} variants, each with copy of its own, none repeating ` +\n 'another, and plain text throughout.',\n },\n ]\n }\n return last ?? stop(AI_EXPERIMENT_NO_VARIANTS_COPY)\n }\n\n const test = str(job.inputs?.['test'])\n if (!test) return stop(AI_EXPERIMENT_NO_TEST_COPY)\n const figures = await readExperimentFigures(context, {\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n })\n if (!figures) return stop(AI_EXPERIMENT_NO_READER_COPY)\n if (!figures.table) {\n if (figures.refusals.length) {\n console.warn('ai experiment figures refused', { jobId: job.$id, refusals: figures.refusals })\n }\n return stop(AI_EXPERIMENT_NO_FIGURES_COPY)\n }\n const reading = readAiExperimentResult(test, readAiExperimentArms(figures.table, test))\n // Nothing to explain, and nothing spent finding that out: the verdict was\n // reached from the figures alone.\n if (reading.verdict === 'unreadable') return stop(AI_EXPERIMENT_UNREADABLE_RESULT_COPY)\n\n const result = await ask(\n [{ role: 'user', content: aiExperimentExplainPrompt({ brief: job.brief, reading }) }],\n aiExperimentExplainTool(),\n )\n if (result.kind === 'refusal') return { outputs: [], ...spent(result), refused: true }\n const explained = checkAiExperimentExplanation(\n parseAiExperimentExplanation(\n result.toolUse.find((use) => use.name === AI_EXPERIMENT_EXPLAIN_TOOL_NAME)?.input,\n ),\n reading,\n )\n if (!explained) {\n return { outputs: [], ...spent(result), failure: AI_EXPERIMENT_NO_EXPLANATION_COPY }\n }\n if (explained.findings.length) {\n console.info('ai experiment explanation cut back to its reading', {\n jobId: job.$id,\n verdict: reading.verdict,\n findings: explained.findings,\n })\n }\n return { outputs: [explanationOutput(job, reading, explained)], ...spent(result) }\n}\n\n/** A variants answer as the job's output. */\nfunction variantsOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n target: AiExperimentTarget,\n proposal: AiExperimentVariantsProposal,\n): AiJobOutput {\n return {\n ...experimentOutput(job, `${proposal.variants.length} variants to test`, {\n task: 'variants',\n target,\n goal: proposal.goal,\n variants: proposal.variants,\n }),\n note: 'Put these into A/B testing to start the test; nothing has been changed on the site.',\n }\n}\n\n/** An explanation as the job's output, carrying the verdict code reached. */\nfunction explanationOutput(\n job: Pick<AiJob, '$id' | 'hostId' | 'inputs'>,\n reading: AiExperimentReading,\n explained: AiExperimentExplanation,\n): AiJobOutput {\n return experimentOutput(job, explained.test || 'A/B test result', {\n task: 'explain',\n verdict: explained.verdict,\n headline: explained.headline,\n points: explained.points,\n // Only ever the reading's arm: an explanation carries no winner the\n // figures did not support, whatever the answer said.\n winnerId: aiExperimentVerdictNamesWinner(reading.verdict) ? explained.winnerId : null,\n next: explained.next,\n threshold: reading.threshold,\n arms: reading.arms,\n })\n}\n\n/**\n * Whether an experiment job may run here: a site of the job's own org, a task\n * the step runs, and A/B testing available on it — which is the results\n * reader being one this workspace may read, asked of the owning plugin rather\n * than restated.\n */\nexport async function aiExperimentAdmissionRefusal(\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n if (!aiExperimentTask(context.inputs)) {\n return { status: 400, error: AI_EXPERIMENT_NO_TASK_COPY }\n }\n if (!context.hostId) return { status: 400, error: AI_EXPERIMENT_NO_SITE_COPY }\n const snapshot = await context.firestore.collection('hosts').doc(context.hostId).get()\n const host = snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n if (!host || host['orgId'] !== context.orgId) {\n return { status: 404, error: 'Unknown site' }\n }\n const readers = await aiInsightReaders({\n orgId: context.orgId,\n hostId: context.hostId,\n org: context.org,\n host,\n surface: 'analytics',\n })\n return readers.some(({ reader }) => reader.id === AI_EXPERIMENT_READER_ID)\n ? null\n : { status: 403, error: AI_EXPERIMENT_NO_READER_COPY }\n}\n\n/**\n * The experiment kind's registrations, called by the console surface\n * (`registerAiJobKinds` in `server.ts`), never made by importing this module\n * (AGL-3025).\n */\nexport function registerAiExperimentJob(): void {\n registerAiJobStep('experiment', runAiJobExperimentStep, {\n minimumMs: AI_JOB_EXPERIMENT_STEP_MINIMUM_MS,\n })\n registerAiJobAdmission('experiment', aiExperimentAdmissionRefusal)\n}\n"],"names":["AI_EXPERIMENT_FIGURE_READER","AI_EXPERIMENT_MAX_VARIANTS","AI_EXPERIMENT_MIN_VARIANTS","AI_EXPERIMENT_SUBJECT_MAX_CHARS","aiExperimentNextStep","aiExperimentTarget","aiExperimentTask","aiExperimentVerdictNamesWinner","checkAiExperimentExplanation","checkAiExperimentVariants","readAiExperimentArms","readAiExperimentResult","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDevEffortFor","AI_ACCEPTABLE_USE_BLOCK","runAiRequest","AI_GENERATION_MAX_ATTEMPTS","aiInsightReaders","readAiInsightTables","registerAiJobAdmission","aiJobStepBudget","registerAiJobStep","AI_EXPERIMENT_EXPLAIN_TOOL_NAME","AI_EXPERIMENT_VARIANTS_TOOL_NAME","aiExperimentExplainTool","aiExperimentVariantsTool","parseAiExperimentExplanation","parseAiExperimentVariants","AI_EXPERIMENT_READER_ID","AI_EXPERIMENT_READ_DAYS","AI_EXPERIMENT_NO_TASK_COPY","AI_EXPERIMENT_NO_TARGET_COPY","AI_EXPERIMENT_NO_SITE_COPY","AI_EXPERIMENT_NO_READER_COPY","AI_EXPERIMENT_NO_SUBJECT_COPY","AI_EXPERIMENT_NO_TEST_COPY","AI_EXPERIMENT_NO_FIGURES_COPY","AI_EXPERIMENT_UNREADABLE_RESULT_COPY","AI_EXPERIMENT_NO_VARIANTS_COPY","AI_EXPERIMENT_NO_EXPLANATION_COPY","AI_JOB_EXPERIMENT_MAX_TOKENS","maxTokens","AI_JOB_EXPERIMENT_STEP_BUDGET","tier","attempts","lookups","AI_JOB_EXPERIMENT_STEP_MINIMUM_MS","minimumMs","AI_EXPERIMENT_RULES","join","AI_JOB_EXPERIMENT_SYSTEM","text","cacheBreakpoint","str","value","trim","TARGET_NOUN","screen","section","email","aiExperimentVariantsPrompt","input","brief","target","goal","subject","armText","arm","index","rate","against","lift","confidence","id","exposures","conversions","aiExperimentVerdictText","reading","verdict","winnerId","threshold","reasons","aiExperimentExplainPrompt","test","arms","map","experimentOutput","job","label","proposal","resource","inputs","$id","versionId","hostId","hostSubdomain","readExperimentFigures","context","place","snapshot","read","firestore","collection","doc","get","host","exists","data","orgId","readers","org","surface","experiments","filter","reader","length","days","params","uid","now","table","tables","refusals","runAiJobExperimentStep","result","signal","model","modelFor","task","spent","usage","estCostUsd","stopReason","effort","stop","failure","outputs","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","ask","messages","tool","system","tools","thinking","stream","slice","role","content","last","attempt","kind","refused","parsed","toolUse","find","use","name","checked","findings","console","info","jobId","variantsOutput","figures","createdBy","warn","explained","explanationOutput","variants","note","headline","points","next","aiExperimentAdmissionRefusal","status","error","some","registerAiExperimentJob"],"mappings":"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"}
|