@aglyn/plugins-ai 1.0.0-beta.228 → 1.0.0-beta.230

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/README.md +3 -3
  2. package/package.json +17 -17
  3. package/src/lib/activity/ai-activity-actions.js +7 -1
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-automation-zones.d.ts +38 -0
  6. package/src/lib/components/ai-automation-zones.js +5 -1
  7. package/src/lib/components/ai-automation-zones.js.map +1 -1
  8. package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
  9. package/src/lib/components/ai-brief-dialog.component.js +87 -8
  10. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  11. package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
  12. package/src/lib/components/ai-campaign-create.component.js +270 -0
  13. package/src/lib/components/ai-campaign-create.component.js.map +1 -0
  14. package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
  15. package/src/lib/components/ai-describe-automation.component.js +15 -6
  16. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-button.component.d.ts +20 -8
  18. package/src/lib/components/ai-describe-button.component.js +19 -36
  19. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  20. package/src/lib/components/ai-describe-email.component.d.ts +31 -0
  21. package/src/lib/components/ai-describe-email.component.js +22 -0
  22. package/src/lib/components/ai-describe-email.component.js.map +1 -0
  23. package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
  24. package/src/lib/components/ai-describe-org-automation.component.js +238 -0
  25. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
  26. package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
  27. package/src/lib/components/ai-experiment-cards.component.js +104 -18
  28. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  29. package/src/lib/components/ai-explain-automation.component.js +1 -5
  30. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  31. package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
  32. package/src/lib/components/ai-funnel-zones.component.js +178 -0
  33. package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
  34. package/src/lib/components/ai-insight-card.component.d.ts +42 -0
  35. package/src/lib/components/ai-insight-card.component.js +137 -0
  36. package/src/lib/components/ai-insight-card.component.js.map +1 -0
  37. package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
  38. package/src/lib/components/ai-insight-dialog.component.js +9 -3
  39. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  40. package/src/lib/components/ai-job-plan.component.d.ts +7 -2
  41. package/src/lib/components/ai-job-plan.component.js +35 -5
  42. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  43. package/src/lib/components/ai-job-requests.d.ts +17 -1
  44. package/src/lib/components/ai-job-requests.js +39 -3
  45. package/src/lib/components/ai-job-requests.js.map +1 -1
  46. package/src/lib/components/ai-jobs-list-page.component.js +12 -1
  47. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  48. package/src/lib/components/ai-logic-zones.d.ts +64 -0
  49. package/src/lib/components/ai-logic-zones.js +18 -0
  50. package/src/lib/components/ai-logic-zones.js.map +1 -0
  51. package/src/lib/components/ai-logic.component.d.ts +77 -0
  52. package/src/lib/components/ai-logic.component.js +553 -0
  53. package/src/lib/components/ai-logic.component.js.map +1 -0
  54. package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
  55. package/src/lib/components/ai-marketing-insight.component.js +80 -0
  56. package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
  57. package/src/lib/components/ai-media-create.component.d.ts +55 -0
  58. package/src/lib/components/ai-media-create.component.js +433 -0
  59. package/src/lib/components/ai-media-create.component.js.map +1 -0
  60. package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
  61. package/src/lib/components/ai-overlay-cards.component.js +505 -0
  62. package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
  63. package/src/lib/components/ai-product-zones.d.ts +5 -0
  64. package/src/lib/components/ai-product-zones.js +1 -1
  65. package/src/lib/components/ai-product-zones.js.map +1 -1
  66. package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
  67. package/src/lib/components/ai-products-create-button.component.js +67 -0
  68. package/src/lib/components/ai-products-create-button.component.js.map +1 -0
  69. package/src/lib/components/ai-products-hub-card.component.js +100 -81
  70. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  71. package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
  72. package/src/lib/components/ai-revise-automation.component.js +207 -0
  73. package/src/lib/components/ai-revise-automation.component.js.map +1 -0
  74. package/src/lib/components/ai-site-build-page.component.d.ts +1 -1
  75. package/src/lib/components/ai-site-build-page.component.js +177 -20
  76. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  77. package/src/lib/components/ai-site-start-card.component.d.ts +10 -1
  78. package/src/lib/components/ai-site-start-card.component.js +7 -4
  79. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  80. package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
  81. package/src/lib/components/ai-upsell-dialog.component.js +177 -0
  82. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
  83. package/src/lib/components/assist-build-card.component.d.ts +42 -0
  84. package/src/lib/components/assist-build-card.component.js +183 -0
  85. package/src/lib/components/assist-build-card.component.js.map +1 -0
  86. package/src/lib/components/assist-edit-canvas.d.ts +2 -0
  87. package/src/lib/components/assist-edit-canvas.js +56 -1
  88. package/src/lib/components/assist-edit-canvas.js.map +1 -1
  89. package/src/lib/components/assist-jobs-drawer.component.d.ts +11 -0
  90. package/src/lib/components/assist-jobs-drawer.component.js +85 -4
  91. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  92. package/src/lib/components/assist-panel.component.js +51 -2
  93. package/src/lib/components/assist-panel.component.js.map +1 -1
  94. package/src/lib/components/use-ai-job-run.d.ts +3 -14
  95. package/src/lib/components/use-ai-job-run.js +0 -50
  96. package/src/lib/components/use-ai-job-run.js.map +1 -1
  97. package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
  98. package/src/lib/jobs/ai-build-capabilities.js +256 -0
  99. package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
  100. package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
  101. package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
  102. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
  103. package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
  104. package/src/lib/jobs/ai-job-build-step.js +657 -0
  105. package/src/lib/jobs/ai-job-build-step.js.map +1 -0
  106. package/src/lib/jobs/ai-job-draft-ids.js +9 -1
  107. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  108. package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
  109. package/src/lib/jobs/ai-job-drafts.js +25 -4
  110. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  111. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +70 -0
  112. package/src/lib/jobs/ai-job-layout-site-pages.js +216 -0
  113. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -0
  114. package/src/lib/jobs/ai-job-layout-step.d.ts +2 -2
  115. package/src/lib/jobs/ai-job-layout-step.js +19 -1
  116. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  117. package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
  118. package/src/lib/jobs/ai-job-logic-step.js +395 -0
  119. package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
  120. package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
  121. package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
  122. package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
  123. package/src/lib/jobs/ai-job-page-step.js +6 -6
  124. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  125. package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
  126. package/src/lib/jobs/ai-job-plan-step.js +79 -11
  127. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  128. package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
  129. package/src/lib/jobs/ai-job-site-step.js +207 -58
  130. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  131. package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
  132. package/src/lib/jobs/ai-job-text-step.js +12 -0
  133. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  134. package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
  135. package/src/lib/jobs/ai-job-workflow-step.js +318 -13
  136. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  137. package/src/lib/jobs/ai-jobs.d.ts +90 -2
  138. package/src/lib/jobs/ai-jobs.js +495 -61
  139. package/src/lib/jobs/ai-jobs.js.map +1 -1
  140. package/src/lib/jobs/ai-logic-records.d.ts +40 -0
  141. package/src/lib/jobs/ai-logic-records.js +55 -0
  142. package/src/lib/jobs/ai-logic-records.js.map +1 -0
  143. package/src/lib/jobs/ai-site-publish.d.ts +7 -0
  144. package/src/lib/jobs/ai-site-publish.js +9 -0
  145. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  146. package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
  147. package/src/lib/jobs/ai-workflow-records.js +40 -1
  148. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  149. package/src/lib/model/ai-automation-draft.d.ts +18 -0
  150. package/src/lib/model/ai-automation-draft.js +86 -0
  151. package/src/lib/model/ai-automation-draft.js.map +1 -1
  152. package/src/lib/model/ai-build-job.d.ts +201 -0
  153. package/src/lib/model/ai-build-job.js +471 -0
  154. package/src/lib/model/ai-build-job.js.map +1 -0
  155. package/src/lib/model/ai-build-plan.d.ts +61 -0
  156. package/src/lib/model/ai-build-plan.js +154 -9
  157. package/src/lib/model/ai-build-plan.js.map +1 -1
  158. package/src/lib/model/ai-build-progress.d.ts +56 -0
  159. package/src/lib/model/ai-build-progress.js +128 -0
  160. package/src/lib/model/ai-build-progress.js.map +1 -0
  161. package/src/lib/model/ai-insight.d.ts +20 -10
  162. package/src/lib/model/ai-insight.js +72 -9
  163. package/src/lib/model/ai-insight.js.map +1 -1
  164. package/src/lib/model/ai-job-activity.js +3 -1
  165. package/src/lib/model/ai-job-activity.js.map +1 -1
  166. package/src/lib/model/ai-job-failure-copy.d.ts +20 -1
  167. package/src/lib/model/ai-job-failure-copy.js +28 -1
  168. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  169. package/src/lib/model/ai-job-notice.d.ts +3 -0
  170. package/src/lib/model/ai-job-notice.js +11 -2
  171. package/src/lib/model/ai-job-notice.js.map +1 -1
  172. package/src/lib/model/ai-jobs.types.d.ts +87 -2
  173. package/src/lib/model/ai-jobs.types.js +3 -1
  174. package/src/lib/model/ai-jobs.types.js.map +1 -1
  175. package/src/lib/model/ai-logic-job.d.ts +108 -0
  176. package/src/lib/model/ai-logic-job.js +327 -0
  177. package/src/lib/model/ai-logic-job.js.map +1 -0
  178. package/src/lib/model/ai-media-credits.d.ts +52 -0
  179. package/src/lib/model/ai-media-credits.js +61 -0
  180. package/src/lib/model/ai-media-credits.js.map +1 -0
  181. package/src/lib/model/ai-overlay-copy.d.ts +114 -0
  182. package/src/lib/model/ai-overlay-copy.js +175 -0
  183. package/src/lib/model/ai-overlay-copy.js.map +1 -0
  184. package/src/lib/model/ai-site-build-progress.d.ts +28 -3
  185. package/src/lib/model/ai-site-build-progress.js +231 -17
  186. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  187. package/src/lib/model/ai-site-start-seo.d.ts +16 -0
  188. package/src/lib/model/ai-site-start-seo.js +54 -4
  189. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  190. package/src/lib/model/ai-site-start.d.ts +14 -0
  191. package/src/lib/model/ai-site-start.js +43 -0
  192. package/src/lib/model/ai-site-start.js.map +1 -1
  193. package/src/lib/model/ai-svg.d.ts +69 -0
  194. package/src/lib/model/ai-svg.js +265 -0
  195. package/src/lib/model/ai-svg.js.map +1 -0
  196. package/src/lib/model/ai-usage-by-user.d.ts +3 -2
  197. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  198. package/src/lib/model/ai-workflow-job.d.ts +92 -4
  199. package/src/lib/model/ai-workflow-job.js +124 -7
  200. package/src/lib/model/ai-workflow-job.js.map +1 -1
  201. package/src/lib/model/assist-build.d.ts +58 -0
  202. package/src/lib/model/assist-build.js +52 -0
  203. package/src/lib/model/assist-build.js.map +1 -0
  204. package/src/lib/model/assist-edit.d.ts +48 -3
  205. package/src/lib/model/assist-edit.js +48 -4
  206. package/src/lib/model/assist-edit.js.map +1 -1
  207. package/src/lib/plugin-config.js +2 -0
  208. package/src/lib/plugin-config.js.map +1 -1
  209. package/src/lib/plugin.js +240 -11
  210. package/src/lib/plugin.js.map +1 -1
  211. package/src/lib/providers/catalog.d.ts +81 -4
  212. package/src/lib/providers/catalog.js +80 -9
  213. package/src/lib/providers/catalog.js.map +1 -1
  214. package/src/lib/providers/image-contract.d.ts +116 -0
  215. package/src/lib/providers/image-contract.js +74 -0
  216. package/src/lib/providers/image-contract.js.map +1 -0
  217. package/src/lib/providers/model-choice.js +11 -0
  218. package/src/lib/providers/model-choice.js.map +1 -1
  219. package/src/lib/providers/routing.js +18 -0
  220. package/src/lib/providers/routing.js.map +1 -1
  221. package/src/lib/providers/vertex-image.d.ts +121 -0
  222. package/src/lib/providers/vertex-image.js +312 -0
  223. package/src/lib/providers/vertex-image.js.map +1 -0
  224. package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
  225. package/src/lib/runtime/ai-doctrine-validators.js +89 -0
  226. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  227. package/src/lib/runtime/ai-doctrine.d.ts +6 -0
  228. package/src/lib/runtime/ai-doctrine.js +16 -6
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-eval-live.js +4 -0
  231. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  232. package/src/lib/runtime/ai-eval.d.ts +15 -1
  233. package/src/lib/runtime/ai-eval.js +52 -1
  234. package/src/lib/runtime/ai-eval.js.map +1 -1
  235. package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
  236. package/src/lib/runtime/experiment-variant-copy.js +102 -0
  237. package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
  238. package/src/lib/server/ai-experiment-versions.d.ts +51 -0
  239. package/src/lib/server/ai-experiment-versions.js +270 -0
  240. package/src/lib/server/ai-experiment-versions.js.map +1 -0
  241. package/src/lib/server/ai-jobs-resume.js +74 -4
  242. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  243. package/src/lib/server/ai-media-image.d.ts +66 -0
  244. package/src/lib/server/ai-media-image.js +521 -0
  245. package/src/lib/server/ai-media-image.js.map +1 -0
  246. package/src/lib/server/ai-media-svg.d.ts +91 -0
  247. package/src/lib/server/ai-media-svg.js +186 -0
  248. package/src/lib/server/ai-media-svg.js.map +1 -0
  249. package/src/lib/server/assist-build.d.ts +54 -0
  250. package/src/lib/server/assist-build.js +121 -0
  251. package/src/lib/server/assist-build.js.map +1 -0
  252. package/src/lib/server/assist-chat.js +108 -12
  253. package/src/lib/server/assist-chat.js.map +1 -1
  254. package/src/lib/server/assist-edit.js +213 -3
  255. package/src/lib/server/assist-edit.js.map +1 -1
  256. package/src/lib/server/assist-view-context.js +242 -4
  257. package/src/lib/server/assist-view-context.js.map +1 -1
  258. package/src/lib/server.js +26 -0
  259. package/src/lib/server.js.map +1 -1
  260. package/src/lib/tools/ai-logic-tool.d.ts +27 -0
  261. package/src/lib/tools/ai-logic-tool.js +207 -0
  262. package/src/lib/tools/ai-logic-tool.js.map +1 -0
  263. package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
  264. package/src/lib/tools/ai-overlay-tool.js +107 -0
  265. package/src/lib/tools/ai-overlay-tool.js.map +1 -0
  266. package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
  267. package/src/lib/tools/ai-workflow-tool.js +31 -0
  268. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  269. package/src/lib/usage/assist-job-refund.d.ts +31 -3
  270. package/src/lib/usage/assist-job-refund.js +46 -10
  271. package/src/lib/usage/assist-job-refund.js.map +1 -1
  272. package/src/lib/usage/assist-usage.d.ts +5 -0
  273. package/src/lib/usage/assist-usage.js +2 -0
  274. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -0,0 +1,327 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { evaluateHostFunction, expressionSyntaxError, functionReferencedNames } from "@aglyn/aglyn/app-utils/functions";
