@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.
Files changed (94) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-brief-dialog.component.js +15 -1
  3. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  4. package/src/lib/components/ai-job-links.js +1 -1
  5. package/src/lib/components/ai-job-links.js.map +1 -1
  6. package/src/lib/components/ai-job-plan.component.js +20 -2
  7. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  8. package/src/lib/components/ai-jobs-store.d.ts +11 -0
  9. package/src/lib/components/ai-jobs-store.js +22 -0
  10. package/src/lib/components/ai-jobs-store.js.map +1 -1
  11. package/src/lib/jobs/ai-job-crm-step.js +7 -4
  12. package/src/lib/jobs/ai-job-crm-step.js.map +1 -1
  13. package/src/lib/jobs/ai-job-drafts.d.ts +24 -0
  14. package/src/lib/jobs/ai-job-drafts.js +46 -0
  15. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  16. package/src/lib/jobs/ai-job-experiment-step.js +6 -1
  17. package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
  18. package/src/lib/jobs/ai-job-form-reuse.d.ts +78 -0
  19. package/src/lib/jobs/ai-job-form-reuse.js +136 -0
  20. package/src/lib/jobs/ai-job-form-reuse.js.map +1 -0
  21. package/src/lib/jobs/ai-job-form-step.d.ts +17 -0
  22. package/src/lib/jobs/ai-job-form-step.js +63 -11
  23. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-insight-step.js +6 -1
  25. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -0
  27. package/src/lib/jobs/ai-job-plan-step.js +39 -1
  28. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  29. package/src/lib/jobs/ai-job-seo-step.js +5 -3
  30. package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
  31. package/src/lib/jobs/ai-job-site-content.d.ts +7 -5
  32. package/src/lib/jobs/ai-job-site-content.js +7 -5
  33. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  34. package/src/lib/jobs/ai-job-text-step.js +6 -3
  35. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  36. package/src/lib/jobs/ai-layout-stock-photos.d.ts +11 -7
  37. package/src/lib/jobs/ai-layout-stock-photos.js +25 -10
  38. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  39. package/src/lib/jobs/ai-stock-business-nouns.d.ts +62 -0
  40. package/src/lib/jobs/ai-stock-business-nouns.js +264 -0
  41. package/src/lib/jobs/ai-stock-business-nouns.js.map +1 -0
  42. package/src/lib/model/ai-build-plan.d.ts +7 -3
  43. package/src/lib/model/ai-build-plan.js +11 -1
  44. package/src/lib/model/ai-build-plan.js.map +1 -1
  45. package/src/lib/model/ai-credit-estimate.d.ts +32 -18
  46. package/src/lib/model/ai-credit-estimate.js +36 -18
  47. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  48. package/src/lib/model/ai-job-activity.d.ts +30 -0
  49. package/src/lib/model/ai-job-activity.js +35 -0
  50. package/src/lib/model/ai-job-activity.js.map +1 -1
  51. package/src/lib/model/ai-media-credits.d.ts +5 -3
  52. package/src/lib/model/ai-media-credits.js +7 -5
  53. package/src/lib/model/ai-media-credits.js.map +1 -1
  54. package/src/lib/model/ai-site-job.js +6 -3
  55. package/src/lib/model/ai-site-job.js.map +1 -1
  56. package/src/lib/model/ai-site-store-pages.d.ts +5 -0
  57. package/src/lib/model/ai-site-store-pages.js +2 -0
  58. package/src/lib/model/ai-site-store-pages.js.map +1 -1
  59. package/src/lib/plugin-config.js +2 -2
  60. package/src/lib/plugin-config.js.map +1 -1
  61. package/src/lib/providers/anthropic-crm-wording-superseded.d.ts +29 -0
  62. package/src/lib/providers/anthropic-crm-wording-superseded.js +31 -0
  63. package/src/lib/providers/anthropic-crm-wording-superseded.js.map +1 -0
  64. package/src/lib/providers/catalog.d.ts +24 -17
  65. package/src/lib/providers/catalog.js +63 -30
  66. package/src/lib/providers/catalog.js.map +1 -1
  67. package/src/lib/providers/model-choice.d.ts +1 -1
  68. package/src/lib/providers/model-choice.js +39 -30
  69. package/src/lib/providers/model-choice.js.map +1 -1
  70. package/src/lib/providers/routing.d.ts +20 -1
  71. package/src/lib/providers/routing.js +47 -8
  72. package/src/lib/providers/routing.js.map +1 -1
  73. package/src/lib/runtime/ai-dev-env.d.ts +37 -1
  74. package/src/lib/runtime/ai-dev-env.js +59 -6
  75. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  76. package/src/lib/runtime/ai-doctrine-validators.js +3 -1
  77. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  78. package/src/lib/runtime/ai-doctrine.js +8 -4
  79. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  80. package/src/lib/runtime/ai-eval-live.d.ts +8 -0
  81. package/src/lib/runtime/ai-eval-live.js +34 -8
  82. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  83. package/src/lib/runtime/ai-palette.generated.js +4 -0
  84. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  85. package/src/lib/runtime/ai-runtime.js +17 -2
  86. package/src/lib/runtime/ai-runtime.js.map +1 -1
  87. package/src/lib/runtime/seo-fields.js +4 -3
  88. package/src/lib/runtime/seo-fields.js.map +1 -1
  89. package/src/lib/server/ai-assist.js +3 -2
  90. package/src/lib/server/ai-assist.js.map +1 -1
  91. package/src/lib/server/ai-media-svg.js +4 -0
  92. package/src/lib/server/ai-media-svg.js.map +1 -1
  93. package/src/lib/usage/ai-allotment-alerts.js +2 -5
  94. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-plan.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 { siteLanguageCode } from '@aglyn/aglyn/app-utils/seo-locale'\nimport type { AiTool } from '../providers/contract'\nimport type { AiBuildStorePages } from './ai-build-store-pages'\n\n/**\n * The plan a generation job builds from (AGL-2935): what the job's plan step\n * (`jobs/ai-job-plan-step.ts`) returns, before a single node is generated.\n *\n * This module is the plan's MODEL — its shape, its tool schema and its\n * reader — and imports nothing of the plugin's at runtime (its one runtime\n * import is core's `seo-locale`, which imports nothing), so the doctrine\n * validators, the plan step, the job document and the proposal can all read\n * it without importing each other.\n *\n * A plan is REFERENCES, never documents (AGL-2937). It names what the site\n * already has by inventory id, what the job will create by a short name, and\n * which screens it builds from which of those — so the doctrine can be held\n * against the plan while fixing it costs a sentence, not a regenerated page,\n * and the customer reads what the job will do before it spends a credit on\n * doing it.\n *\n * `new:<name>` is how an entry refers to something the plan itself creates;\n * any other reference is an id from the site inventory.\n */\n\nexport const AI_PLAN_NEW_REF_PREFIX = 'new:'\n\n/** Records a plan may reuse, by the inventory list they come from. */\nexport type AiBuildPlanReuseKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'dataset'\n | 'collection'\n | 'screen'\n | 'theme'\n\nexport const AI_BUILD_PLAN_REUSE_KINDS: readonly AiBuildPlanReuseKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'dataset',\n 'collection',\n 'screen',\n 'theme',\n]\n\n/** What a plan may create. A theme change is a draft through the theme door, never a literal. */\nexport type AiBuildPlanCreateKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'theme-change'\n | 'dataset'\n | 'email'\n\n/**\n * What each creation is called in a sentence, and where a member makes one.\n *\n * Total over the union deliberately: a refusal that cannot name a creation is\n * a refusal a member cannot act on, so widening `AiBuildPlanCreateKind`\n * without saying where the new thing is made does not compile.\n */\nexport const AI_BUILD_PLAN_CREATION_NOUNS: Record<\n AiBuildPlanCreateKind,\n { noun: string; where: string }\n> = {\n component: { noun: 'component', where: 'on the Components page' },\n form: { noun: 'form', where: 'on the Forms page' },\n layout: { noun: 'layout', where: 'on the Layouts page' },\n template: { noun: 'template', where: 'in the Templates library' },\n 'theme-change': { noun: 'theme change', where: 'in the Theme section' },\n dataset: { noun: 'dataset', where: 'on the Datasets page' },\n email: { noun: 'email design', where: 'in Emails → Templates' },\n}\n\nexport const AI_BUILD_PLAN_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'theme-change',\n 'dataset',\n 'email',\n]\n\nexport interface AiBuildPlanReuse {\n kind: AiBuildPlanReuseKind\n /** The inventory id. */\n id: string\n /** What the plan uses it for, in a few words. */\n purpose: string\n}\n\nexport interface AiBuildPlanCreate {\n kind: AiBuildPlanCreateKind\n /** Unique within the plan; referenced as `new:<name>`. */\n name: string\n /** Why nothing the site has will do — shown in the proposal. */\n why: string\n /** The inventory id this starts from as a duplicate, or null. */\n duplicateOf: string | null\n /**\n * A component: its props as `name:type`, an icon as `name:icon`\n * (AGL-3054). A layout: the regions it has, from the closed vocabulary\n * `AI_LAYOUT_REGIONS` holds (AGL-3024) — a layout has no props, and a\n * region named here is what the built layout is held to. A template: the\n * binding tokens its page shows, from the subject's own closed catalog\n * (`AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].tokens`, AGL-3143) — the\n * template's analogue of a layout's regions, and what the built template is\n * held to. A form or a dataset: its field names. A theme change: the\n * palette paths it adds or changes.\n */\n fields: string[]\n /**\n * A form only: the dataset each of its submissions is also written to as a\n * record (AGL-3616, Zach 2026-10-10: a volunteer sign-up writing to a\n * Volunteers dataset) — `new:<name>` of a dataset this plan creates, or a\n * dataset id from the inventory. Said by the plan, never inferred from a\n * section's `uses`. Absent or null on every other creation, and on a form\n * whose submissions only reach the Inbox.\n */\n writesTo?: string | null\n /**\n * The id the draft this creation becomes is written under, where a unit of\n * the job builds it (AGL-3079). Minted when the job keeps the plan, never\n * part of the model's answer.\n */\n id?: string\n}\n\nexport interface AiBuildPlanSection {\n /** A few words: \"hero\", \"services grid\", \"contact form\". */\n name: string\n /** What the section places: inventory ids or `new:<name>` references. */\n uses: string[]\n /** How many repeated items it shows (cards, rows, tiles); 0 when none. */\n items: number\n}\n\n/**\n * A screen that is one dataset's record template (AGL-3475): the data plugin\n * serves it once per record at `/{base}/{slug}`, its copy bound with\n * `{{item.<field>}}`. The plan names the binding and the build drafts the\n * page; the member saves the binding in Page Properties → Record pages, since\n * a binding is a route and a job writes drafts only (rule 13).\n */\nexport interface AiBuildPlanRecord {\n /** The dataset: an inventory id or `new:<name>`. */\n dataset: string\n /** The path its record pages live under, one or more segments: `services`, `services/residential`. */\n base: string\n}\n\nexport interface AiBuildPlanScreen {\n title: string\n slug: string\n /** The layout it renders inside. */\n layout: string | null\n /** The template it applies, or null for a one-off page. */\n template: string | null\n /** The inventory screen it starts from as a duplicate, or null. */\n duplicateOf: string | null\n /** Whether the site navigation gains an entry for it. */\n nav: boolean\n seoTitle: string\n seoDescription: string\n sections: AiBuildPlanSection[]\n /**\n * The dataset this page is the record template of, or null for a page that\n * is one page. A plan kept before record templates carries none, which\n * reads as null.\n */\n record: AiBuildPlanRecord | null\n /**\n * The id the draft screen is written under, where a unit of the job builds\n * it (AGL-3079). Minted when the job keeps the plan, never part of the\n * model's answer.\n */\n id?: string\n}\n\n/** The third-party players a plan may embed: the hosts the Video embed element plays. */\nexport type AiBuildPlanEmbedHost = 'youtube' | 'vimeo'\n\nexport const AI_BUILD_PLAN_EMBED_HOSTS: readonly AiBuildPlanEmbedHost[] = ['youtube', 'vimeo']\n\n/** What each host is called in a sentence. */\nexport const AI_BUILD_PLAN_EMBED_HOST_NAMES: Record<AiBuildPlanEmbedHost, string> = {\n youtube: 'YouTube',\n vimeo: 'Vimeo',\n}\n\n/**\n * A third-party player the brief asks for (rule 16, AGL-3433). It loads its\n * host's code on the page that carries it, so it is planned, where the member\n * reads that cost before confirming, and a build admits only what the\n * confirmed plan lists.\n */\nexport interface AiBuildPlanEmbed {\n host: AiBuildPlanEmbedHost\n /** Where it plays: a screen of the plan by its slug, or `new:<name>` for a component the plan creates. */\n where: string\n /** The brief's own words asking for it, which the plan step finds in the brief. */\n asked: string\n /** The video's link as the brief gives it, or null for the site owner to paste. */\n url: string | null\n}\n\n/** How the items that depend on a failed item behave (AGL-3616). */\nexport type AiBuildItemDegrade = 'omit' | 'fallback'\n\nexport const AI_BUILD_ITEM_DEGRADES: readonly AiBuildItemDegrade[] = ['omit', 'fallback']\n\n/** One argument value of an item: scalars and lists of text only. */\nexport type AiBuildItemArg = string | number | boolean | string[]\n\n/**\n * One thing a `build` plan makes that its creations and screens do not\n * (AGL-3616): an operation another plugin — or this one — registered as an\n * AI capability, with the arguments its schema declares. `reuse`, `create`\n * and `screens` keep their meaning; an item is everything else.\n */\nexport interface AiBuildItem {\n /** Stable within the plan: `i0`, `i1`… in plan order. */\n slot: string\n /** The capability's operation. */\n op: string\n /** Unique within the plan, creations included; referenced as `new:<name>`. */\n name: string\n /** Why the brief needs it, in one sentence. */\n why: string\n /** What it is built after: `new:<name>` of a creation or another item. */\n dependsOn: string[]\n /** How the items that depend on it behave when it fails. */\n degrade: AiBuildItemDegrade\n /** The operation's arguments, in the shape its capability declares. */\n args: Record<string, AiBuildItemArg>\n /** The id its draft is written under, minted when the job keeps the plan; never the model's. */\n id?: string\n /** The most it can cost in credits, from its capability, recorded when the plan is kept. */\n credits?: number\n}\n\nexport interface AiBuildPlan {\n reuse: AiBuildPlanReuse[]\n create: AiBuildPlanCreate[]\n screens: AiBuildPlanScreen[]\n /**\n * The third-party players the brief asks for. Absent is none: a plan kept\n * before the list existed, and every plan whose brief asks for no player.\n */\n embeds?: AiBuildPlanEmbed[]\n /**\n * What a `build` plan makes beyond its creations and pages (AGL-3616).\n * Absent is none: every other kind's plan.\n */\n items?: AiBuildItem[]\n /**\n * The store pages a `build` adds where its plan turns the site into a store\n * (AGL-3676): worked out by code as the plan is kept, never by the model,\n * and built by a unit of their own after every planned one. Absent is none.\n */\n storePages?: AiBuildStorePages\n /**\n * The language a SITE plan's brief and copy are written in (AGL-3750), as\n * the Languages card stores a code (`siteLanguageCode`). Absent is the site's\n * own language: every other kind's plan, a plan that said null, and one\n * whose code was not a language code, which is dropped rather than refused.\n */\n language?: string\n /**\n * What a SITE plan's photos show, in English whatever language the brief\n * is in (AGL-3760): two to four words a stock photo library is searched by,\n * the business's subject first — \"bakery bread pastries\" for \"una panadería\n * … con pan dulce, conchas\". The libraries are tagged in English, so a site\n * in another language searches by these rather than its own words. Absent\n * on every other kind's plan, on one that said null, and on one whose words\n * were not plain English letters.\n */\n photoWords?: string\n}\n\n/**\n * How much one plan may hold. A job builds what one proposal can describe\n * and one person can read before confirming; a brief asking for more is\n * several jobs.\n */\nexport const AI_BUILD_PLAN_LIMITS = {\n reuse: 40,\n create: 20,\n screens: 12,\n sections: 16,\n uses: 8,\n fields: 30,\n embeds: 4,\n text: 200,\n seoDescription: 320,\n items: 500,\n /** A build plan's items (AGL-3616). */\n buildItems: 16,\n /** What one item depends on. */\n dependsOn: 8,\n /** The arguments one item carries, as JSON. */\n argsChars: 2_000,\n} as const\n\nconst string = (description: string) => ({ type: 'string', description })\nconst nullableString = (description: string) => ({\n anyOf: [{ type: 'string' }, { type: 'null' }],\n description,\n})\nconst strings = (description: string) => ({\n type: 'array',\n items: { type: 'string' },\n description,\n})\n\n/**\n * The ceiling `parseAiBuildPlan` cuts a field's text at, in words (AGL-3022).\n * The reader repairs an overlong value by cutting it, and a model never told\n * where the cut falls writes a rationale that stops mid-sentence there.\n */\nconst atMost = (limit: number = AI_BUILD_PLAN_LIMITS.text) => `at most ${limit} characters`\n\n/**\n * The plan as a strict tool's input schema. Only the keywords strict\n * structured output accepts: every object closes its properties and lists\n * every one as required, an optional value is a `null` branch, and no\n * length or count bound appears as a keyword — those are enforced by\n * `parseAiBuildPlan`, which every answer passes through anyway.\n *\n * A text ceiling is stated in words instead, on each field the model writes.\n * A reference is copied from the inventory or from a creation's name, so its\n * length is not the model's to choose. A search title and description are\n * held by rule 10 to ceilings far inside the reader's, which the doctrine\n * states, so naming the reader's here would contradict the rule.\n */\nexport const AI_BUILD_PLAN_TOOL: AiTool = {\n name: 'submit_build_plan',\n description:\n 'Submit the build plan: what the site already has that this job reuses, what it creates and why, and the pages it builds from them.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['reuse', 'create', 'screens'],\n properties: {\n reuse: {\n type: 'array',\n description: 'Records the site already has that this plan uses, by inventory id.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'id', 'purpose'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_REUSE_KINDS] },\n id: string('The id from the site inventory.'),\n purpose: string(`What the plan uses it for, in a few words, ${atMost()}.`),\n },\n },\n },\n create: {\n type: 'array',\n description:\n 'What the plan creates. Creating is the exception: each entry says why nothing listed will do.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'name', 'why', 'duplicateOf', 'fields'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_CREATE_KINDS] },\n name: string(\n `Unique within the plan, ${atMost()}; other entries refer to it as new:<name>.`,\n ),\n why: string(`Why nothing the site already has will do, in one sentence of ${atMost()}.`),\n duplicateOf: nullableString(\n 'The inventory id this starts from as a duplicate, or null.',\n ),\n fields: strings(\n `A component: its props as name:type, an icon as name:icon. A layout: the regions it has, each one of header, nav, sidebar, main, footer — list every region the finished layout has, not only the ones it gains. A form or dataset: its field names. A theme change: the palette paths it adds or changes. Each ${atMost()}.`,\n ),\n },\n },\n },\n screens: {\n type: 'array',\n description: 'The pages the job builds, each a new draft.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: [\n 'title',\n 'slug',\n 'layout',\n 'template',\n 'duplicateOf',\n 'nav',\n 'seoTitle',\n 'seoDescription',\n 'sections',\n ],\n properties: {\n title: string(`The page name, ${atMost()}.`),\n slug: string(\n `The path, ${atMost()}: lowercase words joined by hyphens, e.g. /services/roof-repair.`,\n ),\n layout: nullableString(\n 'The layout it renders inside: an inventory id or new:<name>.',\n ),\n template: nullableString(\n 'The template it applies (inventory id or new:<name>), or null for a one-off page.',\n ),\n duplicateOf: nullableString(\n 'The inventory page it starts from as a duplicate, or null.',\n ),\n nav: {\n type: 'boolean',\n description: 'Whether the site navigation gains an entry for it.',\n },\n seoTitle: string('The search-result title.'),\n seoDescription: string('The search-result description.'),\n sections: {\n type: 'array',\n description: 'The sections, top to bottom.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'uses', 'items'],\n properties: {\n name: string(`A few words, ${atMost()}: hero, services grid, contact form.`),\n uses: strings('What the section places: inventory ids or new:<name>.'),\n items: {\n type: 'integer',\n description: 'How many repeated items it shows; 0 when none.',\n },\n },\n },\n },\n },\n },\n },\n },\n },\n}\n\n/** Words that ask for a video, or a link to one. */\nexport const AI_PLAN_ASKS_FOR_VIDEO = /\\b(?:videos?|films?|clips?|youtube|vimeo|player)\\b|youtu\\.be/i\n\nconst EMBEDS_SCHEMA = {\n type: 'array',\n description:\n 'A YouTube or Vimeo player the brief asks for, on a screen or a created component. Empty unless the brief asks; a library film is a Video.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['host', 'where', 'asked', 'url'],\n properties: {\n host: { type: 'string', enum: [...AI_BUILD_PLAN_EMBED_HOSTS] },\n where: string('A screen slug, or new:<name> of a component.'),\n asked: string('The words of the brief that ask for it, copied exactly.'),\n url: nullableString('The video link the brief gives, or null.'),\n },\n },\n}\n\n/**\n * The plan tool with its list of third-party players, offered only to a brief\n * that asks for a video (AGL-3433). Every other plan's request is the plain\n * tool, byte for byte, so the list costs nothing on the plans that cannot use\n * it, and a model answering a brief that names no video has nowhere to plan a\n * player it was never asked for.\n */\nexport const AI_BUILD_PLAN_EMBEDS_TOOL: AiTool = {\n ...AI_BUILD_PLAN_TOOL,\n inputSchema: {\n ...AI_BUILD_PLAN_TOOL.inputSchema,\n required: [...(AI_BUILD_PLAN_TOOL.inputSchema['required'] as string[]), 'embeds'],\n properties: {\n ...(AI_BUILD_PLAN_TOOL.inputSchema['properties'] as Record<string, unknown>),\n embeds: EMBEDS_SCHEMA,\n },\n },\n}\n\nconst RECORD_SCHEMA = {\n anyOf: [\n {\n type: 'object',\n additionalProperties: false,\n required: ['dataset', 'base'],\n properties: {\n dataset: string('The dataset: an inventory id or new:<name>.'),\n base: string('The path its record pages live under, e.g. services for /services/roofing.'),\n },\n },\n { type: 'null' },\n ],\n description:\n \"When this page is one dataset's record template, serving a page per record at /<base>/<record address> with its copy bound as {{item.<field>}}: the dataset and the base. Otherwise null.\",\n}\n\nconst WRITES_TO_SCHEMA = nullableString(\n 'A form only, and only where each submission should also be kept as a record (a volunteer sign-up, an RSVP, an inquiry): the dataset it writes to, as new:<name> of a dataset this plan creates or a dataset id from the inventory. Otherwise null.',\n)\n\n/**\n * A plan tool whose screens each name the dataset they are the record\n * template of, or null, and whose creations each name the dataset a form\n * writes its submissions to, or null (AGL-3616).\n */\nfunction withRecords(tool: AiTool): AiTool {\n const properties = tool.inputSchema['properties'] as Record<string, unknown>\n const screens = properties['screens'] as { items: Record<string, unknown> }\n const create = properties['create'] as { items: Record<string, unknown> }\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n properties: {\n ...properties,\n create: {\n ...create,\n items: {\n ...create.items,\n required: [...(create.items['required'] as string[]), 'writesTo'],\n properties: {\n ...(create.items['properties'] as Record<string, unknown>),\n writesTo: WRITES_TO_SCHEMA,\n },\n },\n },\n screens: {\n ...screens,\n items: {\n ...screens.items,\n required: [...(screens.items['required'] as string[]), 'record'],\n properties: {\n ...(screens.items['properties'] as Record<string, unknown>),\n record: RECORD_SCHEMA,\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * The plan tools with a record template on every screen (AGL-3475) and the\n * dataset a form writes to on every creation (AGL-3616), offered only where\n * the job may bind a dataset: a site with a dataset, or a job that may create\n * one. Record pages are a Starter feature, and every other plan's\n * request is the plain tool, byte for byte, so the field costs a Free taste\n * nothing and a model planning a site with no dataset has none to bind.\n */\nexport const AI_BUILD_PLAN_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_TOOL)\nexport const AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_EMBEDS_TOOL)\n\nconst ITEMS_SCHEMA = {\n type: 'array',\n description:\n 'What the plan makes beyond its creations and pages, each one operation from the list in the request. Empty when the brief asks for nothing else.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['op', 'name', 'why', 'dependsOn', 'degrade', 'args'],\n properties: {\n op: string('The operation, exactly as the request lists it.'),\n name: string(`Unique within the plan, creations included, ${atMost()}; other entries refer to it as new:<name>.`),\n why: string(`Why the brief needs it, in one sentence of ${atMost()}.`),\n dependsOn: strings('What it is built after: new:<name> of a creation or another item. Empty when nothing.'),\n degrade: {\n type: 'string',\n enum: [...AI_BUILD_ITEM_DEGRADES],\n description:\n 'omit: what depends on it is built without it if it fails. fallback: what depends on it uses something the site already has instead.',\n },\n args: string('Its arguments as one JSON object, with exactly the fields the request lists for the operation.'),\n },\n },\n}\n\n/**\n * Any plan tool with a build's items (AGL-3616), offered only to a `build`\n * job. The items' arguments travel as one JSON string whose fields the\n * request lists per operation, so the tool stays byte for byte the same\n * whichever plugins registered operations, and the prompt cache keeps it.\n */\nexport function withBuildItems(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'items'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n items: ITEMS_SCHEMA,\n },\n },\n }\n}\n\nconst LANGUAGE_SCHEMA = nullableString(\n \"The language the brief is written in, which the site's copy is written in too: a code such as en, es, fr or pt-BR. Null when unsure.\",\n)\n\n/** The most words a plan's photo words keep. */\nexport const AI_PLAN_PHOTO_WORDS_MAX = 4\n\n/**\n * A plan's photo words as a stock photo search takes them (AGL-3760), or\n * `null`: lowercase English words, at most {@link AI_PLAN_PHOTO_WORDS_MAX}.\n * Words with letters outside English's — \"panadería\" — are no English at all,\n * so the plan is kept without them.\n */\nexport function aiPlanPhotoWords(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const text = value.trim().toLowerCase()\n if (!text || !/^[a-z][a-z\\s,'&-]*$/.test(text)) return null\n const words = text\n .split(/[\\s,&]+/)\n .map((word) => word.replace(/^[-']+|[-']+$/g, ''))\n .filter((word) => word.length > 1)\n return words.length ? words.slice(0, AI_PLAN_PHOTO_WORDS_MAX).join(' ') : null\n}\n\nconst PHOTO_WORDS_SCHEMA = nullableString(\n \"For a brief not in English: 2-4 English words for its stock photos, subject first, e.g. bakery bread pastries; no place. Else null.\",\n)\n\n/**\n * Any plan tool with the site's language (AGL-3750) and its photo words in\n * English (AGL-3760), offered only to a `site` job, whose guided start sets\n * the site's language from one and searches its photos by the other. One\n * schema for every site plan, so the tool is the same bytes across briefs\n * and the prompt cache keeps it; every other kind's tool is unchanged.\n */\nexport function withPlanLanguage(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'language', 'photoWords'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n language: LANGUAGE_SCHEMA,\n photoWords: PHOTO_WORDS_SCHEMA,\n },\n },\n }\n}\n\n/** The plan tool a brief is answered with, on a job that may or may not bind a dataset. */\nexport function aiBuildPlanToolFor(\n brief: string,\n options: { records?: boolean; items?: boolean; language?: boolean } = {},\n): AiTool {\n const video = AI_PLAN_ASKS_FOR_VIDEO.test(brief)\n const tool = options.records\n ? video\n ? AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL\n : AI_BUILD_PLAN_RECORDS_TOOL\n : video\n ? AI_BUILD_PLAN_EMBEDS_TOOL\n : AI_BUILD_PLAN_TOOL\n const built = options.items ? withBuildItems(tool) : tool\n return options.language ? withPlanLanguage(built) : built\n}\n\nexport type AiBuildPlanParse =\n | { ok: true; plan: AiBuildPlan; repairs: string[] }\n | { ok: false; error: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nclass PlanShapeError extends Error {}\n\n/**\n * An item's arguments as the plan keeps them: the model writes one JSON\n * string, a kept plan stores the object. Values are scalars and lists of\n * text; anything else is a shape the item cannot be built from.\n */\nfunction buildItemArgs(value: unknown, path: string): Record<string, AiBuildItemArg> {\n let parsed: unknown = value\n if (value === undefined || value === null || value === '') return {}\n if (typeof value === 'string') {\n if (value.length > AI_BUILD_PLAN_LIMITS.argsChars) {\n throw new PlanShapeError(`${path} is over ${AI_BUILD_PLAN_LIMITS.argsChars} characters`)\n }\n try {\n parsed = JSON.parse(value)\n } catch {\n throw new PlanShapeError(`${path} is not a JSON object`)\n }\n }\n if (!isRecord(parsed)) throw new PlanShapeError(`${path} is not a JSON object`)\n const args: Record<string, AiBuildItemArg> = {}\n for (const [key, raw] of Object.entries(parsed)) {\n if (raw === null || raw === undefined) continue\n if (typeof raw === 'string' || typeof raw === 'boolean' || (typeof raw === 'number' && Number.isFinite(raw))) {\n args[key] = raw\n } else if (Array.isArray(raw) && raw.every((one) => typeof one === 'string')) {\n args[key] = raw as string[]\n } else {\n throw new PlanShapeError(`${path}.${key} is not text, a number, true or false, or a list of text`)\n }\n }\n return args\n}\n\n/**\n * Read a plan out of a tool call, or name what makes it unreadable.\n *\n * Structure is refused — a missing list, an unknown kind, more screens than\n * a job holds, two creations under one name — because a plan that reads\n * differently from what the model meant is worse than a re-ask. Length is\n * repaired: copy past a ceiling is cut and the cut is listed.\n */\n/**\n * A layout's regions, one word each. A plan sometimes writes the whole list as\n * one entry — \"regions: header, nav, main, footer\" — which names regions the\n * platform builds but reads as one unknown word, and the plan was refused for\n * it (job AvVxbFbzQx). A leading label is dropped and a list is split on its\n * commas, slashes, plus signs and \"and\", so each region is checked on its own;\n * a duplicate keeps its first place.\n */\nexport function aiPlanLayoutRegionWords(fields: readonly string[]): string[] {\n const words: string[] = []\n for (const field of fields) {\n const listed = /^\\s*(?:the\\s+)?(?:layout\\s+)?(?:regions?|areas?)\\s*[:=-]\\s*(.+)$/i.exec(field)?.[1] ?? field\n const parts = /[,/+]|\\band\\b/i.test(listed)\n ? listed.split(/\\s*(?:,|\\/|\\+|\\band\\b)\\s*/i)\n : [listed]\n for (const part of parts) {\n const word = part.trim()\n if (word && !words.some((seen) => seen.toLowerCase() === word.toLowerCase())) words.push(word)\n }\n }\n return words\n}\n\nexport function parseAiBuildPlan(input: unknown): AiBuildPlanParse {\n const repairs: string[] = []\n const text = (\n value: unknown,\n path: string,\n limit: number = AI_BUILD_PLAN_LIMITS.text,\n ): string => {\n if (typeof value !== 'string') throw new PlanShapeError(`${path} is not text`)\n const trimmed = value.trim()\n if (trimmed.length > limit) {\n repairs.push(`${path} was over ${limit} characters; truncated`)\n // On the last word break inside the ceiling, not through a word: a\n // rationale is read by the member confirming the plan, and half a word\n // reads as a broken answer rather than a long one (AGL-3022). A run of\n // `limit` characters with no break in it still has to be cut where the\n // ceiling falls.\n const cut = trimmed.slice(0, limit)\n const brk = cut.search(/\\s\\S*$/)\n return (brk > 0 ? cut.slice(0, brk) : cut).trimEnd()\n }\n return trimmed\n }\n const nullable = (value: unknown, path: string): string | null =>\n value === null || value === undefined || value === '' ? null : text(value, path) || null\n const list = (value: unknown, path: string, limit: number): unknown[] => {\n if (!Array.isArray(value)) throw new PlanShapeError(`${path} is not a list`)\n if (value.length > limit) {\n throw new PlanShapeError(`${path} lists ${value.length}; one plan holds at most ${limit}`)\n }\n return value\n }\n const record = (value: unknown, path: string): Record<string, unknown> => {\n if (!isRecord(value)) throw new PlanShapeError(`${path} is not an object`)\n return value\n }\n const oneOf = <T extends string>(value: unknown, allowed: readonly T[], path: string): T => {\n if (typeof value !== 'string' || !(allowed as readonly string[]).includes(value)) {\n throw new PlanShapeError(`${path} is not one of ${allowed.join(', ')}`)\n }\n return value as T\n }\n // Absent or null is a page that is one page. A base is held to the shape a\n // record page's address takes, so the binding the member saves from it is\n // one the save route admits.\n const planRecord = (value: unknown, path: string): AiBuildPlanRecord | null => {\n if (value === null || value === undefined) return null\n const entry = record(value, path)\n const dataset = text(entry['dataset'], `${path}.dataset`)\n if (!dataset) throw new PlanShapeError(`${path}.dataset is empty`)\n const base = aiPlanRecordBase(text(entry['base'], `${path}.base`))\n if (!base) throw new PlanShapeError(`${path}.base is not a path of lowercase words joined by hyphens`)\n return { dataset, base }\n }\n\n try {\n const root = record(input, 'plan')\n const reuse = list(root['reuse'], 'reuse', AI_BUILD_PLAN_LIMITS.reuse).map((raw, index) => {\n const entry = record(raw, `reuse[${index}]`)\n const id = text(entry['id'], `reuse[${index}].id`)\n if (!id) throw new PlanShapeError(`reuse[${index}].id is empty`)\n return {\n kind: oneOf(entry['kind'], AI_BUILD_PLAN_REUSE_KINDS, `reuse[${index}].kind`),\n id,\n purpose: text(entry['purpose'], `reuse[${index}].purpose`),\n }\n })\n const names = new Set<string>()\n const create = list(root['create'], 'create', AI_BUILD_PLAN_LIMITS.create).map(\n (raw, index) => {\n const entry = record(raw, `create[${index}]`)\n const name = text(entry['name'], `create[${index}].name`)\n if (!name) throw new PlanShapeError(`create[${index}].name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) {\n throw new PlanShapeError(`create[${index}].name \"${name}\" is used twice`)\n }\n names.add(key)\n const kind = oneOf(entry['kind'], AI_BUILD_PLAN_CREATE_KINDS, `create[${index}].kind`)\n const fields = list(entry['fields'] ?? [], `create[${index}].fields`, AI_BUILD_PLAN_LIMITS.fields)\n .map((field, fieldIndex) => text(field, `create[${index}].fields[${fieldIndex}]`))\n .filter(Boolean)\n // Only a form writes its submissions to a dataset (AGL-3616); kept\n // only where it says one, so a plan without one reads as it always did.\n const writesTo = kind === 'form' ? nullable(entry['writesTo'], `create[${index}].writesTo`) : null\n return {\n kind,\n name,\n why: text(entry['why'], `create[${index}].why`),\n duplicateOf: nullable(entry['duplicateOf'], `create[${index}].duplicateOf`),\n fields: kind === 'layout' ? aiPlanLayoutRegionWords(fields) : fields,\n ...(writesTo ? { writesTo } : {}),\n }\n },\n )\n const screens = list(root['screens'], 'screens', AI_BUILD_PLAN_LIMITS.screens).map(\n (raw, index) => {\n const entry = record(raw, `screens[${index}]`)\n if (typeof entry['nav'] !== 'boolean') {\n throw new PlanShapeError(`screens[${index}].nav is not true or false`)\n }\n return {\n title: text(entry['title'], `screens[${index}].title`),\n slug: text(entry['slug'], `screens[${index}].slug`),\n layout: nullable(entry['layout'], `screens[${index}].layout`),\n template: nullable(entry['template'], `screens[${index}].template`),\n duplicateOf: nullable(entry['duplicateOf'], `screens[${index}].duplicateOf`),\n nav: entry['nav'],\n seoTitle: text(entry['seoTitle'], `screens[${index}].seoTitle`),\n seoDescription: text(\n entry['seoDescription'],\n `screens[${index}].seoDescription`,\n AI_BUILD_PLAN_LIMITS.seoDescription,\n ),\n sections: list(\n entry['sections'],\n `screens[${index}].sections`,\n AI_BUILD_PLAN_LIMITS.sections,\n ).map((rawSection, sectionIndex) => {\n const path = `screens[${index}].sections[${sectionIndex}]`\n const section = record(rawSection, path)\n const items = Number(section['items'] ?? 0)\n if (!Number.isInteger(items) || items < 0 || items > AI_BUILD_PLAN_LIMITS.items) {\n throw new PlanShapeError(`${path}.items is not a count`)\n }\n return {\n name: text(section['name'], `${path}.name`),\n uses: list(section['uses'] ?? [], `${path}.uses`, AI_BUILD_PLAN_LIMITS.uses)\n .map((ref, refIndex) => text(ref, `${path}.uses[${refIndex}]`))\n .filter(Boolean),\n items,\n }\n }),\n record: planRecord(entry['record'], `screens[${index}].record`),\n }\n },\n )\n const embeds = list(root['embeds'] ?? [], 'embeds', AI_BUILD_PLAN_LIMITS.embeds).map(\n (raw, index) => {\n const entry = record(raw, `embeds[${index}]`)\n const where = text(entry['where'], `embeds[${index}].where`)\n if (!where) throw new PlanShapeError(`embeds[${index}].where is empty`)\n return {\n host: oneOf(entry['host'], AI_BUILD_PLAN_EMBED_HOSTS, `embeds[${index}].host`),\n where,\n asked: text(entry['asked'], `embeds[${index}].asked`),\n url: nullable(entry['url'], `embeds[${index}].url`),\n }\n },\n )\n const items = list(root['items'] ?? [], 'items', AI_BUILD_PLAN_LIMITS.buildItems).map(\n (raw, index): AiBuildItem => {\n const path = `items[${index}]`\n const entry = record(raw, path)\n const op = text(entry['op'], `${path}.op`)\n if (!op) throw new PlanShapeError(`${path}.op is empty`)\n const name = text(entry['name'], `${path}.name`)\n if (!name) throw new PlanShapeError(`${path}.name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) throw new PlanShapeError(`${path}.name \"${name}\" is used twice`)\n names.add(key)\n return {\n slot: `i${index}`,\n op,\n name,\n why: text(entry['why'] ?? '', `${path}.why`),\n dependsOn: list(entry['dependsOn'] ?? [], `${path}.dependsOn`, AI_BUILD_PLAN_LIMITS.dependsOn)\n .map((ref, refIndex) => text(ref, `${path}.dependsOn[${refIndex}]`))\n .filter(Boolean),\n degrade:\n entry['degrade'] === undefined || entry['degrade'] === null\n ? 'omit'\n : oneOf(entry['degrade'], AI_BUILD_ITEM_DEGRADES, `${path}.degrade`),\n args: buildItemArgs(entry['args'], `${path}.args`),\n ...(typeof entry['id'] === 'string' && entry['id'] ? { id: entry['id'] } : {}),\n ...(typeof entry['credits'] === 'number' && Number.isFinite(entry['credits'])\n ? { credits: Math.max(0, Math.floor(entry['credits'])) }\n : {}),\n }\n },\n )\n // A language that is no code is dropped, never refused (AGL-3750): the\n // site keeps the language it has.\n const language = siteLanguageCode(root['language'])\n if (typeof root['language'] === 'string' && root['language'].trim() && !language) {\n repairs.push(`language \"${root['language'].trim().slice(0, 40)}\" is not a language code; ignored`)\n }\n // Photo words that are not plain English are dropped, never refused (AGL-3760).\n const photoWords = aiPlanPhotoWords(root['photoWords'])\n if (typeof root['photoWords'] === 'string' && root['photoWords'].trim() && !photoWords) {\n repairs.push(`photoWords \"${root['photoWords'].trim().slice(0, 40)}\" is not plain English; ignored`)\n }\n // A plan whose brief asks for no player carries no list, as it did before the list existed;\n // so does a plan with no items.\n return {\n ok: true,\n plan: {\n reuse,\n create,\n screens,\n ...(embeds.length ? { embeds } : {}),\n ...(items.length ? { items } : {}),\n ...(language ? { language } : {}),\n ...(photoWords ? { photoWords } : {}),\n },\n repairs,\n }\n } catch (error) {\n if (error instanceof PlanShapeError) return { ok: false, error: error.message }\n throw error\n }\n}\n\n/**\n * A record template's base as the data plugin's save route stores it\n * (`normalizeRecordPageBase`, which this plugin may not import): one to four\n * lowercase segments of letters, digits and hyphens, with no leading or\n * trailing slash; null for a base that is no such path.\n */\nexport function aiPlanRecordBase(base: string): string | null {\n const segments = base.trim().toLowerCase().split('/').filter(Boolean)\n if (!segments.length || segments.length > 4) return null\n return segments.every(\n (segment) => segment.length <= 60 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(segment),\n )\n ? segments.join('/')\n : null\n}\n\n/** Whether a reference names something the plan creates. */\nexport function isAiPlanNewRef(ref: string | null | undefined): ref is string {\n return typeof ref === 'string' && ref.startsWith(AI_PLAN_NEW_REF_PREFIX)\n}\n\n/** The creation a `new:<name>` reference names, case-insensitively. */\nexport function aiPlanCreateFor(\n plan: AiBuildPlan,\n ref: string,\n): AiBuildPlanCreate | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return plan.create.find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** The operation a build's dataset item is planned as (AGL-3616), owned by the data plugin. */\nexport const AI_PLAN_DATASET_ITEM_OP = 'dataset'\n\n/**\n * The datasets this plan makes that one of its forms writes its submissions\n * to (AGL-3616), by name lowercased: a dataset creation of a site start, or a\n * build's dataset item. Such a dataset starts with no records — its records\n * are the submissions — so a site start designs it from its planned fields\n * with no model, and no page lists it.\n */\nexport function aiPlanFormDatasetNames(plan: Pick<AiBuildPlan, 'create'> & Partial<Pick<AiBuildPlan, 'items'>>): Set<string> {\n const names = new Set<string>()\n for (const entry of plan.create) {\n if (entry.kind !== 'form' || !isAiPlanNewRef(entry.writesTo)) continue\n const name = entry.writesTo.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n const made =\n plan.create.some((one) => one.kind === 'dataset' && one.name.trim().toLowerCase() === name) ||\n (plan.items ?? []).some((one) => one.op === AI_PLAN_DATASET_ITEM_OP && one.name.trim().toLowerCase() === name)\n if (made) names.add(name)\n }\n return names\n}\n\n/** The item a `new:<name>` reference names, case-insensitively (AGL-3616). */\nexport function aiPlanItemFor(\n plan: Pick<AiBuildPlan, 'items'>,\n ref: string,\n): AiBuildItem | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return (plan.items ?? []).find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** A slug as two spellings of one path compare: lowercase, one leading slash, no trailing one. */\nexport function aiPlanSlugKey(slug: string): string {\n const path = slug.trim().toLowerCase().replace(/^\\/*/, '/').replace(/\\/+$/, '')\n return path || '/'\n}\n\n/**\n * The players the plan lists for one output (AGL-3433): a screen by its slug,\n * or a creation by its name. A build admits these and no other.\n */\nexport function aiPlanEmbedsFor(\n plan: Pick<AiBuildPlan, 'embeds'> | null | undefined,\n target: { slug: string } | { create: string },\n): AiBuildPlanEmbed[] {\n const key =\n 'slug' in target\n ? aiPlanSlugKey(target.slug)\n : `${AI_PLAN_NEW_REF_PREFIX}${target.create.trim().toLowerCase()}`\n return (plan?.embeds ?? []).filter((embed) =>\n 'slug' in target\n ? !isAiPlanNewRef(embed.where) && aiPlanSlugKey(embed.where) === key\n : embed.where.trim().toLowerCase() === key,\n )\n}\n\n/** The host a video link plays from, or null for a link neither host serves. */\nexport function aiEmbedHostOf(url: string): AiBuildPlanEmbedHost | null {\n try {\n const parsed = new URL(url.trim())\n if (parsed.protocol !== 'https:') return null\n const host = parsed.hostname.replace(/^(?:www|m)\\./, '')\n if (host === 'youtu.be' || host === 'youtube.com' || host === 'youtube-nocookie.com') {\n return 'youtube'\n }\n if (host === 'vimeo.com' || host === 'player.vimeo.com') return 'vimeo'\n } catch {\n /* not a link */\n }\n return null\n}\n\n/**\n * One video however its link is spelled — `youtu.be/<id>`, a watch link, an\n * embed or a Shorts link — as `<host>:<id>`, the way the Video embed element\n * reads a link; null for a link it cannot play.\n */\nexport function aiEmbedVideoKey(url: string): string | null {\n const host = aiEmbedHostOf(url)\n if (!host) return null\n const parsed = new URL(url.trim())\n const parts = parsed.pathname.split('/').filter(Boolean)\n const id =\n host === 'vimeo'\n ? parts.find((part) => /^\\d{6,}$/.test(part))\n : parsed.hostname.endsWith('youtu.be')\n ? parts[0]\n : (parsed.searchParams.get('v') ??\n (parts[0] === 'embed' || parts[0] === 'shorts' ? parts[1] : undefined))\n return id && /^[\\w-]{6,20}$/.test(id) ? `${host}:${id}` : null\n}\n\n/**\n * A `new:<name>` reference that names no entry of the plan's create list: a\n * screen's own layout or template, or what one of its sections places, with\n * that section's index.\n */\nexport type AiPlanUndeclaredRef = {\n /** The name the reference gives the creation, as written after `new:`. */\n name: string\n /** Where the plan writes it: `screens[0].template`, `screens[0].sections[4].uses[0]`. */\n path: string\n /** The screen that writes it, by index. */\n screenIndex: number\n} & ({ field: 'layout' | 'template'; sectionIndex: null } | { field: 'uses'; sectionIndex: number })\n\n/**\n * Every `new:<name>` reference the plan's create list does not carry, in plan\n * order: each screen's layout, then its template, then what its sections\n * place. A plan that places a creation it never declares has nothing to build\n * it from, so the plan rules refuse each (AGL-3040), and the page and\n * scaffold doors still name one in a plan confirmed before they did.\n */\nexport function aiPlanUndeclaredRefs(plan: AiBuildPlan): AiPlanUndeclaredRef[] {\n const found: AiPlanUndeclaredRef[] = []\n const undeclared = (ref: string | null): ref is string =>\n isAiPlanNewRef(ref) && !aiPlanCreateFor(plan, ref) && !aiPlanItemFor(plan, ref)\n const nameOf = (ref: string) => ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim()\n plan.screens.forEach((screen, screenIndex) => {\n for (const field of ['layout', 'template'] as const) {\n const ref = screen[field]\n if (!undeclared(ref)) continue\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].${field}`,\n screenIndex,\n field,\n sectionIndex: null,\n })\n }\n screen.sections.forEach((section, sectionIndex) => {\n section.uses.forEach((ref, useIndex) => {\n if (!undeclared(ref)) return\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].sections[${sectionIndex}].uses[${useIndex}]`,\n screenIndex,\n field: 'uses',\n sectionIndex,\n })\n })\n })\n })\n return found\n}\n"],"names":["siteLanguageCode","AI_PLAN_NEW_REF_PREFIX","AI_BUILD_PLAN_REUSE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","component","noun","where","form","layout","template","dataset","email","AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_EMBED_HOSTS","AI_BUILD_PLAN_EMBED_HOST_NAMES","youtube","vimeo","AI_BUILD_ITEM_DEGRADES","AI_BUILD_PLAN_LIMITS","reuse","create","screens","sections","uses","fields","embeds","text","seoDescription","items","buildItems","dependsOn","argsChars","string","description","type","nullableString","anyOf","strings","atMost","limit","AI_BUILD_PLAN_TOOL","name","strict","inputSchema","additionalProperties","required","properties","kind","enum","id","purpose","why","duplicateOf","title","slug","nav","seoTitle","AI_PLAN_ASKS_FOR_VIDEO","EMBEDS_SCHEMA","host","asked","url","AI_BUILD_PLAN_EMBEDS_TOOL","RECORD_SCHEMA","base","WRITES_TO_SCHEMA","withRecords","tool","writesTo","record","AI_BUILD_PLAN_RECORDS_TOOL","AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL","ITEMS_SCHEMA","op","degrade","args","withBuildItems","LANGUAGE_SCHEMA","AI_PLAN_PHOTO_WORDS_MAX","aiPlanPhotoWords","value","trim","toLowerCase","test","words","split","map","word","replace","filter","length","slice","join","PHOTO_WORDS_SCHEMA","withPlanLanguage","language","photoWords","aiBuildPlanToolFor","brief","options","video","records","built","isRecord","Array","isArray","PlanShapeError","Error","buildItemArgs","path","parsed","undefined","JSON","parse","key","raw","Object","entries","Number","isFinite","every","one","aiPlanLayoutRegionWords","field","listed","exec","parts","part","some","seen","push","parseAiBuildPlan","input","repairs","trimmed","cut","brk","search","trimEnd","nullable","list","oneOf","allowed","includes","planRecord","entry","aiPlanRecordBase","root","index","names","Set","has","add","fieldIndex","Boolean","rawSection","sectionIndex","section","isInteger","ref","refIndex","slot","credits","Math","max","floor","ok","plan","error","message","segments","segment","isAiPlanNewRef","startsWith","aiPlanCreateFor","find","AI_PLAN_DATASET_ITEM_OP","aiPlanFormDatasetNames","made","aiPlanItemFor","aiPlanSlugKey","aiPlanEmbedsFor","target","embed","aiEmbedHostOf","URL","protocol","hostname","aiEmbedVideoKey","pathname","endsWith","searchParams","get","aiPlanUndeclaredRefs","found","undeclared","nameOf","forEach","screen","screenIndex","useIndex"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-plan.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 { siteLanguageCode } from '@aglyn/aglyn/app-utils/seo-locale'\nimport type { AiTool } from '../providers/contract'\nimport type { AiBuildStorePages } from './ai-build-store-pages'\n\n/**\n * The plan a generation job builds from (AGL-2935): what the job's plan step\n * (`jobs/ai-job-plan-step.ts`) returns, before a single node is generated.\n *\n * This module is the plan's MODEL — its shape, its tool schema and its\n * reader — and imports nothing of the plugin's at runtime (its one runtime\n * import is core's `seo-locale`, which imports nothing), so the doctrine\n * validators, the plan step, the job document and the proposal can all read\n * it without importing each other.\n *\n * A plan is REFERENCES, never documents (AGL-2937). It names what the site\n * already has by inventory id, what the job will create by a short name, and\n * which screens it builds from which of those — so the doctrine can be held\n * against the plan while fixing it costs a sentence, not a regenerated page,\n * and the customer reads what the job will do before it spends a credit on\n * doing it.\n *\n * `new:<name>` is how an entry refers to something the plan itself creates;\n * any other reference is an id from the site inventory.\n */\n\nexport const AI_PLAN_NEW_REF_PREFIX = 'new:'\n\n/** Records a plan may reuse, by the inventory list they come from. */\nexport type AiBuildPlanReuseKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'dataset'\n | 'collection'\n | 'screen'\n | 'theme'\n\nexport const AI_BUILD_PLAN_REUSE_KINDS: readonly AiBuildPlanReuseKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'dataset',\n 'collection',\n 'screen',\n 'theme',\n]\n\n/** What a plan may create. A theme change is a draft through the theme door, never a literal. */\nexport type AiBuildPlanCreateKind =\n | 'component'\n | 'layout'\n | 'template'\n | 'form'\n | 'theme-change'\n | 'dataset'\n | 'email'\n\n/**\n * What each creation is called in a sentence, and where a member makes one.\n *\n * Total over the union deliberately: a refusal that cannot name a creation is\n * a refusal a member cannot act on, so widening `AiBuildPlanCreateKind`\n * without saying where the new thing is made does not compile.\n */\nexport const AI_BUILD_PLAN_CREATION_NOUNS: Record<\n AiBuildPlanCreateKind,\n { noun: string; where: string }\n> = {\n component: { noun: 'component', where: 'on the Components page' },\n form: { noun: 'form', where: 'on the Forms page' },\n layout: { noun: 'layout', where: 'on the Layouts page' },\n template: { noun: 'template', where: 'in the Templates library' },\n 'theme-change': { noun: 'theme change', where: 'in the Theme section' },\n dataset: { noun: 'dataset', where: 'on the Datasets page' },\n email: { noun: 'email design', where: 'in Emails → Templates' },\n}\n\nexport const AI_BUILD_PLAN_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'component',\n 'layout',\n 'template',\n 'form',\n 'theme-change',\n 'dataset',\n 'email',\n]\n\nexport interface AiBuildPlanReuse {\n kind: AiBuildPlanReuseKind\n /** The inventory id. */\n id: string\n /** What the plan uses it for, in a few words. */\n purpose: string\n}\n\nexport interface AiBuildPlanCreate {\n kind: AiBuildPlanCreateKind\n /** Unique within the plan; referenced as `new:<name>`. */\n name: string\n /** Why nothing the site has will do — shown in the proposal. */\n why: string\n /** The inventory id this starts from as a duplicate, or null. */\n duplicateOf: string | null\n /**\n * A component: its props as `name:type`, an icon as `name:icon`\n * (AGL-3054). A layout: the regions it has, from the closed vocabulary\n * `AI_LAYOUT_REGIONS` holds (AGL-3024) — a layout has no props, and a\n * region named here is what the built layout is held to. A template: the\n * binding tokens its page shows, from the subject's own closed catalog\n * (`AI_TEMPLATE_SUBJECT_DEFINITIONS[subject].tokens`, AGL-3143) — the\n * template's analogue of a layout's regions, and what the built template is\n * held to. A form or a dataset: its field names. A theme change: the\n * palette paths it adds or changes.\n */\n fields: string[]\n /**\n * A form only: the dataset each of its submissions is also written to as a\n * record (AGL-3616, Zach 2026-10-10: a volunteer sign-up writing to a\n * Volunteers dataset) — `new:<name>` of a dataset this plan creates, or a\n * dataset id from the inventory. Said by the plan, never inferred from a\n * section's `uses`. Absent or null on every other creation, and on a form\n * whose submissions only reach the Inbox.\n */\n writesTo?: string | null\n /**\n * The id the draft this creation becomes is written under, where a unit of\n * the job builds it (AGL-3079). Minted when the job keeps the plan, never\n * part of the model's answer.\n */\n id?: string\n}\n\nexport interface AiBuildPlanSection {\n /** A few words: \"hero\", \"services grid\", \"contact form\". */\n name: string\n /** What the section places: inventory ids or `new:<name>` references. */\n uses: string[]\n /** How many repeated items it shows (cards, rows, tiles); 0 when none. */\n items: number\n}\n\n/**\n * A screen that is one dataset's record template (AGL-3475): the data plugin\n * serves it once per record at `/{base}/{slug}`, its copy bound with\n * `{{item.<field>}}`. The plan names the binding and the build drafts the\n * page; the member saves the binding in Page Properties → Record pages, since\n * a binding is a route and a job writes drafts only (rule 13).\n */\nexport interface AiBuildPlanRecord {\n /** The dataset: an inventory id or `new:<name>`. */\n dataset: string\n /** The path its record pages live under, one or more segments: `services`, `services/residential`. */\n base: string\n}\n\nexport interface AiBuildPlanScreen {\n title: string\n slug: string\n /** The layout it renders inside. */\n layout: string | null\n /** The template it applies, or null for a one-off page. */\n template: string | null\n /** The inventory screen it starts from as a duplicate, or null. */\n duplicateOf: string | null\n /** Whether the site navigation gains an entry for it. */\n nav: boolean\n seoTitle: string\n seoDescription: string\n sections: AiBuildPlanSection[]\n /**\n * The dataset this page is the record template of, or null for a page that\n * is one page. A plan kept before record templates carries none, which\n * reads as null.\n */\n record: AiBuildPlanRecord | null\n /**\n * The id the draft screen is written under, where a unit of the job builds\n * it (AGL-3079). Minted when the job keeps the plan, never part of the\n * model's answer.\n */\n id?: string\n}\n\n/** The third-party players a plan may embed: the hosts the Video embed element plays. */\nexport type AiBuildPlanEmbedHost = 'youtube' | 'vimeo'\n\nexport const AI_BUILD_PLAN_EMBED_HOSTS: readonly AiBuildPlanEmbedHost[] = ['youtube', 'vimeo']\n\n/** What each host is called in a sentence. */\nexport const AI_BUILD_PLAN_EMBED_HOST_NAMES: Record<AiBuildPlanEmbedHost, string> = {\n youtube: 'YouTube',\n vimeo: 'Vimeo',\n}\n\n/**\n * A third-party player the brief asks for (rule 16, AGL-3433). It loads its\n * host's code on the page that carries it, so it is planned, where the member\n * reads that cost before confirming, and a build admits only what the\n * confirmed plan lists.\n */\nexport interface AiBuildPlanEmbed {\n host: AiBuildPlanEmbedHost\n /** Where it plays: a screen of the plan by its slug, or `new:<name>` for a component the plan creates. */\n where: string\n /** The brief's own words asking for it, which the plan step finds in the brief. */\n asked: string\n /** The video's link as the brief gives it, or null for the site owner to paste. */\n url: string | null\n}\n\n/** How the items that depend on a failed item behave (AGL-3616). */\nexport type AiBuildItemDegrade = 'omit' | 'fallback'\n\nexport const AI_BUILD_ITEM_DEGRADES: readonly AiBuildItemDegrade[] = ['omit', 'fallback']\n\n/** One argument value of an item: scalars and lists of text only. */\nexport type AiBuildItemArg = string | number | boolean | string[]\n\n/**\n * One thing a `build` plan makes that its creations and screens do not\n * (AGL-3616): an operation another plugin — or this one — registered as an\n * AI capability, with the arguments its schema declares. `reuse`, `create`\n * and `screens` keep their meaning; an item is everything else.\n */\nexport interface AiBuildItem {\n /** Stable within the plan: `i0`, `i1`… in plan order. */\n slot: string\n /** The capability's operation. */\n op: string\n /** Unique within the plan, creations included; referenced as `new:<name>`. */\n name: string\n /** Why the brief needs it, in one sentence. */\n why: string\n /** What it is built after: `new:<name>` of a creation or another item. */\n dependsOn: string[]\n /** How the items that depend on it behave when it fails. */\n degrade: AiBuildItemDegrade\n /** The operation's arguments, in the shape its capability declares. */\n args: Record<string, AiBuildItemArg>\n /** The id its draft is written under, minted when the job keeps the plan; never the model's. */\n id?: string\n /** The most it can cost in credits, from its capability, recorded when the plan is kept. */\n credits?: number\n}\n\nexport interface AiBuildPlan {\n reuse: AiBuildPlanReuse[]\n create: AiBuildPlanCreate[]\n screens: AiBuildPlanScreen[]\n /**\n * The third-party players the brief asks for. Absent is none: a plan kept\n * before the list existed, and every plan whose brief asks for no player.\n */\n embeds?: AiBuildPlanEmbed[]\n /**\n * What a `build` plan makes beyond its creations and pages (AGL-3616).\n * Absent is none: every other kind's plan.\n */\n items?: AiBuildItem[]\n /**\n * The store pages a `build` adds where its plan turns the site into a store\n * (AGL-3676): worked out by code as the plan is kept, never by the model,\n * and built by a unit of their own after every planned one. Absent is none.\n */\n storePages?: AiBuildStorePages\n /**\n * The language a SITE plan's brief and copy are written in (AGL-3750), as\n * the Languages card stores a code (`siteLanguageCode`). Absent is the site's\n * own language: every other kind's plan, a plan that said null, and one\n * whose code was not a language code, which is dropped rather than refused.\n */\n language?: string\n /**\n * What a SITE plan's photos show, in English whatever language the brief\n * is in (AGL-3760): two to four words a stock photo library is searched by,\n * the business's subject first — \"bakery bread pastries\" for \"una panadería\n * … con pan dulce, conchas\". The libraries are tagged in English, so a site\n * in another language searches by these rather than its own words — unless\n * its business type names a business the curated table knows in English\n * (`aiStockBusinessNoun`), which leads — and its plan is asked for them\n * again once when it gave none (AGL-3779). Absent on every other kind's\n * plan, on one that said null, on one whose words were not plain English\n * letters, and on a plan in another language whose re-ask still gave none,\n * whose photos then fall back to its kind of site.\n */\n photoWords?: string\n}\n\n/**\n * How much one plan may hold. A job builds what one proposal can describe\n * and one person can read before confirming; a brief asking for more is\n * several jobs.\n */\nexport const AI_BUILD_PLAN_LIMITS = {\n reuse: 40,\n create: 20,\n screens: 12,\n sections: 16,\n uses: 8,\n fields: 30,\n embeds: 4,\n text: 200,\n seoDescription: 320,\n items: 500,\n /** A build plan's items (AGL-3616). */\n buildItems: 16,\n /** What one item depends on. */\n dependsOn: 8,\n /** The arguments one item carries, as JSON. */\n argsChars: 2_000,\n} as const\n\nconst string = (description: string) => ({ type: 'string', description })\nconst nullableString = (description: string) => ({\n anyOf: [{ type: 'string' }, { type: 'null' }],\n description,\n})\nconst strings = (description: string) => ({\n type: 'array',\n items: { type: 'string' },\n description,\n})\n\n/**\n * The ceiling `parseAiBuildPlan` cuts a field's text at, in words (AGL-3022).\n * The reader repairs an overlong value by cutting it, and a model never told\n * where the cut falls writes a rationale that stops mid-sentence there.\n */\nconst atMost = (limit: number = AI_BUILD_PLAN_LIMITS.text) => `at most ${limit} characters`\n\n/**\n * The plan as a strict tool's input schema. Only the keywords strict\n * structured output accepts: every object closes its properties and lists\n * every one as required, an optional value is a `null` branch, and no\n * length or count bound appears as a keyword — those are enforced by\n * `parseAiBuildPlan`, which every answer passes through anyway.\n *\n * A text ceiling is stated in words instead, on each field the model writes.\n * A reference is copied from the inventory or from a creation's name, so its\n * length is not the model's to choose. A search title and description are\n * held by rule 10 to ceilings far inside the reader's, which the doctrine\n * states, so naming the reader's here would contradict the rule.\n */\nexport const AI_BUILD_PLAN_TOOL: AiTool = {\n name: 'submit_build_plan',\n description:\n 'Submit the build plan: what the site already has that this job reuses, what it creates and why, and the pages it builds from them.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['reuse', 'create', 'screens'],\n properties: {\n reuse: {\n type: 'array',\n description: 'Records the site already has that this plan uses, by inventory id.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'id', 'purpose'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_REUSE_KINDS] },\n id: string('The id from the site inventory.'),\n purpose: string(`What the plan uses it for, in a few words, ${atMost()}.`),\n },\n },\n },\n create: {\n type: 'array',\n description:\n 'What the plan creates. Creating is the exception: each entry says why nothing listed will do.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'name', 'why', 'duplicateOf', 'fields'],\n properties: {\n kind: { type: 'string', enum: [...AI_BUILD_PLAN_CREATE_KINDS] },\n name: string(\n `Unique within the plan, ${atMost()}; other entries refer to it as new:<name>.`,\n ),\n why: string(`Why nothing the site already has will do, in one sentence of ${atMost()}.`),\n duplicateOf: nullableString(\n 'The inventory id this starts from as a duplicate, or null.',\n ),\n fields: strings(\n `A component: its props as name:type, an icon as name:icon. A layout: the regions it has, each one of header, nav, sidebar, main, footer — list every region the finished layout has, not only the ones it gains. A form or dataset: its field names. A theme change: the palette paths it adds or changes. Each ${atMost()}.`,\n ),\n },\n },\n },\n screens: {\n type: 'array',\n description: 'The pages the job builds, each a new draft.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: [\n 'title',\n 'slug',\n 'layout',\n 'template',\n 'duplicateOf',\n 'nav',\n 'seoTitle',\n 'seoDescription',\n 'sections',\n ],\n properties: {\n title: string(`The page name, ${atMost()}.`),\n slug: string(\n `The path, ${atMost()}: lowercase words joined by hyphens, e.g. /services/roof-repair.`,\n ),\n layout: nullableString(\n 'The layout it renders inside: an inventory id or new:<name>.',\n ),\n template: nullableString(\n 'The template it applies (inventory id or new:<name>), or null for a one-off page.',\n ),\n duplicateOf: nullableString(\n 'The inventory page it starts from as a duplicate, or null.',\n ),\n nav: {\n type: 'boolean',\n description: 'Whether the site navigation gains an entry for it.',\n },\n seoTitle: string('The search-result title.'),\n seoDescription: string('The search-result description.'),\n sections: {\n type: 'array',\n description: 'The sections, top to bottom.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['name', 'uses', 'items'],\n properties: {\n name: string(`A few words, ${atMost()}: hero, services grid, contact form.`),\n uses: strings('What the section places: inventory ids or new:<name>.'),\n items: {\n type: 'integer',\n description: 'How many repeated items it shows; 0 when none.',\n },\n },\n },\n },\n },\n },\n },\n },\n },\n}\n\n/** Words that ask for a video, or a link to one. */\nexport const AI_PLAN_ASKS_FOR_VIDEO = /\\b(?:videos?|films?|clips?|youtube|vimeo|player)\\b|youtu\\.be/i\n\nconst EMBEDS_SCHEMA = {\n type: 'array',\n description:\n 'A YouTube or Vimeo player the brief asks for, on a screen or a created component. Empty unless the brief asks; a library film is a Video.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['host', 'where', 'asked', 'url'],\n properties: {\n host: { type: 'string', enum: [...AI_BUILD_PLAN_EMBED_HOSTS] },\n where: string('A screen slug, or new:<name> of a component.'),\n asked: string('The words of the brief that ask for it, copied exactly.'),\n url: nullableString('The video link the brief gives, or null.'),\n },\n },\n}\n\n/**\n * The plan tool with its list of third-party players, offered only to a brief\n * that asks for a video (AGL-3433). Every other plan's request is the plain\n * tool, byte for byte, so the list costs nothing on the plans that cannot use\n * it, and a model answering a brief that names no video has nowhere to plan a\n * player it was never asked for.\n */\nexport const AI_BUILD_PLAN_EMBEDS_TOOL: AiTool = {\n ...AI_BUILD_PLAN_TOOL,\n inputSchema: {\n ...AI_BUILD_PLAN_TOOL.inputSchema,\n required: [...(AI_BUILD_PLAN_TOOL.inputSchema['required'] as string[]), 'embeds'],\n properties: {\n ...(AI_BUILD_PLAN_TOOL.inputSchema['properties'] as Record<string, unknown>),\n embeds: EMBEDS_SCHEMA,\n },\n },\n}\n\nconst RECORD_SCHEMA = {\n anyOf: [\n {\n type: 'object',\n additionalProperties: false,\n required: ['dataset', 'base'],\n properties: {\n dataset: string('The dataset: an inventory id or new:<name>.'),\n base: string('The path its record pages live under, e.g. services for /services/roofing.'),\n },\n },\n { type: 'null' },\n ],\n description:\n \"When this page is one dataset's record template, serving a page per record at /<base>/<record address> with its copy bound as {{item.<field>}}: the dataset and the base. Otherwise null.\",\n}\n\nconst WRITES_TO_SCHEMA = nullableString(\n 'A form only, and only where each submission should also be kept as a record (a volunteer sign-up, an RSVP, an inquiry): the dataset it writes to, as new:<name> of a dataset this plan creates or a dataset id from the inventory. Otherwise null.',\n)\n\n/**\n * A plan tool whose screens each name the dataset they are the record\n * template of, or null, and whose creations each name the dataset a form\n * writes its submissions to, or null (AGL-3616).\n */\nfunction withRecords(tool: AiTool): AiTool {\n const properties = tool.inputSchema['properties'] as Record<string, unknown>\n const screens = properties['screens'] as { items: Record<string, unknown> }\n const create = properties['create'] as { items: Record<string, unknown> }\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n properties: {\n ...properties,\n create: {\n ...create,\n items: {\n ...create.items,\n required: [...(create.items['required'] as string[]), 'writesTo'],\n properties: {\n ...(create.items['properties'] as Record<string, unknown>),\n writesTo: WRITES_TO_SCHEMA,\n },\n },\n },\n screens: {\n ...screens,\n items: {\n ...screens.items,\n required: [...(screens.items['required'] as string[]), 'record'],\n properties: {\n ...(screens.items['properties'] as Record<string, unknown>),\n record: RECORD_SCHEMA,\n },\n },\n },\n },\n },\n }\n}\n\n/**\n * The plan tools with a record template on every screen (AGL-3475) and the\n * dataset a form writes to on every creation (AGL-3616), offered only where\n * the job may bind a dataset: a site with a dataset, or a job that may create\n * one. Record pages are a Starter feature, and every other plan's\n * request is the plain tool, byte for byte, so the field costs a Free taste\n * nothing and a model planning a site with no dataset has none to bind.\n */\nexport const AI_BUILD_PLAN_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_TOOL)\nexport const AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL: AiTool = withRecords(AI_BUILD_PLAN_EMBEDS_TOOL)\n\nconst ITEMS_SCHEMA = {\n type: 'array',\n description:\n 'What the plan makes beyond its creations and pages, each one operation from the list in the request. Empty when the brief asks for nothing else.',\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['op', 'name', 'why', 'dependsOn', 'degrade', 'args'],\n properties: {\n op: string('The operation, exactly as the request lists it.'),\n name: string(`Unique within the plan, creations included, ${atMost()}; other entries refer to it as new:<name>.`),\n why: string(`Why the brief needs it, in one sentence of ${atMost()}.`),\n dependsOn: strings('What it is built after: new:<name> of a creation or another item. Empty when nothing.'),\n degrade: {\n type: 'string',\n enum: [...AI_BUILD_ITEM_DEGRADES],\n description:\n 'omit: what depends on it is built without it if it fails. fallback: what depends on it uses something the site already has instead.',\n },\n args: string('Its arguments as one JSON object, with exactly the fields the request lists for the operation.'),\n },\n },\n}\n\n/**\n * Any plan tool with a build's items (AGL-3616), offered only to a `build`\n * job. The items' arguments travel as one JSON string whose fields the\n * request lists per operation, so the tool stays byte for byte the same\n * whichever plugins registered operations, and the prompt cache keeps it.\n */\nexport function withBuildItems(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'items'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n items: ITEMS_SCHEMA,\n },\n },\n }\n}\n\nconst LANGUAGE_SCHEMA = nullableString(\n \"The language the brief is written in, which the site's copy is written in too: a code such as en, es, fr or pt-BR. Null when unsure.\",\n)\n\n/** The most words a plan's photo words keep. */\nexport const AI_PLAN_PHOTO_WORDS_MAX = 4\n\n/**\n * A plan's photo words as a stock photo search takes them (AGL-3760), or\n * `null`: lowercase English words, at most {@link AI_PLAN_PHOTO_WORDS_MAX}.\n * Words with letters outside English's — \"panadería\" — are no English at all,\n * so the plan is kept without them.\n */\nexport function aiPlanPhotoWords(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const text = value.trim().toLowerCase()\n if (!text || !/^[a-z][a-z\\s,'&-]*$/.test(text)) return null\n const words = text\n .split(/[\\s,&]+/)\n .map((word) => word.replace(/^[-']+|[-']+$/g, ''))\n .filter((word) => word.length > 1)\n return words.length ? words.slice(0, AI_PLAN_PHOTO_WORDS_MAX).join(' ') : null\n}\n\n/**\n * Required whenever the plan's language is not English (AGL-3779), the field\n * beside it. Asked \"for a brief not in English\", two Spanish bakeries' plans\n * said `es` and gave none (jobs ih6kVvCGP1, iGmqrEgyW4): a guided start's\n * brief is English sentences around the person's own words. A strict schema\n * has no conditional keyword, so the condition is said here and held by the\n * plan step (`aiSitePhotoWordsCheck`), which re-asks once. The kind of\n * business comes first because the first word is the subject every photo is\n * searched by (`aiStockPhotoWordsSubject`): asked for the most specific word\n * first, a florist's live plan wrote \"wedding bouquets event flowers\".\n */\nconst PHOTO_WORDS_SCHEMA = nullableString(\n 'Required when language is not en: 2-4 English words, the kind of business first, then what it sells, e.g. florist wedding bouquets; never a place or the business name. Null when language is en.',\n)\n\n/**\n * Any plan tool with the site's language (AGL-3750) and its photo words in\n * English (AGL-3760), offered only to a `site` job, whose guided start sets\n * the site's language from one and searches its photos by the other. One\n * schema for every site plan, so the tool is the same bytes across briefs\n * and the prompt cache keeps it; every other kind's tool is unchanged.\n */\nexport function withPlanLanguage(tool: AiTool): AiTool {\n return {\n ...tool,\n inputSchema: {\n ...tool.inputSchema,\n required: [...(tool.inputSchema['required'] as string[]), 'language', 'photoWords'],\n properties: {\n ...(tool.inputSchema['properties'] as Record<string, unknown>),\n language: LANGUAGE_SCHEMA,\n photoWords: PHOTO_WORDS_SCHEMA,\n },\n },\n }\n}\n\n/** The plan tool a brief is answered with, on a job that may or may not bind a dataset. */\nexport function aiBuildPlanToolFor(\n brief: string,\n options: { records?: boolean; items?: boolean; language?: boolean } = {},\n): AiTool {\n const video = AI_PLAN_ASKS_FOR_VIDEO.test(brief)\n const tool = options.records\n ? video\n ? AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL\n : AI_BUILD_PLAN_RECORDS_TOOL\n : video\n ? AI_BUILD_PLAN_EMBEDS_TOOL\n : AI_BUILD_PLAN_TOOL\n const built = options.items ? withBuildItems(tool) : tool\n return options.language ? withPlanLanguage(built) : built\n}\n\nexport type AiBuildPlanParse =\n | { ok: true; plan: AiBuildPlan; repairs: string[] }\n | { ok: false; error: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nclass PlanShapeError extends Error {}\n\n/**\n * An item's arguments as the plan keeps them: the model writes one JSON\n * string, a kept plan stores the object. Values are scalars and lists of\n * text; anything else is a shape the item cannot be built from.\n */\nfunction buildItemArgs(value: unknown, path: string): Record<string, AiBuildItemArg> {\n let parsed: unknown = value\n if (value === undefined || value === null || value === '') return {}\n if (typeof value === 'string') {\n if (value.length > AI_BUILD_PLAN_LIMITS.argsChars) {\n throw new PlanShapeError(`${path} is over ${AI_BUILD_PLAN_LIMITS.argsChars} characters`)\n }\n try {\n parsed = JSON.parse(value)\n } catch {\n throw new PlanShapeError(`${path} is not a JSON object`)\n }\n }\n if (!isRecord(parsed)) throw new PlanShapeError(`${path} is not a JSON object`)\n const args: Record<string, AiBuildItemArg> = {}\n for (const [key, raw] of Object.entries(parsed)) {\n if (raw === null || raw === undefined) continue\n if (typeof raw === 'string' || typeof raw === 'boolean' || (typeof raw === 'number' && Number.isFinite(raw))) {\n args[key] = raw\n } else if (Array.isArray(raw) && raw.every((one) => typeof one === 'string')) {\n args[key] = raw as string[]\n } else {\n throw new PlanShapeError(`${path}.${key} is not text, a number, true or false, or a list of text`)\n }\n }\n return args\n}\n\n/**\n * Read a plan out of a tool call, or name what makes it unreadable.\n *\n * Structure is refused — a missing list, an unknown kind, more screens than\n * a job holds, two creations under one name — because a plan that reads\n * differently from what the model meant is worse than a re-ask. Length is\n * repaired: copy past a ceiling is cut and the cut is listed.\n */\n/**\n * A layout's regions, one word each. A plan sometimes writes the whole list as\n * one entry — \"regions: header, nav, main, footer\" — which names regions the\n * platform builds but reads as one unknown word, and the plan was refused for\n * it (job AvVxbFbzQx). A leading label is dropped and a list is split on its\n * commas, slashes, plus signs and \"and\", so each region is checked on its own;\n * a duplicate keeps its first place.\n */\nexport function aiPlanLayoutRegionWords(fields: readonly string[]): string[] {\n const words: string[] = []\n for (const field of fields) {\n const listed = /^\\s*(?:the\\s+)?(?:layout\\s+)?(?:regions?|areas?)\\s*[:=-]\\s*(.+)$/i.exec(field)?.[1] ?? field\n const parts = /[,/+]|\\band\\b/i.test(listed)\n ? listed.split(/\\s*(?:,|\\/|\\+|\\band\\b)\\s*/i)\n : [listed]\n for (const part of parts) {\n const word = part.trim()\n if (word && !words.some((seen) => seen.toLowerCase() === word.toLowerCase())) words.push(word)\n }\n }\n return words\n}\n\nexport function parseAiBuildPlan(input: unknown): AiBuildPlanParse {\n const repairs: string[] = []\n const text = (\n value: unknown,\n path: string,\n limit: number = AI_BUILD_PLAN_LIMITS.text,\n ): string => {\n if (typeof value !== 'string') throw new PlanShapeError(`${path} is not text`)\n const trimmed = value.trim()\n if (trimmed.length > limit) {\n repairs.push(`${path} was over ${limit} characters; truncated`)\n // On the last word break inside the ceiling, not through a word: a\n // rationale is read by the member confirming the plan, and half a word\n // reads as a broken answer rather than a long one (AGL-3022). A run of\n // `limit` characters with no break in it still has to be cut where the\n // ceiling falls.\n const cut = trimmed.slice(0, limit)\n const brk = cut.search(/\\s\\S*$/)\n return (brk > 0 ? cut.slice(0, brk) : cut).trimEnd()\n }\n return trimmed\n }\n const nullable = (value: unknown, path: string): string | null =>\n value === null || value === undefined || value === '' ? null : text(value, path) || null\n const list = (value: unknown, path: string, limit: number): unknown[] => {\n if (!Array.isArray(value)) throw new PlanShapeError(`${path} is not a list`)\n if (value.length > limit) {\n throw new PlanShapeError(`${path} lists ${value.length}; one plan holds at most ${limit}`)\n }\n return value\n }\n const record = (value: unknown, path: string): Record<string, unknown> => {\n if (!isRecord(value)) throw new PlanShapeError(`${path} is not an object`)\n return value\n }\n const oneOf = <T extends string>(value: unknown, allowed: readonly T[], path: string): T => {\n if (typeof value !== 'string' || !(allowed as readonly string[]).includes(value)) {\n throw new PlanShapeError(`${path} is not one of ${allowed.join(', ')}`)\n }\n return value as T\n }\n // Absent or null is a page that is one page. A base is held to the shape a\n // record page's address takes, so the binding the member saves from it is\n // one the save route admits.\n const planRecord = (value: unknown, path: string): AiBuildPlanRecord | null => {\n if (value === null || value === undefined) return null\n const entry = record(value, path)\n const dataset = text(entry['dataset'], `${path}.dataset`)\n if (!dataset) throw new PlanShapeError(`${path}.dataset is empty`)\n const base = aiPlanRecordBase(text(entry['base'], `${path}.base`))\n if (!base) throw new PlanShapeError(`${path}.base is not a path of lowercase words joined by hyphens`)\n return { dataset, base }\n }\n\n try {\n const root = record(input, 'plan')\n const reuse = list(root['reuse'], 'reuse', AI_BUILD_PLAN_LIMITS.reuse).map((raw, index) => {\n const entry = record(raw, `reuse[${index}]`)\n const id = text(entry['id'], `reuse[${index}].id`)\n if (!id) throw new PlanShapeError(`reuse[${index}].id is empty`)\n return {\n kind: oneOf(entry['kind'], AI_BUILD_PLAN_REUSE_KINDS, `reuse[${index}].kind`),\n id,\n purpose: text(entry['purpose'], `reuse[${index}].purpose`),\n }\n })\n const names = new Set<string>()\n const create = list(root['create'], 'create', AI_BUILD_PLAN_LIMITS.create).map(\n (raw, index) => {\n const entry = record(raw, `create[${index}]`)\n const name = text(entry['name'], `create[${index}].name`)\n if (!name) throw new PlanShapeError(`create[${index}].name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) {\n throw new PlanShapeError(`create[${index}].name \"${name}\" is used twice`)\n }\n names.add(key)\n const kind = oneOf(entry['kind'], AI_BUILD_PLAN_CREATE_KINDS, `create[${index}].kind`)\n const fields = list(entry['fields'] ?? [], `create[${index}].fields`, AI_BUILD_PLAN_LIMITS.fields)\n .map((field, fieldIndex) => text(field, `create[${index}].fields[${fieldIndex}]`))\n .filter(Boolean)\n // Only a form writes its submissions to a dataset (AGL-3616); kept\n // only where it says one, so a plan without one reads as it always did.\n const writesTo = kind === 'form' ? nullable(entry['writesTo'], `create[${index}].writesTo`) : null\n return {\n kind,\n name,\n why: text(entry['why'], `create[${index}].why`),\n duplicateOf: nullable(entry['duplicateOf'], `create[${index}].duplicateOf`),\n fields: kind === 'layout' ? aiPlanLayoutRegionWords(fields) : fields,\n ...(writesTo ? { writesTo } : {}),\n }\n },\n )\n const screens = list(root['screens'], 'screens', AI_BUILD_PLAN_LIMITS.screens).map(\n (raw, index) => {\n const entry = record(raw, `screens[${index}]`)\n if (typeof entry['nav'] !== 'boolean') {\n throw new PlanShapeError(`screens[${index}].nav is not true or false`)\n }\n return {\n title: text(entry['title'], `screens[${index}].title`),\n slug: text(entry['slug'], `screens[${index}].slug`),\n layout: nullable(entry['layout'], `screens[${index}].layout`),\n template: nullable(entry['template'], `screens[${index}].template`),\n duplicateOf: nullable(entry['duplicateOf'], `screens[${index}].duplicateOf`),\n nav: entry['nav'],\n seoTitle: text(entry['seoTitle'], `screens[${index}].seoTitle`),\n seoDescription: text(\n entry['seoDescription'],\n `screens[${index}].seoDescription`,\n AI_BUILD_PLAN_LIMITS.seoDescription,\n ),\n sections: list(\n entry['sections'],\n `screens[${index}].sections`,\n AI_BUILD_PLAN_LIMITS.sections,\n ).map((rawSection, sectionIndex) => {\n const path = `screens[${index}].sections[${sectionIndex}]`\n const section = record(rawSection, path)\n const items = Number(section['items'] ?? 0)\n if (!Number.isInteger(items) || items < 0 || items > AI_BUILD_PLAN_LIMITS.items) {\n throw new PlanShapeError(`${path}.items is not a count`)\n }\n return {\n name: text(section['name'], `${path}.name`),\n uses: list(section['uses'] ?? [], `${path}.uses`, AI_BUILD_PLAN_LIMITS.uses)\n .map((ref, refIndex) => text(ref, `${path}.uses[${refIndex}]`))\n .filter(Boolean),\n items,\n }\n }),\n record: planRecord(entry['record'], `screens[${index}].record`),\n }\n },\n )\n const embeds = list(root['embeds'] ?? [], 'embeds', AI_BUILD_PLAN_LIMITS.embeds).map(\n (raw, index) => {\n const entry = record(raw, `embeds[${index}]`)\n const where = text(entry['where'], `embeds[${index}].where`)\n if (!where) throw new PlanShapeError(`embeds[${index}].where is empty`)\n return {\n host: oneOf(entry['host'], AI_BUILD_PLAN_EMBED_HOSTS, `embeds[${index}].host`),\n where,\n asked: text(entry['asked'], `embeds[${index}].asked`),\n url: nullable(entry['url'], `embeds[${index}].url`),\n }\n },\n )\n const items = list(root['items'] ?? [], 'items', AI_BUILD_PLAN_LIMITS.buildItems).map(\n (raw, index): AiBuildItem => {\n const path = `items[${index}]`\n const entry = record(raw, path)\n const op = text(entry['op'], `${path}.op`)\n if (!op) throw new PlanShapeError(`${path}.op is empty`)\n const name = text(entry['name'], `${path}.name`)\n if (!name) throw new PlanShapeError(`${path}.name is empty`)\n const key = name.toLowerCase()\n if (names.has(key)) throw new PlanShapeError(`${path}.name \"${name}\" is used twice`)\n names.add(key)\n return {\n slot: `i${index}`,\n op,\n name,\n why: text(entry['why'] ?? '', `${path}.why`),\n dependsOn: list(entry['dependsOn'] ?? [], `${path}.dependsOn`, AI_BUILD_PLAN_LIMITS.dependsOn)\n .map((ref, refIndex) => text(ref, `${path}.dependsOn[${refIndex}]`))\n .filter(Boolean),\n degrade:\n entry['degrade'] === undefined || entry['degrade'] === null\n ? 'omit'\n : oneOf(entry['degrade'], AI_BUILD_ITEM_DEGRADES, `${path}.degrade`),\n args: buildItemArgs(entry['args'], `${path}.args`),\n ...(typeof entry['id'] === 'string' && entry['id'] ? { id: entry['id'] } : {}),\n ...(typeof entry['credits'] === 'number' && Number.isFinite(entry['credits'])\n ? { credits: Math.max(0, Math.floor(entry['credits'])) }\n : {}),\n }\n },\n )\n // A language that is no code is dropped, never refused (AGL-3750): the\n // site keeps the language it has.\n const language = siteLanguageCode(root['language'])\n if (typeof root['language'] === 'string' && root['language'].trim() && !language) {\n repairs.push(`language \"${root['language'].trim().slice(0, 40)}\" is not a language code; ignored`)\n }\n // Photo words that are not plain English are dropped, never refused (AGL-3760).\n const photoWords = aiPlanPhotoWords(root['photoWords'])\n if (typeof root['photoWords'] === 'string' && root['photoWords'].trim() && !photoWords) {\n repairs.push(`photoWords \"${root['photoWords'].trim().slice(0, 40)}\" is not plain English; ignored`)\n }\n // A plan whose brief asks for no player carries no list, as it did before the list existed;\n // so does a plan with no items.\n return {\n ok: true,\n plan: {\n reuse,\n create,\n screens,\n ...(embeds.length ? { embeds } : {}),\n ...(items.length ? { items } : {}),\n ...(language ? { language } : {}),\n ...(photoWords ? { photoWords } : {}),\n },\n repairs,\n }\n } catch (error) {\n if (error instanceof PlanShapeError) return { ok: false, error: error.message }\n throw error\n }\n}\n\n/**\n * A record template's base as the data plugin's save route stores it\n * (`normalizeRecordPageBase`, which this plugin may not import): one to four\n * lowercase segments of letters, digits and hyphens, with no leading or\n * trailing slash; null for a base that is no such path.\n */\nexport function aiPlanRecordBase(base: string): string | null {\n const segments = base.trim().toLowerCase().split('/').filter(Boolean)\n if (!segments.length || segments.length > 4) return null\n return segments.every(\n (segment) => segment.length <= 60 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(segment),\n )\n ? segments.join('/')\n : null\n}\n\n/** Whether a reference names something the plan creates. */\nexport function isAiPlanNewRef(ref: string | null | undefined): ref is string {\n return typeof ref === 'string' && ref.startsWith(AI_PLAN_NEW_REF_PREFIX)\n}\n\n/** The creation a `new:<name>` reference names, case-insensitively. */\nexport function aiPlanCreateFor(\n plan: AiBuildPlan,\n ref: string,\n): AiBuildPlanCreate | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return plan.create.find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** The operation a build's dataset item is planned as (AGL-3616), owned by the data plugin. */\nexport const AI_PLAN_DATASET_ITEM_OP = 'dataset'\n\n/**\n * The datasets this plan makes that one of its forms writes its submissions\n * to (AGL-3616), by name lowercased: a dataset creation of a site start, or a\n * build's dataset item. Such a dataset starts with no records — its records\n * are the submissions — so a site start designs it from its planned fields\n * with no model, and no page lists it.\n */\nexport function aiPlanFormDatasetNames(plan: Pick<AiBuildPlan, 'create'> & Partial<Pick<AiBuildPlan, 'items'>>): Set<string> {\n const names = new Set<string>()\n for (const entry of plan.create) {\n if (entry.kind !== 'form' || !isAiPlanNewRef(entry.writesTo)) continue\n const name = entry.writesTo.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n const made =\n plan.create.some((one) => one.kind === 'dataset' && one.name.trim().toLowerCase() === name) ||\n (plan.items ?? []).some((one) => one.op === AI_PLAN_DATASET_ITEM_OP && one.name.trim().toLowerCase() === name)\n if (made) names.add(name)\n }\n return names\n}\n\n/** The item a `new:<name>` reference names, case-insensitively (AGL-3616). */\nexport function aiPlanItemFor(\n plan: Pick<AiBuildPlan, 'items'>,\n ref: string,\n): AiBuildItem | undefined {\n if (!isAiPlanNewRef(ref)) return undefined\n const name = ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim().toLowerCase()\n return (plan.items ?? []).find((entry) => entry.name.toLowerCase() === name)\n}\n\n/** A slug as two spellings of one path compare: lowercase, one leading slash, no trailing one. */\nexport function aiPlanSlugKey(slug: string): string {\n const path = slug.trim().toLowerCase().replace(/^\\/*/, '/').replace(/\\/+$/, '')\n return path || '/'\n}\n\n/**\n * The players the plan lists for one output (AGL-3433): a screen by its slug,\n * or a creation by its name. A build admits these and no other.\n */\nexport function aiPlanEmbedsFor(\n plan: Pick<AiBuildPlan, 'embeds'> | null | undefined,\n target: { slug: string } | { create: string },\n): AiBuildPlanEmbed[] {\n const key =\n 'slug' in target\n ? aiPlanSlugKey(target.slug)\n : `${AI_PLAN_NEW_REF_PREFIX}${target.create.trim().toLowerCase()}`\n return (plan?.embeds ?? []).filter((embed) =>\n 'slug' in target\n ? !isAiPlanNewRef(embed.where) && aiPlanSlugKey(embed.where) === key\n : embed.where.trim().toLowerCase() === key,\n )\n}\n\n/** The host a video link plays from, or null for a link neither host serves. */\nexport function aiEmbedHostOf(url: string): AiBuildPlanEmbedHost | null {\n try {\n const parsed = new URL(url.trim())\n if (parsed.protocol !== 'https:') return null\n const host = parsed.hostname.replace(/^(?:www|m)\\./, '')\n if (host === 'youtu.be' || host === 'youtube.com' || host === 'youtube-nocookie.com') {\n return 'youtube'\n }\n if (host === 'vimeo.com' || host === 'player.vimeo.com') return 'vimeo'\n } catch {\n /* not a link */\n }\n return null\n}\n\n/**\n * One video however its link is spelled — `youtu.be/<id>`, a watch link, an\n * embed or a Shorts link — as `<host>:<id>`, the way the Video embed element\n * reads a link; null for a link it cannot play.\n */\nexport function aiEmbedVideoKey(url: string): string | null {\n const host = aiEmbedHostOf(url)\n if (!host) return null\n const parsed = new URL(url.trim())\n const parts = parsed.pathname.split('/').filter(Boolean)\n const id =\n host === 'vimeo'\n ? parts.find((part) => /^\\d{6,}$/.test(part))\n : parsed.hostname.endsWith('youtu.be')\n ? parts[0]\n : (parsed.searchParams.get('v') ??\n (parts[0] === 'embed' || parts[0] === 'shorts' ? parts[1] : undefined))\n return id && /^[\\w-]{6,20}$/.test(id) ? `${host}:${id}` : null\n}\n\n/**\n * A `new:<name>` reference that names no entry of the plan's create list: a\n * screen's own layout or template, or what one of its sections places, with\n * that section's index.\n */\nexport type AiPlanUndeclaredRef = {\n /** The name the reference gives the creation, as written after `new:`. */\n name: string\n /** Where the plan writes it: `screens[0].template`, `screens[0].sections[4].uses[0]`. */\n path: string\n /** The screen that writes it, by index. */\n screenIndex: number\n} & ({ field: 'layout' | 'template'; sectionIndex: null } | { field: 'uses'; sectionIndex: number })\n\n/**\n * Every `new:<name>` reference the plan's create list does not carry, in plan\n * order: each screen's layout, then its template, then what its sections\n * place. A plan that places a creation it never declares has nothing to build\n * it from, so the plan rules refuse each (AGL-3040), and the page and\n * scaffold doors still name one in a plan confirmed before they did.\n */\nexport function aiPlanUndeclaredRefs(plan: AiBuildPlan): AiPlanUndeclaredRef[] {\n const found: AiPlanUndeclaredRef[] = []\n const undeclared = (ref: string | null): ref is string =>\n isAiPlanNewRef(ref) && !aiPlanCreateFor(plan, ref) && !aiPlanItemFor(plan, ref)\n const nameOf = (ref: string) => ref.slice(AI_PLAN_NEW_REF_PREFIX.length).trim()\n plan.screens.forEach((screen, screenIndex) => {\n for (const field of ['layout', 'template'] as const) {\n const ref = screen[field]\n if (!undeclared(ref)) continue\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].${field}`,\n screenIndex,\n field,\n sectionIndex: null,\n })\n }\n screen.sections.forEach((section, sectionIndex) => {\n section.uses.forEach((ref, useIndex) => {\n if (!undeclared(ref)) return\n found.push({\n name: nameOf(ref),\n path: `screens[${screenIndex}].sections[${sectionIndex}].uses[${useIndex}]`,\n screenIndex,\n field: 'uses',\n sectionIndex,\n })\n })\n })\n })\n return found\n}\n"],"names":["siteLanguageCode","AI_PLAN_NEW_REF_PREFIX","AI_BUILD_PLAN_REUSE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","component","noun","where","form","layout","template","dataset","email","AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_EMBED_HOSTS","AI_BUILD_PLAN_EMBED_HOST_NAMES","youtube","vimeo","AI_BUILD_ITEM_DEGRADES","AI_BUILD_PLAN_LIMITS","reuse","create","screens","sections","uses","fields","embeds","text","seoDescription","items","buildItems","dependsOn","argsChars","string","description","type","nullableString","anyOf","strings","atMost","limit","AI_BUILD_PLAN_TOOL","name","strict","inputSchema","additionalProperties","required","properties","kind","enum","id","purpose","why","duplicateOf","title","slug","nav","seoTitle","AI_PLAN_ASKS_FOR_VIDEO","EMBEDS_SCHEMA","host","asked","url","AI_BUILD_PLAN_EMBEDS_TOOL","RECORD_SCHEMA","base","WRITES_TO_SCHEMA","withRecords","tool","writesTo","record","AI_BUILD_PLAN_RECORDS_TOOL","AI_BUILD_PLAN_EMBEDS_RECORDS_TOOL","ITEMS_SCHEMA","op","degrade","args","withBuildItems","LANGUAGE_SCHEMA","AI_PLAN_PHOTO_WORDS_MAX","aiPlanPhotoWords","value","trim","toLowerCase","test","words","split","map","word","replace","filter","length","slice","join","PHOTO_WORDS_SCHEMA","withPlanLanguage","language","photoWords","aiBuildPlanToolFor","brief","options","video","records","built","isRecord","Array","isArray","PlanShapeError","Error","buildItemArgs","path","parsed","undefined","JSON","parse","key","raw","Object","entries","Number","isFinite","every","one","aiPlanLayoutRegionWords","field","listed","exec","parts","part","some","seen","push","parseAiBuildPlan","input","repairs","trimmed","cut","brk","search","trimEnd","nullable","list","oneOf","allowed","includes","planRecord","entry","aiPlanRecordBase","root","index","names","Set","has","add","fieldIndex","Boolean","rawSection","sectionIndex","section","isInteger","ref","refIndex","slot","credits","Math","max","floor","ok","plan","error","message","segments","segment","isAiPlanNewRef","startsWith","aiPlanCreateFor","find","AI_PLAN_DATASET_ITEM_OP","aiPlanFormDatasetNames","made","aiPlanItemFor","aiPlanSlugKey","aiPlanEmbedsFor","target","embed","aiEmbedHostOf","URL","protocol","hostname","aiEmbedVideoKey","pathname","endsWith","searchParams","get","aiPlanUndeclaredRefs","found","undeclared","nameOf","forEach","screen","screenIndex","useIndex"],"mappings":"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"}
