@aglyn/plugins-ai 1.0.0-beta.231 → 1.0.0-beta.232

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 (220) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  43. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  44. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  45. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  46. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  47. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  48. package/src/lib/jobs/ai-job-build-step.js +3 -1
  49. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  51. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  52. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  53. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  54. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  56. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  57. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  58. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  59. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  61. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  62. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  64. package/src/lib/jobs/ai-job-page-language.js +72 -9
  65. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  66. package/src/lib/jobs/ai-job-page-step.js +12 -2
  67. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
  69. package/src/lib/jobs/ai-job-plan-step.js +54 -20
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  72. package/src/lib/jobs/ai-job-site-content.js +443 -0
  73. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  74. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  75. package/src/lib/jobs/ai-job-site-look.js +322 -0
  76. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  77. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  78. package/src/lib/jobs/ai-job-site-step.js +163 -24
  79. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  80. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  81. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  82. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  84. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  85. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  86. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  87. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  89. package/src/lib/layout-language/ai-layout-language.js +92 -9
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  92. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  93. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  94. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  95. package/src/lib/layout-language/ai-layout-store.js +7 -1
  96. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  97. package/src/lib/model/ai-build-progress.js +4 -1
  98. package/src/lib/model/ai-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-edit-job.d.ts +109 -0
  100. package/src/lib/model/ai-edit-job.js +157 -0
  101. package/src/lib/model/ai-edit-job.js.map +1 -0
  102. package/src/lib/model/ai-job-activity.js +1 -0
  103. package/src/lib/model/ai-job-activity.js.map +1 -1
  104. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  105. package/src/lib/model/ai-jobs.types.js.map +1 -1
  106. package/src/lib/model/ai-logic-job.d.ts +18 -1
  107. package/src/lib/model/ai-logic-job.js +24 -3
  108. package/src/lib/model/ai-logic-job.js.map +1 -1
  109. package/src/lib/model/ai-media-credits.d.ts +8 -4
  110. package/src/lib/model/ai-media-credits.js +11 -4
  111. package/src/lib/model/ai-media-credits.js.map +1 -1
  112. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  113. package/src/lib/model/ai-media-kinds.js +98 -0
  114. package/src/lib/model/ai-media-kinds.js.map +1 -0
  115. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  116. package/src/lib/model/ai-site-build-progress.js +28 -11
  117. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  118. package/src/lib/model/ai-site-context.d.ts +111 -0
  119. package/src/lib/model/ai-site-context.js +222 -0
  120. package/src/lib/model/ai-site-context.js.map +1 -0
  121. package/src/lib/model/ai-site-job.d.ts +10 -2
  122. package/src/lib/model/ai-site-job.js +12 -5
  123. package/src/lib/model/ai-site-job.js.map +1 -1
  124. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  125. package/src/lib/model/ai-site-kinds.js +1942 -0
  126. package/src/lib/model/ai-site-kinds.js.map +1 -0
  127. package/src/lib/model/ai-site-look.d.ts +150 -0
  128. package/src/lib/model/ai-site-look.js +1236 -0
  129. package/src/lib/model/ai-site-look.js.map +1 -0
  130. package/src/lib/model/ai-site-memory.d.ts +80 -0
  131. package/src/lib/model/ai-site-memory.js +171 -0
  132. package/src/lib/model/ai-site-memory.js.map +1 -0
  133. package/src/lib/model/ai-site-start.d.ts +10 -1
  134. package/src/lib/model/ai-site-start.js +16 -2
  135. package/src/lib/model/ai-site-start.js.map +1 -1
  136. package/src/lib/model/assist-follow-up.d.ts +92 -0
  137. package/src/lib/model/assist-follow-up.js +128 -0
  138. package/src/lib/model/assist-follow-up.js.map +1 -0
  139. package/src/lib/plugin-config.js +2 -0
  140. package/src/lib/plugin-config.js.map +1 -1
  141. package/src/lib/plugin.js +20 -1
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.d.ts +9 -1
  144. package/src/lib/providers/anthropic.js +143 -9
  145. package/src/lib/providers/anthropic.js.map +1 -1
  146. package/src/lib/providers/catalog.d.ts +37 -7
  147. package/src/lib/providers/catalog.js +103 -11
  148. package/src/lib/providers/catalog.js.map +1 -1
  149. package/src/lib/providers/contract.d.ts +29 -0
  150. package/src/lib/providers/contract.js.map +1 -1
  151. package/src/lib/providers/image-contract.d.ts +22 -1
  152. package/src/lib/providers/image-contract.js +40 -1
  153. package/src/lib/providers/image-contract.js.map +1 -1
  154. package/src/lib/providers/image-endpoints.d.ts +24 -0
  155. package/src/lib/providers/image-endpoints.js +28 -0
  156. package/src/lib/providers/image-endpoints.js.map +1 -0
  157. package/src/lib/providers/model-choice.js +23 -2
  158. package/src/lib/providers/model-choice.js.map +1 -1
  159. package/src/lib/providers/routing.js +18 -0
  160. package/src/lib/providers/routing.js.map +1 -1
  161. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  162. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  163. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  164. package/src/lib/providers/vertex-image.d.ts +13 -27
  165. package/src/lib/providers/vertex-image.js +15 -42
  166. package/src/lib/providers/vertex-image.js.map +1 -1
  167. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  168. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  169. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  170. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  171. package/src/lib/runtime/ai-dev-env.js +72 -0
  172. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  173. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  174. package/src/lib/runtime/ai-dev-replay.js +313 -0
  175. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  176. package/src/lib/runtime/ai-doctrine.js +5 -1
  177. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  178. package/src/lib/runtime/ai-eval-live.js +4 -0
  179. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  180. package/src/lib/runtime/ai-eval.d.ts +13 -1
  181. package/src/lib/runtime/ai-eval.js +84 -1
  182. package/src/lib/runtime/ai-eval.js.map +1 -1
  183. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  184. package/src/lib/runtime/ai-live-batch.js +89 -0
  185. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  186. package/src/lib/runtime/ai-runtime.js +46 -2
  187. package/src/lib/runtime/ai-runtime.js.map +1 -1
  188. package/src/lib/runtime/site-context.d.ts +58 -0
  189. package/src/lib/runtime/site-context.js +127 -0
  190. package/src/lib/runtime/site-context.js.map +1 -0
  191. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  192. package/src/lib/server/ai-admin-conversations.js +197 -0
  193. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  194. package/src/lib/server/ai-media-image.d.ts +5 -5
  195. package/src/lib/server/ai-media-image.js +25 -11
  196. package/src/lib/server/ai-media-image.js.map +1 -1
  197. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  198. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  199. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  200. package/src/lib/server/assist-chat.d.ts +11 -0
  201. package/src/lib/server/assist-chat.js +57 -18
  202. package/src/lib/server/assist-chat.js.map +1 -1
  203. package/src/lib/server/assist-edit-applied.js +16 -3
  204. package/src/lib/server/assist-edit-applied.js.map +1 -1
  205. package/src/lib/server/assist-view-context.d.ts +19 -1
  206. package/src/lib/server/assist-view-context.js +38 -1
  207. package/src/lib/server/assist-view-context.js.map +1 -1
  208. package/src/lib/server.js +10 -0
  209. package/src/lib/server.js.map +1 -1
  210. package/src/lib/subprocessors.js +17 -1
  211. package/src/lib/subprocessors.js.map +1 -1
  212. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  213. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  214. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  215. package/src/lib/usage/assist-usage.d.ts +3 -0
  216. package/src/lib/usage/assist-usage.js +4 -2
  217. package/src/lib/usage/assist-usage.js.map +1 -1
  218. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  219. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  220. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -13,7 +13,8 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ import { AI_BUILD_PLAN_CREATION_NOUNS, aiPlanUndeclaredRefs } from "./ai-build-plan.js";
16
+ */ import { aiSiteKind } from "./ai-site-kinds.js";
17
+ import { AI_BUILD_PLAN_CREATION_NOUNS, aiPlanUndeclaredRefs } from "./ai-build-plan.js";
17
18
  import { AI_JOB_CREATE_KINDS } from "./ai-job-creations.js";
18
19
  import { AI_PAGE_CREATE_KINDS } from "./ai-page-job.js";
19
20
  /**
@@ -82,6 +83,7 @@ import { AI_PAGE_CREATE_KINDS } from "./ai-page-job.js";
82
83
  * moves and this does not.
83
84
  */ export const AI_FREE_SITE_WORST_CASE_CREDITS = {
84
85
  plan: 35,
86
+ look: 7,
85
87
  layout: 64,
86
88
  firstSection: 44,
87
89
  laterSection: 20,
@@ -89,25 +91,25 @@ import { AI_PAGE_CREATE_KINDS } from "./ai-page-job.js";
89
91
  };
