@aglyn/plugins-ai 1.0.0-beta.230 → 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 (305) 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-assist-provider-on-host.component.d.ts +66 -0
  5. package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
  6. package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
  7. package/src/lib/components/ai-assist-provider.component.js +9 -10
  8. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  9. package/src/lib/components/ai-job-follow.component.js +1 -0
  10. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  11. package/src/lib/components/ai-job-links.js +13 -0
  12. package/src/lib/components/ai-job-links.js.map +1 -1
  13. package/src/lib/components/ai-job-plan.component.js +3 -1
  14. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  15. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  16. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  17. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  18. package/src/lib/components/ai-media-create.component.js +107 -55
  19. package/src/lib/components/ai-media-create.component.js.map +1 -1
  20. package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
  21. package/src/lib/components/ai-permissions-on-host.component.js +5 -53
  22. package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
  23. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  24. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  25. package/src/lib/components/ai-site-build-page.component.js +20 -8
  26. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  27. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  28. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  29. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  30. package/src/lib/components/ai-site-start-card.component.js +80 -77
  31. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  32. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  33. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  34. package/src/lib/components/ai-usage-strip.component.js +1 -0
  35. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  36. package/src/lib/components/assist-panel.component.d.ts +10 -0
  37. package/src/lib/components/assist-panel.component.js +119 -9
  38. package/src/lib/components/assist-panel.component.js.map +1 -1
  39. package/src/lib/components/assist-signals-page.component.js +2 -0
  40. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  41. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  42. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  43. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  44. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  45. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  46. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  47. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  48. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  49. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  50. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  51. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  52. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  53. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  54. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  55. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  56. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  57. package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
  58. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  59. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  60. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  61. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  62. package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
  63. package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
  64. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
  65. package/src/lib/jobs/ai-job-build-step.js +14 -1
  66. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  68. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  69. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  70. package/src/lib/jobs/ai-job-generation.d.ts +8 -0
  71. package/src/lib/jobs/ai-job-generation.js +13 -2
  72. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  73. package/src/lib/jobs/ai-job-insight-step.js +29 -5
  74. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  75. package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
  76. package/src/lib/jobs/ai-job-layout-language.js +200 -0
  77. package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
  78. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
  79. package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
  80. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  81. package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
  82. package/src/lib/jobs/ai-job-layout-step.js +107 -6
  83. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  85. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  86. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  87. package/src/lib/jobs/ai-job-page-language.d.ts +194 -0
  88. package/src/lib/jobs/ai-job-page-language.js +418 -0
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -0
  90. package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
  91. package/src/lib/jobs/ai-job-page-sections.js +93 -11
  92. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  93. package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
  94. package/src/lib/jobs/ai-job-page-step.js +115 -14
  95. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
  97. package/src/lib/jobs/ai-job-plan-step.js +62 -22
  98. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  99. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  100. package/src/lib/jobs/ai-job-site-content.js +443 -0
  101. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  102. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  103. package/src/lib/jobs/ai-job-site-look.js +322 -0
  104. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  105. package/src/lib/jobs/ai-job-site-step.d.ts +30 -5
  106. package/src/lib/jobs/ai-job-site-step.js +183 -27
  107. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-workflow-step.js +2 -2
  109. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  110. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  111. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  112. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  113. package/src/lib/jobs/ai-jobs.d.ts +1 -1
  114. package/src/lib/jobs/ai-jobs.js +17 -8
  115. package/src/lib/jobs/ai-jobs.js.map +1 -1
  116. package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
  117. package/src/lib/jobs/ai-layout-full-height.js +82 -0
  118. package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
  119. package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
  120. package/src/lib/jobs/ai-layout-site-facts.js +153 -0
  121. package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
  122. package/src/lib/layout-language/ai-layout-compiler.d.ts +154 -0
  123. package/src/lib/layout-language/ai-layout-compiler.js +1235 -0
  124. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
  125. package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
  126. package/src/lib/layout-language/ai-layout-copy.js +137 -0
  127. package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
  128. package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
  129. package/src/lib/layout-language/ai-layout-frame.js +458 -0
  130. package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
  131. package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
  132. package/src/lib/layout-language/ai-layout-gaps.js +345 -0
  133. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
  134. package/src/lib/layout-language/ai-layout-language.d.ts +168 -0
  135. package/src/lib/layout-language/ai-layout-language.js +529 -0
  136. package/src/lib/layout-language/ai-layout-language.js.map +1 -0
  137. package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
  138. package/src/lib/layout-language/ai-layout-links.js +109 -0
  139. package/src/lib/layout-language/ai-layout-links.js.map +1 -0
  140. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  141. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  142. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  143. package/src/lib/layout-language/ai-layout-store.d.ts +41 -0
  144. package/src/lib/layout-language/ai-layout-store.js +75 -0
  145. package/src/lib/layout-language/ai-layout-store.js.map +1 -0
  146. package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
  147. package/src/lib/layout-language/ai-layout-tree.js +84 -0
  148. package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
  149. package/src/lib/lazy-widget.d.ts +45 -0
  150. package/src/lib/lazy-widget.js +53 -0
  151. package/src/lib/lazy-widget.js.map +1 -0
  152. package/src/lib/model/ai-build-job.js +2 -1
  153. package/src/lib/model/ai-build-job.js.map +1 -1
  154. package/src/lib/model/ai-build-progress.d.ts +20 -3
  155. package/src/lib/model/ai-build-progress.js +52 -7
  156. package/src/lib/model/ai-build-progress.js.map +1 -1
  157. package/src/lib/model/ai-edit-job.d.ts +109 -0
  158. package/src/lib/model/ai-edit-job.js +157 -0
  159. package/src/lib/model/ai-edit-job.js.map +1 -0
  160. package/src/lib/model/ai-host-publish-context.d.ts +51 -0
  161. package/src/lib/model/ai-host-publish-context.js +104 -0
  162. package/src/lib/model/ai-host-publish-context.js.map +1 -0
  163. package/src/lib/model/ai-job-activity.d.ts +22 -0
  164. package/src/lib/model/ai-job-activity.js +23 -1
  165. package/src/lib/model/ai-job-activity.js.map +1 -1
  166. package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
  167. package/src/lib/model/ai-job-failure-copy.js +56 -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 -1
  170. package/src/lib/model/ai-job-notice.js +31 -9
  171. package/src/lib/model/ai-job-notice.js.map +1 -1
  172. package/src/lib/model/ai-jobs.types.d.ts +22 -1
  173. package/src/lib/model/ai-jobs.types.js.map +1 -1
  174. package/src/lib/model/ai-logic-job.d.ts +18 -1
  175. package/src/lib/model/ai-logic-job.js +24 -3
  176. package/src/lib/model/ai-logic-job.js.map +1 -1
  177. package/src/lib/model/ai-media-credits.d.ts +8 -4
  178. package/src/lib/model/ai-media-credits.js +11 -4
  179. package/src/lib/model/ai-media-credits.js.map +1 -1
  180. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  181. package/src/lib/model/ai-media-kinds.js +98 -0
  182. package/src/lib/model/ai-media-kinds.js.map +1 -0
  183. package/src/lib/model/ai-site-build-progress.d.ts +9 -1
  184. package/src/lib/model/ai-site-build-progress.js +54 -14
  185. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  186. package/src/lib/model/ai-site-context.d.ts +111 -0
  187. package/src/lib/model/ai-site-context.js +222 -0
  188. package/src/lib/model/ai-site-context.js.map +1 -0
  189. package/src/lib/model/ai-site-job.d.ts +18 -2
  190. package/src/lib/model/ai-site-job.js +21 -5
  191. package/src/lib/model/ai-site-job.js.map +1 -1
  192. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  193. package/src/lib/model/ai-site-kinds.js +1942 -0
  194. package/src/lib/model/ai-site-kinds.js.map +1 -0
  195. package/src/lib/model/ai-site-look.d.ts +150 -0
  196. package/src/lib/model/ai-site-look.js +1236 -0
  197. package/src/lib/model/ai-site-look.js.map +1 -0
  198. package/src/lib/model/ai-site-memory.d.ts +80 -0
  199. package/src/lib/model/ai-site-memory.js +171 -0
  200. package/src/lib/model/ai-site-memory.js.map +1 -0
  201. package/src/lib/model/ai-site-start-seo.d.ts +1 -1
  202. package/src/lib/model/ai-site-start.d.ts +15 -3
  203. package/src/lib/model/ai-site-start.js +21 -4
  204. package/src/lib/model/ai-site-start.js.map +1 -1
  205. package/src/lib/model/assist-follow-up.d.ts +92 -0
  206. package/src/lib/model/assist-follow-up.js +128 -0
  207. package/src/lib/model/assist-follow-up.js.map +1 -0
  208. package/src/lib/plugin-config.js +2 -0
  209. package/src/lib/plugin-config.js.map +1 -1
  210. package/src/lib/plugin.js +99 -48
  211. package/src/lib/plugin.js.map +1 -1
  212. package/src/lib/providers/anthropic.d.ts +9 -1
  213. package/src/lib/providers/anthropic.js +143 -9
  214. package/src/lib/providers/anthropic.js.map +1 -1
  215. package/src/lib/providers/catalog.d.ts +38 -8
  216. package/src/lib/providers/catalog.js +106 -14
  217. package/src/lib/providers/catalog.js.map +1 -1
  218. package/src/lib/providers/contract.d.ts +29 -0
  219. package/src/lib/providers/contract.js.map +1 -1
  220. package/src/lib/providers/image-contract.d.ts +22 -1
  221. package/src/lib/providers/image-contract.js +40 -1
  222. package/src/lib/providers/image-contract.js.map +1 -1
  223. package/src/lib/providers/image-endpoints.d.ts +24 -0
  224. package/src/lib/providers/image-endpoints.js +28 -0
  225. package/src/lib/providers/image-endpoints.js.map +1 -0
  226. package/src/lib/providers/model-choice.js +23 -2
  227. package/src/lib/providers/model-choice.js.map +1 -1
  228. package/src/lib/providers/routing.js +18 -0
  229. package/src/lib/providers/routing.js.map +1 -1
  230. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  231. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  232. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  233. package/src/lib/providers/vertex-image.d.ts +13 -27
  234. package/src/lib/providers/vertex-image.js +15 -42
  235. package/src/lib/providers/vertex-image.js.map +1 -1
  236. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  237. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  238. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  239. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  240. package/src/lib/runtime/ai-dev-env.js +72 -0
  241. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  242. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  243. package/src/lib/runtime/ai-dev-replay.js +313 -0
  244. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  245. package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
  246. package/src/lib/runtime/ai-doctrine-validators.js +436 -4
  247. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  248. package/src/lib/runtime/ai-doctrine.js +16 -3
  249. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  250. package/src/lib/runtime/ai-eval-live.js +4 -0
  251. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  252. package/src/lib/runtime/ai-eval.d.ts +13 -1
  253. package/src/lib/runtime/ai-eval.js +84 -1
  254. package/src/lib/runtime/ai-eval.js.map +1 -1
  255. package/src/lib/runtime/ai-icon-library.d.ts +257 -0
  256. package/src/lib/runtime/ai-icon-library.js +254 -0
  257. package/src/lib/runtime/ai-icon-library.js.map +1 -0
  258. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  259. package/src/lib/runtime/ai-live-batch.js +89 -0
  260. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  261. package/src/lib/runtime/ai-node-tree.d.ts +9 -0
  262. package/src/lib/runtime/ai-node-tree.js +30 -4
  263. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  264. package/src/lib/runtime/ai-palette.d.ts +6 -0
  265. package/src/lib/runtime/ai-palette.generated.js +70 -0
  266. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  267. package/src/lib/runtime/ai-palette.js.map +1 -1
  268. package/src/lib/runtime/ai-runtime.js +46 -2
  269. package/src/lib/runtime/ai-runtime.js.map +1 -1
  270. package/src/lib/runtime/site-context.d.ts +58 -0
  271. package/src/lib/runtime/site-context.js +127 -0
  272. package/src/lib/runtime/site-context.js.map +1 -0
  273. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  274. package/src/lib/server/ai-admin-conversations.js +197 -0
  275. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  276. package/src/lib/server/ai-media-image.d.ts +5 -5
  277. package/src/lib/server/ai-media-image.js +25 -11
  278. package/src/lib/server/ai-media-image.js.map +1 -1
  279. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  280. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  281. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  282. package/src/lib/server/assist-chat.d.ts +11 -0
  283. package/src/lib/server/assist-chat.js +57 -18
  284. package/src/lib/server/assist-chat.js.map +1 -1
  285. package/src/lib/server/assist-edit-applied.js +16 -3
  286. package/src/lib/server/assist-edit-applied.js.map +1 -1
  287. package/src/lib/server/assist-view-context.d.ts +19 -1
  288. package/src/lib/server/assist-view-context.js +38 -1
  289. package/src/lib/server/assist-view-context.js.map +1 -1
  290. package/src/lib/server.js +10 -0
  291. package/src/lib/server.js.map +1 -1
  292. package/src/lib/subprocessors.js +17 -1
  293. package/src/lib/subprocessors.js.map +1 -1
  294. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  295. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  296. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  297. package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
  298. package/src/lib/tools/ai-workflow-tool.js +16 -14
  299. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  300. package/src/lib/usage/assist-usage.d.ts +3 -0
  301. package/src/lib/usage/assist-usage.js +4 -2
  302. package/src/lib/usage/assist-usage.js.map +1 -1
  303. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  304. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  305. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-palette.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 { JsonLinealOrder } from '@aglyn/aglyn/app-utils/lineal-order'\n\n/**\n * The element palette as a model sees it (AGL-2905).\n *\n * `ai-palette.generated.ts` is produced from the registered plugin bundles by\n * `libs/plugins/ai/scripts/generate-ai-palette.mts` and holds the data; this module\n * holds the shapes that data is typed against and the small readers over it,\n * so a regeneration never rewrites a type.\n */\n\n/** The document kinds a model is asked to compose. */\nexport type AiSurface = 'screen' | 'email' | 'form' | 'layout' | 'component'\n\nexport const AI_SURFACE_NAMES: readonly AiSurface[] = [\n 'screen',\n 'email',\n 'form',\n 'layout',\n 'component',\n]\n\n/**\n * The subset of JSON Schema a prop declaration is expressed in. Enough to\n * validate what an attribute field can persist; nothing the field editor\n * cannot itself model.\n */\nexport interface AiPropSchema {\n type?: 'string' | 'number' | 'integer' | 'boolean'\n enum?: readonly string[]\n minLength?: number\n maxLength?: number\n minimum?: number\n maximum?: number\n pattern?: string\n description?: string\n}\n\nexport interface AiPropsSchema {\n type: 'object'\n properties: Record<string, AiPropSchema>\n required: readonly string[]\n additionalProperties: false\n}\n\n/**\n * What a string prop MEANS, which decides how the validator checks it.\n *\n * - `text` — copy the visitor reads; held to a length ceiling.\n * - `url` — a navigable address: an `https:` URL or a root-relative path.\n * - `screen` — a screen id, checked against the screens the host has.\n * - `media` — an image or video source: a DAM reference or an `https:` URL.\n */\nexport type AiPropRole = 'text' | 'url' | 'screen' | 'media'\n\n/**\n * A `restrictChildren` / `restrictParent` directive as generated data: the\n * same tuple the schema declares, with the flag spelled as the string the\n * enum holds so a JSON emitter can write it.\n */\nexport type AiLinealOrder = JsonLinealOrder\n\nexport interface AiPaletteEntry {\n pluginId: string\n kind: 'element' | 'plaintext' | 'markdown'\n category: string\n displayName: string\n /** One sentence, from the schema's own description. */\n summary: string\n /** Whether the editor lets anything be dropped inside this element. */\n acceptsChildren: boolean\n restrictChildren?: AiLinealOrder\n restrictParent?: AiLinealOrder\n propsSchema: AiPropsSchema\n /** Roles of the string props the validator treats specially. */\n propRoles: Record<string, AiPropRole>\n /**\n * The attribute field kind each declared prop is edited with, as its\n * `FieldComponentType` value (`switch`, `select`, `screen-select`, …): what\n * the editor reads to decide which component properties a field can be\n * bound to (AGL-2908).\n */\n propFields: Record<string, string>\n /** Length ceilings for `text` props, in characters. */\n textLimits: Record<string, number>\n /** Names of the presets that place this component. */\n presets: readonly string[]\n}\n\n/**\n * What every NODE can do, as against what one component declares (AGL-3156).\n *\n * Generated from the node capabilities core declares, beside the per-component\n * schemas rather than out of them: repeating is not a Stack's feature, so no\n * component schema names it and a palette built from schemas alone advertises\n * it on nothing. Declared ONCE for the whole palette — a model may write these\n * props on any element the surface allows.\n */\nexport interface AiNodeCapability {\n /** Stable id, as core declares it. */\n id: string\n displayName: string\n /** One sentence, for the prompt catalog. */\n summary: string\n propsSchema: AiPropsSchema\n propRoles: Record<string, AiPropRole>\n propFields: Record<string, string>\n textLimits: Record<string, number>\n /** Props offered only on a node that holds a child list. */\n childrenOnlyProps: readonly string[]\n /**\n * Declared attributes the palette deliberately does not offer: a picker\n * that chooses a record the site holds, which a model cannot name from a\n * description. Recorded so a field left off on purpose reads differently\n * from one that went missing.\n */\n omittedProps: readonly string[]\n}\n\n/**\n * The capability props a node with this child contract may carry, merged into\n * one map the prop sanitizer can read beside the element's own declarations.\n */\nexport function nodeCapabilityProps(\n capabilities: Readonly<Record<string, AiNodeCapability>>,\n acceptsChildren: boolean,\n): {\n properties: Record<string, AiPropSchema>\n propRoles: Record<string, AiPropRole>\n textLimits: Record<string, number>\n} {\n const properties: Record<string, AiPropSchema> = {}\n const propRoles: Record<string, AiPropRole> = {}\n const textLimits: Record<string, number> = {}\n for (const capability of Object.values(capabilities)) {\n for (const [name, schema] of Object.entries(\n capability.propsSchema.properties,\n )) {\n if (!acceptsChildren && capability.childrenOnlyProps.includes(name))\n continue\n properties[name] = schema\n const role = capability.propRoles[name]\n if (role) propRoles[name] = role\n const limit = capability.textLimits[name]\n if (limit !== undefined) textLimits[name] = limit\n }\n }\n return { properties, propRoles, textLimits }\n}\n\n/**\n * What a node capability declared in core and one carried by the palette\n * disagree about (AGL-3156).\n *\n * `check:ai-palette` pins the generated file to the generator's output for its\n * inputs, which cannot notice a capability leaving those inputs: when repeat\n * stopped being a component's attribute, the generator regenerated cleanly and\n * the palette simply stopped mentioning it. This reads the core declarations\n * instead and asks whether the palette still accounts for each one, so the\n * next generic capability cannot vanish the same way.\n *\n * `offeredFieldKinds` is every field kind the palette DOES carry somewhere,\n * derived from the generated entries rather than restated here: a capability\n * attribute drawn with one of those kinds has no excuse for being omitted.\n */\nexport function auditNodeCapabilities(\n declared: ReadonlyArray<{\n id: string\n label: string\n attributes: ReadonlyArray<{ name: string; component: string }>\n }>,\n carried: Readonly<Record<string, AiNodeCapability>>,\n offeredFieldKinds: ReadonlySet<string>,\n): string[] {\n const problems: string[] = []\n for (const capability of declared) {\n const entry = carried[capability.id]\n if (!entry) {\n problems.push(\n `the palette carries no node capability \"${capability.id}\": a model cannot use it at all`,\n )\n continue\n }\n if (!Object.keys(entry.propsSchema.properties).length) {\n problems.push(\n `node capability \"${capability.id}\" is carried with no prop a model can write`,\n )\n }\n for (const attribute of capability.attributes) {\n if (entry.propsSchema.properties[attribute.name]) continue\n if (!entry.omittedProps.includes(attribute.name)) {\n problems.push(\n `node capability \"${capability.id}\" declares \"${attribute.name}\", which the palette neither offers nor records as omitted`,\n )\n continue\n }\n if (offeredFieldKinds.has(attribute.component)) {\n problems.push(\n `node capability \"${capability.id}\" omits \"${attribute.name}\", whose \"${attribute.component}\" field the palette offers elsewhere`,\n )\n }\n }\n }\n for (const id of Object.keys(carried)) {\n if (!declared.some((capability) => capability.id === id)) {\n problems.push(\n `the palette carries node capability \"${id}\", which core no longer declares`,\n )\n }\n }\n return problems\n}\n\nexport interface AiSurfaceDefinition {\n /** The component id at the top of a tree for this surface. */\n root: string\n /** Every component id a node in this surface may carry. */\n allow: readonly string[]\n}\n\n/**\n * Theme vocabulary an `sx` value may name, read off the theme sources so the\n * validator's allowlist is derived rather than typed by hand.\n */\nexport interface AiSxTokens {\n /** Dotted palette paths such as `primary.main`. */\n palette: readonly string[]\n /** Responsive object keys. */\n breakpoints: readonly string[]\n /** Values `typography` may take. */\n typographyVariants: readonly string[]\n}\n\n/**\n * Text ceilings per role of copy (AGL-2905). A headline is one line of a\n * hero, body copy is a paragraph or two, and a button label has to fit a\n * button.\n */\nexport const AI_TEXT_LIMITS = {\n headline: 120,\n body: 2_000,\n button: 40,\n /** A short label, title or caption. */\n label: 200,\n} as const\n\n/**\n * Prompt budget per surface catalog, in characters at four per token.\n * Asserted by a spec so a component that grows its description cannot\n * silently push a system prompt past what the route pays for.\n */\nexport const AI_PALETTE_CATALOG_MAX_TOKENS = 2_500\nexport const AI_PALETTE_CATALOG_MAX_CHARS = AI_PALETTE_CATALOG_MAX_TOKENS * 4\n\n/** The model's own token estimate: characters over four. */\nexport function estimateCatalogTokens(catalog: string): number {\n return Math.ceil(catalog.length / 4)\n}\n\n/**\n * The documents a generator emits (AGL-2935). Each is composed on one\n * surface of the palette and held to its own budget below.\n */\nexport type AiOutputKind =\n | 'page'\n | 'template'\n | 'component'\n | 'layout'\n | 'form'\n | 'email'\n\nexport const AI_OUTPUT_KINDS: readonly AiOutputKind[] = [\n 'page',\n 'template',\n 'component',\n 'layout',\n 'form',\n 'email',\n]\n\n/** The surface each output kind is composed on. */\nexport const AI_OUTPUT_SURFACE: Record<AiOutputKind, AiSurface> = {\n page: 'screen',\n template: 'screen',\n component: 'component',\n layout: 'layout',\n form: 'form',\n email: 'email',\n}\n\n/**\n * What the rule-17 scorer measures on an emitted tree:\n *\n * - `nodes` — how many elements the tree carries;\n * - `bytes` — the stored node map, in the unit the save ceiling uses\n * (`nodeMapBytes`, msgpack);\n * - `imageBytes` — what the placed library images request at the slot's\n * variant, from each asset's recorded size;\n * - `embeds` — third-party players and frames the output loads;\n * - `fontFamilies` — families named beyond the theme's own;\n * - `emailHtmlBytes` — an email's rendered HTML, which is what a mail\n * client clips.\n */\nexport type AiBudgetMetric =\n | 'nodes'\n | 'bytes'\n | 'imageBytes'\n | 'embeds'\n | 'fontFamilies'\n | 'emailHtmlBytes'\n\nexport type AiOutputBudget = Record<Exclude<AiBudgetMetric, 'emailHtmlBytes'>, number> & {\n emailHtmlBytes?: number\n}\n\n/**\n * The size past which Gmail clips a message and hides the rest behind a\n * \"View entire message\" link — the unsubscribe footer included.\n */\nexport const AI_EMAIL_CLIP_BYTES = 102_000\n\n/**\n * The budget per output kind (AGL-2935, rule 17), beside the catalog budget\n * above for the same reason: a generator that grows its output past what\n * its kind is for is caught by a number, not noticed by a reader.\n *\n * A page is a few hundred elements and tens of kilobytes of stored map; a\n * reusable component is one repeated block; a layout is the chrome every\n * page loads, so it carries the least imagery; a form is fields and a\n * button; an email is held to the clipping threshold on its rendered HTML.\n * No kind names a font beyond the theme's.\n *\n * A third-party player is a page's or a component's to carry, and only where\n * a confirmed plan lists it (rule 16). A template is rendered for every\n * record it serves and a layout around every page, so neither carries one\n * (AGL-3433): an embed there loads a stranger's code on every page of a\n * collection or a site.\n */\nexport const AI_OUTPUT_BUDGETS: Record<AiOutputKind, AiOutputBudget> = {\n page: {\n nodes: 400,\n bytes: 60_000,\n imageBytes: 1_500_000,\n embeds: 1,\n fontFamilies: 0,\n },\n template: {\n nodes: 400,\n bytes: 60_000,\n imageBytes: 1_500_000,\n embeds: 0,\n fontFamilies: 0,\n },\n component: {\n nodes: 80,\n bytes: 12_000,\n imageBytes: 500_000,\n embeds: 1,\n fontFamilies: 0,\n },\n layout: {\n nodes: 160,\n bytes: 24_000,\n imageBytes: 200_000,\n embeds: 0,\n fontFamilies: 0,\n },\n form: {\n nodes: 60,\n bytes: 8_000,\n imageBytes: 0,\n embeds: 0,\n fontFamilies: 0,\n },\n email: {\n nodes: 150,\n bytes: 24_000,\n imageBytes: 1_000_000,\n embeds: 0,\n fontFamilies: 0,\n emailHtmlBytes: AI_EMAIL_CLIP_BYTES,\n },\n}\n\n/**\n * What a first visit to a generated output is estimated to transfer — the\n * weight the proposal shows before anything is applied (rule 17). A page\n * starts from the platform's measured page weight; a component or a layout\n * reports only what it adds to a page; an email is not a page load.\n */\nexport interface AiLoadEstimate {\n /** The measured weight of a published page before this output's own bytes; 0 for a fragment. */\n pageBytes: number\n /** The stored node map, in the save's own bytes. */\n documentBytes: number\n /** What the placed library images request at their slots. */\n imageBytes: number\n /** Images the estimate could not size: no recorded size, or not from the library. */\n imagesUnmeasured: number\n embeds: number\n totalBytes: number\n}\n\n/** Typography variants that read as headlines, for the `children` ceiling. */\nexport function isHeadlineVariant(variant: unknown): boolean {\n return typeof variant === 'string' && /^(display\\w*|h[1-6]|heading)$/.test(variant)\n}\n"],"names":["AI_SURFACE_NAMES","nodeCapabilityProps","capabilities","acceptsChildren","properties","propRoles","textLimits","capability","Object","values","name","schema","entries","propsSchema","childrenOnlyProps","includes","role","limit","undefined","auditNodeCapabilities","declared","carried","offeredFieldKinds","problems","entry","id","push","keys","length","attribute","attributes","omittedProps","has","component","some","AI_TEXT_LIMITS","headline","body","button","label","AI_PALETTE_CATALOG_MAX_TOKENS","AI_PALETTE_CATALOG_MAX_CHARS","estimateCatalogTokens","catalog","Math","ceil","AI_OUTPUT_KINDS","AI_OUTPUT_SURFACE","page","template","layout","form","email","AI_EMAIL_CLIP_BYTES","AI_OUTPUT_BUDGETS","nodes","bytes","imageBytes","embeds","fontFamilies","emailHtmlBytes","isHeadlineVariant","variant","test"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-palette.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 { JsonLinealOrder } from '@aglyn/aglyn/app-utils/lineal-order'\n\n/**\n * The element palette as a model sees it (AGL-2905).\n *\n * `ai-palette.generated.ts` is produced from the registered plugin bundles by\n * `libs/plugins/ai/scripts/generate-ai-palette.mts` and holds the data; this module\n * holds the shapes that data is typed against and the small readers over it,\n * so a regeneration never rewrites a type.\n */\n\n/** The document kinds a model is asked to compose. */\nexport type AiSurface = 'screen' | 'email' | 'form' | 'layout' | 'component'\n\nexport const AI_SURFACE_NAMES: readonly AiSurface[] = [\n 'screen',\n 'email',\n 'form',\n 'layout',\n 'component',\n]\n\n/**\n * The subset of JSON Schema a prop declaration is expressed in. Enough to\n * validate what an attribute field can persist; nothing the field editor\n * cannot itself model.\n */\nexport interface AiPropSchema {\n type?: 'string' | 'number' | 'integer' | 'boolean'\n enum?: readonly string[]\n minLength?: number\n maxLength?: number\n minimum?: number\n maximum?: number\n pattern?: string\n description?: string\n}\n\nexport interface AiPropsSchema {\n type: 'object'\n properties: Record<string, AiPropSchema>\n required: readonly string[]\n additionalProperties: false\n}\n\n/**\n * What a string prop MEANS, which decides how the validator checks it.\n *\n * - `text` — copy the visitor reads; held to a length ceiling.\n * - `url` — a navigable address: an `https:` URL or a root-relative path.\n * - `screen` — a screen id, checked against the screens the host has.\n * - `media` — an image or video source: a DAM reference or an `https:` URL.\n */\nexport type AiPropRole = 'text' | 'url' | 'screen' | 'media'\n\n/**\n * A `restrictChildren` / `restrictParent` directive as generated data: the\n * same tuple the schema declares, with the flag spelled as the string the\n * enum holds so a JSON emitter can write it.\n */\nexport type AiLinealOrder = JsonLinealOrder\n\nexport interface AiPaletteEntry {\n pluginId: string\n kind: 'element' | 'plaintext' | 'markdown'\n category: string\n displayName: string\n /** One sentence, from the schema's own description. */\n summary: string\n /** Whether the editor lets anything be dropped inside this element. */\n acceptsChildren: boolean\n restrictChildren?: AiLinealOrder\n restrictParent?: AiLinealOrder\n propsSchema: AiPropsSchema\n /** Roles of the string props the validator treats specially. */\n propRoles: Record<string, AiPropRole>\n /**\n * The attribute field kind each declared prop is edited with, as its\n * `FieldComponentType` value (`switch`, `select`, `screen-select`, …): what\n * the editor reads to decide which component properties a field can be\n * bound to (AGL-2908).\n */\n propFields: Record<string, string>\n /** Length ceilings for `text` props, in characters. */\n textLimits: Record<string, number>\n /** Names of the presets that place this component. */\n presets: readonly string[]\n}\n\n/**\n * What every NODE can do, as against what one component declares (AGL-3156).\n *\n * Generated from the node capabilities core declares, beside the per-component\n * schemas rather than out of them: repeating is not a Stack's feature, so no\n * component schema names it and a palette built from schemas alone advertises\n * it on nothing. Declared ONCE for the whole palette — a model may write these\n * props on any element the surface allows.\n */\nexport interface AiNodeCapability {\n /** Stable id, as core declares it. */\n id: string\n displayName: string\n /** One sentence, for the prompt catalog. */\n summary: string\n propsSchema: AiPropsSchema\n propRoles: Record<string, AiPropRole>\n propFields: Record<string, string>\n textLimits: Record<string, number>\n /** Props offered only on a node that holds a child list. */\n childrenOnlyProps: readonly string[]\n /**\n * Declared attributes the palette deliberately does not offer: a picker\n * that chooses a record the site holds, which a model cannot name from a\n * description. Recorded so a field left off on purpose reads differently\n * from one that went missing.\n */\n omittedProps: readonly string[]\n}\n\n/**\n * The capability props a node with this child contract may carry, merged into\n * one map the prop sanitizer can read beside the element's own declarations.\n */\nexport function nodeCapabilityProps(\n capabilities: Readonly<Record<string, AiNodeCapability>>,\n acceptsChildren: boolean,\n): {\n properties: Record<string, AiPropSchema>\n propRoles: Record<string, AiPropRole>\n textLimits: Record<string, number>\n} {\n const properties: Record<string, AiPropSchema> = {}\n const propRoles: Record<string, AiPropRole> = {}\n const textLimits: Record<string, number> = {}\n for (const capability of Object.values(capabilities)) {\n for (const [name, schema] of Object.entries(\n capability.propsSchema.properties,\n )) {\n if (!acceptsChildren && capability.childrenOnlyProps.includes(name))\n continue\n properties[name] = schema\n const role = capability.propRoles[name]\n if (role) propRoles[name] = role\n const limit = capability.textLimits[name]\n if (limit !== undefined) textLimits[name] = limit\n }\n }\n return { properties, propRoles, textLimits }\n}\n\n/**\n * What a node capability declared in core and one carried by the palette\n * disagree about (AGL-3156).\n *\n * `check:ai-palette` pins the generated file to the generator's output for its\n * inputs, which cannot notice a capability leaving those inputs: when repeat\n * stopped being a component's attribute, the generator regenerated cleanly and\n * the palette simply stopped mentioning it. This reads the core declarations\n * instead and asks whether the palette still accounts for each one, so the\n * next generic capability cannot vanish the same way.\n *\n * `offeredFieldKinds` is every field kind the palette DOES carry somewhere,\n * derived from the generated entries rather than restated here: a capability\n * attribute drawn with one of those kinds has no excuse for being omitted.\n */\nexport function auditNodeCapabilities(\n declared: ReadonlyArray<{\n id: string\n label: string\n attributes: ReadonlyArray<{ name: string; component: string }>\n }>,\n carried: Readonly<Record<string, AiNodeCapability>>,\n offeredFieldKinds: ReadonlySet<string>,\n): string[] {\n const problems: string[] = []\n for (const capability of declared) {\n const entry = carried[capability.id]\n if (!entry) {\n problems.push(\n `the palette carries no node capability \"${capability.id}\": a model cannot use it at all`,\n )\n continue\n }\n if (!Object.keys(entry.propsSchema.properties).length) {\n problems.push(\n `node capability \"${capability.id}\" is carried with no prop a model can write`,\n )\n }\n for (const attribute of capability.attributes) {\n if (entry.propsSchema.properties[attribute.name]) continue\n if (!entry.omittedProps.includes(attribute.name)) {\n problems.push(\n `node capability \"${capability.id}\" declares \"${attribute.name}\", which the palette neither offers nor records as omitted`,\n )\n continue\n }\n if (offeredFieldKinds.has(attribute.component)) {\n problems.push(\n `node capability \"${capability.id}\" omits \"${attribute.name}\", whose \"${attribute.component}\" field the palette offers elsewhere`,\n )\n }\n }\n }\n for (const id of Object.keys(carried)) {\n if (!declared.some((capability) => capability.id === id)) {\n problems.push(\n `the palette carries node capability \"${id}\", which core no longer declares`,\n )\n }\n }\n return problems\n}\n\nexport interface AiSurfaceDefinition {\n /** The component id at the top of a tree for this surface. */\n root: string\n /** Every component id a node in this surface may carry. */\n allow: readonly string[]\n /**\n * Component ids admitted only in a tree the platform's own code composed\n * (AGL-3660), never listed in the surface's catalog: see\n * `AiNodeTreeContext.codeBuilt`.\n */\n codeOnly?: readonly string[]\n}\n\n/**\n * Theme vocabulary an `sx` value may name, read off the theme sources so the\n * validator's allowlist is derived rather than typed by hand.\n */\nexport interface AiSxTokens {\n /** Dotted palette paths such as `primary.main`. */\n palette: readonly string[]\n /** Responsive object keys. */\n breakpoints: readonly string[]\n /** Values `typography` may take. */\n typographyVariants: readonly string[]\n}\n\n/**\n * Text ceilings per role of copy (AGL-2905). A headline is one line of a\n * hero, body copy is a paragraph or two, and a button label has to fit a\n * button.\n */\nexport const AI_TEXT_LIMITS = {\n headline: 120,\n body: 2_000,\n button: 40,\n /** A short label, title or caption. */\n label: 200,\n} as const\n\n/**\n * Prompt budget per surface catalog, in characters at four per token.\n * Asserted by a spec so a component that grows its description cannot\n * silently push a system prompt past what the route pays for.\n */\nexport const AI_PALETTE_CATALOG_MAX_TOKENS = 2_500\nexport const AI_PALETTE_CATALOG_MAX_CHARS = AI_PALETTE_CATALOG_MAX_TOKENS * 4\n\n/** The model's own token estimate: characters over four. */\nexport function estimateCatalogTokens(catalog: string): number {\n return Math.ceil(catalog.length / 4)\n}\n\n/**\n * The documents a generator emits (AGL-2935). Each is composed on one\n * surface of the palette and held to its own budget below.\n */\nexport type AiOutputKind =\n | 'page'\n | 'template'\n | 'component'\n | 'layout'\n | 'form'\n | 'email'\n\nexport const AI_OUTPUT_KINDS: readonly AiOutputKind[] = [\n 'page',\n 'template',\n 'component',\n 'layout',\n 'form',\n 'email',\n]\n\n/** The surface each output kind is composed on. */\nexport const AI_OUTPUT_SURFACE: Record<AiOutputKind, AiSurface> = {\n page: 'screen',\n template: 'screen',\n component: 'component',\n layout: 'layout',\n form: 'form',\n email: 'email',\n}\n\n/**\n * What the rule-17 scorer measures on an emitted tree:\n *\n * - `nodes` — how many elements the tree carries;\n * - `bytes` — the stored node map, in the unit the save ceiling uses\n * (`nodeMapBytes`, msgpack);\n * - `imageBytes` — what the placed library images request at the slot's\n * variant, from each asset's recorded size;\n * - `embeds` — third-party players and frames the output loads;\n * - `fontFamilies` — families named beyond the theme's own;\n * - `emailHtmlBytes` — an email's rendered HTML, which is what a mail\n * client clips.\n */\nexport type AiBudgetMetric =\n | 'nodes'\n | 'bytes'\n | 'imageBytes'\n | 'embeds'\n | 'fontFamilies'\n | 'emailHtmlBytes'\n\nexport type AiOutputBudget = Record<Exclude<AiBudgetMetric, 'emailHtmlBytes'>, number> & {\n emailHtmlBytes?: number\n}\n\n/**\n * The size past which Gmail clips a message and hides the rest behind a\n * \"View entire message\" link — the unsubscribe footer included.\n */\nexport const AI_EMAIL_CLIP_BYTES = 102_000\n\n/**\n * The budget per output kind (AGL-2935, rule 17), beside the catalog budget\n * above for the same reason: a generator that grows its output past what\n * its kind is for is caught by a number, not noticed by a reader.\n *\n * A page is a few hundred elements and tens of kilobytes of stored map; a\n * reusable component is one repeated block; a layout is the chrome every\n * page loads, so it carries the least imagery; a form is fields and a\n * button; an email is held to the clipping threshold on its rendered HTML.\n * No kind names a font beyond the theme's.\n *\n * A third-party player is a page's or a component's to carry, and only where\n * a confirmed plan lists it (rule 16). A template is rendered for every\n * record it serves and a layout around every page, so neither carries one\n * (AGL-3433): an embed there loads a stranger's code on every page of a\n * collection or a site.\n */\nexport const AI_OUTPUT_BUDGETS: Record<AiOutputKind, AiOutputBudget> = {\n page: {\n nodes: 400,\n bytes: 60_000,\n imageBytes: 1_500_000,\n embeds: 1,\n fontFamilies: 0,\n },\n template: {\n nodes: 400,\n bytes: 60_000,\n imageBytes: 1_500_000,\n embeds: 0,\n fontFamilies: 0,\n },\n component: {\n nodes: 80,\n bytes: 12_000,\n imageBytes: 500_000,\n embeds: 1,\n fontFamilies: 0,\n },\n layout: {\n nodes: 160,\n bytes: 24_000,\n imageBytes: 200_000,\n embeds: 0,\n fontFamilies: 0,\n },\n form: {\n nodes: 60,\n bytes: 8_000,\n imageBytes: 0,\n embeds: 0,\n fontFamilies: 0,\n },\n email: {\n nodes: 150,\n bytes: 24_000,\n imageBytes: 1_000_000,\n embeds: 0,\n fontFamilies: 0,\n emailHtmlBytes: AI_EMAIL_CLIP_BYTES,\n },\n}\n\n/**\n * What a first visit to a generated output is estimated to transfer — the\n * weight the proposal shows before anything is applied (rule 17). A page\n * starts from the platform's measured page weight; a component or a layout\n * reports only what it adds to a page; an email is not a page load.\n */\nexport interface AiLoadEstimate {\n /** The measured weight of a published page before this output's own bytes; 0 for a fragment. */\n pageBytes: number\n /** The stored node map, in the save's own bytes. */\n documentBytes: number\n /** What the placed library images request at their slots. */\n imageBytes: number\n /** Images the estimate could not size: no recorded size, or not from the library. */\n imagesUnmeasured: number\n embeds: number\n totalBytes: number\n}\n\n/** Typography variants that read as headlines, for the `children` ceiling. */\nexport function isHeadlineVariant(variant: unknown): boolean {\n return typeof variant === 'string' && /^(display\\w*|h[1-6]|heading)$/.test(variant)\n}\n"],"names":["AI_SURFACE_NAMES","nodeCapabilityProps","capabilities","acceptsChildren","properties","propRoles","textLimits","capability","Object","values","name","schema","entries","propsSchema","childrenOnlyProps","includes","role","limit","undefined","auditNodeCapabilities","declared","carried","offeredFieldKinds","problems","entry","id","push","keys","length","attribute","attributes","omittedProps","has","component","some","AI_TEXT_LIMITS","headline","body","button","label","AI_PALETTE_CATALOG_MAX_TOKENS","AI_PALETTE_CATALOG_MAX_CHARS","estimateCatalogTokens","catalog","Math","ceil","AI_OUTPUT_KINDS","AI_OUTPUT_SURFACE","page","template","layout","form","email","AI_EMAIL_CLIP_BYTES","AI_OUTPUT_BUDGETS","nodes","bytes","imageBytes","embeds","fontFamilies","emailHtmlBytes","isHeadlineVariant","variant","test"],"mappings":"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"}