17
+ import { functionGlobals, VARIABLE_NAME_PATTERN } from "@aglyn/aglyn/app-utils/variables";
18
+ /**
19
+ * A `logic` job (AGL-3603): a site's Functions & Variables, by AI.
20
+ *
21
+ * - `function` proposes a function — parameters, locals, the numbered
22
+ * if/then/otherwise operations and the value it returns — from a
23
+ * description, or, naming a saved function, a changed or fixed copy of
24
+ * it. Every expression is held to the evaluator's own grammar
25
+ * (`expressionSyntaxError`), every name it reads to the function's own
26
+ * names and the site's variables, and the whole definition is run once
27
+ * with each parameter at its starting value before anyone sees it.
28
+ * - `variable` proposes one site variable: a name, a type and a value in
29
+ * that type's stored form.
30
+ * - `explain` reads a saved function and answers in plain words what it
31
+ * computes, and anything in it worth checking.
32
+ *
33
+ * Nothing is written: a proposal rides on the job's output, and the logic
34
+ * editor is where a person saves it, or does not.
35
+ */ export const AI_LOGIC_JOB_MODES = [
36
+ 'function',
37
+ 'variable',
38
+ 'explain'
39
+ ];
40
+ const DOCUMENT_ID = /^[A-Za-z0-9_-]{1,128}$/;
41
+ export const AI_LOGIC_PICK_FUNCTION_COPY = 'Pick the function to explain';
42
+ /** A job's inputs read into its mode, or the sentence that says what is wrong with them. */ export function parseAiLogicJobInputs(inputs) {
43
+ var _ref;
44
+ const mode = (_ref = inputs == null ? void 0 : inputs['mode']) != null ? _ref : 'function';
45
+ if (!AI_LOGIC_JOB_MODES.includes(mode)) {
46
+ return 'inputs.mode must be function, variable or explain';
47
+ }
48
+ if (mode === 'variable') return {
49
+ mode
50
+ };
51
+ const functionId = inputs == null ? void 0 : inputs['functionId'];
52
+ const named = typeof functionId === 'string' && DOCUMENT_ID.test(functionId) ? functionId : null;
53
+ if (functionId != null && !named) return AI_LOGIC_PICK_FUNCTION_COPY;
54
+ if (mode === 'explain') return named ? {
55
+ mode,
56
+ functionId: named
57
+ } : AI_LOGIC_PICK_FUNCTION_COPY;
58
+ return {
59
+ mode: 'function',
60
+ functionId: named
61
+ };
62
+ }
63
+ // ── The bounds a proposal is held to ──────────────────────────────────────
64
+ export const AI_LOGIC_MAX_PARAMETERS = 12;
65
+ export const AI_LOGIC_MAX_LOCALS = 20;
66
+ export const AI_LOGIC_MAX_OPERATIONS = 24;
67
+ export const AI_LOGIC_MAX_SETS = 12;
68
+ export const AI_LOGIC_EXPRESSION_MAX_CHARS = 400;
69
+ export const AI_LOGIC_LABEL_MAX_CHARS = 80;
70
+ export const AI_LOGIC_VARIABLE_VALUE_MAX_CHARS = 2000;
71
+ export const AI_LOGIC_VALUE_TYPES = [
72
+ 'number',
73
+ 'text',
74
+ 'boolean'
75
+ ];
76
+ export const AI_LOGIC_COMPARATORS = [
77
+ '==',
78
+ '!=',
79
+ '<',
80
+ '<=',
81
+ '>',
82
+ '>='
83
+ ];
84
+ /** The variable types a proposal may use: every type the Variables editor offers. */ export const AI_LOGIC_VARIABLE_TYPES = [
85
+ 'text',
86
+ 'number',
87
+ 'boolean',
88
+ 'date',
89
+ 'time',
90
+ 'dictionary',
91
+ 'collection'
92
+ ];
93
+ const violation = (code, message)=>({
94
+ rule: null,
95
+ code,
96
+ message
97
+ });
98
+ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
99
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
100
+ const MARKUP = /<\s*\/?\s*(script|iframe|style|object|embed)\b|javascript:/i;
101
+ /** A starting value a parameter's type can hold, for the run every proposal is given. */ function sampleFor(type, defaultValue) {
102
+ if (defaultValue) return defaultValue;
103
+ return type === 'number' ? '1' : type === 'boolean' ? 'true' : 'sample';
104
+ }
105
+ /**
106
+ * A `submit_function` answer read into a definition the function editor
107
+ * opens, or the violations a re-ask names. Only what the evaluator can run
108
+ * is kept: an expression it cannot parse, a name it cannot find, a SET on a
109
+ * site variable, or a run that fails on starting values is refused.
110
+ */ export function checkAiLogicFunction(answer, context) {
111
+ const violations = [];
112
+ const name = str(answer['name']);
113
+ if (!VARIABLE_NAME_PATTERN.test(name)) {
114
+ violations.push(violation('logic-name', 'The function name must start with a letter or _, then letters, digits or _; at most 40 characters.'));
115
+ } else if (name !== context.editing && context.functions.includes(name)) {
116
+ violations.push(violation('logic-name-taken', `The site already has a function named ${name}. Choose another name.`));
117
+ }
118
+ const own = new Set();
119
+ const ownName = (raw, what)=>{
120
+ const value = str(raw);
121
+ if (!VARIABLE_NAME_PATTERN.test(value)) {
122
+ violations.push(violation('logic-name', `A ${what} name must start with a letter or _, then letters, digits or _.`));
123
+ } else if (own.has(value)) {
124
+ violations.push(violation('logic-name-repeated', `The name ${value} is used twice. Each parameter and local needs its own name.`));
125
+ }
126
+ own.add(value);
127
+ return value;
128
+ };
129
+ const typeOf = (raw)=>{
130
+ if (AI_LOGIC_VALUE_TYPES.includes(raw)) return raw;
131
+ violations.push(violation('logic-type', 'A type is number, text or boolean.'));
132
+ return 'number';
133
+ };
134
+ const rawParameters = Array.isArray(answer['parameters']) ? answer['parameters'].filter(isRecord) : [];
135
+ const rawLocals = Array.isArray(answer['locals']) ? answer['locals'].filter(isRecord) : [];
136
+ const rawOperations = Array.isArray(answer['operations']) ? answer['operations'].filter(isRecord) : [];
137
+ if (rawParameters.length > AI_LOGIC_MAX_PARAMETERS) {
138
+ violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_PARAMETERS} parameters.`));
139
+ }
140
+ if (rawLocals.length > AI_LOGIC_MAX_LOCALS) {
141
+ violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_LOCALS} locals.`));
142
+ }
143
+ if (!rawOperations.length || rawOperations.length > AI_LOGIC_MAX_OPERATIONS) {
144
+ violations.push(violation('logic-size', `Between 1 and ${AI_LOGIC_MAX_OPERATIONS} operations.`));
145
+ }
146
+ const parameters = rawParameters.slice(0, AI_LOGIC_MAX_PARAMETERS).map((raw)=>{
147
+ const parameter = {
148
+ name: ownName(raw['name'], 'parameter'),
149
+ type: typeOf(raw['type']),
150
+ required: raw['required'] === true
151
+ };
152
+ const label = str(raw['label']).slice(0, AI_LOGIC_LABEL_MAX_CHARS);
153
+ if (label) parameter.label = label;
154
+ const start = str(raw['defaultValue']).slice(0, AI_LOGIC_LABEL_MAX_CHARS);
155
+ if (start) parameter.defaultValue = start;
156
+ return parameter;
157
+ });
158
+ const variables = rawLocals.slice(0, AI_LOGIC_MAX_LOCALS).map((raw)=>({
159
+ name: ownName(raw['name'], 'local'),
160
+ type: typeOf(raw['type'])
161
+ }));
162
+ const site = new Set(context.variables.map((variable)=>variable.name));
163
+ const expression = (raw, where)=>{
164
+ const text = typeof raw === 'string' ? raw.trim() : '';
165
+ if (!text) {
166
+ violations.push(violation('logic-expression', `${where} is empty. Write a value, a name or an expression.`));
167
+ return text;
168
+ }
169
+ if (text.length > AI_LOGIC_EXPRESSION_MAX_CHARS || MARKUP.test(text)) {
170
+ violations.push(violation('logic-expression', `${where} is not an expression the function can run.`));
171
+ return text;
172
+ }
173
+ const problem = expressionSyntaxError(text);
174
+ if (problem) violations.push(violation('logic-syntax', `${where} cannot be read: ${problem}.`));
175
+ return text;
176
+ };
177
+ const sets = (raw, where)=>{
178
+ const list = Array.isArray(raw) ? raw.filter(isRecord) : [];
179
+ if (list.length > AI_LOGIC_MAX_SETS) {
180
+ violations.push(violation('logic-size', `${where} has more than ${AI_LOGIC_MAX_SETS} assignments.`));
181
+ }
182
+ return list.slice(0, AI_LOGIC_MAX_SETS).map((set, index)=>{
183
+ const target = str(set['set']);
184
+ if (!own.has(target)) {
185
+ violations.push(violation('logic-set-target', site.has(target) ? `${where}, assignment ${index + 1}, sets the site variable ${target}. A function reads site variables and sets only its own parameters and locals.` : `${where}, assignment ${index + 1}, sets ${target || 'nothing'}, which is not one of the function's parameters or locals.`));
186
+ }
187
+ return {
188
+ set: target,
189
+ expression: expression(set['expression'], `${where}, assignment ${index + 1}`)
190
+ };
191
+ });
192
+ };
193
+ const operations = rawOperations.slice(0, AI_LOGIC_MAX_OPERATIONS).map((raw, index)=>{
194
+ const where = `Operation ${index + 1}`;
195
+ const condition = isRecord(raw['if']) ? raw['if'] : {};
196
+ const comparator = AI_LOGIC_COMPARATORS.includes(condition['comparator']) ? condition['comparator'] : null;
197
+ if (!comparator) violations.push(violation('logic-comparator', `${where}'s condition compares with one of == != < <= > >=.`));
198
+ return {
199
+ if: {
200
+ left: expression(condition['left'], `${where}'s condition (left)`),
201
+ comparator: comparator != null ? comparator : '==',
202
+ right: expression(condition['right'], `${where}'s condition (right)`)
203
+ },
204
+ then: sets(raw['then'], `${where} (then)`),
205
+ otherwise: sets(raw['otherwise'], `${where} (otherwise)`)
206
+ };
207
+ });
208
+ const returnValue = str(answer['returnValue']);
209
+ if (!own.has(returnValue)) {
210
+ violations.push(violation('logic-return', 'returnValue names one of the function’s own parameters or locals.'));
211
+ }
212
+ const definition = {
213
+ name,
214
+ parameters,
215
+ variables,
216
+ operations,
217
+ returnValue
218
+ };
219
+ // Every name an expression reads that is not the function's own must be a site variable.
220
+ const unknown = functionReferencedNames(definition).filter((read)=>!site.has(read));
221
+ if (unknown.length) {
222
+ violations.push(violation('logic-unknown-name', `The function reads ${unknown.join(', ')}, which is neither one of its own names nor a site variable. Use only the names listed.`));
223
+ }
224
+ if (!violations.length) {
225
+ // One run with every parameter at a starting value: what the editor's
226
+ // own test run would show first, so a definition that cannot run on its
227
+ // first try is refused here rather than handed to a person.
228
+ const variablesByName = Object.fromEntries(context.variables.map((variable)=>[
229
+ variable.name,
230
+ variable
231
+ ]));
232
+ const args = Object.fromEntries(parameters.map((parameter)=>[
233
+ parameter.name,
234
+ sampleFor(parameter.type, parameter.defaultValue)
235
+ ]));
236
+ const run = evaluateHostFunction(definition, args, {
237
+ globals: functionGlobals(definition, variablesByName)
238
+ });
239
+ if (run.ok === false) violations.push(violation('logic-run', `The function fails when it runs: ${run.error}.`));
240
+ }
241
+ return {
242
+ value: violations.length ? null : definition,
243
+ violations
244
+ };
245
+ }
246
+ /** A `submit_variable` answer read into the variable the Variables editor opens. */ export function checkAiLogicVariable(answer, context) {
247
+ const violations = [];
248
+ const name = str(answer['name']);
249
+ if (!VARIABLE_NAME_PATTERN.test(name)) {
250
+ violations.push(violation('logic-name', 'The variable name must start with a letter or _, then letters, digits or _; at most 40 characters.'));
251
+ } else if (context.variables.some((variable)=>variable.name === name)) {
252
+ violations.push(violation('logic-name-taken', `The site already has a variable named ${name}. Choose another name.`));
253
+ }
254
+ const type = answer['type'];
255
+ if (!AI_LOGIC_VARIABLE_TYPES.includes(type)) {
256
+ violations.push(violation('logic-type', `A variable's type is one of ${AI_LOGIC_VARIABLE_TYPES.join(', ')}.`));
257
+ }
258
+ const value = typeof answer['value'] === 'string' ? answer['value'] : '';
259
+ if (value.length > AI_LOGIC_VARIABLE_VALUE_MAX_CHARS || MARKUP.test(value)) {
260
+ violations.push(violation('logic-value', 'The value is too long, or holds markup.'));
261
+ } else if (type === 'number' && !Number.isFinite(Number(value))) {
262
+ violations.push(violation('logic-value', 'A number variable’s value is a number, such as 49 or 0.5.'));
263
+ } else if (type === 'boolean' && value !== 'true' && value !== 'false') {
264
+ violations.push(violation('logic-value', 'A boolean variable’s value is true or false.'));
265
+ } else if (type === 'date' && Number.isNaN(new Date(value).getTime())) {
266
+ violations.push(violation('logic-value', 'A date variable’s value is a date such as 2026-10-01.'));
267
+ } else if (type === 'dictionary' || type === 'collection') {
268
+ let parsed;
269
+ try {
270
+ parsed = JSON.parse(value);
271
+ } catch (unused) {
272
+ parsed = null;
273
+ }
274
+ const fits = type === 'collection' ? Array.isArray(parsed) : isRecord(parsed);
275
+ if (!fits) {
276
+ violations.push(violation('logic-value', type === 'collection' ? 'A collection’s value is a JSON list, such as ["red","green"].' : 'A dictionary’s value is a JSON object of names to values, such as {"starter":19,"pro":49}.'));
277
+ }
278
+ }
279
+ return {
280
+ value: violations.length ? null : {
281
+ name,
282
+ type: type,
283
+ value
284
+ },
285
+ violations
286
+ };
287
+ }
288
+ // ── What a job hands the logic editor ─────────────────────────────────────
289
+ /** The output resource a logic proposal rides on. */ export const AI_LOGIC_RESOURCE = 'logic';
290
+ /**
291
+ * A proposal read back from a job's output, in the browser, before the
292
+ * editor is handed it; `null` for anything that is not one. The server held
293
+ * the definition to the grammar; this only refuses a shape that is not one.
294
+ */ export function readAiLogicProposal(value) {
295
+ if (!isRecord(value)) return null;
296
+ if (value['kind'] === 'variable' && isRecord(value['variable'])) {
297
+ const variable = value['variable'];
298
+ if (typeof variable['name'] !== 'string' || typeof variable['type'] !== 'string') return null;
299
+ return {
300
+ kind: 'variable',
301
+ variable: {
302
+ name: variable['name'],
303
+ type: variable['type'],
304
+ value: typeof variable['value'] === 'string' ? variable['value'] : ''
305
+ }
306
+ };
307
+ }
308
+ if (value['kind'] !== 'function' || !isRecord(value['definition'])) return null;
309
+ const definition = value['definition'];
310
+ if (typeof definition['name'] !== 'string' || !Array.isArray(definition['parameters']) || !Array.isArray(definition['variables']) || !Array.isArray(definition['operations'])) {
311
+ return null;
312
+ }
313
+ return {
314
+ kind: 'function',
315
+ functionId: typeof value['functionId'] === 'string' ? value['functionId'] : null,
316
+ definition: definition
317
+ };
318
+ }
319
+ // ── What a job tells a person ─────────────────────────────────────────────
320
+ export const AI_LOGIC_NO_SITE_COPY = 'This logic job names a site this workspace does not have.';
321
+ export const AI_LOGIC_UNAVAILABLE_COPY = 'Turn on Logic for this site before starting the job.';
322
+ export const AI_LOGIC_GONE_COPY = 'That function no longer exists.';
323
+ export const AI_LOGIC_NO_FUNCTION_COPY = 'The AI could not write a function the site can run from this description. Try describing what goes in, and what should come out.';
324
+ export const AI_LOGIC_NO_VARIABLE_COPY = 'The AI could not write a variable from this description. Try naming what it holds, and its value.';
325
+ export const AI_LOGIC_NO_EXPLANATION_COPY = 'The AI could not explain this function. Try again.';
326
+
327
+ //# sourceMappingURL=ai-logic-job.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-logic-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 evaluateHostFunction,\n expressionSyntaxError,\n type FunctionComparator,\n type FunctionValueType,\n type HostFunction,\n functionReferencedNames,\n} from '@aglyn/aglyn/app-utils/functions'\nimport {\n functionGlobals,\n type HostVariable,\n type HostVariableType,\n VARIABLE_NAME_PATTERN,\n} from '@aglyn/aglyn/app-utils/variables'\nimport type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\n\n/**\n * A `logic` job (AGL-3603): a site's Functions & Variables, by AI.\n *\n * - `function` proposes a function — parameters, locals, the numbered\n * if/then/otherwise operations and the value it returns — from a\n * description, or, naming a saved function, a changed or fixed copy of\n * it. Every expression is held to the evaluator's own grammar\n * (`expressionSyntaxError`), every name it reads to the function's own\n * names and the site's variables, and the whole definition is run once\n * with each parameter at its starting value before anyone sees it.\n * - `variable` proposes one site variable: a name, a type and a value in\n * that type's stored form.\n * - `explain` reads a saved function and answers in plain words what it\n * computes, and anything in it worth checking.\n *\n * Nothing is written: a proposal rides on the job's output, and the logic\n * editor is where a person saves it, or does not.\n */\n\nexport const AI_LOGIC_JOB_MODES = ['function', 'variable', 'explain'] as const\nexport type AiLogicJobMode = (typeof AI_LOGIC_JOB_MODES)[number]\n\nexport type AiLogicJobInputs =\n | { mode: 'function'; functionId: string | null }\n | { mode: 'variable' }\n | { mode: 'explain'; functionId: string }\n\nconst DOCUMENT_ID = /^[A-Za-z0-9_-]{1,128}$/\n\nexport const AI_LOGIC_PICK_FUNCTION_COPY = 'Pick the function to explain'\n\n/** A job's inputs read into its mode, or the sentence that says what is wrong with them. */\nexport function parseAiLogicJobInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): AiLogicJobInputs | string {\n const mode = inputs?.['mode'] ?? 'function'\n if (!(AI_LOGIC_JOB_MODES as readonly unknown[]).includes(mode)) {\n return 'inputs.mode must be function, variable or explain'\n }\n if (mode === 'variable') return { mode }\n const functionId = inputs?.['functionId']\n const named = typeof functionId === 'string' && DOCUMENT_ID.test(functionId) ? functionId : null\n if (functionId != null && !named) return AI_LOGIC_PICK_FUNCTION_COPY\n if (mode === 'explain') return named ? { mode, functionId: named } : AI_LOGIC_PICK_FUNCTION_COPY\n return { mode: 'function', functionId: named }\n}\n\n// ── The bounds a proposal is held to ──────────────────────────────────────\n\nexport const AI_LOGIC_MAX_PARAMETERS = 12\nexport const AI_LOGIC_MAX_LOCALS = 20\nexport const AI_LOGIC_MAX_OPERATIONS = 24\nexport const AI_LOGIC_MAX_SETS = 12\nexport const AI_LOGIC_EXPRESSION_MAX_CHARS = 400\nexport const AI_LOGIC_LABEL_MAX_CHARS = 80\nexport const AI_LOGIC_VARIABLE_VALUE_MAX_CHARS = 2_000\n\nexport const AI_LOGIC_VALUE_TYPES: readonly FunctionValueType[] = ['number', 'text', 'boolean']\nexport const AI_LOGIC_COMPARATORS: readonly FunctionComparator[] = ['==', '!=', '<', '<=', '>', '>=']\n\n/** The variable types a proposal may use: every type the Variables editor offers. */\nexport const AI_LOGIC_VARIABLE_TYPES: readonly HostVariableType[] = [\n 'text',\n 'number',\n 'boolean',\n 'date',\n 'time',\n 'dictionary',\n 'collection',\n]\n\n/** A site variable as a proposal is checked against: its name, type and value. */\nexport type AiLogicSiteVariable = Pick<HostVariable, 'name' | 'type' | 'value'>\n\n/** What a proposal's names are checked against: the site's variables and functions. */\nexport interface AiLogicContext {\n variables: readonly AiLogicSiteVariable[]\n /** The site's function names, which a new function's name must not repeat. */\n functions: readonly string[]\n /** The saved function a change starts from; its own name is free to keep. */\n editing?: string | null\n}\n\nexport interface AiLogicCheckResult<T> {\n value: T | null\n violations: AiDoctrineViolation[]\n}\n\nconst violation = (code: string, message: string): AiDoctrineViolation => ({ rule: null, code, message })\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nconst MARKUP = /<\\s*\\/?\\s*(script|iframe|style|object|embed)\\b|javascript:/i\n\n/** A starting value a parameter's type can hold, for the run every proposal is given. */\nfunction sampleFor(type: FunctionValueType, defaultValue: string | undefined): string {\n if (defaultValue) return defaultValue\n return type === 'number' ? '1' : type === 'boolean' ? 'true' : 'sample'\n}\n\n/**\n * A `submit_function` answer read into a definition the function editor\n * opens, or the violations a re-ask names. Only what the evaluator can run\n * is kept: an expression it cannot parse, a name it cannot find, a SET on a\n * site variable, or a run that fails on starting values is refused.\n */\nexport function checkAiLogicFunction(\n answer: Record<string, unknown>,\n context: AiLogicContext,\n): AiLogicCheckResult<HostFunction> {\n const violations: AiDoctrineViolation[] = []\n const name = str(answer['name'])\n if (!VARIABLE_NAME_PATTERN.test(name)) {\n violations.push(\n violation('logic-name', 'The function name must start with a letter or _, then letters, digits or _; at most 40 characters.'),\n )\n } else if (name !== context.editing && context.functions.includes(name)) {\n violations.push(violation('logic-name-taken', `The site already has a function named ${name}. Choose another name.`))\n }\n\n const own = new Set<string>()\n const ownName = (raw: unknown, what: string): string => {\n const value = str(raw)\n if (!VARIABLE_NAME_PATTERN.test(value)) {\n violations.push(violation('logic-name', `A ${what} name must start with a letter or _, then letters, digits or _.`))\n } else if (own.has(value)) {\n violations.push(violation('logic-name-repeated', `The name ${value} is used twice. Each parameter and local needs its own name.`))\n }\n own.add(value)\n return value\n }\n const typeOf = (raw: unknown): FunctionValueType => {\n if ((AI_LOGIC_VALUE_TYPES as readonly unknown[]).includes(raw)) return raw as FunctionValueType\n violations.push(violation('logic-type', 'A type is number, text or boolean.'))\n return 'number'\n }\n\n const rawParameters = Array.isArray(answer['parameters']) ? answer['parameters'].filter(isRecord) : []\n const rawLocals = Array.isArray(answer['locals']) ? answer['locals'].filter(isRecord) : []\n const rawOperations = Array.isArray(answer['operations']) ? answer['operations'].filter(isRecord) : []\n if (rawParameters.length > AI_LOGIC_MAX_PARAMETERS) {\n violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_PARAMETERS} parameters.`))\n }\n if (rawLocals.length > AI_LOGIC_MAX_LOCALS) {\n violations.push(violation('logic-size', `At most ${AI_LOGIC_MAX_LOCALS} locals.`))\n }\n if (!rawOperations.length || rawOperations.length > AI_LOGIC_MAX_OPERATIONS) {\n violations.push(violation('logic-size', `Between 1 and ${AI_LOGIC_MAX_OPERATIONS} operations.`))\n }\n\n const parameters = rawParameters.slice(0, AI_LOGIC_MAX_PARAMETERS).map((raw) => {\n const parameter: HostFunction['parameters'][number] = {\n name: ownName(raw['name'], 'parameter'),\n type: typeOf(raw['type']),\n required: raw['required'] === true,\n }\n const label = str(raw['label']).slice(0, AI_LOGIC_LABEL_MAX_CHARS)\n if (label) parameter.label = label\n const start = str(raw['defaultValue']).slice(0, AI_LOGIC_LABEL_MAX_CHARS)\n if (start) parameter.defaultValue = start\n return parameter\n })\n const variables = rawLocals.slice(0, AI_LOGIC_MAX_LOCALS).map((raw) => ({\n name: ownName(raw['name'], 'local'),\n type: typeOf(raw['type']),\n }))\n\n const site = new Set(context.variables.map((variable) => variable.name))\n const expression = (raw: unknown, where: string): string => {\n const text = typeof raw === 'string' ? raw.trim() : ''\n if (!text) {\n violations.push(violation('logic-expression', `${where} is empty. Write a value, a name or an expression.`))\n return text\n }\n if (text.length > AI_LOGIC_EXPRESSION_MAX_CHARS || MARKUP.test(text)) {\n violations.push(violation('logic-expression', `${where} is not an expression the function can run.`))\n return text\n }\n const problem = expressionSyntaxError(text)\n if (problem) violations.push(violation('logic-syntax', `${where} cannot be read: ${problem}.`))\n return text\n }\n const sets = (raw: unknown, where: string) => {\n const list = Array.isArray(raw) ? raw.filter(isRecord) : []\n if (list.length > AI_LOGIC_MAX_SETS) {\n violations.push(violation('logic-size', `${where} has more than ${AI_LOGIC_MAX_SETS} assignments.`))\n }\n return list.slice(0, AI_LOGIC_MAX_SETS).map((set, index) => {\n const target = str(set['set'])\n if (!own.has(target)) {\n violations.push(\n violation(\n 'logic-set-target',\n site.has(target)\n ? `${where}, assignment ${index + 1}, sets the site variable ${target}. A function reads site variables and sets only its own parameters and locals.`\n : `${where}, assignment ${index + 1}, sets ${target || 'nothing'}, which is not one of the function's parameters or locals.`,\n ),\n )\n }\n return { set: target, expression: expression(set['expression'], `${where}, assignment ${index + 1}`) }\n })\n }\n const operations = rawOperations.slice(0, AI_LOGIC_MAX_OPERATIONS).map((raw, index) => {\n const where = `Operation ${index + 1}`\n const condition = isRecord(raw['if']) ? raw['if'] : {}\n const comparator = (AI_LOGIC_COMPARATORS as readonly unknown[]).includes(condition['comparator'])\n ? (condition['comparator'] as FunctionComparator)\n : null\n if (!comparator) violations.push(violation('logic-comparator', `${where}'s condition compares with one of == != < <= > >=.`))\n return {\n if: {\n left: expression(condition['left'], `${where}'s condition (left)`),\n comparator: comparator ?? '==',\n right: expression(condition['right'], `${where}'s condition (right)`),\n },\n then: sets(raw['then'], `${where} (then)`),\n otherwise: sets(raw['otherwise'], `${where} (otherwise)`),\n }\n })\n\n const returnValue = str(answer['returnValue'])\n if (!own.has(returnValue)) {\n violations.push(violation('logic-return', 'returnValue names one of the function’s own parameters or locals.'))\n }\n const definition: HostFunction = { name, parameters, variables, operations, returnValue }\n\n // Every name an expression reads that is not the function's own must be a site variable.\n const unknown = functionReferencedNames(definition).filter((read) => !site.has(read))\n if (unknown.length) {\n violations.push(\n violation(\n 'logic-unknown-name',\n `The function reads ${unknown.join(', ')}, which is neither one of its own names nor a site variable. Use only the names listed.`,\n ),\n )\n }\n\n if (!violations.length) {\n // One run with every parameter at a starting value: what the editor's\n // own test run would show first, so a definition that cannot run on its\n // first try is refused here rather than handed to a person.\n const variablesByName = Object.fromEntries(\n context.variables.map((variable) => [variable.name, variable as HostVariable]),\n )\n const args = Object.fromEntries(\n parameters.map((parameter) => [parameter.name, sampleFor(parameter.type, parameter.defaultValue)]),\n )\n const run = evaluateHostFunction(definition, args, { globals: functionGlobals(definition, variablesByName) })\n if (run.ok === false) violations.push(violation('logic-run', `The function fails when it runs: ${run.error}.`))\n }\n return { value: violations.length ? null : definition, violations }\n}\n\n/** A `submit_variable` answer read into the variable the Variables editor opens. */\nexport function checkAiLogicVariable(\n answer: Record<string, unknown>,\n context: Pick<AiLogicContext, 'variables'>,\n): AiLogicCheckResult<AiLogicSiteVariable> {\n const violations: AiDoctrineViolation[] = []\n const name = str(answer['name'])\n if (!VARIABLE_NAME_PATTERN.test(name)) {\n violations.push(violation('logic-name', 'The variable name must start with a letter or _, then letters, digits or _; at most 40 characters.'))\n } else if (context.variables.some((variable) => variable.name === name)) {\n violations.push(violation('logic-name-taken', `The site already has a variable named ${name}. Choose another name.`))\n }\n const type = answer['type']\n if (!(AI_LOGIC_VARIABLE_TYPES as readonly unknown[]).includes(type)) {\n violations.push(violation('logic-type', `A variable's type is one of ${AI_LOGIC_VARIABLE_TYPES.join(', ')}.`))\n }\n const value = typeof answer['value'] === 'string' ? answer['value'] : ''\n if (value.length > AI_LOGIC_VARIABLE_VALUE_MAX_CHARS || MARKUP.test(value)) {\n violations.push(violation('logic-value', 'The value is too long, or holds markup.'))\n } else if (type === 'number' && !Number.isFinite(Number(value))) {\n violations.push(violation('logic-value', 'A number variable’s value is a number, such as 49 or 0.5.'))\n } else if (type === 'boolean' && value !== 'true' && value !== 'false') {\n violations.push(violation('logic-value', 'A boolean variable’s value is true or false.'))\n } else if (type === 'date' && Number.isNaN(new Date(value).getTime())) {\n violations.push(violation('logic-value', 'A date variable’s value is a date such as 2026-10-01.'))\n } else if (type === 'dictionary' || type === 'collection') {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n parsed = null\n }\n const fits = type === 'collection' ? Array.isArray(parsed) : isRecord(parsed)\n if (!fits) {\n violations.push(\n violation(\n 'logic-value',\n type === 'collection'\n ? 'A collection’s value is a JSON list, such as [\"red\",\"green\"].'\n : 'A dictionary’s value is a JSON object of names to values, such as {\"starter\":19,\"pro\":49}.',\n ),\n )\n }\n }\n return {\n value: violations.length ? null : { name, type: type as HostVariableType, value },\n violations,\n }\n}\n\n// ── What a job hands the logic editor ─────────────────────────────────────\n\n/** The output resource a logic proposal rides on. */\nexport const AI_LOGIC_RESOURCE = 'logic'\n\n/** A logic output's proposal: a function to open in the editor, or a variable. */\nexport type AiLogicProposal =\n | { kind: 'function'; functionId: string | null; definition: HostFunction }\n | { kind: 'variable'; variable: AiLogicSiteVariable }\n\n/**\n * A proposal read back from a job's output, in the browser, before the\n * editor is handed it; `null` for anything that is not one. The server held\n * the definition to the grammar; this only refuses a shape that is not one.\n */\nexport function readAiLogicProposal(value: unknown): AiLogicProposal | null {\n if (!isRecord(value)) return null\n if (value['kind'] === 'variable' && isRecord(value['variable'])) {\n const variable = value['variable']\n if (typeof variable['name'] !== 'string' || typeof variable['type'] !== 'string') return null\n return {\n kind: 'variable',\n variable: {\n name: variable['name'],\n type: variable['type'] as HostVariableType,\n value: typeof variable['value'] === 'string' ? variable['value'] : '',\n },\n }\n }\n if (value['kind'] !== 'function' || !isRecord(value['definition'])) return null\n const definition = value['definition']\n if (\n typeof definition['name'] !== 'string' ||\n !Array.isArray(definition['parameters']) ||\n !Array.isArray(definition['variables']) ||\n !Array.isArray(definition['operations'])\n ) {\n return null\n }\n return {\n kind: 'function',\n functionId: typeof value['functionId'] === 'string' ? value['functionId'] : null,\n definition: definition as unknown as HostFunction,\n }\n}\n\n// ── What a job tells a person ─────────────────────────────────────────────\n\nexport const AI_LOGIC_NO_SITE_COPY = 'This logic job names a site this workspace does not have.'\nexport const AI_LOGIC_UNAVAILABLE_COPY = 'Turn on Logic for this site before starting the job.'\nexport const AI_LOGIC_GONE_COPY = 'That function no longer exists.'\nexport const AI_LOGIC_NO_FUNCTION_COPY =\n 'The AI could not write a function the site can run from this description. Try describing what goes in, and what should come out.'\nexport const AI_LOGIC_NO_VARIABLE_COPY =\n 'The AI could not write a variable from this description. Try naming what it holds, and its value.'\nexport const AI_LOGIC_NO_EXPLANATION_COPY = 'The AI could not explain this function. Try again.'\n"],"names":["evaluateHostFunction","expressionSyntaxError","functionReferencedNames","functionGlobals","VARIABLE_NAME_PATTERN","AI_LOGIC_JOB_MODES","DOCUMENT_ID","AI_LOGIC_PICK_FUNCTION_COPY","parseAiLogicJobInputs","inputs","mode","includes","functionId","named","test","AI_LOGIC_MAX_PARAMETERS","AI_LOGIC_MAX_LOCALS","AI_LOGIC_MAX_OPERATIONS","AI_LOGIC_MAX_SETS","AI_LOGIC_EXPRESSION_MAX_CHARS","AI_LOGIC_LABEL_MAX_CHARS","AI_LOGIC_VARIABLE_VALUE_MAX_CHARS","AI_LOGIC_VALUE_TYPES","AI_LOGIC_COMPARATORS","AI_LOGIC_VARIABLE_TYPES","violation","code","message","rule","isRecord","value","Array","isArray","str","trim","MARKUP","sampleFor","type","defaultValue","checkAiLogicFunction","answer","context","violations","name","push","editing","functions","own","Set","ownName","raw","what","has","add","typeOf","rawParameters","filter","rawLocals","rawOperations","length","parameters","slice","map","parameter","required","label","start","variables","site","variable","expression","where","text","problem","sets","list","set","index","target","operations","condition","comparator","if","left","right","then","otherwise","returnValue","definition","unknown","read","join","variablesByName","Object","fromEntries","args","run","globals","ok","error","checkAiLogicVariable","some","Number","isFinite","isNaN","Date","getTime","parsed","JSON","parse","fits","AI_LOGIC_RESOURCE","readAiLogicProposal","kind","AI_LOGIC_NO_SITE_COPY","AI_LOGIC_UNAVAILABLE_COPY","AI_LOGIC_GONE_COPY","AI_LOGIC_NO_FUNCTION_COPY","AI_LOGIC_NO_VARIABLE_COPY","AI_LOGIC_NO_EXPLANATION_COPY"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiUsage } from '../providers/contract';
18
+ import type { AiImageMode } from '../providers/image-contract';
19
+ /**
20
+ * What one picture in Media is expected to draw (AGL-3602), in credits: the
21
+ * estimate the dialog shows before anything is spent, and the figure the
22
+ * door holds a walled band to. Browser-safe, so both read the same number.
23
+ *
24
+ * Each is an ESTIMATE at nominal usage, priced at the billed rates:
25
+ *
26
+ * - a photo is one picture at its per-picture rate, plus a prompt and the
27
+ * thinking Google's model does before it draws;
28
+ * - an illustration is the text provider's answer — the instructions, the
29
+ * description, and a few thousand tokens of SVG.
30
+ *
31
+ * What is actually charged is what was spent, measured: a picture held back
32
+ * or not stored is never charged, and an illustration that needed its second
33
+ * attempt costs about twice its estimate.
34
+ */
35
+ /** A photo's nominal tokens beside its picture. */
36
+ export declare const AI_MEDIA_PHOTO_NOMINAL_USAGE: AiUsage & {
37
+ images: number;
38
+ };
39
+ /**
40
+ * An illustration's nominal tokens: about 2,100 of instructions — under the
41
+ * fast tier's caching minimum, so read at full rate every time — the tool and
42
+ * the description, and a few thousand of SVG.
43
+ */
44
+ export declare const AI_MEDIA_SVG_NOMINAL_USAGE: AiUsage;
45
+ /**
46
+ * The model an illustration is estimated at where the server's choice is
47
+ * not known: the catalog's first fast-tier row, which is what the default
48
+ * text provider serves illustrations on.
49
+ */
50
+ export declare const AI_MEDIA_SVG_ESTIMATE_MODEL: string;
51
+ /** The credits one picture is expected to draw in `mode`, on `model`. */
52
+ export declare function aiMediaCreditsPerPicture(mode: AiImageMode, model?: string): number;
@@ -0,0 +1,61 @@
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
+ */ var _ref;
17
+ var _AI_MODEL_CATALOG_find;
18
+ import { AI_IMAGE_DEFAULT_MODEL, AI_MODEL_CATALOG, estimateAiBilledUsd } from "../providers/catalog.js";
19
+ import { assistCreditsFromUsd } from "../usage/assist-credits.js";
20
+ /**
21
+ * What one picture in Media is expected to draw (AGL-3602), in credits: the
22
+ * estimate the dialog shows before anything is spent, and the figure the
23
+ * door holds a walled band to. Browser-safe, so both read the same number.
24
+ *
25
+ * Each is an ESTIMATE at nominal usage, priced at the billed rates:
26
+ *
27
+ * - a photo is one picture at its per-picture rate, plus a prompt and the
28
+ * thinking Google's model does before it draws;
29
+ * - an illustration is the text provider's answer — the instructions, the
30
+ * description, and a few thousand tokens of SVG.
31
+ *
32
+ * What is actually charged is what was spent, measured: a picture held back
33
+ * or not stored is never charged, and an illustration that needed its second
34
+ * attempt costs about twice its estimate.
35
+ */ /** A photo's nominal tokens beside its picture. */ export const AI_MEDIA_PHOTO_NOMINAL_USAGE = {
36
+ inputTokens: 600,
37
+ outputTokens: 1500,
38
+ cacheReadTokens: 0,
39
+ cacheWriteTokens: 0,
40
+ images: 1
41
+ };
42
+ /**
43
+ * An illustration's nominal tokens: about 2,100 of instructions — under the
44
+ * fast tier's caching minimum, so read at full rate every time — the tool and
45
+ * the description, and a few thousand of SVG.
46
+ */ export const AI_MEDIA_SVG_NOMINAL_USAGE = {
47
+ inputTokens: 2500,
48
+ outputTokens: 3000,
49
+ cacheReadTokens: 0,
50
+ cacheWriteTokens: 0
51
+ };
52
+ /**
53
+ * The model an illustration is estimated at where the server's choice is
54
+ * not known: the catalog's first fast-tier row, which is what the default
55
+ * text provider serves illustrations on.
56
+ */ export const AI_MEDIA_SVG_ESTIMATE_MODEL = (_ref = (_AI_MODEL_CATALOG_find = AI_MODEL_CATALOG.find((entry)=>entry.tier === 'fast')) == null ? void 0 : _AI_MODEL_CATALOG_find.id) != null ? _ref : AI_MODEL_CATALOG[0].id;
57
+ /** The credits one picture is expected to draw in `mode`, on `model`. */ export function aiMediaCreditsPerPicture(mode, model) {
58
+ return mode === 'photo' ? assistCreditsFromUsd(estimateAiBilledUsd(AI_MEDIA_PHOTO_NOMINAL_USAGE, model != null ? model : AI_IMAGE_DEFAULT_MODEL)) : assistCreditsFromUsd(estimateAiBilledUsd(AI_MEDIA_SVG_NOMINAL_USAGE, model != null ? model : AI_MEDIA_SVG_ESTIMATE_MODEL));
59
+ }
60
+
61
+ //# sourceMappingURL=ai-media-credits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-media-credits.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiUsage } from '../providers/contract'\nimport {\n AI_IMAGE_DEFAULT_MODEL,\n AI_MODEL_CATALOG,\n estimateAiBilledUsd,\n} from '../providers/catalog'\nimport type { AiImageMode } from '../providers/image-contract'\nimport { assistCreditsFromUsd } from '../usage/assist-credits'\n\n/**\n * What one picture in Media is expected to draw (AGL-3602), in credits: the\n * estimate the dialog shows before anything is spent, and the figure the\n * door holds a walled band to. Browser-safe, so both read the same number.\n *\n * Each is an ESTIMATE at nominal usage, priced at the billed rates:\n *\n * - a photo is one picture at its per-picture rate, plus a prompt and the\n * thinking Google's model does before it draws;\n * - an illustration is the text provider's answer — the instructions, the\n * description, and a few thousand tokens of SVG.\n *\n * What is actually charged is what was spent, measured: a picture held back\n * or not stored is never charged, and an illustration that needed its second\n * attempt costs about twice its estimate.\n */\n\n/** A photo's nominal tokens beside its picture. */\nexport const AI_MEDIA_PHOTO_NOMINAL_USAGE: AiUsage & { images: number } = {\n inputTokens: 600,\n outputTokens: 1_500,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n images: 1,\n}\n\n/**\n * An illustration's nominal tokens: about 2,100 of instructions — under the\n * fast tier's caching minimum, so read at full rate every time — the tool and\n * the description, and a few thousand of SVG.\n */\nexport const AI_MEDIA_SVG_NOMINAL_USAGE: AiUsage = {\n inputTokens: 2_500,\n outputTokens: 3_000,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n}\n\n/**\n * The model an illustration is estimated at where the server's choice is\n * not known: the catalog's first fast-tier row, which is what the default\n * text provider serves illustrations on.\n */\nexport const AI_MEDIA_SVG_ESTIMATE_MODEL =\n AI_MODEL_CATALOG.find((entry) => entry.tier === 'fast')?.id ?? AI_MODEL_CATALOG[0].id\n\n/** The credits one picture is expected to draw in `mode`, on `model`. */\nexport function aiMediaCreditsPerPicture(mode: AiImageMode, model?: string): number {\n return mode === 'photo'\n ? assistCreditsFromUsd(estimateAiBilledUsd(AI_MEDIA_PHOTO_NOMINAL_USAGE, model ?? AI_IMAGE_DEFAULT_MODEL))\n : assistCreditsFromUsd(estimateAiBilledUsd(AI_MEDIA_SVG_NOMINAL_USAGE, model ?? AI_MEDIA_SVG_ESTIMATE_MODEL))\n}\n"],"names":["AI_MODEL_CATALOG","AI_IMAGE_DEFAULT_MODEL","estimateAiBilledUsd","assistCreditsFromUsd","AI_MEDIA_PHOTO_NOMINAL_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","images","AI_MEDIA_SVG_NOMINAL_USAGE","AI_MEDIA_SVG_ESTIMATE_MODEL","find","entry","tier","id","aiMediaCreditsPerPicture","mode","model"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;IAuDCA;AApDF,SACEC,sBAAsB,EACtBD,gBAAgB,EAChBE,mBAAmB,QACd,0BAAsB;AAE7B,SAASC,oBAAoB,QAAQ,6BAAyB;AAE9D;;;;;;;;;;;;;;;CAeC,GAED,iDAAiD,GACjD,OAAO,MAAMC,+BAA6D;IACxEC,aAAa;IACbC,cAAc;IACdC,iBAAiB;IACjBC,kBAAkB;IAClBC,QAAQ;AACV,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMC,6BAAsC;IACjDL,aAAa;IACbC,cAAc;IACdC,iBAAiB;IACjBC,kBAAkB;AACpB,EAAC;AAED;;;;CAIC,GACD,OAAO,MAAMG,uCACXX,yBAAAA,iBAAiBY,IAAI,CAAC,CAACC,QAAUA,MAAMC,IAAI,KAAK,4BAAhDd,uBAAyDe,EAAE,mBAAIf,gBAAgB,CAAC,EAAE,CAACe,EAAE,CAAA;AAEvF,uEAAuE,GACvE,OAAO,SAASC,yBAAyBC,IAAiB,EAAEC,KAAc;IACxE,OAAOD,SAAS,UACZd,qBAAqBD,oBAAoBE,8BAA8Bc,gBAAAA,QAASjB,2BAChFE,qBAAqBD,oBAAoBQ,4BAA4BQ,gBAAAA,QAASP;AACpF"}
@@ -0,0 +1,114 @@
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
+ * Overlay copy by AI (AGL-3603): what a `text` job asked for an announcement
19
+ * bar's or a popup's copy answers, in the shape the step writes and the
20
+ * console reads. Pure data, so the widgets and the step share one reading.
21
+ *
22
+ * The overlay is the marketing plugin's, and neither plugin imports the other:
23
+ * the limits and the trigger catalog are handed to the widget by the zone,
24
+ * and the widget passes them on as the job's inputs. The step then holds the
25
+ * answer to the TIGHTER of those and its own ceilings below, so a job started
26
+ * by anything else is still bounded, and drops a trigger the caller did not
27
+ * name rather than inventing one.
28
+ */
29
+ /** The `text` job's `inputs.task` that asks for overlay copy. */
30
+ export declare const AI_OVERLAY_COPY_TASK = "overlay";
31
+ /** What the copy is for. */
32
+ export type AiOverlayKind = 'bar' | 'popup';
33
+ /** The fields an answer fills. */
34
+ export type AiOverlayCopyField = 'name' | 'text' | 'headline' | 'body' | 'ctaLabel';
35
+ /** The step's own ceilings: no zone may widen a field past these. */
36
+ export declare const AI_OVERLAY_COPY_CEILINGS: Readonly<Record<AiOverlayCopyField, number>>;
37
+ /** The triggers a popup may be proposed to open on, as the site runtime names them. */
38
+ export declare const AI_OVERLAY_TRIGGERS: readonly ["delay", "scroll", "exit"];
39
+ export type AiOverlayTrigger = (typeof AI_OVERLAY_TRIGGERS)[number];
40
+ /** One trigger as the zone hands it over: its id and the range of its value. */
41
+ export interface AiOverlayTriggerRule {
42
+ id: string;
43
+ label: string;
44
+ unit: 'seconds' | 'percent' | null;
45
+ min: number;
46
+ max: number;
47
+ }
48
+ /** The longest description of the current copy a job sends. */
49
+ export declare const AI_OVERLAY_CURRENT_MAX_CHARS = 1200;
50
+ /** What a checked answer proposes. Absent fields propose nothing. */
51
+ export interface AiOverlayCopyProposal {
52
+ kind: AiOverlayKind;
53
+ name: string;
54
+ text: string;
55
+ headline: string;
56
+ body: string;
57
+ ctaLabel: string;
58
+ trigger: AiOverlayTrigger | null;
59
+ triggerValue: number | null;
60
+ /** One sentence on what the copy leans on; shown, never written. */
61
+ rationale: string;
62
+ }
63
+ /** What the model answered, before the check. */
64
+ export interface AiOverlayCopyAnswer {
65
+ name: string;
66
+ text: string;
67
+ headline: string;
68
+ body: string;
69
+ ctaLabel: string;
70
+ trigger: string;
71
+ triggerValue: number | null;
72
+ rationale: string;
73
+ }
74
+ /** The kind a job's inputs name, or `null`. */
75
+ export declare function aiOverlayKind(inputs: Readonly<Record<string, unknown>> | null | undefined): AiOverlayKind | null;
76
+ /** Whether a `text` job's inputs ask for overlay copy. */
77
+ export declare function aiOverlayCopyRequested(inputs: Readonly<Record<string, unknown>> | null | undefined): boolean;
78
+ /**
79
+ * The limit each field is held to: the caller's, where it named one, never
80
+ * past the step's ceiling. Inputs carry them as `limit_<field>`.
81
+ */
82
+ export declare function aiOverlayLimits(inputs: Readonly<Record<string, unknown>> | null | undefined): Record<AiOverlayCopyField, number>;
83
+ /**
84
+ * The triggers the caller allows, with their ranges. Inputs carry them as
85
+ * `triggers` (`delay:0-120,scroll:1-100,exit`); an id the runtime does not
86
+ * know is dropped, and with no list a popup is proposed no trigger at all.
87
+ */
88
+ export declare function aiOverlayTriggerRanges(inputs: Readonly<Record<string, unknown>> | null | undefined): Map<AiOverlayTrigger, {
89
+ min: number;
90
+ max: number;
91
+ } | null>;
92
+ /** The zone's trigger catalog as the job's `triggers` input. */
93
+ export declare function aiOverlayTriggersInput(rules: readonly AiOverlayTriggerRule[]): string;
94
+ /** The zone's limits as the job's `limit_<field>` inputs. */
95
+ export declare function aiOverlayLimitInputs(limits: Readonly<Partial<Record<AiOverlayCopyField, number>>>): Record<string, number>;
96
+ export interface AiOverlayCopyCheck {
97
+ proposal: AiOverlayCopyProposal;
98
+ /** What was cut or dropped, for the log. */
99
+ findings: string[];
100
+ }
101
+ /**
102
+ * An answer held to the overlay's rules, or `null` when nothing usable is
103
+ * left: a bar with no text, a popup with no body, or copy carrying markup or
104
+ * a link. A field over its limit is cut rather than refused, and a trigger
105
+ * outside the caller's catalog is dropped, with each recorded as a finding.
106
+ */
107
+ export declare function checkAiOverlayCopy(answer: AiOverlayCopyAnswer | null, kind: AiOverlayKind, limits: Readonly<Record<AiOverlayCopyField, number>>, triggers: ReadonlyMap<AiOverlayTrigger, {
108
+ min: number;
109
+ max: number;
110
+ } | null>): AiOverlayCopyCheck | null;
111
+ /** The proposal a finished overlay job's output carries, or `null` for anything else. */
112
+ export declare function readAiOverlayCopy(proposal: Record<string, unknown> | null | undefined): AiOverlayCopyProposal | null;
113
+ /** A proposal as the plain text a `text` output shows on the AI jobs page. */
114
+ export declare function aiOverlayCopyText(proposal: AiOverlayCopyProposal): string;