@@ -47,6 +47,15 @@ export interface AiMeasuredCredits {
47
47
  * theme 4 / 8 (a site start's look)
48
48
  * plan step 21 / 70
49
49
  *
50
+ * THE FAST TIER'S PART, REPRICED (AGL-3781). A site start's look, a Free
51
+ * start's plan and every page's listing run on the fast tier, measured above
52
+ * on Claude Haiku 4.5 at its cost. They run on Claude Haiku 5.5 at the
53
+ * platform's markup now, so those parts are repriced from what production
54
+ * sent, at the new rates and about 30% more tokens: over October 1–14 a look
55
+ * 4 / 7 → 1 / 2, a Free start's plan 15 / 22 → 3 / 5, a listing 3 / 5 → 1 / 1
56
+ * (2 less on every page), and a whole two-page Free site 148 → 121 at the
57
+ * median. The other rows ran on the balanced tier and do not move.
58
+ *
50
59
  * HOW TO RE-MEASURE: a read-only collection-group read of `aiJobs` in
51
60
  * production where `status == 'done'` and `createdAt` is in the window; group
52
61
  * each ledger row's (`items[]`) `creditsSpent` by its `op` (the site start's
@@ -59,8 +68,8 @@ export interface AiMeasuredCredits {
59
68
  */
60
69
  export declare const AI_MEASURED_UNIT_CREDITS: {
61
70
  readonly page: {
62
- readonly median: 30;
63
- readonly p90: 40;
71
+ readonly median: 28;
72
+ readonly p90: 38;
64
73
  };
65
74
  readonly form: {
66
75
  readonly median: 29;
@@ -71,11 +80,11 @@ export declare const AI_MEASURED_UNIT_CREDITS: {
71
80
  readonly p90: 24;
72
81
  };
73
82
  readonly theme: {
74
- readonly median: 4;
75
- readonly p90: 8;
83
+ readonly median: 1;
84
+ readonly p90: 2;
76
85
  };
77
86
  readonly plan: {
78
- readonly median: 21;
87
+ readonly median: 4;
79
88
  readonly p90: 70;
80
89
  };
81
90
  };
@@ -85,11 +94,11 @@ export declare const AI_MEASURED_UNIT_CREDITS: {
85
94
  * (Haiku) the table above is mostly made of: every `done` paid site job
86
95
  * since 2026-10-01, read 2026-10-12 (6 jobs; products from the 4 stores; AGL-3739).
87
96
  *
88
- * plan step 81 / 86 (a Free start's plan: 20 / 42)
89
- * page item 29 / 49 (about `AI_MEASURED_PAGE_SECTIONS` sections)
97
+ * plan step 81 / 86 (a Free start's plan: 20 / 42 on Haiku 4.5, 3 / 5 repriced)
98
+ * page item 29 / 49 (about `AI_MEASURED_PAGE_SECTIONS` sections; 27 / 47 with its listing repriced)
90
99
  * form 32 / 39
91
100
  * layout 29 / 29
92
- * theme 4 / 4
101
+ * theme 4 / 4 (1 / 1 repriced)
93
102
  * email 49 / 51
94
103
  * products 64 / 66 (a store's first products, one answer)
95
104
  *
@@ -107,8 +116,8 @@ export declare const AI_MEASURED_PAID_SITE_CREDITS: {
107
116
  readonly p90: 86;
108
117
  };
109
118
  readonly page: {
110
- readonly median: 29;
111
- readonly p90: 49;
119
+ readonly median: 27;
120
+ readonly p90: 47;
112
121
  };
113
122
  readonly form: {
114
123
  readonly median: 32;
@@ -119,8 +128,8 @@ export declare const AI_MEASURED_PAID_SITE_CREDITS: {
119
128
  readonly p90: 29;
120
129
  };
121
130
  readonly theme: {
122
- readonly median: 4;
123
- readonly p90: 4;
131
+ readonly median: 1;
132
+ readonly p90: 1;
124
133
  };
125
134
  readonly email: {
126
135
  readonly median: 49;
@@ -136,10 +145,14 @@ export declare const AI_MEASURED_AT: {
136
145
  readonly read: "2026-10-09";
137
146
  readonly since: "2026-10-01";
138
147
  };
139
- /** What a whole Free two-page site job measured at, in credits, in the same window. */
148
+ /**
149
+ * What a whole Free two-page site job measured at, in credits, in the same
150
+ * window: 148–160 with its plan, look and listings at Haiku 4.5's cost, 27
151
+ * less at the median with them repriced (AGL-3781).
152
+ */
140
153
  export declare const AI_MEASURED_FREE_SITE_CREDITS: {
141
- readonly low: 148;
142
- readonly high: 160;
154
+ readonly low: 121;
155
+ readonly high: 133;
143
156
  };
144
157
  /** The sections the measured page item is about: a page is scaled around it. */
145
158
  export declare const AI_MEASURED_PAGE_SECTIONS = 5;
@@ -164,12 +177,13 @@ export declare function aiMeasuredPageCredits(sections: number): AiMeasuredCredi
164
177
  * "about 48 credits" for two 4-section pages (AGL-3722, 2026-10-10).
165
178
  *
166
179
  * So it is priced as a fixed cost — the prompt, the site, the listing — and a
167
- * cost per section: a 4-section page about 40 (p90 78), a 5-section 46 (p90 90).
180
+ * cost per section: a 4-section page about 38 (p90 76), a 5-section 44 (p90 88).
181
+ * The fixed part was 16 / 30 with its listing at Haiku 4.5's cost (AGL-3781).
168
182
  */
169
183
  export declare const AI_MEASURED_STANDALONE_PAGE: {
170
184
  readonly fixed: {
171
- readonly median: 16;
172
- readonly p90: 30;
185
+ readonly median: 14;
186
+ readonly p90: 28;
173
187
  };
174
188
  readonly perSection: {
175
189
  readonly median: 6;
@@ -41,6 +41,15 @@
41
41
  * theme 4 / 8 (a site start's look)
42
42
  * plan step 21 / 70
43
43
  *
44
+ * THE FAST TIER'S PART, REPRICED (AGL-3781). A site start's look, a Free
45
+ * start's plan and every page's listing run on the fast tier, measured above
46
+ * on Claude Haiku 4.5 at its cost. They run on Claude Haiku 5.5 at the
47
+ * platform's markup now, so those parts are repriced from what production
48
+ * sent, at the new rates and about 30% more tokens: over October 1–14 a look
49
+ * 4 / 7 → 1 / 2, a Free start's plan 15 / 22 → 3 / 5, a listing 3 / 5 → 1 / 1
50
+ * (2 less on every page), and a whole two-page Free site 148 → 121 at the
51
+ * median. The other rows ran on the balanced tier and do not move.
52
+ *
44
53
  * HOW TO RE-MEASURE: a read-only collection-group read of `aiJobs` in
45
54
  * production where `status == 'done'` and `createdAt` is in the window; group
46
55
  * each ledger row's (`items[]`) `creditsSpent` by its `op` (the site start's
@@ -51,9 +60,10 @@
51
60
  * scaling and the measured whole-site band together, so a re-measure that
52
61
  * moves one moves the spec with it.
53
62
  */ export const AI_MEASURED_UNIT_CREDITS = {
63
+ // 30 / 40 with its listing at Haiku 4.5's cost.
54
64
  page: {
55
- median: 30,
56
- p90: 40
65
+ median: 28,
66
+ p90: 38
57
67
  },
58
68
  form: {
59
69
  median: 29,
@@ -63,12 +73,15 @@
63
73
  median: 23,
64
74
  p90: 24
65
75
  },
76
+ // 4 / 8 on Haiku 4.5.
66
77
  theme: {
67
- median: 4,
68
- p90: 8
78
+ median: 1,
79
+ p90: 2
69
80
  },
81
+ // 21 / 70: the median is a Free start's plan, on the fast tier; the p90 a
82
+ // plan on the balanced tier, which does not move.
70
83
  plan: {
71
- median: 21,
84
+ median: 4,
72
85
  p90: 70
73
86
  }
74
87
  };
@@ -78,11 +91,11 @@
78
91
  * (Haiku) the table above is mostly made of: every `done` paid site job
79
92
  * since 2026-10-01, read 2026-10-12 (6 jobs; products from the 4 stores; AGL-3739).
80
93
  *
81
- * plan step 81 / 86 (a Free start's plan: 20 / 42)
82
- * page item 29 / 49 (about `AI_MEASURED_PAGE_SECTIONS` sections)
94
+ * plan step 81 / 86 (a Free start's plan: 20 / 42 on Haiku 4.5, 3 / 5 repriced)
95
+ * page item 29 / 49 (about `AI_MEASURED_PAGE_SECTIONS` sections; 27 / 47 with its listing repriced)
83
96
  * form 32 / 39
84
97
  * layout 29 / 29
85
- * theme 4 / 4
98
+ * theme 4 / 4 (1 / 1 repriced)
86
99
  * email 49 / 51
87
100
  * products 64 / 66 (a store's first products, one answer)
88
101
  *
@@ -99,8 +112,8 @@
99
112
  p90: 86
100
113
  },
101
114
  page: {
102
- median: 29,
103
- p90: 49
115
+ median: 27,
116
+ p90: 47
104
117
  },
105
118
  form: {
106
119
  median: 32,
@@ -111,8 +124,8 @@
111
124
  p90: 29
112
125
  },
113
126
  theme: {
114
- median: 4,
115
- p90: 4
127
+ median: 1,
128
+ p90: 1
116
129
  },
117
130
  email: {
118
131
  median: 49,
@@ -127,9 +140,13 @@
127
140
  read: '2026-10-09',
128
141
  since: '2026-10-01'
129
142
  };
130
- /** What a whole Free two-page site job measured at, in credits, in the same window. */ export const AI_MEASURED_FREE_SITE_CREDITS = {
131
- low: 148,
132
- high: 160
143
+ /**
144
+ * What a whole Free two-page site job measured at, in credits, in the same
145
+ * window: 148–160 with its plan, look and listings at Haiku 4.5's cost, 27
146
+ * less at the median with them repriced (AGL-3781).
147
+ */ export const AI_MEASURED_FREE_SITE_CREDITS = {
148
+ low: 121,
149
+ high: 133
133
150
  };
134
151
  /** The sections the measured page item is about: a page is scaled around it. */ export const AI_MEASURED_PAGE_SECTIONS = 5;
135
152
  /**
@@ -159,11 +176,12 @@
159
176
  * "about 48 credits" for two 4-section pages (AGL-3722, 2026-10-10).
160
177
  *
161
178
  * So it is priced as a fixed cost — the prompt, the site, the listing — and a
162
- * cost per section: a 4-section page about 40 (p90 78), a 5-section 46 (p90 90).
179
+ * cost per section: a 4-section page about 38 (p90 76), a 5-section 44 (p90 88).
180
+ * The fixed part was 16 / 30 with its listing at Haiku 4.5's cost (AGL-3781).
163
181
  */ export const AI_MEASURED_STANDALONE_PAGE = {
164
182
  fixed: {
165
- median: 16,
166
- p90: 30
183
+ median: 14,
184
+ p90: 28
167
185
  },
168
186
  perSection: {
169
187
  median: 6,