@@ -18,6 +18,8 @@ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_wi
18
18
  */ import { aiModelCacheMinTokens } from "../providers/catalog.js";
19
19
  import { AI_IMAGE_MAX_BYTES, AI_IMAGE_MEDIA_TYPES, AI_REQUEST_MAX_IMAGES, AiRequestShapeError, AiUpstreamError, aiBase64Bytes, aiMessageImages } from "../providers/contract.js";
20
20
  import { resolveAiProvider } from "../providers/routing.js";
21
+ import { aiDevCacheTtl, aiDevReplayMode, aiIsDeployedRuntime, aiLiveBatchWanted, aiWarnIfReplayAskedInDeployment } from "./ai-dev-env.js";
22
+ import { aiCompleteInBatch } from "./ai-live-batch.js";
21
23
  export { AI_UPSTREAM_FAILURE_COPY, AiRequestShapeError, AiUpstreamError } from "../providers/contract.js";
22
24
  /**
23
25
  * The one runtime every AI door calls (AGL-2903), provider-generic since
@@ -84,6 +86,13 @@ When a brief is ordinary business content, write it well and do not lecture.`;
84
86
  throw new AiRequestShapeError(`system block ${index} is volatile but sits inside the cached prefix (last breakpoint at block ${lastBreakpoint})`);
85
87
  }
86
88
  });
89
+ // A site's block is cached per site (AGL-3661), so it closes the cached
90
+ // span: a platform-wide breakpoint after it would be keyed on the site too,
91
+ // and every workspace would warm its own copy of a shared prompt.
92
+ const firstSite = system.findIndex((block)=>block.site && block.cacheBreakpoint);
93
+ if (firstSite >= 0 && firstSite !== lastBreakpoint) {
94
+ throw new AiRequestShapeError(`system block ${firstSite} is a site's cached block but breakpoint ${lastBreakpoint} follows it`);
95
+ }
87
96
  }
88
97
  /**
89
98
  * Whether a provider's model reads a picture in a user turn (AGL-2916): what
@@ -220,6 +229,7 @@ function providerFor(input) {
220
229
  };
221
230
  }
222
231
  export async function runAiRequest(input) {
232
+ var _request_cacheTtl;
223
233
  validateAiSystemBlocks(input.system);
224
234
  const { provider, apiKey } = providerFor(input);
225
235
  validateAiMessages(input.messages, ()=>aiModelReadsImages(provider, input.model));
@@ -228,16 +238,50 @@ export async function runAiRequest(input) {
228
238
  "settings",
229
239
  "provider"
230
240
  ]);
231
- const providerRequest = _extends({}, request, {
241
+ aiWarnIfReplayAskedInDeployment();
242
+ const cacheTtl = (_request_cacheTtl = request.cacheTtl) != null ? _request_cacheTtl : aiDevCacheTtl();
243
+ const providerRequest = _extends({}, request, cacheTtl ? {
244
+ cacheTtl
245
+ } : {}, {
232
246
  apiKey
233
247
  });
234
248
  try {
235
- return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest));
249
+ if (aiIsDeployedRuntime()) {
250
+ return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest));
251
+ }
252
+ return await runInDevelopment(provider, providerRequest, stream);
236
253
  } catch (error) {
237
254
  await reportAccountProblem(provider, error);
238
255
  throw error;
239
256
  }
240
257
  }
258
+ /**
259
+ * A request in a development process (AGL-3660): through the replay cache,
260
+ * batch mode and the first-request-warms-the-prefix rule when this process
261
+ * asked for them (`ai-dev-env.ts`), straight to the provider when it asked
262
+ * for none. The file-backed half is loaded only when a layer is on, so a dev
263
+ * server that never asked loads nothing it did not before.
264
+ */ async function runInDevelopment(provider, request, stream) {
265
+ const mode = aiDevReplayMode();
266
+ const batch = !stream && aiLiveBatchWanted() && Boolean(provider.completeBatch);
267
+ if (mode === 'off' && !batch) {
268
+ return stream ? provider.stream(request) : provider.complete(request);
269
+ }
270
+ const dev = await import("./ai-dev-replay.js");
271
+ const ttlMs = request.cacheTtl === '1h' ? 60 * 60000 : 5 * 60000;
272
+ const prefix = dev.aiCachedPrefixKey(request);
273
+ if (stream) {
274
+ const live = ()=>dev.aiDevWarmPrefixFirst(prefix, ttlMs, ()=>provider.stream(request));
275
+ return mode === 'off' ? live() : dev.aiDevReplayStream(provider, request, mode, live);
276
+ }
277
+ const live = async ()=>{
278
+ const result = batch ? await aiCompleteInBatch(provider, request) : await dev.aiDevWarmPrefixFirst(prefix, ttlMs, ()=>provider.complete(request));
279
+ dev.aiDevCountLive(result.usage, request.model, batch ? AI_BATCH_PRICE_FACTOR : 1);
280
+ return result;
281
+ };
282
+ return mode === 'off' ? live() : dev.aiDevReplayComplete(provider, request, mode, live);
283
+ }
284
+ /** What a batch's discount leaves of a request's price, for the development count. */ const AI_BATCH_PRICE_FACTOR = 0.5;
241
285
  /**
242
286
  * Tells the operator when the failure is the platform's provider account —
243
287
  * a refused key, no credit — rather than the request (AGL-3377). Once per
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-runtime.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 { aiModelCacheMinTokens } from '../providers/catalog'\nimport {\n AI_IMAGE_MAX_BYTES,\n AI_IMAGE_MEDIA_TYPES,\n AI_REQUEST_MAX_IMAGES,\n AiRequestShapeError,\n AiUpstreamError,\n aiBase64Bytes,\n aiMessageImages,\n type AiMessage,\n type AiProvider,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiTool,\n} from '../providers/contract'\nimport { resolveAiProvider, type AiPluginSettings } from '../providers/routing'\n\nexport {\n AI_UPSTREAM_FAILURE_COPY,\n AiRequestShapeError,\n AiUpstreamError,\n type AiCompletion,\n type AiEffort,\n type AiImagePart,\n type AiMessage,\n type AiMessagePart,\n type AiRefusal,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiThinking,\n type AiTool,\n type AiToolUse,\n type AiUsage,\n} from '../providers/contract'\n\n/**\n * The one runtime every AI door calls (AGL-2903), provider-generic since\n * AGL-2939.\n *\n * `/api/assist/chat` and `/api/ai/assist` each carried their own `fetch` to\n * one vendor, their own SSE parser and their own usage extraction, and a\n * third door would have been a third copy. This module owns the request\n * shape, the cache-prefix rule, the provider resolution and the error\n * boundary; an adapter owns one vendor's wire protocol; a door owns its\n * prompt, its gate ladder and its wire format.\n *\n * The cache-prefix rule (AGL-2352) is enforced HERE, before any adapter:\n * a block marked `volatile` may not sit at or before the last\n * `cacheBreakpoint`, and more breakpoints than any provider caches are\n * refused. A provider without a cache ignores the markers and keeps the\n * order, so a request that is right for the caching adapter is right for\n * every adapter.\n *\n * The picture rule (AGL-2916) is enforced here too, and for the same reason:\n * a picture on an assistant turn, a picture for a model whose provider does\n * not say it reads one, or a picture past the bounds is refused before any\n * adapter is reached, so no door can send one a provider would reject.\n */\n\n/** The most `cacheBreakpoint` markers a request may carry. */\nexport const AI_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * The acceptable-use rules every generation prompt carries (AGL-2925).\n *\n * ONE constant, appended to each door's STATIC system text — inside the\n * cached prefix on the console chat, ahead of the mode prompt on the copy\n * assistant — so the rules the model is held to are the same at every\n * door and carry no per-org byte. The Free taste is what makes them worth\n * stating: a generated page that nobody paid for is the cheapest phishing\n * kit there is, and the model declining it is the first line, ahead of the\n * abuse-report route and the lockdown key.\n *\n * The categories are the platform's own Acceptable Use Policy, restated in\n * the second person for the model. A decline from the model's safety layer\n * arrives as a refusal, which the meter counts against the account's daily\n * pause and never against its credits.\n */\nexport const AI_ACCEPTABLE_USE_BLOCK = `Acceptable use — decline these rather than write them, and say briefly why:\n- Pages, emails or copy that harvest credentials, payment details or personal data by deception (phishing, fake sign-in or checkout pages, fake \"verify your account\" notices).\n- Impersonating a real business, brand, public body or person you were not told the user represents — their name, logo, sign-in page or voice — or copy that claims an endorsement, certification or partnership that was not given to you as a fact.\n- Content that promotes or sells prohibited goods and services: weapons, illegal drugs, counterfeit goods, malware or deceptive downloads, unlicensed gambling, or adult content involving minors.\n- Scams and fraud: fake investment or crypto schemes, advance-fee offers, tech-support scams, fake charities, deceptive \"you have won\" pages, or copy built to defraud.\n- Harassment, threats, or hateful content targeting a person or a group.\n- Spam farms: bulk near-duplicate pages or copy whose only purpose is to game search engines or send unsolicited mail.\nWhen a brief is ordinary business content, write it well and do not lecture.`\n\n/** A door's request, with the org's settings when the door knows the org. */\ninterface AiRuntimeOptions {\n /** The org's resolved `pluginSettings/ai`, for its provider and model choices. */\n settings?: AiPluginSettings\n /** A provider chosen by the caller, ahead of every setting; specs use it. */\n provider?: AiProvider\n}\n\nexport interface AiRequest extends AiRequestBase, AiRuntimeOptions {\n stream: false\n}\n\nexport interface AiStreamRequest extends AiRequestBase, AiRuntimeOptions {\n stream: true\n}\n\n/**\n * The cache guard, applied to the system blocks before any adapter sees\n * them. Exported so a spec and a door can assert a prompt's shape without\n * a provider.\n */\nexport function validateAiSystemBlocks(system: readonly AiSystemBlock[]): void {\n const breakpoints = system.filter((block) => block.cacheBreakpoint)\n if (breakpoints.length > AI_MAX_CACHE_BREAKPOINTS) {\n throw new AiRequestShapeError(\n `${breakpoints.length} cache breakpoints; at most ${AI_MAX_CACHE_BREAKPOINTS} are cached`,\n )\n }\n // Caching is a prefix match, so the LAST breakpoint decides what is\n // cached, and every block at or before it is inside the cached prefix. A\n // volatile block anywhere in that span — including one that is itself\n // the breakpoint — puts a per-request byte into the prefix (AGL-2352).\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n system.forEach((block, index) => {\n if (block.volatile && index <= lastBreakpoint) {\n throw new AiRequestShapeError(\n `system block ${index} is volatile but sits inside the cached prefix (last breakpoint at block ${lastBreakpoint})`,\n )\n }\n })\n}\n\n/**\n * Whether a provider's model reads a picture in a user turn (AGL-2916): what\n * the provider's own descriptor of the model says. A model the provider does\n * not describe reads none, so an adopter's provider is never sent a picture\n * it did not say it can take.\n */\nexport function aiModelReadsImages(provider: AiProvider, model: string): boolean {\n return provider\n .models()\n .some((descriptor) => descriptor.id === model && descriptor.capabilities.vision === true)\n}\n\n/**\n * Whether a model a door resolved reads pictures on the provider that would\n * serve the request: what a door asks before it attaches one, so a model that\n * does not is sent the text alone rather than refused.\n */\nexport function aiRouteReadsImages(model: string, settings?: AiPluginSettings): boolean {\n const provider = resolveAiProvider(settings)\n return provider ? aiModelReadsImages(provider, model) : false\n}\n\n/** A character standard base64 does not use, outside its padding. */\nconst NOT_BASE64 = /[^A-Za-z0-9+/]/\n\n/**\n * Whether `data` is standard base64 and nothing else: its alphabet, then at\n * most two `=` of padding; no `data:` prefix, no line breaks.\n *\n * Scanned for a character outside the alphabet rather than matched whole: a\n * picture is megabytes of base64, and a whole-string pattern with a greedy\n * loop can exhaust the regular expression engine's backtrack stack on input\n * that large, which throws a RangeError instead of answering.\n */\nfunction isBareBase64(data: string): boolean {\n const body = data.endsWith('==') ? data.slice(0, -2) : data.endsWith('=') ? data.slice(0, -1) : data\n return body.length > 0 && !NOT_BASE64.test(body)\n}\n\n/**\n * The picture guard (AGL-2916), applied to the messages before any adapter\n * sees them. A picture rides only a user turn, only to a model that reads\n * pictures, only in a format every such model takes, as bare base64 within\n * the size a provider accepts, and no more of them than a request may carry.\n * `readsImages` is asked only when a turn carries a picture. Exported so a\n * spec and a door can assert a request's shape without a provider.\n */\nexport function validateAiMessages(messages: readonly AiMessage[], readsImages: () => boolean): void {\n messages.forEach((message, index) => {\n if (typeof message.content === 'string') return\n if (!Array.isArray(message.content) || message.content.length === 0) {\n throw new AiRequestShapeError(`message ${index} has neither text nor parts`)\n }\n for (const part of message.content) {\n if (part.type === 'text') {\n if (typeof part.text !== 'string') {\n throw new AiRequestShapeError(`message ${index} has a text part with no text`)\n }\n continue\n }\n if (part.type !== 'image') {\n throw new AiRequestShapeError(`message ${index} has a part of an unknown type`)\n }\n if (message.role !== 'user') {\n throw new AiRequestShapeError(`message ${index} is an assistant turn and carries a picture`)\n }\n if (!(AI_IMAGE_MEDIA_TYPES as readonly string[]).includes(part.mediaType)) {\n throw new AiRequestShapeError(`message ${index} carries a picture of an unsupported type`)\n }\n if (typeof part.data !== 'string') {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n // The size first: it needs only the length, so an oversized picture is\n // refused without reading its bytes.\n if (aiBase64Bytes(part.data) > AI_IMAGE_MAX_BYTES) {\n throw new AiRequestShapeError(\n `message ${index} carries a picture over ${AI_IMAGE_MAX_BYTES} bytes`,\n )\n }\n if (!isBareBase64(part.data)) {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n }\n })\n const images = aiMessageImages(messages).length\n if (images > AI_REQUEST_MAX_IMAGES) {\n throw new AiRequestShapeError(`${images} pictures; a request carries at most ${AI_REQUEST_MAX_IMAGES}`)\n }\n if (images > 0 && !readsImages()) {\n throw new AiRequestShapeError('the request carries a picture and its model does not read pictures')\n }\n}\n\n/**\n * Characters to a token when a prompt is measured rather than tokenized\n * (AGL-2937). Four is the high end of English prose, so reading a prefix\n * through it errs LOW on the token count — and a prefix counts as cached\n * only when it clears its model's minimum even at that reading. Erring the\n * other way would let a prompt claim a cache it does not get.\n */\nexport const AI_CACHE_PREFIX_TOKEN_CHARS = 4\n\n/**\n * The characters inside the cached span of a request: the tools, which a\n * provider renders ahead of the system blocks and which therefore sit in\n * every prefix, plus every system block up to and including the last\n * breakpoint. Zero when the request marks no breakpoint at all.\n */\nexport function aiCachedPrefixChars(\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): number {\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n if (lastBreakpoint < 0) return 0\n const toolChars = tools.reduce((total, tool) => total + JSON.stringify(tool).length, 0)\n return system\n .slice(0, lastBreakpoint + 1)\n .reduce((total, block) => total + block.text.length, toolChars)\n}\n\n/**\n * Whether this request's cached span is long enough for the model to cache\n * it at all (AGL-2937). A prefix under the model's `cacheMinTokens` is\n * served with the breakpoints honored and nothing cached — no error, and a\n * usage report that reads as a permanent miss — so a door designed around a\n * cache has to answer this question before it believes its own markers, and\n * `runtime/ai-prompt-cache.spec.ts` answers it for every door at once.\n */\nexport function aiCachedPrefixCaches(\n model: string,\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): boolean {\n const chars = aiCachedPrefixChars(system, tools)\n return chars / AI_CACHE_PREFIX_TOKEN_CHARS >= aiModelCacheMinTokens(model)\n}\n\n/**\n * Whether a provider is configured to answer: registered, and its key\n * present. Every door asks this before it spends a reservation, and\n * answers 501 (or the docs fallback) when it is false.\n */\nexport function aiProviderReady(settings?: AiPluginSettings): boolean {\n return Boolean(resolveAiProvider(settings)?.readApiKey())\n}\n\n/** The provider that would answer, for a door that names it in its logs or its inventory. */\nexport function aiActiveProvider(settings?: AiPluginSettings): AiProvider | undefined {\n return resolveAiProvider(settings)\n}\n\nfunction providerFor(input: AiRuntimeOptions): { provider: AiProvider; apiKey: string } {\n const provider = input.provider ?? resolveAiProvider(input.settings)\n if (!provider) throw new Error('no AI provider is registered')\n const apiKey = provider.readApiKey()\n if (!apiKey) {\n // Every door gates on `aiProviderReady` before it gets here, so this is\n // a wiring fault rather than a customer path.\n throw new Error(`${provider.apiKeyEnv} is not set`)\n }\n return { provider, apiKey }\n}\n\n/**\n * Send one request through the resolved provider.\n *\n * Streaming: resolves once the provider has ACCEPTED the request — after\n * the headers, before the first token — with an iterator of simplified\n * events the caller wraps in its own wire format. A non-2xx answer rejects\n * with `AiUpstreamError` before the iterator exists, so a door can still\n * send a plain error status and hand back its reservation.\n *\n * Non-streaming: resolves with the parsed result, a decline typed as\n * `AiRefusal` rather than thrown.\n */\nexport async function runAiRequest(\n input: AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent>>\nexport async function runAiRequest(input: AiRequest): Promise<AiResult>\nexport async function runAiRequest(\n input: AiRequest | AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent> | AiResult> {\n validateAiSystemBlocks(input.system)\n const { provider, apiKey } = providerFor(input)\n validateAiMessages(input.messages, () => aiModelReadsImages(provider, input.model))\n const { stream, settings: _settings, provider: _provider, ...request } = input\n const providerRequest = { ...request, apiKey }\n try {\n return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest))\n } catch (error) {\n await reportAccountProblem(provider, error)\n throw error\n }\n}\n\n/**\n * Tells the operator when the failure is the platform's provider account —\n * a refused key, no credit — rather than the request (AGL-3377). Once per\n * provider and problem per window; loaded lazily so the runtime pulls no\n * Admin SDK until an account is actually broken. Never throws.\n */\nasync function reportAccountProblem(provider: AiProvider, error: unknown): Promise<void> {\n if (!(error instanceof AiUpstreamError) || !error.accountProblem) return\n try {\n const [{ raiseOperatorAlert }, { AI_PROVIDER_UNAVAILABLE }] = await Promise.all([\n import('./operator-alerts-seam'),\n import('../operator-alerts'),\n ])\n await raiseOperatorAlert(AI_PROVIDER_UNAVAILABLE, {\n dedupeKey: `${provider.id}:${error.accountProblem}`,\n context: {\n provider: provider.label,\n keyEnv: provider.apiKeyEnv,\n reason:\n error.accountProblem === 'credentials'\n ? `its API key was refused (HTTP ${error.status ?? 'unknown'})`\n : 'the account is out of credit',\n },\n })\n } catch (alertError) {\n console.error('[ai] the provider account alert could not be raised', alertError)\n }\n}\n"],"names":["aiModelCacheMinTokens","AI_IMAGE_MAX_BYTES","AI_IMAGE_MEDIA_TYPES","AI_REQUEST_MAX_IMAGES","AiRequestShapeError","AiUpstreamError","aiBase64Bytes","aiMessageImages","resolveAiProvider","AI_UPSTREAM_FAILURE_COPY","AI_MAX_CACHE_BREAKPOINTS","AI_ACCEPTABLE_USE_BLOCK","validateAiSystemBlocks","system","breakpoints","filter","block","cacheBreakpoint","length","lastBreakpoint","reduce","last","index","forEach","volatile","aiModelReadsImages","provider","model","models","some","descriptor","id","capabilities","vision","aiRouteReadsImages","settings","NOT_BASE64","isBareBase64","data","body","endsWith","slice","test","validateAiMessages","messages","readsImages","message","content","Array","isArray","part","type","text","role","includes","mediaType","images","AI_CACHE_PREFIX_TOKEN_CHARS","aiCachedPrefixChars","tools","toolChars","total","tool","JSON","stringify","aiCachedPrefixCaches","chars","aiProviderReady","Boolean","readApiKey","aiActiveProvider","providerFor","input","Error","apiKey","apiKeyEnv","runAiRequest","stream","_settings","_provider","request","providerRequest","complete","error","reportAccountProblem","accountProblem","raiseOperatorAlert","AI_PROVIDER_UNAVAILABLE","Promise","all","dedupeKey","context","label","keyEnv","reason","status","alertError","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/ai-runtime.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 { aiModelCacheMinTokens } from '../providers/catalog'\nimport {\n AI_IMAGE_MAX_BYTES,\n AI_IMAGE_MEDIA_TYPES,\n AI_REQUEST_MAX_IMAGES,\n AiRequestShapeError,\n AiUpstreamError,\n aiBase64Bytes,\n aiMessageImages,\n type AiMessage,\n type AiProvider,\n type AiProviderRequest,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiTool,\n} from '../providers/contract'\nimport { resolveAiProvider, type AiPluginSettings } from '../providers/routing'\nimport {\n aiDevCacheTtl,\n aiDevReplayMode,\n aiIsDeployedRuntime,\n aiLiveBatchWanted,\n aiWarnIfReplayAskedInDeployment,\n} from './ai-dev-env'\nimport { aiCompleteInBatch } from './ai-live-batch'\n\nexport {\n AI_UPSTREAM_FAILURE_COPY,\n AiRequestShapeError,\n AiUpstreamError,\n type AiCompletion,\n type AiEffort,\n type AiImagePart,\n type AiMessage,\n type AiMessagePart,\n type AiRefusal,\n type AiRequestBase,\n type AiResult,\n type AiStreamEvent,\n type AiSystemBlock,\n type AiThinking,\n type AiTool,\n type AiToolUse,\n type AiUsage,\n} from '../providers/contract'\n\n/**\n * The one runtime every AI door calls (AGL-2903), provider-generic since\n * AGL-2939.\n *\n * `/api/assist/chat` and `/api/ai/assist` each carried their own `fetch` to\n * one vendor, their own SSE parser and their own usage extraction, and a\n * third door would have been a third copy. This module owns the request\n * shape, the cache-prefix rule, the provider resolution and the error\n * boundary; an adapter owns one vendor's wire protocol; a door owns its\n * prompt, its gate ladder and its wire format.\n *\n * The cache-prefix rule (AGL-2352) is enforced HERE, before any adapter:\n * a block marked `volatile` may not sit at or before the last\n * `cacheBreakpoint`, and more breakpoints than any provider caches are\n * refused. A provider without a cache ignores the markers and keeps the\n * order, so a request that is right for the caching adapter is right for\n * every adapter.\n *\n * The picture rule (AGL-2916) is enforced here too, and for the same reason:\n * a picture on an assistant turn, a picture for a model whose provider does\n * not say it reads one, or a picture past the bounds is refused before any\n * adapter is reached, so no door can send one a provider would reject.\n */\n\n/** The most `cacheBreakpoint` markers a request may carry. */\nexport const AI_MAX_CACHE_BREAKPOINTS = 4\n\n/**\n * The acceptable-use rules every generation prompt carries (AGL-2925).\n *\n * ONE constant, appended to each door's STATIC system text — inside the\n * cached prefix on the console chat, ahead of the mode prompt on the copy\n * assistant — so the rules the model is held to are the same at every\n * door and carry no per-org byte. The Free taste is what makes them worth\n * stating: a generated page that nobody paid for is the cheapest phishing\n * kit there is, and the model declining it is the first line, ahead of the\n * abuse-report route and the lockdown key.\n *\n * The categories are the platform's own Acceptable Use Policy, restated in\n * the second person for the model. A decline from the model's safety layer\n * arrives as a refusal, which the meter counts against the account's daily\n * pause and never against its credits.\n */\nexport const AI_ACCEPTABLE_USE_BLOCK = `Acceptable use — decline these rather than write them, and say briefly why:\n- Pages, emails or copy that harvest credentials, payment details or personal data by deception (phishing, fake sign-in or checkout pages, fake \"verify your account\" notices).\n- Impersonating a real business, brand, public body or person you were not told the user represents — their name, logo, sign-in page or voice — or copy that claims an endorsement, certification or partnership that was not given to you as a fact.\n- Content that promotes or sells prohibited goods and services: weapons, illegal drugs, counterfeit goods, malware or deceptive downloads, unlicensed gambling, or adult content involving minors.\n- Scams and fraud: fake investment or crypto schemes, advance-fee offers, tech-support scams, fake charities, deceptive \"you have won\" pages, or copy built to defraud.\n- Harassment, threats, or hateful content targeting a person or a group.\n- Spam farms: bulk near-duplicate pages or copy whose only purpose is to game search engines or send unsolicited mail.\nWhen a brief is ordinary business content, write it well and do not lecture.`\n\n/** A door's request, with the org's settings when the door knows the org. */\ninterface AiRuntimeOptions {\n /** The org's resolved `pluginSettings/ai`, for its provider and model choices. */\n settings?: AiPluginSettings\n /** A provider chosen by the caller, ahead of every setting; specs use it. */\n provider?: AiProvider\n}\n\nexport interface AiRequest extends AiRequestBase, AiRuntimeOptions {\n stream: false\n}\n\nexport interface AiStreamRequest extends AiRequestBase, AiRuntimeOptions {\n stream: true\n}\n\n/**\n * The cache guard, applied to the system blocks before any adapter sees\n * them. Exported so a spec and a door can assert a prompt's shape without\n * a provider.\n */\nexport function validateAiSystemBlocks(system: readonly AiSystemBlock[]): void {\n const breakpoints = system.filter((block) => block.cacheBreakpoint)\n if (breakpoints.length > AI_MAX_CACHE_BREAKPOINTS) {\n throw new AiRequestShapeError(\n `${breakpoints.length} cache breakpoints; at most ${AI_MAX_CACHE_BREAKPOINTS} are cached`,\n )\n }\n // Caching is a prefix match, so the LAST breakpoint decides what is\n // cached, and every block at or before it is inside the cached prefix. A\n // volatile block anywhere in that span — including one that is itself\n // the breakpoint — puts a per-request byte into the prefix (AGL-2352).\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n system.forEach((block, index) => {\n if (block.volatile && index <= lastBreakpoint) {\n throw new AiRequestShapeError(\n `system block ${index} is volatile but sits inside the cached prefix (last breakpoint at block ${lastBreakpoint})`,\n )\n }\n })\n // A site's block is cached per site (AGL-3661), so it closes the cached\n // span: a platform-wide breakpoint after it would be keyed on the site too,\n // and every workspace would warm its own copy of a shared prompt.\n const firstSite = system.findIndex((block) => block.site && block.cacheBreakpoint)\n if (firstSite >= 0 && firstSite !== lastBreakpoint) {\n throw new AiRequestShapeError(\n `system block ${firstSite} is a site's cached block but breakpoint ${lastBreakpoint} follows it`,\n )\n }\n}\n\n/**\n * Whether a provider's model reads a picture in a user turn (AGL-2916): what\n * the provider's own descriptor of the model says. A model the provider does\n * not describe reads none, so an adopter's provider is never sent a picture\n * it did not say it can take.\n */\nexport function aiModelReadsImages(provider: AiProvider, model: string): boolean {\n return provider\n .models()\n .some((descriptor) => descriptor.id === model && descriptor.capabilities.vision === true)\n}\n\n/**\n * Whether a model a door resolved reads pictures on the provider that would\n * serve the request: what a door asks before it attaches one, so a model that\n * does not is sent the text alone rather than refused.\n */\nexport function aiRouteReadsImages(model: string, settings?: AiPluginSettings): boolean {\n const provider = resolveAiProvider(settings)\n return provider ? aiModelReadsImages(provider, model) : false\n}\n\n/** A character standard base64 does not use, outside its padding. */\nconst NOT_BASE64 = /[^A-Za-z0-9+/]/\n\n/**\n * Whether `data` is standard base64 and nothing else: its alphabet, then at\n * most two `=` of padding; no `data:` prefix, no line breaks.\n *\n * Scanned for a character outside the alphabet rather than matched whole: a\n * picture is megabytes of base64, and a whole-string pattern with a greedy\n * loop can exhaust the regular expression engine's backtrack stack on input\n * that large, which throws a RangeError instead of answering.\n */\nfunction isBareBase64(data: string): boolean {\n const body = data.endsWith('==') ? data.slice(0, -2) : data.endsWith('=') ? data.slice(0, -1) : data\n return body.length > 0 && !NOT_BASE64.test(body)\n}\n\n/**\n * The picture guard (AGL-2916), applied to the messages before any adapter\n * sees them. A picture rides only a user turn, only to a model that reads\n * pictures, only in a format every such model takes, as bare base64 within\n * the size a provider accepts, and no more of them than a request may carry.\n * `readsImages` is asked only when a turn carries a picture. Exported so a\n * spec and a door can assert a request's shape without a provider.\n */\nexport function validateAiMessages(messages: readonly AiMessage[], readsImages: () => boolean): void {\n messages.forEach((message, index) => {\n if (typeof message.content === 'string') return\n if (!Array.isArray(message.content) || message.content.length === 0) {\n throw new AiRequestShapeError(`message ${index} has neither text nor parts`)\n }\n for (const part of message.content) {\n if (part.type === 'text') {\n if (typeof part.text !== 'string') {\n throw new AiRequestShapeError(`message ${index} has a text part with no text`)\n }\n continue\n }\n if (part.type !== 'image') {\n throw new AiRequestShapeError(`message ${index} has a part of an unknown type`)\n }\n if (message.role !== 'user') {\n throw new AiRequestShapeError(`message ${index} is an assistant turn and carries a picture`)\n }\n if (!(AI_IMAGE_MEDIA_TYPES as readonly string[]).includes(part.mediaType)) {\n throw new AiRequestShapeError(`message ${index} carries a picture of an unsupported type`)\n }\n if (typeof part.data !== 'string') {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n // The size first: it needs only the length, so an oversized picture is\n // refused without reading its bytes.\n if (aiBase64Bytes(part.data) > AI_IMAGE_MAX_BYTES) {\n throw new AiRequestShapeError(\n `message ${index} carries a picture over ${AI_IMAGE_MAX_BYTES} bytes`,\n )\n }\n if (!isBareBase64(part.data)) {\n throw new AiRequestShapeError(`message ${index} carries a picture that is not bare base64`)\n }\n }\n })\n const images = aiMessageImages(messages).length\n if (images > AI_REQUEST_MAX_IMAGES) {\n throw new AiRequestShapeError(`${images} pictures; a request carries at most ${AI_REQUEST_MAX_IMAGES}`)\n }\n if (images > 0 && !readsImages()) {\n throw new AiRequestShapeError('the request carries a picture and its model does not read pictures')\n }\n}\n\n/**\n * Characters to a token when a prompt is measured rather than tokenized\n * (AGL-2937). Four is the high end of English prose, so reading a prefix\n * through it errs LOW on the token count — and a prefix counts as cached\n * only when it clears its model's minimum even at that reading. Erring the\n * other way would let a prompt claim a cache it does not get.\n */\nexport const AI_CACHE_PREFIX_TOKEN_CHARS = 4\n\n/**\n * The characters inside the cached span of a request: the tools, which a\n * provider renders ahead of the system blocks and which therefore sit in\n * every prefix, plus every system block up to and including the last\n * breakpoint. Zero when the request marks no breakpoint at all.\n */\nexport function aiCachedPrefixChars(\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): number {\n const lastBreakpoint = system.reduce(\n (last, block, index) => (block.cacheBreakpoint ? index : last),\n -1,\n )\n if (lastBreakpoint < 0) return 0\n const toolChars = tools.reduce((total, tool) => total + JSON.stringify(tool).length, 0)\n return system\n .slice(0, lastBreakpoint + 1)\n .reduce((total, block) => total + block.text.length, toolChars)\n}\n\n/**\n * Whether this request's cached span is long enough for the model to cache\n * it at all (AGL-2937). A prefix under the model's `cacheMinTokens` is\n * served with the breakpoints honored and nothing cached — no error, and a\n * usage report that reads as a permanent miss — so a door designed around a\n * cache has to answer this question before it believes its own markers, and\n * `runtime/ai-prompt-cache.spec.ts` answers it for every door at once.\n */\nexport function aiCachedPrefixCaches(\n model: string,\n system: readonly AiSystemBlock[],\n tools: readonly AiTool[] = [],\n): boolean {\n const chars = aiCachedPrefixChars(system, tools)\n return chars / AI_CACHE_PREFIX_TOKEN_CHARS >= aiModelCacheMinTokens(model)\n}\n\n/**\n * Whether a provider is configured to answer: registered, and its key\n * present. Every door asks this before it spends a reservation, and\n * answers 501 (or the docs fallback) when it is false.\n */\nexport function aiProviderReady(settings?: AiPluginSettings): boolean {\n return Boolean(resolveAiProvider(settings)?.readApiKey())\n}\n\n/** The provider that would answer, for a door that names it in its logs or its inventory. */\nexport function aiActiveProvider(settings?: AiPluginSettings): AiProvider | undefined {\n return resolveAiProvider(settings)\n}\n\nfunction providerFor(input: AiRuntimeOptions): { provider: AiProvider; apiKey: string } {\n const provider = input.provider ?? resolveAiProvider(input.settings)\n if (!provider) throw new Error('no AI provider is registered')\n const apiKey = provider.readApiKey()\n if (!apiKey) {\n // Every door gates on `aiProviderReady` before it gets here, so this is\n // a wiring fault rather than a customer path.\n throw new Error(`${provider.apiKeyEnv} is not set`)\n }\n return { provider, apiKey }\n}\n\n/**\n * Send one request through the resolved provider.\n *\n * Streaming: resolves once the provider has ACCEPTED the request — after\n * the headers, before the first token — with an iterator of simplified\n * events the caller wraps in its own wire format. A non-2xx answer rejects\n * with `AiUpstreamError` before the iterator exists, so a door can still\n * send a plain error status and hand back its reservation.\n *\n * Non-streaming: resolves with the parsed result, a decline typed as\n * `AiRefusal` rather than thrown.\n */\nexport async function runAiRequest(\n input: AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent>>\nexport async function runAiRequest(input: AiRequest): Promise<AiResult>\nexport async function runAiRequest(\n input: AiRequest | AiStreamRequest,\n): Promise<AsyncIterable<AiStreamEvent> | AiResult> {\n validateAiSystemBlocks(input.system)\n const { provider, apiKey } = providerFor(input)\n validateAiMessages(input.messages, () => aiModelReadsImages(provider, input.model))\n const { stream, settings: _settings, provider: _provider, ...request } = input\n aiWarnIfReplayAskedInDeployment()\n const cacheTtl = request.cacheTtl ?? aiDevCacheTtl()\n const providerRequest: AiProviderRequest = { ...request, ...(cacheTtl ? { cacheTtl } : {}), apiKey }\n try {\n if (aiIsDeployedRuntime()) {\n return await (stream ? provider.stream(providerRequest) : provider.complete(providerRequest))\n }\n return await runInDevelopment(provider, providerRequest, stream)\n } catch (error) {\n await reportAccountProblem(provider, error)\n throw error\n }\n}\n\n/**\n * A request in a development process (AGL-3660): through the replay cache,\n * batch mode and the first-request-warms-the-prefix rule when this process\n * asked for them (`ai-dev-env.ts`), straight to the provider when it asked\n * for none. The file-backed half is loaded only when a layer is on, so a dev\n * server that never asked loads nothing it did not before.\n */\nasync function runInDevelopment(\n provider: AiProvider,\n request: AiProviderRequest,\n stream: boolean,\n): Promise<AsyncIterable<AiStreamEvent> | AiResult> {\n const mode = aiDevReplayMode()\n const batch = !stream && aiLiveBatchWanted() && Boolean(provider.completeBatch)\n if (mode === 'off' && !batch) {\n return stream ? provider.stream(request) : provider.complete(request)\n }\n const dev = await import('./ai-dev-replay')\n const ttlMs = request.cacheTtl === '1h' ? 60 * 60_000 : 5 * 60_000\n const prefix = dev.aiCachedPrefixKey(request)\n if (stream) {\n const live = () => dev.aiDevWarmPrefixFirst(prefix, ttlMs, () => provider.stream(request))\n return mode === 'off' ? live() : dev.aiDevReplayStream(provider, request, mode, live)\n }\n const live = async (): Promise<AiResult> => {\n const result = batch\n ? await aiCompleteInBatch(provider, request)\n : await dev.aiDevWarmPrefixFirst(prefix, ttlMs, () => provider.complete(request))\n dev.aiDevCountLive(result.usage, request.model, batch ? AI_BATCH_PRICE_FACTOR : 1)\n return result\n }\n return mode === 'off' ? live() : dev.aiDevReplayComplete(provider, request, mode, live)\n}\n\n/** What a batch's discount leaves of a request's price, for the development count. */\nconst AI_BATCH_PRICE_FACTOR = 0.5\n\n/**\n * Tells the operator when the failure is the platform's provider account —\n * a refused key, no credit — rather than the request (AGL-3377). Once per\n * provider and problem per window; loaded lazily so the runtime pulls no\n * Admin SDK until an account is actually broken. Never throws.\n */\nasync function reportAccountProblem(provider: AiProvider, error: unknown): Promise<void> {\n if (!(error instanceof AiUpstreamError) || !error.accountProblem) return\n try {\n const [{ raiseOperatorAlert }, { AI_PROVIDER_UNAVAILABLE }] = await Promise.all([\n import('./operator-alerts-seam'),\n import('../operator-alerts'),\n ])\n await raiseOperatorAlert(AI_PROVIDER_UNAVAILABLE, {\n dedupeKey: `${provider.id}:${error.accountProblem}`,\n context: {\n provider: provider.label,\n keyEnv: provider.apiKeyEnv,\n reason:\n error.accountProblem === 'credentials'\n ? `its API key was refused (HTTP ${error.status ?? 'unknown'})`\n : 'the account is out of credit',\n },\n })\n } catch (alertError) {\n console.error('[ai] the provider account alert could not be raised', alertError)\n }\n}\n"],"names":["aiModelCacheMinTokens","AI_IMAGE_MAX_BYTES","AI_IMAGE_MEDIA_TYPES","AI_REQUEST_MAX_IMAGES","AiRequestShapeError","AiUpstreamError","aiBase64Bytes","aiMessageImages","resolveAiProvider","aiDevCacheTtl","aiDevReplayMode","aiIsDeployedRuntime","aiLiveBatchWanted","aiWarnIfReplayAskedInDeployment","aiCompleteInBatch","AI_UPSTREAM_FAILURE_COPY","AI_MAX_CACHE_BREAKPOINTS","AI_ACCEPTABLE_USE_BLOCK","validateAiSystemBlocks","system","breakpoints","filter","block","cacheBreakpoint","length","lastBreakpoint","reduce","last","index","forEach","volatile","firstSite","findIndex","site","aiModelReadsImages","provider","model","models","some","descriptor","id","capabilities","vision","aiRouteReadsImages","settings","NOT_BASE64","isBareBase64","data","body","endsWith","slice","test","validateAiMessages","messages","readsImages","message","content","Array","isArray","part","type","text","role","includes","mediaType","images","AI_CACHE_PREFIX_TOKEN_CHARS","aiCachedPrefixChars","tools","toolChars","total","tool","JSON","stringify","aiCachedPrefixCaches","chars","aiProviderReady","Boolean","readApiKey","aiActiveProvider","providerFor","input","Error","apiKey","apiKeyEnv","runAiRequest","request","stream","_settings","_provider","cacheTtl","providerRequest","complete","runInDevelopment","error","reportAccountProblem","mode","batch","completeBatch","dev","ttlMs","prefix","aiCachedPrefixKey","live","aiDevWarmPrefixFirst","aiDevReplayStream","result","aiDevCountLive","usage","AI_BATCH_PRICE_FACTOR","aiDevReplayComplete","accountProblem","raiseOperatorAlert","AI_PROVIDER_UNAVAILABLE","Promise","all","dedupeKey","context","label","keyEnv","reason","status","alertError","console"],"mappings":"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"}
@@ -0,0 +1,58 @@
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 BusinessProfileValues } from '@aglyn/aglyn/app-utils/business-profile';
18
+ import type { AiSiteContextInput } from '../model/ai-site-context';
19
+ import { type AiSitePreference, type AiSitePreferenceMatch } from '../model/ai-site-memory';
20
+ /**
21
+ * The server half of the site context (AGL-3661): the reads that make an
22
+ * `AiSiteContextInput`, the guided start's prefill and the memory writer.
23
+ * Every function takes the Firestore it works on, so a door, a step and a
24
+ * spec hand in their own.
25
+ *
26
+ * FAIL-SOFT on purpose. The context makes a job better; it never decides
27
+ * whether a job runs. A read that fails is logged and answered with `null`,
28
+ * and the job runs as it did before the context existed.
29
+ */
30
+ type Data = Record<string, unknown>;
31
+ /** The site's own preferences, read whole (they are capped at write). */
32
+ export declare function readAiSitePreferences(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<AiSitePreference[]>;
33
+ /**
34
+ * Everything the block says about one site, read in one window: the host,
35
+ * the site's profile, the workspace defaults and the remembered preferences.
36
+ * `null` for a site of another workspace (never read for it) and for any
37
+ * read that failed. A caller that already holds the host passes it.
38
+ */
39
+ export declare function readAiSiteContext(firestore: FirebaseFirestore.Firestore, input: {
40
+ orgId: string;
41
+ hostId: string | null | undefined;
42
+ host?: Data | null;
43
+ }): Promise<AiSiteContextInput | null>;
44
+ /**
45
+ * Fills the site's profile from the guided start's answers or a generator's
46
+ * output (AGL-3661), never over what the owner typed: the merge decides,
47
+ * inside a transaction so an owner's save in between is never overwritten.
48
+ * Returns whether anything was written.
49
+ */
50
+ export declare function prefillAiBusinessProfile(firestore: FirebaseFirestore.Firestore, hostId: string, values: BusinessProfileValues, source: 'start' | 'ai', now?: Date): Promise<boolean>;
51
+ /**
52
+ * Remembers the preferences one applied edit showed (AGL-3661): each one's
53
+ * count goes up, a preference of the same group that it replaces is
54
+ * forgotten, and the site keeps at most `AI_SITE_MEMORY_MAX`, dropping the
55
+ * least repeated and oldest first.
56
+ */
57
+ export declare function rememberAiSitePreferences(firestore: FirebaseFirestore.Firestore, hostId: string, matches: readonly AiSitePreferenceMatch[], now?: Date): Promise<void>;
58
+ export {};
@@ -0,0 +1,127 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { BUSINESS_PROFILE_SUBCOLLECTION, BUSINESS_PROFILE_SITE_DOC, BUSINESS_PROFILE_WORKSPACE_DOC, mergeBusinessProfilePrefill, resolveBusinessProfile } from "@aglyn/aglyn/app-utils/business-profile";
18
+ import { aiHostPublishContext } from "../model/ai-host-publish-context.js";
19
+ import { AI_SITE_MEMORY_SUBCOLLECTION, AI_SITE_MEMORY_MAX, aiListedPreferences, aiSitePreferenceOf } from "../model/ai-site-memory.js";
20
+ const docData = async (ref)=>{
21
+ var _snapshot_data;
22
+ const snapshot = await ref.get();
23
+ return snapshot.exists ? (_snapshot_data = snapshot.data()) != null ? _snapshot_data : null : null;
24
+ };
25
+ /** The site's own preferences, read whole (they are capped at write). */ export async function readAiSitePreferences(firestore, hostId) {
26
+ const snapshot = await firestore.collection('hosts').doc(hostId).collection(AI_SITE_MEMORY_SUBCOLLECTION).limit(AI_SITE_MEMORY_MAX * 2).get();
27
+ return snapshot.docs.map((row)=>aiSitePreferenceOf(row.id, row.data())).filter((row)=>row !== null);
28
+ }
29
+ /**
30
+ * Everything the block says about one site, read in one window: the host,
31
+ * the site's profile, the workspace defaults and the remembered preferences.
32
+ * `null` for a site of another workspace (never read for it) and for any
33
+ * read that failed. A caller that already holds the host passes it.
34
+ */ export async function readAiSiteContext(firestore, input) {
35
+ const { orgId } = input;
36
+ const hostId = typeof input.hostId === 'string' ? input.hostId.trim() : '';
37
+ if (!orgId || !hostId) return null;
38
+ try {
39
+ const hostRef = firestore.collection('hosts').doc(hostId);
40
+ const [host, site, workspace, preferences] = await Promise.all([
41
+ input.host !== undefined ? Promise.resolve(input.host) : docData(hostRef),
42
+ docData(hostRef.collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_SITE_DOC)),
43
+ docData(firestore.collection('orgs').doc(orgId).collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_WORKSPACE_DOC)),
44
+ readAiSitePreferences(firestore, hostId)
45
+ ]);
46
+ // A site of another workspace is never described to this one.
47
+ if (!host || host['orgId'] !== orgId) return null;
48
+ return {
49
+ profile: resolveBusinessProfile({
50
+ host: host,
51
+ site: site,
52
+ workspace: workspace
53
+ }),
54
+ publish: aiHostPublishContext(host),
55
+ preferences: aiListedPreferences(preferences)
56
+ };
57
+ } catch (error) {
58
+ console.warn('ai site context unread', {
59
+ orgId,
60
+ hostId,
61
+ error
62
+ });
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * Fills the site's profile from the guided start's answers or a generator's
68
+ * output (AGL-3661), never over what the owner typed: the merge decides,
69
+ * inside a transaction so an owner's save in between is never overwritten.
70
+ * Returns whether anything was written.
71
+ */ export async function prefillAiBusinessProfile(firestore, hostId, values, source, now = new Date()) {
72
+ const ref = firestore.collection('hosts').doc(hostId).collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_SITE_DOC);
73
+ return firestore.runTransaction(async (tx)=>{
74
+ const snapshot = await tx.get(ref);
75
+ const current = snapshot.exists ? snapshot.data() : null;
76
+ const next = mergeBusinessProfilePrefill(current, values, source);
77
+ if (!next) return false;
78
+ tx.set(ref, _extends({}, next, {
79
+ updatedAt: now,
80
+ updatedBy: null
81
+ }));
82
+ return true;
83
+ });
84
+ }
85
+ /**
86
+ * Remembers the preferences one applied edit showed (AGL-3661): each one's
87
+ * count goes up, a preference of the same group that it replaces is
88
+ * forgotten, and the site keeps at most `AI_SITE_MEMORY_MAX`, dropping the
89
+ * least repeated and oldest first.
90
+ */ export async function rememberAiSitePreferences(firestore, hostId, matches, now = new Date()) {
91
+ if (!matches.length) return;
92
+ const collection = firestore.collection('hosts').doc(hostId).collection(AI_SITE_MEMORY_SUBCOLLECTION);
93
+ const existing = await readAiSitePreferences(firestore, hostId);
94
+ const byId = new Map(existing.map((row)=>[
95
+ row.id,
96
+ row
97
+ ]));
98
+ const batch = firestore.batch();
99
+ const kept = new Map(existing.map((row)=>[
100
+ row.id,
101
+ row
102
+ ]));
103
+ for (const match of matches){
104
+ var _ref;
105
+ for (const row of existing){
106
+ if (row.group === match.group && row.id !== match.id) {
107
+ batch.delete(collection.doc(row.id));
108
+ kept.delete(row.id);
109
+ }
110
+ }
111
+ const held = byId.get(match.id);
112
+ const row = _extends({}, match, {
113
+ count: ((_ref = held == null ? void 0 : held.count) != null ? _ref : 0) + 1,
114
+ lastSeenAtMs: now.getTime(),
115
+ source: 'assist-edit'
116
+ });
117
+ kept.set(match.id, row);
118
+ batch.set(collection.doc(match.id), row);
119
+ }
120
+ const overflow = [
121
+ ...kept.values()
122
+ ].sort((a, b)=>b.count - a.count || b.lastSeenAtMs - a.lastSeenAtMs).slice(AI_SITE_MEMORY_MAX);
123
+ for (const row of overflow)batch.delete(collection.doc(row.id));
124
+ await batch.commit();
125
+ }
126
+
127
+ //# sourceMappingURL=site-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/runtime/site-context.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 BUSINESS_PROFILE_SUBCOLLECTION,\n BUSINESS_PROFILE_SITE_DOC,\n BUSINESS_PROFILE_WORKSPACE_DOC,\n mergeBusinessProfilePrefill,\n resolveBusinessProfile,\n type BusinessProfileDoc,\n type BusinessProfileHost,\n type BusinessProfileValues,\n} from '@aglyn/aglyn/app-utils/business-profile'\nimport { aiHostPublishContext } from '../model/ai-host-publish-context'\nimport type { AiSiteContextInput } from '../model/ai-site-context'\nimport {\n AI_SITE_MEMORY_SUBCOLLECTION,\n AI_SITE_MEMORY_MAX,\n aiListedPreferences,\n aiSitePreferenceOf,\n type AiSitePreference,\n type AiSitePreferenceMatch,\n} from '../model/ai-site-memory'\n\n/**\n * The server half of the site context (AGL-3661): the reads that make an\n * `AiSiteContextInput`, the guided start's prefill and the memory writer.\n * Every function takes the Firestore it works on, so a door, a step and a\n * spec hand in their own.\n *\n * FAIL-SOFT on purpose. The context makes a job better; it never decides\n * whether a job runs. A read that fails is logged and answered with `null`,\n * and the job runs as it did before the context existed.\n */\n\ntype Data = Record<string, unknown>\n\nconst docData = async (ref: FirebaseFirestore.DocumentReference): Promise<Data | null> => {\n const snapshot = await ref.get()\n return snapshot.exists ? ((snapshot.data() ?? null) as Data | null) : null\n}\n\n/** The site's own preferences, read whole (they are capped at write). */\nexport async function readAiSitePreferences(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<AiSitePreference[]> {\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection(AI_SITE_MEMORY_SUBCOLLECTION)\n .limit(AI_SITE_MEMORY_MAX * 2)\n .get()\n return snapshot.docs\n .map((row) => aiSitePreferenceOf(row.id, row.data() as Data))\n .filter((row): row is AiSitePreference => row !== null)\n}\n\n/**\n * Everything the block says about one site, read in one window: the host,\n * the site's profile, the workspace defaults and the remembered preferences.\n * `null` for a site of another workspace (never read for it) and for any\n * read that failed. A caller that already holds the host passes it.\n */\nexport async function readAiSiteContext(\n firestore: FirebaseFirestore.Firestore,\n input: { orgId: string; hostId: string | null | undefined; host?: Data | null },\n): Promise<AiSiteContextInput | null> {\n const { orgId } = input\n const hostId = typeof input.hostId === 'string' ? input.hostId.trim() : ''\n if (!orgId || !hostId) return null\n try {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [host, site, workspace, preferences] = await Promise.all([\n input.host !== undefined ? Promise.resolve(input.host) : docData(hostRef),\n docData(hostRef.collection(BUSINESS_PROFILE_SUBCOLLECTION).doc(BUSINESS_PROFILE_SITE_DOC)),\n docData(\n firestore\n .collection('orgs')\n .doc(orgId)\n .collection(BUSINESS_PROFILE_SUBCOLLECTION)\n .doc(BUSINESS_PROFILE_WORKSPACE_DOC),\n ),\n readAiSitePreferences(firestore, hostId),\n ])\n // A site of another workspace is never described to this one.\n if (!host || host['orgId'] !== orgId) return null\n return {\n profile: resolveBusinessProfile({\n host: host as BusinessProfileHost,\n site: site as BusinessProfileDoc | null,\n workspace: workspace as BusinessProfileDoc | null,\n }),\n publish: aiHostPublishContext(host),\n preferences: aiListedPreferences(preferences),\n }\n } catch (error) {\n console.warn('ai site context unread', { orgId, hostId, error })\n return null\n }\n}\n\n/**\n * Fills the site's profile from the guided start's answers or a generator's\n * output (AGL-3661), never over what the owner typed: the merge decides,\n * inside a transaction so an owner's save in between is never overwritten.\n * Returns whether anything was written.\n */\nexport async function prefillAiBusinessProfile(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n values: BusinessProfileValues,\n source: 'start' | 'ai',\n now: Date = new Date(),\n): Promise<boolean> {\n const ref = firestore\n .collection('hosts')\n .doc(hostId)\n .collection(BUSINESS_PROFILE_SUBCOLLECTION)\n .doc(BUSINESS_PROFILE_SITE_DOC)\n return firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(ref)\n const current = snapshot.exists ? (snapshot.data() as BusinessProfileDoc) : null\n const next = mergeBusinessProfilePrefill(current, values, source)\n if (!next) return false\n tx.set(ref, { ...next, updatedAt: now, updatedBy: null })\n return true\n })\n}\n\n/**\n * Remembers the preferences one applied edit showed (AGL-3661): each one's\n * count goes up, a preference of the same group that it replaces is\n * forgotten, and the site keeps at most `AI_SITE_MEMORY_MAX`, dropping the\n * least repeated and oldest first.\n */\nexport async function rememberAiSitePreferences(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n matches: readonly AiSitePreferenceMatch[],\n now: Date = new Date(),\n): Promise<void> {\n if (!matches.length) return\n const collection = firestore.collection('hosts').doc(hostId).collection(AI_SITE_MEMORY_SUBCOLLECTION)\n const existing = await readAiSitePreferences(firestore, hostId)\n const byId = new Map(existing.map((row) => [row.id, row]))\n const batch = firestore.batch()\n const kept = new Map(existing.map((row) => [row.id, row]))\n for (const match of matches) {\n for (const row of existing) {\n if (row.group === match.group && row.id !== match.id) {\n batch.delete(collection.doc(row.id))\n kept.delete(row.id)\n }\n }\n const held = byId.get(match.id)\n const row: AiSitePreference = {\n ...match,\n count: (held?.count ?? 0) + 1,\n lastSeenAtMs: now.getTime(),\n source: 'assist-edit',\n }\n kept.set(match.id, row)\n batch.set(collection.doc(match.id), row)\n }\n const overflow = [...kept.values()]\n .sort((a, b) => b.count - a.count || b.lastSeenAtMs - a.lastSeenAtMs)\n .slice(AI_SITE_MEMORY_MAX)\n for (const row of overflow) batch.delete(collection.doc(row.id))\n await batch.commit()\n}\n"],"names":["BUSINESS_PROFILE_SUBCOLLECTION","BUSINESS_PROFILE_SITE_DOC","BUSINESS_PROFILE_WORKSPACE_DOC","mergeBusinessProfilePrefill","resolveBusinessProfile","aiHostPublishContext","AI_SITE_MEMORY_SUBCOLLECTION","AI_SITE_MEMORY_MAX","aiListedPreferences","aiSitePreferenceOf","docData","ref","snapshot","get","exists","data","readAiSitePreferences","firestore","hostId","collection","doc","limit","docs","map","row","id","filter","readAiSiteContext","input","orgId","trim","hostRef","host","site","workspace","preferences","Promise","all","undefined","resolve","profile","publish","error","console","warn","prefillAiBusinessProfile","values","source","now","Date","runTransaction","tx","current","next","set","updatedAt","updatedBy","rememberAiSitePreferences","matches","length","existing","byId","Map","batch","kept","match","group","delete","held","count","lastSeenAtMs","getTime","overflow","sort","a","b","slice","commit"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,8BAA8B,EAC9BC,yBAAyB,EACzBC,8BAA8B,EAC9BC,2BAA2B,EAC3BC,sBAAsB,QAIjB,0CAAyC;AAChD,SAASC,oBAAoB,QAAQ,sCAAkC;AAEvE,SACEC,4BAA4B,EAC5BC,kBAAkB,EAClBC,mBAAmB,EACnBC,kBAAkB,QAGb,6BAAyB;AAehC,MAAMC,UAAU,OAAOC;QAEMC;IAD3B,MAAMA,WAAW,MAAMD,IAAIE,GAAG;IAC9B,OAAOD,SAASE,MAAM,IAAKF,iBAAAA,SAASG,IAAI,cAAbH,iBAAmB,OAAwB;AACxE;AAEA,uEAAuE,GACvE,OAAO,eAAeI,sBACpBC,SAAsC,EACtCC,MAAc;IAEd,MAAMN,WAAW,MAAMK,UACpBE,UAAU,CAAC,SACXC,GAAG,CAACF,QACJC,UAAU,CAACb,8BACXe,KAAK,CAACd,qBAAqB,GAC3BM,GAAG;IACN,OAAOD,SAASU,IAAI,CACjBC,GAAG,CAAC,CAACC,MAAQf,mBAAmBe,IAAIC,EAAE,EAAED,IAAIT,IAAI,KAChDW,MAAM,CAAC,CAACF,MAAiCA,QAAQ;AACtD;AAEA;;;;;CAKC,GACD,OAAO,eAAeG,kBACpBV,SAAsC,EACtCW,KAA+E;IAE/E,MAAM,EAAEC,KAAK,EAAE,GAAGD;IAClB,MAAMV,SAAS,OAAOU,MAAMV,MAAM,KAAK,WAAWU,MAAMV,MAAM,CAACY,IAAI,KAAK;IACxE,IAAI,CAACD,SAAS,CAACX,QAAQ,OAAO;IAC9B,IAAI;QACF,MAAMa,UAAUd,UAAUE,UAAU,CAAC,SAASC,GAAG,CAACF;QAClD,MAAM,CAACc,MAAMC,MAAMC,WAAWC,YAAY,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAC7DT,MAAMI,IAAI,KAAKM,YAAYF,QAAQG,OAAO,CAACX,MAAMI,IAAI,IAAItB,QAAQqB;YACjErB,QAAQqB,QAAQZ,UAAU,CAACnB,gCAAgCoB,GAAG,CAACnB;YAC/DS,QACEO,UACGE,UAAU,CAAC,QACXC,GAAG,CAACS,OACJV,UAAU,CAACnB,gCACXoB,GAAG,CAAClB;YAETc,sBAAsBC,WAAWC;SAClC;QACD,8DAA8D;QAC9D,IAAI,CAACc,QAAQA,IAAI,CAAC,QAAQ,KAAKH,OAAO,OAAO;QAC7C,OAAO;YACLW,SAASpC,uBAAuB;gBAC9B4B,MAAMA;gBACNC,MAAMA;gBACNC,WAAWA;YACb;YACAO,SAASpC,qBAAqB2B;YAC9BG,aAAa3B,oBAAoB2B;QACnC;IACF,EAAE,OAAOO,OAAO;QACdC,QAAQC,IAAI,CAAC,0BAA0B;YAAEf;YAAOX;YAAQwB;QAAM;QAC9D,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeG,yBACpB5B,SAAsC,EACtCC,MAAc,EACd4B,MAA6B,EAC7BC,MAAsB,EACtBC,MAAY,IAAIC,MAAM;IAEtB,MAAMtC,MAAMM,UACTE,UAAU,CAAC,SACXC,GAAG,CAACF,QACJC,UAAU,CAACnB,gCACXoB,GAAG,CAACnB;IACP,OAAOgB,UAAUiC,cAAc,CAAC,OAAOC;QACrC,MAAMvC,WAAW,MAAMuC,GAAGtC,GAAG,CAACF;QAC9B,MAAMyC,UAAUxC,SAASE,MAAM,GAAIF,SAASG,IAAI,KAA4B;QAC5E,MAAMsC,OAAOlD,4BAA4BiD,SAASN,QAAQC;QAC1D,IAAI,CAACM,MAAM,OAAO;QAClBF,GAAGG,GAAG,CAAC3C,KAAK,aAAK0C;YAAME,WAAWP;YAAKQ,WAAW;;QAClD,OAAO;IACT;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeC,0BACpBxC,SAAsC,EACtCC,MAAc,EACdwC,OAAyC,EACzCV,MAAY,IAAIC,MAAM;IAEtB,IAAI,CAACS,QAAQC,MAAM,EAAE;IACrB,MAAMxC,aAAaF,UAAUE,UAAU,CAAC,SAASC,GAAG,CAACF,QAAQC,UAAU,CAACb;IACxE,MAAMsD,WAAW,MAAM5C,sBAAsBC,WAAWC;IACxD,MAAM2C,OAAO,IAAIC,IAAIF,SAASrC,GAAG,CAAC,CAACC,MAAQ;YAACA,IAAIC,EAAE;YAAED;SAAI;IACxD,MAAMuC,QAAQ9C,UAAU8C,KAAK;IAC7B,MAAMC,OAAO,IAAIF,IAAIF,SAASrC,GAAG,CAAC,CAACC,MAAQ;YAACA,IAAIC,EAAE;YAAED;SAAI;IACxD,KAAK,MAAMyC,SAASP,QAAS;;QAC3B,KAAK,MAAMlC,OAAOoC,SAAU;YAC1B,IAAIpC,IAAI0C,KAAK,KAAKD,MAAMC,KAAK,IAAI1C,IAAIC,EAAE,KAAKwC,MAAMxC,EAAE,EAAE;gBACpDsC,MAAMI,MAAM,CAAChD,WAAWC,GAAG,CAACI,IAAIC,EAAE;gBAClCuC,KAAKG,MAAM,CAAC3C,IAAIC,EAAE;YACpB;QACF;QACA,MAAM2C,OAAOP,KAAKhD,GAAG,CAACoD,MAAMxC,EAAE;QAC9B,MAAMD,MAAwB,aACzByC;YACHI,OAAO,SAACD,wBAAAA,KAAMC,KAAK,mBAAI,KAAK;YAC5BC,cAActB,IAAIuB,OAAO;YACzBxB,QAAQ;;QAEViB,KAAKV,GAAG,CAACW,MAAMxC,EAAE,EAAED;QACnBuC,MAAMT,GAAG,CAACnC,WAAWC,GAAG,CAAC6C,MAAMxC,EAAE,GAAGD;IACtC;IACA,MAAMgD,WAAW;WAAIR,KAAKlB,MAAM;KAAG,CAChC2B,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEN,KAAK,GAAGK,EAAEL,KAAK,IAAIM,EAAEL,YAAY,GAAGI,EAAEJ,YAAY,EACnEM,KAAK,CAACrE;IACT,KAAK,MAAMiB,OAAOgD,SAAUT,MAAMI,MAAM,CAAChD,WAAWC,GAAG,CAACI,IAAIC,EAAE;IAC9D,MAAMsC,MAAMc,MAAM;AACpB"}
@@ -0,0 +1,22 @@
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 StaffAiConversationKind, type StaffAiConversationsResponse } from '../usage/staff-org-ai-conversations';
18
+ type Firestore = FirebaseFirestore.Firestore;
19
+ /** One page of the org's conversations of `kind`. Exported for the spec. */
20
+ export declare function readStaffAiConversations(db: Firestore, orgId: string, kind: StaffAiConversationKind, after: string | null): Promise<StaffAiConversationsResponse>;
21
+ declare function handler(request: Request): Promise<Response>;
22
+ export { handler as GET };