90
92
  /**
91
93
  * The most sections a Free site's plan fits in the Free taste at its worst,
92
- * across all its pages: the plan, the layouts it builds first, each page's
94
+ * across all its pages: the plan, the look, the layouts it builds first, each page's
93
95
  * listing and first pass, and ROOM FOR ONE RETRIED SECTION, then as many later
94
96
  * passes as the rest pays for; none when that is already past the wall. Every
95
97
  * page's first pass is counted as a cache write, which errs dear.
96
98
  */ export function aiFreeSiteSectionsWithin(creations, taste, credits = AI_FREE_SITE_WORST_CASE_CREDITS) {
97
99
  const pages = Math.max(1, Math.floor(creations.pages));
98
- const before = credits.plan + creations.layouts * credits.layout + pages * (credits.listing + credits.firstSection) + credits.firstSection;
100
+ const before = credits.plan + credits.look + creations.layouts * credits.layout + pages * (credits.listing + credits.firstSection) + credits.firstSection;
99
101
  if (before > taste) return 0;
100
102
  return pages + Math.floor((taste - before) / credits.laterSection);
101
103
  }
102
104
  /** Sections a Free site's page is assumed to hold before its plan names them. */ export const AI_FREE_SITE_NOMINAL_SECTIONS = 3;
103
105
  /**
104
106
  * About what a Free site start of this many pages costs at its worst, in
105
- * credits, for the dialog that asks for one: the site plan, each page's
107
+ * credits, for the dialog that asks for one: the site plan, its look, each page's
106
108
  * listing and its sections at the nominal count, from the figures the wall is
107
109
  * proven with rather than the nominal credits a paid estimate counts.
108
110
  */ export function aiFreeSiteCreditEstimate(pages, credits = AI_FREE_SITE_WORST_CASE_CREDITS) {
109
111
  const count = Math.max(1, Math.floor(pages));
110
- return credits.plan + count * (credits.listing + credits.firstSection) + count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection;
112
+ return credits.plan + credits.look + count * (credits.listing + credits.firstSection) + count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection;
111
113
  }
112
114
  /**
113
115
  * The most sections one scaffolded page may hold. The plan model allows more
@@ -194,6 +196,8 @@ import { AI_PAGE_CREATE_KINDS } from "./ai-page-job.js";
194
196
  }
195
197
  const ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/;
196
198
  /** A scaffold's inputs, or the sentence the door refuses them with. */ export function parseAiSiteJobInputs(inputs) {
199
+ var _ref;
200
+ var _aiSiteKind;
197
201
  const text = (key)=>{
198
202
  const value = inputs == null ? void 0 : inputs[key];
199
203
  if (value === undefined || value === null || value === '') return '';
@@ -211,6 +215,8 @@ const ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/;
211
215
  const brand = text('brand');
212
216
  const audience = text('audience');
213
217
  const starter = text('starter');
218
+ // A kind this deployment does not know is nobody having picked one.
219
+ const siteKind = (_ref = (_aiSiteKind = aiSiteKind(inputs == null ? void 0 : inputs['siteKind'])) == null ? void 0 : _aiSiteKind.id) != null ? _ref : '';
214
220
  for (const [key, value] of [
215
221
  [
216
222
  'businessName',
@@ -253,6 +259,7 @@ const ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/;
253
259
  businessType,
254
260
  audience: audience,
255
261
  starter: starter,
262
+ siteKind,
256
263
  pages,
257
264
  businessName: businessName,
258
265
  city: city,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n aiPlanUndeclaredRefs,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\nimport { AI_JOB_CREATE_KINDS } from './ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS } from './ai-page-job'\n\n/**\n * What a site scaffold is (AGL-2911): the inputs a `site` job is admitted\n * with, the plans a scaffold can build, and what one is estimated to cost\n * before it starts.\n *\n * A scaffold is the one job kind that builds a whole site rather than one\n * record: a plan of four to eight screens, the layout they render inside,\n * the contact form they place, a palette suggestion, and — where the email\n * step is loaded — a welcome email. It builds each of those through the job\n * kind that already owns it, so this module describes what a scaffold may\n * ask for and refuses the rest at confirmation, before a credit is spent,\n * naming what to make first.\n *\n * ── The estimate is the guard rail ───────────────────────────────────────\n *\n * A scaffold is tens of model calls, where every other kind is a few, so the\n * member reads what it will cost before they confirm the plan that spends\n * it. The figure is the machine's own nominal credits per step multiplied by\n * the passes the plan implies — an estimate, and said to be one wherever it\n * is shown.\n *\n * This module imports nothing at runtime, so the doors, the step, the batch\n * card and the plan proposal read one vocabulary.\n */\n\n/** How many pages a scaffold builds: enough to be a site, few enough to read. */\nexport const AI_SITE_PAGES = { min: 4, max: 8 } as const\n\n/**\n * How many pages a Free workspace's scaffold builds (AGL-3594): its home page\n * and the one page the brief most needs. A product decision, not a figure the\n * wall derives — the wall's proof (`jobs/ai-job-free-site.spec.ts`) shows a\n * plan and a build of this many pages fit the Free taste with room for one\n * retried section.\n */\nexport const AI_SITE_FREE_PAGES = { min: 1, max: 2 } as const\n\n/** The pages a scaffold builds for a workspace on the Free taste or on a paid plan. */\nexport function aiSitePagesBand(\n freeTaste: boolean | null | undefined,\n): { readonly min: number; readonly max: number } {\n return freeTaste ? AI_SITE_FREE_PAGES : AI_SITE_PAGES\n}\n\n/**\n * The job input a guided site start sets so its plan is confirmed for it\n * (AGL-3594); `jobs/ai-job-auto-confirm.ts` says where it is honored.\n */\nexport const AI_JOB_AUTO_CONFIRM_INPUT = 'autoConfirm'\n\n/** What a Free site start's page selector says under it (AGL-3594). */\nexport const AI_SITE_FREE_PAGES_NOTE = 'Paid plans can generate more pages.'\n\n/**\n * The plan's answer ceiling for a scaffold (AGL-3594), Free and paid. A plan\n * is an outline — each page's title, address, search listing and sections by\n * name — and the build steps write everything else, so a scaffold plans with\n * no thinking and at a ceiling sized to the outline of its largest band with\n * room to spare: about 300 tokens a page. The 13,491-token plan a Free site\n * spent 227 of its 300 credits on was its thinking, at the routing table's\n * 8,000 a call.\n */\nexport const AI_SITE_PLAN_MAX_TOKENS = { free: 2_000, paid: 4_000 } as const\n\n/** What each exchange of a Free site job comes to at its worst, in credits. */\nexport interface AiFreeSiteWorstCase {\n /** The site plan: its answer and its one re-ask, each at its ceiling. */\n plan: number\n /** The one layout a Free plan includes, built first on a site with none. */\n layout: number\n /** A page's first section pass, which writes the cached prefix. */\n firstSection: number\n /** Each later section pass. */\n laterSection: number\n /** A page's listing. */\n listing: number\n}\n\n/**\n * The Free site start's wall at its worst (AGL-3594): the site plan as\n * `jobs/ai-job-free-site.spec.ts` derives it from the plan request as it\n * stands, on the model a Free site plans with, every answer at its ceiling;\n * the build's exchanges are the Free page's (`AI_FREE_PAGE_WORST_CASE_CREDITS`),\n * which the same spec holds these to. The spec fails when a figure it derives\n * moves and this does not.\n */\nexport const AI_FREE_SITE_WORST_CASE_CREDITS: Readonly<AiFreeSiteWorstCase> = {\n plan: 35,\n layout: 64,\n firstSection: 44,\n laterSection: 20,\n listing: 3,\n}\n\n/**\n * The most sections a Free site's plan fits in the Free taste at its worst,\n * across all its pages: the plan, the layouts it builds first, each page's\n * listing and first pass, and ROOM FOR ONE RETRIED SECTION, then as many later\n * passes as the rest pays for; none when that is already past the wall. Every\n * page's first pass is counted as a cache write, which errs dear.\n */\nexport function aiFreeSiteSectionsWithin(\n creations: { layouts: number; pages: number },\n taste: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const pages = Math.max(1, Math.floor(creations.pages))\n const before =\n credits.plan +\n creations.layouts * credits.layout +\n pages * (credits.listing + credits.firstSection) +\n credits.firstSection\n if (before > taste) return 0\n return pages + Math.floor((taste - before) / credits.laterSection)\n}\n\n/** Sections a Free site's page is assumed to hold before its plan names them. */\nexport const AI_FREE_SITE_NOMINAL_SECTIONS = 3\n\n/**\n * About what a Free site start of this many pages costs at its worst, in\n * credits, for the dialog that asks for one: the site plan, each page's\n * listing and its sections at the nominal count, from the figures the wall is\n * proven with rather than the nominal credits a paid estimate counts.\n */\nexport function aiFreeSiteCreditEstimate(\n pages: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const count = Math.max(1, Math.floor(pages))\n return (\n credits.plan +\n count * (credits.listing + credits.firstSection) +\n count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection\n )\n}\n\n/**\n * The most sections one scaffolded page may hold. The plan model allows more\n * for a page job, which builds one page; a scaffold builds eight, and each\n * section is a pass of its own.\n */\nexport const AI_SITE_MAX_SECTIONS = 8\n\n/**\n * Sections a page is assumed to hold before a plan names them, for the\n * estimate a member reads when they ask for a scaffold. A declared\n * assumption, not a measurement: the plan's own section counts replace it\n * the moment the plan exists.\n */\nexport const AI_SITE_NOMINAL_SECTIONS = 5\n\n/**\n * The credits one pass is held against: the machine's\n * `AI_JOB_STEP_RESERVE_CREDITS`, declared here so the estimate stays a pure\n * number the console computes without the Admin SDK. `ai-site-job.spec.ts`\n * holds the two equal.\n */\nexport const AI_SITE_PASS_CREDITS = 50\n\n/** The longest a scaffold's scalar inputs may run; they are names, not copy. */\nexport const AI_SITE_INPUT_MAX_CHARS = 120\n\n/** The sites one agency batch may generate for in one request. */\nexport const AI_SITE_BATCH_MAX = 25\n\n/**\n * The sites a plan must hold before the org Sites page offers the batch at\n * all: the Agency and Advanced bands, which the plan's `hostLimit` is what\n * distinguishes.\n */\nexport const AI_SITE_BATCH_MIN_HOST_LIMIT = 25\n\nexport interface AiSiteJobInputs {\n /** What the business is, in a few words: the brief's subject. */\n businessType: string\n /**\n * Who the site is for, in a few words (AGL-2918); empty when nobody said.\n * The plan step puts it in front of the model on a line of its own, as it\n * does every other scalar here, so it narrows the pages a plan proposes\n * without a prompt of its own.\n */\n audience: string\n /**\n * The starter site whose shape the person liked (AGL-2918), by its\n * `STARTER_TEMPLATES` id; empty when they picked none.\n *\n * A hint carried as the id rather than the starter, because an id is what\n * the guided start's list, this door and the plan's line all agree on\n * without any of them loading a catalog of node maps. Unrecognized ids are\n * admitted for the same reason an unrecognized business type is: it is a\n * few words in a brief, not a lookup.\n */\n starter: string\n /** How many pages the member asked for. */\n pages: number\n /** The per-site variables an agency batch varies; empty when none was given. */\n businessName: string\n city: string\n brand: string\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /**\n * Where the contact form's submissions go (AGL-2918); `null` where nobody\n * said, which is every job created before the question existed and every\n * door that does not ask it.\n *\n * The one setting a new site owner actually needs made, and the one thing\n * about a contact form a model cannot know: whether a submission is a note\n * to read or a lead to chase is a decision about how the business runs. So\n * an answer BINDS the form step rather than hinting to it, and `null`\n * leaves the model's own proposal standing, exactly as it stood before.\n */\n submissions: AiSiteSubmissions | null\n /** The batch this job was created under (AGL-2911); `null` for a single site. */\n batchId: string | null\n}\n\n/**\n * Where a site's contact form sends what a visitor writes.\n *\n * Two, not three. The form step's own vocabulary has a third — a mailing\n * list — which it can only answer with a note, because the stored routing has\n * no place for a list; offering it as a question would be asking somebody to\n * pick an outcome the platform then explains it cannot store.\n */\nexport const AI_SITE_SUBMISSIONS = ['inbox', 'lead'] as const\n\nexport type AiSiteSubmissions = (typeof AI_SITE_SUBMISSIONS)[number]\n\n/**\n * What each answer to \"where do submissions go\" is called, and what it means.\n *\n * Here rather than beside the guided start's own questions, because it is not\n * only the guided start that asks: the agency batch asks the same thing of a\n * whole run (AGL-2918), and two surfaces wording one decision two ways is two\n * decisions. `ai-site-job.spec.ts` holds the list to {@link AI_SITE_SUBMISSIONS}.\n */\nexport const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{\n id: AiSiteSubmissions\n label: string\n blurb: string\n}> = [\n {\n id: 'inbox',\n label: 'The Inbox',\n blurb: 'Every message is one to read and reply to.',\n },\n {\n id: 'lead',\n label: 'The Inbox, and CRM as a lead',\n blurb: 'Every message with an email address is also somebody to follow up.',\n },\n]\n\n/**\n * Which kind of email a scaffold's email unit is (AGL-2918), carried on the\n * derived job as `inputs.emailType`. The email step names the kind on the one\n * line it puts above the brief, and a scaffold was naming none.\n *\n * A key of the email step's own `AI_EMAIL_TYPES`, held here because neither\n * step may import the other: a scaffold delegates by job kind, and a\n * deployment that scaffolds is not necessarily one that has loaded the email\n * step. `ai-job-email-step.spec.ts` holds this key to that catalog.\n *\n * `reply` and not `welcome`. The five kinds a member picks are campaigns\n * somebody sends to an audience, and this is an answer to one person who\n * wrote in through the site; the nearest, `welcome`, is a label about signing\n * up, and a kind that misdescribes the email is worse than an absent one\n * because it is the first thing the model is told.\n */\nexport const AI_SITE_EMAIL_TYPE = 'reply'\n\n/**\n * What a site job's plan and every unit it builds are told about the\n * business's name (AGL-3596): that it is the name, used as written. Said on\n * the job's own turn, never in a cached system block, because it is one\n * site's. A model handed only what the business does named it itself —\n * \"Hillside Dog Grooming\" was built as \"Austin Paws Grooming\".\n */\nexport function aiSiteNameSentence(name: string): string {\n return `The business is named “${name}”: the site names it exactly that way, in the header, the footer, the copy and the search listing, and never by any other name.`\n}\n\n/** Where a job's inputs say submissions go, or `null` where they do not say. */\nexport function aiSiteSubmissions(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteSubmissions | null {\n const value = inputs?.['submissions']\n return (AI_SITE_SUBMISSIONS as readonly unknown[]).includes(value)\n ? (value as AiSiteSubmissions)\n : null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/\n\n/** A scaffold's inputs, or the sentence the door refuses them with. */\nexport function parseAiSiteJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteJobInputs | string {\n const text = (key: string): string | null => {\n const value = inputs?.[key]\n if (value === undefined || value === null || value === '') return ''\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > AI_SITE_INPUT_MAX_CHARS ? null : trimmed\n }\n const businessType = text('businessType')\n if (businessType === null) {\n return `businessType must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n if (!businessType) return 'Say what kind of business the site is for'\n const businessName = text('businessName')\n const city = text('city')\n const brand = text('brand')\n const audience = text('audience')\n const starter = text('starter')\n for (const [key, value] of [\n ['businessName', businessName],\n ['city', city],\n ['brand', brand],\n ['audience', audience],\n ['starter', starter],\n ] as const) {\n if (value === null)\n return `${key} must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n const rawPages = inputs?.['pages']\n const pages =\n typeof rawPages === 'number' ? rawPages : Number(rawPages ?? NaN)\n // Read here across both bands; the door holds a workspace to its own\n // (`aiSitePagesRefusal`), since only it knows the workspace's plan.\n if (\n !Number.isInteger(pages) ||\n pages < AI_SITE_FREE_PAGES.min ||\n pages > AI_SITE_PAGES.max\n ) {\n return `pages must be a whole number from ${AI_SITE_FREE_PAGES.min} to ${AI_SITE_PAGES.max}`\n }\n // Where submissions go is admitted only as one of the two the form step\n // can actually bind; anything else is nobody having said, and the model\n // proposes as it always did rather than the door refusing the whole job.\n const submissions = aiSiteSubmissions(inputs)\n const rawBatch = inputs?.['batchId']\n const batchId =\n rawBatch === undefined || rawBatch === null || rawBatch === ''\n ? null\n : typeof rawBatch === 'string' && ID_CHARS.test(rawBatch)\n ? rawBatch\n : undefined\n if (batchId === undefined) return 'batchId is not a batch id'\n return {\n businessType,\n audience: audience as string,\n starter: starter as string,\n pages,\n businessName: businessName as string,\n city: city as string,\n brand: brand as string,\n welcomeEmail: inputs?.['welcomeEmail'] !== false,\n submissions,\n batchId,\n }\n}\n\n/**\n * Why a workspace cannot ask for this many pages (AGL-3594), in a sentence a\n * member reads; `null` when it can. A Free workspace builds one or two, and a\n * paid one four to eight, as it always has. Refused rather than clamped: the\n * guided start offers only the band, so a request outside it was not made\n * there, and a job quietly building fewer pages than it was asked for is a\n * worse answer than a sentence saying why.\n */\nexport function aiSitePagesRefusal(pages: number, freeTaste: boolean | null | undefined): string | null {\n const band = aiSitePagesBand(freeTaste)\n if (pages >= band.min && pages <= band.max) return null\n return freeTaste\n ? `A Free workspace's AI site start builds ${AI_SITE_FREE_PAGES.min} or ${AI_SITE_FREE_PAGES.max} pages. ${AI_SITE_FREE_PAGES_NOTE}`\n : `A site is planned with ${band.min} to ${band.max} pages.`\n}\n\n/**\n * What the site itself is, as a step that is NOT the scaffold reads it off a\n * job's inputs (AGL-2918).\n *\n * A scaffold delegates unit by unit under a job derived from its own — a page\n * job, a form job — and the derived job carries the scaffold's inputs. So the\n * two answers that describe the site rather than one record of it are\n * readable by every delegated step, and a step that wants them does not have\n * to know it was delegated to.\n *\n * Lenient where {@link parseAiSiteJobInputs} is strict, because it reads the\n * inputs of jobs that are not scaffolds: a page job started from the Screens\n * page has no `businessType` and no `pages`, and that is not an error here —\n * it is a site nobody described, and the answer is two empty strings.\n */\nexport interface AiSiteWords {\n /** What kind of site it is; empty when the job's inputs do not say. */\n about: string\n /** Who it is for; empty when the job's inputs do not say. */\n audience: string\n}\n\n/** Whether either half of {@link AiSiteWords} says anything. */\nexport function aiSiteWordsSaidAnything(words: AiSiteWords): boolean {\n return Boolean(words.about || words.audience)\n}\n\n/** What a job's inputs say the site is, read defensively and trimmed to the input ceiling. */\nexport function aiSiteWords(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteWords {\n const read = (key: string): string => {\n const value = inputs?.[key]\n if (typeof value !== 'string') return ''\n return value.replace(/\\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim()\n }\n return { about: read('businessType'), audience: read('audience') }\n}\n\n/**\n * What a scaffold builds for itself: the layout its pages render inside, the\n * form they place, and the palette suggestion a member applies in the Theme\n * section. Anything else a plan asks to create is a job of its own.\n */\nexport const AI_SITE_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'layout',\n 'form',\n 'theme-change',\n]\n\n/** The creations a scaffold cannot build, in the order the plan lists them, each named once. */\nexport function aiSitePlanPrerequisites(\n plan: AiBuildPlan,\n): Array<{ kind: AiBuildPlanCreateKind; name: string }> {\n const seen = new Set<string>()\n const prerequisites: Array<{ kind: AiBuildPlanCreateKind; name: string }> = []\n const add = (kind: AiBuildPlanCreateKind, name: string) => {\n const key = `${kind}:${name.toLowerCase()}`\n if (seen.has(key)) return\n seen.add(key)\n prerequisites.push({ kind, name })\n }\n for (const entry of plan.create) {\n if (!AI_SITE_CREATE_KINDS.includes(entry.kind)) add(entry.kind, entry.name)\n }\n // A reference the create list does not carry is refused by the plan rules\n // before a plan is kept: rule 2 for a screen's layout, rule 7 for anything\n // else (`plan-creation-undeclared`, AGL-3040). A plan confirmed before that\n // rule existed can still carry one. It is named as a component: the\n // reference carries no kind, and the refusal must say where one is made.\n for (const { name } of aiPlanUndeclaredRefs(plan)) add('component', name)\n return prerequisites\n}\n\n/**\n * Why a scaffold cannot build a plan of this SHAPE — the page band, a page's\n * sections, its addresses, its navigation — in a sentence a member reads;\n * `null` when the shape is one it builds. The plan step holds a plan to it\n * with a re-ask (AGL-3030), and the doors hold a confirmed plan to it again\n * through `aiSitePlanRefusal`.\n */\nexport function aiSitePlanShapeRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const { screens } = plan\n const band = aiSitePagesBand(options.freeTaste)\n if (screens.length < band.min || screens.length > band.max) {\n return `This plan builds ${screens.length} ${screens.length === 1 ? 'page' : 'pages'}, and ${\n options.freeTaste ? \"a Free workspace's site scaffold\" : 'a site scaffold'\n } builds ${band.min} to ${band.max}. Describe the site again.`\n }\n const slugs = new Set<string>()\n for (const screen of screens) {\n if (!screen.sections.length) {\n return `The page “${screen.title}” has no sections to build. Describe the site again.`\n }\n if (screen.sections.length > AI_SITE_MAX_SECTIONS) {\n return `The page “${screen.title}” has ${screen.sections.length} sections, and a scaffolded page holds ${AI_SITE_MAX_SECTIONS}. Describe the site again.`\n }\n const slug = screen.slug.trim().toLowerCase()\n if (slugs.has(slug)) {\n return `Two pages in this plan share the address ${screen.slug}. Describe the site again.`\n }\n slugs.add(slug)\n }\n if (!screens.some((screen) => screen.nav)) {\n return 'No page in this plan is in the site navigation. Describe the site again.'\n }\n return null\n}\n\n/**\n * Why a scaffold cannot build this plan, in a sentence a member reads when\n * confirming it; `null` when it can.\n */\nexport function aiSitePlanRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const shape = aiSitePlanShapeRefusal(plan, options)\n if (shape) return shape\n const prerequisites = aiSitePlanPrerequisites(plan)\n if (!prerequisites.length) return null\n const parts = prerequisites.map(\n ({ kind, name }) =>\n `the ${AI_BUILD_PLAN_CREATION_NOUNS[kind].noun} “${name}” ${AI_BUILD_PLAN_CREATION_NOUNS[kind].where}`,\n )\n const listed =\n parts.length === 1\n ? parts[0]\n : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`\n return `This site needs what the workspace does not have yet. Create ${listed}, then describe the site again.`\n}\n\n/** What the estimate counts a plan in: whether a welcome email follows, and the creations the job builds. */\nexport interface AiPlanPassOptions {\n welcomeEmail?: boolean\n /** The creation kinds the job builds itself; a scaffold's when absent. */\n creates?: readonly AiBuildPlanCreateKind[]\n}\n\n/**\n * The passes a plan implies: one per section of every screen, one more per\n * screen for its search listing and its draft, and one for each thing the\n * plan creates that the job builds. What the estimate is counted in.\n */\nexport function aiPlanPasses(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n const screens = plan.screens.reduce(\n (total, screen) => total + screen.sections.length + 1,\n 0,\n )\n const creates = options.creates ?? AI_SITE_CREATE_KINDS\n const creations = plan.create.filter((entry) => creates.includes(entry.kind)).length\n return screens + creations + (options.welcomeEmail ? 1 : 0)\n}\n\n/**\n * About what a plan costs to build, in credits. An ESTIMATE: the nominal\n * credits the machine holds per step, times the passes the plan implies.\n * What each pass actually costs is its model's tokens, recorded on the job\n * as it runs.\n */\nexport function aiPlanCreditEstimate(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n return aiPlanPasses(plan, options) * AI_SITE_PASS_CREDITS\n}\n\n/**\n * About what a job of this kind costs to build from its plan (AGL-3031): a\n * page job counts the layout, forms and components it builds before its\n * page; a template, layout, component, form or email job counts its own\n * record and every creation it builds before it (AGL-3143 §15); and a\n * scaffold counts what it builds. The figure the proposal shows beside\n * Confirm, and the hold a confirmation takes.\n */\nexport function aiJobPlanCreditEstimate(kind: string, plan: AiBuildPlan): number {\n const creates =\n kind === 'page' ? AI_PAGE_CREATE_KINDS : AI_JOB_CREATE_KINDS[kind as keyof typeof AI_JOB_CREATE_KINDS]\n return aiPlanCreditEstimate(plan, creates ? { creates } : {})\n}\n\n/**\n * About what a scaffold of this many pages costs, before a plan names its\n * sections: the same arithmetic on the nominal section count, plus the plan\n * step that proposes it. Shown where a member asks for a scaffold.\n */\nexport function aiSiteCreditEstimate(\n pages: number,\n options: { welcomeEmail?: boolean } = {},\n): number {\n const screens = Math.max(0, Math.floor(pages))\n const passes =\n 1 +\n screens * (AI_SITE_NOMINAL_SECTIONS + 1) +\n 2 +\n (options.welcomeEmail ? 1 : 0)\n return passes * AI_SITE_PASS_CREDITS\n}\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","aiPlanUndeclaredRefs","AI_JOB_CREATE_KINDS","AI_PAGE_CREATE_KINDS","AI_SITE_PAGES","min","max","AI_SITE_FREE_PAGES","aiSitePagesBand","freeTaste","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES_NOTE","AI_SITE_PLAN_MAX_TOKENS","free","paid","AI_FREE_SITE_WORST_CASE_CREDITS","plan","layout","firstSection","laterSection","listing","aiFreeSiteSectionsWithin","creations","taste","credits","pages","Math","floor","before","layouts","AI_FREE_SITE_NOMINAL_SECTIONS","aiFreeSiteCreditEstimate","count","AI_SITE_MAX_SECTIONS","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","AI_SITE_INPUT_MAX_CHARS","AI_SITE_BATCH_MAX","AI_SITE_BATCH_MIN_HOST_LIMIT","AI_SITE_SUBMISSIONS","AI_SITE_SUBMISSION_CHOICES","id","label","blurb","AI_SITE_EMAIL_TYPE","aiSiteNameSentence","name","aiSiteSubmissions","inputs","value","includes","ID_CHARS","parseAiSiteJobInputs","text","key","undefined","trimmed","trim","length","businessType","businessName","city","brand","audience","starter","rawPages","Number","NaN","isInteger","submissions","rawBatch","batchId","test","welcomeEmail","aiSitePagesRefusal","band","aiSiteWordsSaidAnything","words","Boolean","about","aiSiteWords","read","replace","slice","AI_SITE_CREATE_KINDS","aiSitePlanPrerequisites","seen","Set","prerequisites","add","kind","toLowerCase","has","push","entry","create","aiSitePlanShapeRefusal","options","screens","slugs","screen","sections","title","slug","some","nav","aiSitePlanRefusal","shape","parts","map","noun","where","listed","join","aiPlanPasses","reduce","total","creates","filter","aiPlanCreditEstimate","aiJobPlanCreditEstimate","aiSiteCreditEstimate","passes"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-job.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 { aiSiteKind } from './ai-site-kinds'\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n aiPlanUndeclaredRefs,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\nimport { AI_JOB_CREATE_KINDS } from './ai-job-creations'\nimport { AI_PAGE_CREATE_KINDS } from './ai-page-job'\n\n/**\n * What a site scaffold is (AGL-2911): the inputs a `site` job is admitted\n * with, the plans a scaffold can build, and what one is estimated to cost\n * before it starts.\n *\n * A scaffold is the one job kind that builds a whole site rather than one\n * record: a plan of four to eight screens, the layout they render inside,\n * the contact form they place, a palette suggestion, and — where the email\n * step is loaded — a welcome email. It builds each of those through the job\n * kind that already owns it, so this module describes what a scaffold may\n * ask for and refuses the rest at confirmation, before a credit is spent,\n * naming what to make first.\n *\n * ── The estimate is the guard rail ───────────────────────────────────────\n *\n * A scaffold is tens of model calls, where every other kind is a few, so the\n * member reads what it will cost before they confirm the plan that spends\n * it. The figure is the machine's own nominal credits per step multiplied by\n * the passes the plan implies — an estimate, and said to be one wherever it\n * is shown.\n *\n * This module imports nothing at runtime, so the doors, the step, the batch\n * card and the plan proposal read one vocabulary.\n */\n\n/** How many pages a scaffold builds: enough to be a site, few enough to read. */\nexport const AI_SITE_PAGES = { min: 4, max: 8 } as const\n\n/**\n * How many pages a Free workspace's scaffold builds (AGL-3594): its home page\n * and the one page the brief most needs. A product decision, not a figure the\n * wall derives — the wall's proof (`jobs/ai-job-free-site.spec.ts`) shows a\n * plan and a build of this many pages fit the Free taste with room for one\n * retried section.\n */\nexport const AI_SITE_FREE_PAGES = { min: 1, max: 2 } as const\n\n/** The pages a scaffold builds for a workspace on the Free taste or on a paid plan. */\nexport function aiSitePagesBand(\n freeTaste: boolean | null | undefined,\n): { readonly min: number; readonly max: number } {\n return freeTaste ? AI_SITE_FREE_PAGES : AI_SITE_PAGES\n}\n\n/**\n * The job input a guided site start sets so its plan is confirmed for it\n * (AGL-3594); `jobs/ai-job-auto-confirm.ts` says where it is honored.\n */\nexport const AI_JOB_AUTO_CONFIRM_INPUT = 'autoConfirm'\n\n/** What a Free site start's page selector says under it (AGL-3594). */\nexport const AI_SITE_FREE_PAGES_NOTE = 'Paid plans can generate more pages.'\n\n/**\n * The plan's answer ceiling for a scaffold (AGL-3594), Free and paid. A plan\n * is an outline — each page's title, address, search listing and sections by\n * name — and the build steps write everything else, so a scaffold plans with\n * no thinking and at a ceiling sized to the outline of its largest band with\n * room to spare: about 300 tokens a page. The 13,491-token plan a Free site\n * spent 227 of its 300 credits on was its thinking, at the routing table's\n * 8,000 a call.\n */\nexport const AI_SITE_PLAN_MAX_TOKENS = { free: 2_000, paid: 4_000 } as const\n\n/** What each exchange of a Free site job comes to at its worst, in credits. */\nexport interface AiFreeSiteWorstCase {\n /** The site plan: its answer and its one re-ask, each at its ceiling. */\n plan: number\n /** The site's look (AGL-3660): its one answer at its ceiling, on the fast tier. */\n look: number\n /** The one layout a Free plan includes, built first on a site with none. */\n layout: number\n /** A page's first section pass, which writes the cached prefix. */\n firstSection: number\n /** Each later section pass. */\n laterSection: number\n /** A page's listing. */\n listing: number\n}\n\n/**\n * The Free site start's wall at its worst (AGL-3594): the site plan as\n * `jobs/ai-job-free-site.spec.ts` derives it from the plan request as it\n * stands, on the model a Free site plans with, every answer at its ceiling;\n * the build's exchanges are the Free page's (`AI_FREE_PAGE_WORST_CASE_CREDITS`),\n * which the same spec holds these to. The spec fails when a figure it derives\n * moves and this does not.\n */\nexport const AI_FREE_SITE_WORST_CASE_CREDITS: Readonly<AiFreeSiteWorstCase> = {\n plan: 35,\n look: 7,\n layout: 64,\n firstSection: 44,\n laterSection: 20,\n listing: 3,\n}\n\n/**\n * The most sections a Free site's plan fits in the Free taste at its worst,\n * across all its pages: the plan, the look, the layouts it builds first, each page's\n * listing and first pass, and ROOM FOR ONE RETRIED SECTION, then as many later\n * passes as the rest pays for; none when that is already past the wall. Every\n * page's first pass is counted as a cache write, which errs dear.\n */\nexport function aiFreeSiteSectionsWithin(\n creations: { layouts: number; pages: number },\n taste: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const pages = Math.max(1, Math.floor(creations.pages))\n const before =\n credits.plan +\n credits.look +\n creations.layouts * credits.layout +\n pages * (credits.listing + credits.firstSection) +\n credits.firstSection\n if (before > taste) return 0\n return pages + Math.floor((taste - before) / credits.laterSection)\n}\n\n/** Sections a Free site's page is assumed to hold before its plan names them. */\nexport const AI_FREE_SITE_NOMINAL_SECTIONS = 3\n\n/**\n * About what a Free site start of this many pages costs at its worst, in\n * credits, for the dialog that asks for one: the site plan, its look, each page's\n * listing and its sections at the nominal count, from the figures the wall is\n * proven with rather than the nominal credits a paid estimate counts.\n */\nexport function aiFreeSiteCreditEstimate(\n pages: number,\n credits: Readonly<AiFreeSiteWorstCase> = AI_FREE_SITE_WORST_CASE_CREDITS,\n): number {\n const count = Math.max(1, Math.floor(pages))\n return (\n credits.plan +\n credits.look +\n count * (credits.listing + credits.firstSection) +\n count * (AI_FREE_SITE_NOMINAL_SECTIONS - 1) * credits.laterSection\n )\n}\n\n/**\n * The most sections one scaffolded page may hold. The plan model allows more\n * for a page job, which builds one page; a scaffold builds eight, and each\n * section is a pass of its own.\n */\nexport const AI_SITE_MAX_SECTIONS = 8\n\n/**\n * Sections a page is assumed to hold before a plan names them, for the\n * estimate a member reads when they ask for a scaffold. A declared\n * assumption, not a measurement: the plan's own section counts replace it\n * the moment the plan exists.\n */\nexport const AI_SITE_NOMINAL_SECTIONS = 5\n\n/**\n * The credits one pass is held against: the machine's\n * `AI_JOB_STEP_RESERVE_CREDITS`, declared here so the estimate stays a pure\n * number the console computes without the Admin SDK. `ai-site-job.spec.ts`\n * holds the two equal.\n */\nexport const AI_SITE_PASS_CREDITS = 50\n\n/** The longest a scaffold's scalar inputs may run; they are names, not copy. */\nexport const AI_SITE_INPUT_MAX_CHARS = 120\n\n/** The sites one agency batch may generate for in one request. */\nexport const AI_SITE_BATCH_MAX = 25\n\n/**\n * The sites a plan must hold before the org Sites page offers the batch at\n * all: the Agency and Advanced bands, which the plan's `hostLimit` is what\n * distinguishes.\n */\nexport const AI_SITE_BATCH_MIN_HOST_LIMIT = 25\n\nexport interface AiSiteJobInputs {\n /** What the business is, in a few words: the brief's subject. */\n businessType: string\n /**\n * Who the site is for, in a few words (AGL-2918); empty when nobody said.\n * The plan step puts it in front of the model on a line of its own, as it\n * does every other scalar here, so it narrows the pages a plan proposes\n * without a prompt of its own.\n */\n audience: string\n /**\n * The starter site whose shape the person liked (AGL-2918), by its\n * `STARTER_TEMPLATES` id; empty when they picked none.\n *\n * A hint carried as the id rather than the starter, because an id is what\n * the guided start's list, this door and the plan's line all agree on\n * without any of them loading a catalog of node maps. Unrecognized ids are\n * admitted for the same reason an unrecognized business type is: it is a\n * few words in a brief, not a lookup.\n */\n starter: string\n /**\n * The kind of site the person picked (AGL-3660), by its `AI_SITE_KINDS`\n * id; empty when nobody picked one, and the business type suggests it.\n * It sets the look the site is designed in and how its pages are arranged.\n */\n siteKind: string\n /** How many pages the member asked for. */\n pages: number\n /** The per-site variables an agency batch varies; empty when none was given. */\n businessName: string\n city: string\n brand: string\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /**\n * Where the contact form's submissions go (AGL-2918); `null` where nobody\n * said, which is every job created before the question existed and every\n * door that does not ask it.\n *\n * The one setting a new site owner actually needs made, and the one thing\n * about a contact form a model cannot know: whether a submission is a note\n * to read or a lead to chase is a decision about how the business runs. So\n * an answer BINDS the form step rather than hinting to it, and `null`\n * leaves the model's own proposal standing, exactly as it stood before.\n */\n submissions: AiSiteSubmissions | null\n /** The batch this job was created under (AGL-2911); `null` for a single site. */\n batchId: string | null\n}\n\n/**\n * Where a site's contact form sends what a visitor writes.\n *\n * Two, not three. The form step's own vocabulary has a third — a mailing\n * list — which it can only answer with a note, because the stored routing has\n * no place for a list; offering it as a question would be asking somebody to\n * pick an outcome the platform then explains it cannot store.\n */\nexport const AI_SITE_SUBMISSIONS = ['inbox', 'lead'] as const\n\nexport type AiSiteSubmissions = (typeof AI_SITE_SUBMISSIONS)[number]\n\n/**\n * What each answer to \"where do submissions go\" is called, and what it means.\n *\n * Here rather than beside the guided start's own questions, because it is not\n * only the guided start that asks: the agency batch asks the same thing of a\n * whole run (AGL-2918), and two surfaces wording one decision two ways is two\n * decisions. `ai-site-job.spec.ts` holds the list to {@link AI_SITE_SUBMISSIONS}.\n */\nexport const AI_SITE_SUBMISSION_CHOICES: ReadonlyArray<{\n id: AiSiteSubmissions\n label: string\n blurb: string\n}> = [\n {\n id: 'inbox',\n label: 'The Inbox',\n blurb: 'Every message is one to read and reply to.',\n },\n {\n id: 'lead',\n label: 'The Inbox, and CRM as a lead',\n blurb: 'Every message with an email address is also somebody to follow up.',\n },\n]\n\n/**\n * Which kind of email a scaffold's email unit is (AGL-2918), carried on the\n * derived job as `inputs.emailType`. The email step names the kind on the one\n * line it puts above the brief, and a scaffold was naming none.\n *\n * A key of the email step's own `AI_EMAIL_TYPES`, held here because neither\n * step may import the other: a scaffold delegates by job kind, and a\n * deployment that scaffolds is not necessarily one that has loaded the email\n * step. `ai-job-email-step.spec.ts` holds this key to that catalog.\n *\n * `reply` and not `welcome`. The five kinds a member picks are campaigns\n * somebody sends to an audience, and this is an answer to one person who\n * wrote in through the site; the nearest, `welcome`, is a label about signing\n * up, and a kind that misdescribes the email is worse than an absent one\n * because it is the first thing the model is told.\n */\nexport const AI_SITE_EMAIL_TYPE = 'reply'\n\n/**\n * What a site job's plan and every unit it builds are told about the\n * business's name (AGL-3596): that it is the name, used as written. Said on\n * the job's own turn, never in a cached system block, because it is one\n * site's. A model handed only what the business does named it itself —\n * \"Hillside Dog Grooming\" was built as \"Austin Paws Grooming\".\n */\nexport function aiSiteNameSentence(name: string): string {\n return `The business is named “${name}”: the site names it exactly that way, in the header, the footer, the copy and the search listing, and never by any other name.`\n}\n\n/** Where a job's inputs say submissions go, or `null` where they do not say. */\nexport function aiSiteSubmissions(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteSubmissions | null {\n const value = inputs?.['submissions']\n return (AI_SITE_SUBMISSIONS as readonly unknown[]).includes(value)\n ? (value as AiSiteSubmissions)\n : null\n}\n\nconst ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/\n\n/** A scaffold's inputs, or the sentence the door refuses them with. */\nexport function parseAiSiteJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteJobInputs | string {\n const text = (key: string): string | null => {\n const value = inputs?.[key]\n if (value === undefined || value === null || value === '') return ''\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > AI_SITE_INPUT_MAX_CHARS ? null : trimmed\n }\n const businessType = text('businessType')\n if (businessType === null) {\n return `businessType must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n if (!businessType) return 'Say what kind of business the site is for'\n const businessName = text('businessName')\n const city = text('city')\n const brand = text('brand')\n const audience = text('audience')\n const starter = text('starter')\n // A kind this deployment does not know is nobody having picked one.\n const siteKind = aiSiteKind(inputs?.['siteKind'])?.id ?? ''\n for (const [key, value] of [\n ['businessName', businessName],\n ['city', city],\n ['brand', brand],\n ['audience', audience],\n ['starter', starter],\n ] as const) {\n if (value === null)\n return `${key} must be text under ${AI_SITE_INPUT_MAX_CHARS} characters`\n }\n const rawPages = inputs?.['pages']\n const pages =\n typeof rawPages === 'number' ? rawPages : Number(rawPages ?? NaN)\n // Read here across both bands; the door holds a workspace to its own\n // (`aiSitePagesRefusal`), since only it knows the workspace's plan.\n if (\n !Number.isInteger(pages) ||\n pages < AI_SITE_FREE_PAGES.min ||\n pages > AI_SITE_PAGES.max\n ) {\n return `pages must be a whole number from ${AI_SITE_FREE_PAGES.min} to ${AI_SITE_PAGES.max}`\n }\n // Where submissions go is admitted only as one of the two the form step\n // can actually bind; anything else is nobody having said, and the model\n // proposes as it always did rather than the door refusing the whole job.\n const submissions = aiSiteSubmissions(inputs)\n const rawBatch = inputs?.['batchId']\n const batchId =\n rawBatch === undefined || rawBatch === null || rawBatch === ''\n ? null\n : typeof rawBatch === 'string' && ID_CHARS.test(rawBatch)\n ? rawBatch\n : undefined\n if (batchId === undefined) return 'batchId is not a batch id'\n return {\n businessType,\n audience: audience as string,\n starter: starter as string,\n siteKind,\n pages,\n businessName: businessName as string,\n city: city as string,\n brand: brand as string,\n welcomeEmail: inputs?.['welcomeEmail'] !== false,\n submissions,\n batchId,\n }\n}\n\n/**\n * Why a workspace cannot ask for this many pages (AGL-3594), in a sentence a\n * member reads; `null` when it can. A Free workspace builds one or two, and a\n * paid one four to eight, as it always has. Refused rather than clamped: the\n * guided start offers only the band, so a request outside it was not made\n * there, and a job quietly building fewer pages than it was asked for is a\n * worse answer than a sentence saying why.\n */\nexport function aiSitePagesRefusal(pages: number, freeTaste: boolean | null | undefined): string | null {\n const band = aiSitePagesBand(freeTaste)\n if (pages >= band.min && pages <= band.max) return null\n return freeTaste\n ? `A Free workspace's AI site start builds ${AI_SITE_FREE_PAGES.min} or ${AI_SITE_FREE_PAGES.max} pages. ${AI_SITE_FREE_PAGES_NOTE}`\n : `A site is planned with ${band.min} to ${band.max} pages.`\n}\n\n/**\n * What the site itself is, as a step that is NOT the scaffold reads it off a\n * job's inputs (AGL-2918).\n *\n * A scaffold delegates unit by unit under a job derived from its own — a page\n * job, a form job — and the derived job carries the scaffold's inputs. So the\n * two answers that describe the site rather than one record of it are\n * readable by every delegated step, and a step that wants them does not have\n * to know it was delegated to.\n *\n * Lenient where {@link parseAiSiteJobInputs} is strict, because it reads the\n * inputs of jobs that are not scaffolds: a page job started from the Screens\n * page has no `businessType` and no `pages`, and that is not an error here —\n * it is a site nobody described, and the answer is two empty strings.\n */\nexport interface AiSiteWords {\n /** What kind of site it is; empty when the job's inputs do not say. */\n about: string\n /** Who it is for; empty when the job's inputs do not say. */\n audience: string\n}\n\n/** Whether either half of {@link AiSiteWords} says anything. */\nexport function aiSiteWordsSaidAnything(words: AiSiteWords): boolean {\n return Boolean(words.about || words.audience)\n}\n\n/** What a job's inputs say the site is, read defensively and trimmed to the input ceiling. */\nexport function aiSiteWords(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiSiteWords {\n const read = (key: string): string => {\n const value = inputs?.[key]\n if (typeof value !== 'string') return ''\n return value.replace(/\\s+/g, ' ').trim().slice(0, AI_SITE_INPUT_MAX_CHARS).trim()\n }\n return { about: read('businessType'), audience: read('audience') }\n}\n\n/**\n * What a scaffold builds for itself: the layout its pages render inside, the\n * form they place, and the palette suggestion a member applies in the Theme\n * section. Anything else a plan asks to create is a job of its own.\n */\nexport const AI_SITE_CREATE_KINDS: readonly AiBuildPlanCreateKind[] = [\n 'layout',\n 'form',\n 'theme-change',\n]\n\n/** The creations a scaffold cannot build, in the order the plan lists them, each named once. */\nexport function aiSitePlanPrerequisites(\n plan: AiBuildPlan,\n): Array<{ kind: AiBuildPlanCreateKind; name: string }> {\n const seen = new Set<string>()\n const prerequisites: Array<{ kind: AiBuildPlanCreateKind; name: string }> = []\n const add = (kind: AiBuildPlanCreateKind, name: string) => {\n const key = `${kind}:${name.toLowerCase()}`\n if (seen.has(key)) return\n seen.add(key)\n prerequisites.push({ kind, name })\n }\n for (const entry of plan.create) {\n if (!AI_SITE_CREATE_KINDS.includes(entry.kind)) add(entry.kind, entry.name)\n }\n // A reference the create list does not carry is refused by the plan rules\n // before a plan is kept: rule 2 for a screen's layout, rule 7 for anything\n // else (`plan-creation-undeclared`, AGL-3040). A plan confirmed before that\n // rule existed can still carry one. It is named as a component: the\n // reference carries no kind, and the refusal must say where one is made.\n for (const { name } of aiPlanUndeclaredRefs(plan)) add('component', name)\n return prerequisites\n}\n\n/**\n * Why a scaffold cannot build a plan of this SHAPE — the page band, a page's\n * sections, its addresses, its navigation — in a sentence a member reads;\n * `null` when the shape is one it builds. The plan step holds a plan to it\n * with a re-ask (AGL-3030), and the doors hold a confirmed plan to it again\n * through `aiSitePlanRefusal`.\n */\nexport function aiSitePlanShapeRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const { screens } = plan\n const band = aiSitePagesBand(options.freeTaste)\n if (screens.length < band.min || screens.length > band.max) {\n return `This plan builds ${screens.length} ${screens.length === 1 ? 'page' : 'pages'}, and ${\n options.freeTaste ? \"a Free workspace's site scaffold\" : 'a site scaffold'\n } builds ${band.min} to ${band.max}. Describe the site again.`\n }\n const slugs = new Set<string>()\n for (const screen of screens) {\n if (!screen.sections.length) {\n return `The page “${screen.title}” has no sections to build. Describe the site again.`\n }\n if (screen.sections.length > AI_SITE_MAX_SECTIONS) {\n return `The page “${screen.title}” has ${screen.sections.length} sections, and a scaffolded page holds ${AI_SITE_MAX_SECTIONS}. Describe the site again.`\n }\n const slug = screen.slug.trim().toLowerCase()\n if (slugs.has(slug)) {\n return `Two pages in this plan share the address ${screen.slug}. Describe the site again.`\n }\n slugs.add(slug)\n }\n if (!screens.some((screen) => screen.nav)) {\n return 'No page in this plan is in the site navigation. Describe the site again.'\n }\n return null\n}\n\n/**\n * Why a scaffold cannot build this plan, in a sentence a member reads when\n * confirming it; `null` when it can.\n */\nexport function aiSitePlanRefusal(\n plan: AiBuildPlan,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const shape = aiSitePlanShapeRefusal(plan, options)\n if (shape) return shape\n const prerequisites = aiSitePlanPrerequisites(plan)\n if (!prerequisites.length) return null\n const parts = prerequisites.map(\n ({ kind, name }) =>\n `the ${AI_BUILD_PLAN_CREATION_NOUNS[kind].noun} “${name}” ${AI_BUILD_PLAN_CREATION_NOUNS[kind].where}`,\n )\n const listed =\n parts.length === 1\n ? parts[0]\n : `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`\n return `This site needs what the workspace does not have yet. Create ${listed}, then describe the site again.`\n}\n\n/** What the estimate counts a plan in: whether a welcome email follows, and the creations the job builds. */\nexport interface AiPlanPassOptions {\n welcomeEmail?: boolean\n /** The creation kinds the job builds itself; a scaffold's when absent. */\n creates?: readonly AiBuildPlanCreateKind[]\n}\n\n/**\n * The passes a plan implies: one per section of every screen, one more per\n * screen for its search listing and its draft, and one for each thing the\n * plan creates that the job builds. What the estimate is counted in.\n */\nexport function aiPlanPasses(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n const screens = plan.screens.reduce(\n (total, screen) => total + screen.sections.length + 1,\n 0,\n )\n const creates = options.creates ?? AI_SITE_CREATE_KINDS\n const creations = plan.create.filter((entry) => creates.includes(entry.kind)).length\n return screens + creations + (options.welcomeEmail ? 1 : 0)\n}\n\n/**\n * About what a plan costs to build, in credits. An ESTIMATE: the nominal\n * credits the machine holds per step, times the passes the plan implies.\n * What each pass actually costs is its model's tokens, recorded on the job\n * as it runs.\n */\nexport function aiPlanCreditEstimate(plan: AiBuildPlan, options: AiPlanPassOptions = {}): number {\n return aiPlanPasses(plan, options) * AI_SITE_PASS_CREDITS\n}\n\n/**\n * About what a job of this kind costs to build from its plan (AGL-3031): a\n * page job counts the layout, forms and components it builds before its\n * page; a template, layout, component, form or email job counts its own\n * record and every creation it builds before it (AGL-3143 §15); and a\n * scaffold counts what it builds. The figure the proposal shows beside\n * Confirm, and the hold a confirmation takes.\n */\nexport function aiJobPlanCreditEstimate(kind: string, plan: AiBuildPlan): number {\n const creates =\n kind === 'page' ? AI_PAGE_CREATE_KINDS : AI_JOB_CREATE_KINDS[kind as keyof typeof AI_JOB_CREATE_KINDS]\n return aiPlanCreditEstimate(plan, creates ? { creates } : {})\n}\n\n/**\n * About what a scaffold of this many pages costs, before a plan names its\n * sections: the same arithmetic on the nominal section count, plus the plan\n * step that proposes it. Shown where a member asks for a scaffold.\n */\nexport function aiSiteCreditEstimate(\n pages: number,\n options: { welcomeEmail?: boolean } = {},\n): number {\n const screens = Math.max(0, Math.floor(pages))\n const passes =\n 1 +\n screens * (AI_SITE_NOMINAL_SECTIONS + 1) +\n 2 +\n (options.welcomeEmail ? 1 : 0)\n return passes * AI_SITE_PASS_CREDITS\n}\n"],"names":["aiSiteKind","AI_BUILD_PLAN_CREATION_NOUNS","aiPlanUndeclaredRefs","AI_JOB_CREATE_KINDS","AI_PAGE_CREATE_KINDS","AI_SITE_PAGES","min","max","AI_SITE_FREE_PAGES","aiSitePagesBand","freeTaste","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES_NOTE","AI_SITE_PLAN_MAX_TOKENS","free","paid","AI_FREE_SITE_WORST_CASE_CREDITS","plan","look","layout","firstSection","laterSection","listing","aiFreeSiteSectionsWithin","creations","taste","credits","pages","Math","floor","before","layouts","AI_FREE_SITE_NOMINAL_SECTIONS","aiFreeSiteCreditEstimate","count","AI_SITE_MAX_SECTIONS","AI_SITE_NOMINAL_SECTIONS","AI_SITE_PASS_CREDITS","AI_SITE_INPUT_MAX_CHARS","AI_SITE_BATCH_MAX","AI_SITE_BATCH_MIN_HOST_LIMIT","AI_SITE_SUBMISSIONS","AI_SITE_SUBMISSION_CHOICES","id","label","blurb","AI_SITE_EMAIL_TYPE","aiSiteNameSentence","name","aiSiteSubmissions","inputs","value","includes","ID_CHARS","parseAiSiteJobInputs","text","key","undefined","trimmed","trim","length","businessType","businessName","city","brand","audience","starter","siteKind","rawPages","Number","NaN","isInteger","submissions","rawBatch","batchId","test","welcomeEmail","aiSitePagesRefusal","band","aiSiteWordsSaidAnything","words","Boolean","about","aiSiteWords","read","replace","slice","AI_SITE_CREATE_KINDS","aiSitePlanPrerequisites","seen","Set","prerequisites","add","kind","toLowerCase","has","push","entry","create","aiSitePlanShapeRefusal","options","screens","slugs","screen","sections","title","slug","some","nav","aiSitePlanRefusal","shape","parts","map","noun","where","listed","join","aiPlanPasses","reduce","total","creates","filter","aiPlanCreditEstimate","aiJobPlanCreditEstimate","aiSiteCreditEstimate","passes"],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The kinds of website a guided start offers (AGL-3660), and the style
19
+ * direction each one sets.
20
+ *
21
+ * A kind is NOT a template. It is a direction in two halves:
22
+ *
23
+ * - a LOOK FAMILY the code applies — how saturated the palette is, the hues
24
+ * that suit the kind, the page grounds, the font pairings, the corners,
25
+ * the button style, the heading scale and the density — inside which the
26
+ * model picks this business's own look and a per-job seed varies it
27
+ * (`ai-site-look.ts`), so two sites of one kind never share a theme;
28
+ * - GUIDANCE the model reads: how the kind's pages are usually arranged
29
+ * (band rhythm, image share, density) and which pages it usually has.
30
+ *
31
+ * Every page a kind suggests is one the site job builds itself out of the
32
+ * layout language: sections, cards, images, a form. On a paid plan the site
33
+ * job also writes a blog's first posts into a content collection and a
34
+ * store's first products as unpriced drafts (AGL-3676,
35
+ * `jobs/ai-job-site-content.ts`), and the pages are told their titles; on
36
+ * the Free taste it writes neither. No event records are created, and no
37
+ * guidance here promises posts, products or events, since the plan reads it
38
+ * on every plan (the owner adds more from the Content, Commerce and Events
39
+ * sections afterward).
40
+ *
41
+ * Pure data and two small readers, safe for the console to import: no
42
+ * theme compiler, no network.
43
+ */
44
+ /** A font pairing the look may choose: a heading family over a body family. */
45
+ export interface AiSiteFontPairing {
46
+ id: string;
47
+ /** What the pairing reads like, as the model is told. */
48
+ feel: string;
49
+ heading: {
50
+ family: string;
51
+ category: 'sans-serif' | 'serif';
52
+ weight: number;
53
+ sub: number;
54
+ };
55
+ body: {
56
+ family: string;
57
+ category: 'sans-serif' | 'serif';
58
+ weights: number[];
59
+ };
60
+ /** Letter spacing the headings carry, in em. */
61
+ tracking?: number;
62
+ /** Headings set in capitals: a condensed display face. */
63
+ caps?: boolean;
64
+ }
65
+ /**
66
+ * The pairings, every family from the Google Fonts catalog the fonts plugin
67
+ * serves (`google-fonts.catalog.json`) and every weight one that family
68
+ * has — `ai-site-look.spec.ts` holds both. The tenant loads them the way it
69
+ * loads the starter theme's family, so nothing here adds a font host.
70
+ */
71
+ export declare const AI_SITE_FONT_PAIRINGS: readonly AiSiteFontPairing[];
72
+ export type AiSiteFontPairingId = string;
73
+ /**
74
+ * The platform's built-in themes a site's look is layered on, by the short
75
+ * name of the themes plugin's preset id (\`theme-presets.<name>\`), and
76
+ * \`starter\`: the theme a new site is born with (AGL-3497).
77
+ */
78
+ export declare const AI_SITE_BASES: readonly ["starter", "material-ui", "bootstrap", "minimal", "material3", "ant-design", "fluent", "carbon", "cupertino"];
79
+ export type AiSiteBase = (typeof AI_SITE_BASES)[number];
80
+ /** How saturated a kind's palette runs. `neutral` is an ink-and-paper ground with one accent. */
81
+ export type AiSiteChroma = 'neutral' | 'muted' | 'balanced' | 'vivid';
82
+ /** The page ground the light scheme sits on. */
83
+ export type AiSiteGround = 'white' | 'warm' | 'cool' | 'tinted';
84
+ export declare const AI_SITE_GROUNDS: readonly AiSiteGround[];
85
+ /** Corner rounding, cards and fields. */
86
+ export type AiSiteCorners = 'sharp' | 'soft' | 'round' | 'pill';
87
+ export declare const AI_SITE_CORNERS: readonly AiSiteCorners[];
88
+ /** How buttons are drawn: on the corner radius, as pills, square, in capitals, or with quiet buttons as underlined links. */
89
+ export type AiSiteButtons = 'rounded' | 'pill' | 'square' | 'caps' | 'link';
90
+ /**
91
+ * How a site's pages alternate their bands: calm (mostly plain, one soft),
92
+ * alternating (plain and soft by turns), or bold (brand and dark bands for
93
+ * its key moments).
94
+ */
95
+ export type AiSiteRhythm = 'calm' | 'alternating' | 'bold';
96
+ export declare const AI_SITE_RHYTHMS: readonly AiSiteRhythm[];
97
+ /** Spacing: tighter, regular or airier than the platform's unit. */
98
+ export type AiSiteDensity = 'compact' | 'regular' | 'airy';
99
+ /** What the code applies for a kind: the bounds a look is chosen and varied in. */
100
+ export interface AiSiteLookFamily {
101
+ /** The platform's base themes that suit the kind, best first (`AI_SITE_BASES`). */
102
+ bases: readonly AiSiteBase[];
103
+ chroma: AiSiteChroma;
104
+ /** How the header is arranged: the brand at the start with the links after it, or both centered. */
105
+ headerAligns?: readonly ('start' | 'center')[];
106
+ /** The band rhythm its pages lean to (`AI_SITE_RHYTHMS`). */
107
+ rhythms?: readonly AiSiteRhythm[];
108
+ /** Hue ranges in degrees that suit the kind, `[from, to]`, wrapping past 360. */
109
+ hues: ReadonlyArray<readonly [number, number]>;
110
+ grounds: readonly AiSiteGround[];
111
+ fonts: readonly AiSiteFontPairingId[];
112
+ corners: readonly AiSiteCorners[];
113
+ buttons: AiSiteButtons;
114
+ /** Display and heading sizes against the platform's, 1 = as shipped. */
115
+ headingScale: number;
116
+ density: AiSiteDensity;
117
+ }
118
+ export interface AiSiteKind {
119
+ /** A persisted identifier: the guided start's answer and the job's `siteKind` input. */
120
+ id: string;
121
+ /** The card's title, in customer copy. */
122
+ label: string;
123
+ /** One line under it. */
124
+ blurb: string;
125
+ /** A word from the icon set the card shows. */
126
+ icon: string;
127
+ /** Words in "what kind of site" that suggest this kind, matched whole. */
128
+ keywords: readonly string[];
129
+ /** The starter whose shape this kind follows, where one fits (`STARTER_TEMPLATES`). */
130
+ starter: string;
131
+ look: AiSiteLookFamily;
132
+ /** How its pages are arranged, as every page and the header and footer are told. */
133
+ design: string;
134
+ /** The pages it usually has, as the plan is told. */
135
+ pages: string;
136
+ }
137
+ /**
138
+ * The kinds, in the order the guided start shows them. Drawn from the template
139
+ * categories the large site builders offer, merged where two would build the
140
+ * same site: designer and agency are one studio, law and accounting one
141
+ * practice, health and therapy one wellness kind.
142
+ */
143
+ export declare const AI_SITE_KINDS: readonly AiSiteKind[];
144
+ /** A kind by id, or `null`. */
145
+ export declare function aiSiteKind(id: unknown): AiSiteKind | null;
146
+ /** A pairing by id, or `null`. */
147
+ export declare function aiSiteFontPairing(id: unknown): AiSiteFontPairing | null;
148
+ /**
149
+ * The kind a "what kind of site" answer suggests, or `business` when nothing
150
+ * in it matches: the card the guided start selects until the person picks
151
+ * another. A longer keyword wins over a shorter one ("wedding photographer"
152
+ * is photography, not events), and of two as long the later one wins, since
153
+ * English puts the noun last ("a food blog" is a blog).
154
+ */
155
+ export declare function aiSiteKindFor(siteType: string): AiSiteKind;
156
+ /** The kind a job's inputs name, else the one its business type suggests; `null` for a job that names neither. */
157
+ export declare function aiSiteKindOfInputs(inputs: Readonly<Record<string, unknown>> | null | undefined): AiSiteKind | null;
158
+ /** The site's own look tokens a unit is handed (AGL-3660), read defensively. */
159
+ export declare function aiSiteStyleTokens(inputs: Readonly<Record<string, unknown>> | null | undefined): {
160
+ headerAlign?: 'start' | 'center';
161
+ rhythm?: AiSiteRhythm;
162
+ };
163
+ /** The style lines a page, the header and the footer are told for the job's kind and look; empty for none. */
164
+ export declare function aiSiteKindDesignLines(inputs: Readonly<Record<string, unknown>> | null | undefined): string[];