@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
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { pluginRequestFromWeb } from "@aglyn/aglyn/server";
17
+ import { emailUnverifiedResponse, firebaseAdmin, isImpersonationSession } from "@aglyn/tenant-data-admin";
18
+ import { addAdminAudit } from "@aglyn/tenant-data-admin/server/admin-audit-write";
19
+ import { composeStaffAiAssistRow, composeStaffAiJobRow, isStaffAiConversationKind, STAFF_AI_CONVERSATIONS_PAGE, staffAiCrmResult, staffAiInsightResult, staffAiJobResultCollection } from "../usage/staff-org-ai-conversations.js";
20
+ /**
21
+ * `/api/ai/admin/conversations` (AGL-3675): one page of what an org asked
22
+ * Aglyn AI and what it answered, for the staff org page.
23
+ *
24
+ * `?orgId=` and `?kind=assist|jobs`, newest first, and `?after=<id>` for the
25
+ * next page. Staff-gated exactly as `/api/ai/admin/org` is. The collections
26
+ * it reads have no client rules at all — default-deny for every browser —
27
+ * which is why the read is here, on the Admin SDK.
28
+ *
29
+ * ## Every page read is audited, and the audit holds no text
30
+ *
31
+ * Unlike the usage card, which loads with the page and so writes no audit
32
+ * row, this route returns what a person typed, and staff read it only when
33
+ * they ask to. Each page writes an `org.ai-conversations-viewed` access row
34
+ * naming who read which list of which org and how many rows came back. The
35
+ * Privacy policy says our audit log records an AI job "without the text of
36
+ * its brief or of what it wrote", so the row carries none of it.
37
+ */ /** Members read to name who asked; past it a row shows the uid alone. */ const MEMBERS_SCAN = 500;
38
+ const ACTION = 'org.ai-conversations-viewed';
39
+ /** One page of a collection under the org, newest first, after the document `after` names. */ async function readPage(orgRef, collection, after) {
40
+ var _ref;
41
+ var _docs_;
42
+ let query = orgRef.collection(collection).orderBy('createdAt', 'desc').limit(STAFF_AI_CONVERSATIONS_PAGE + 1);
43
+ if (after) {
44
+ const cursor = await orgRef.collection(collection).doc(after).get();
45
+ if (cursor.exists) query = query.startAfter(cursor);
46
+ }
47
+ const snapshot = await query.get();
48
+ const docs = snapshot.docs.slice(0, STAFF_AI_CONVERSATIONS_PAGE);
49
+ const next = snapshot.docs.length > STAFF_AI_CONVERSATIONS_PAGE ? (_ref = (_docs_ = docs[docs.length - 1]) == null ? void 0 : _docs_.id) != null ? _ref : null : null;
50
+ return {
51
+ docs,
52
+ next
53
+ };
54
+ }
55
+ async function readMembers(orgRef) {
56
+ const snapshot = await orgRef.collection('members').limit(MEMBERS_SCAN).get();
57
+ return new Map(snapshot.docs.map((doc)=>{
58
+ var _doc_data;
59
+ return [
60
+ doc.id,
61
+ (_doc_data = doc.data()) != null ? _doc_data : {}
62
+ ];
63
+ }));
64
+ }
65
+ /** The documents `refs` name, by id, in one round trip; a missing one is absent. */ async function readAll(db, refs) {
66
+ if (!refs.length) return new Map();
67
+ const snapshots = await db.getAll(...refs);
68
+ return new Map(snapshots.filter((snapshot)=>snapshot.exists).map((snapshot)=>{
69
+ var _snapshot_data;
70
+ return [
71
+ snapshot.id,
72
+ (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {}
73
+ ];
74
+ }));
75
+ }
76
+ /** One page of the org's conversations of `kind`. Exported for the spec. */ export async function readStaffAiConversations(db, orgId, kind, after) {
77
+ const orgRef = db.collection('orgs').doc(orgId);
78
+ if (kind === 'assist') {
79
+ const [{ docs, next }, members] = await Promise.all([
80
+ readPage(orgRef, 'assistExchanges', after),
81
+ readMembers(orgRef)
82
+ ]);
83
+ // The signal shares the exchange's id (AGL-1972): the route, the model, the cost and the thumbs.
84
+ const signals = await readAll(db, docs.map((doc)=>orgRef.collection('assistSignals').doc(doc.id)));
85
+ return {
86
+ kind,
87
+ rows: docs.map((doc)=>{
88
+ var _doc_data, _signals_get;
89
+ return composeStaffAiAssistRow(doc.id, (_doc_data = doc.data()) != null ? _doc_data : {}, (_signals_get = signals.get(doc.id)) != null ? _signals_get : null, members);
90
+ }),
91
+ next
92
+ };
93
+ }
94
+ const [{ docs, next }, members] = await Promise.all([
95
+ readPage(orgRef, 'aiJobs', after),
96
+ readMembers(orgRef)
97
+ ]);
98
+ const insightIds = docs.filter((doc)=>staffAiJobResultCollection(doc.get('kind')) === 'aiInsights').map((doc)=>doc.id);
99
+ const crmIds = docs.filter((doc)=>staffAiJobResultCollection(doc.get('kind')) === 'aiCrmAnswers').map((doc)=>doc.id);
100
+ const [insights, crm] = await Promise.all([
101
+ readAll(db, insightIds.map((id)=>orgRef.collection('aiInsights').doc(id))),
102
+ readAll(db, crmIds.map((id)=>orgRef.collection('aiCrmAnswers').doc(id)))
103
+ ]);
104
+ return {
105
+ kind,
106
+ rows: docs.map((doc)=>{
107
+ var _insights_get, _crm_get, _doc_data;
108
+ const collection = staffAiJobResultCollection(doc.get('kind'));
109
+ const result = collection === 'aiInsights' ? staffAiInsightResult((_insights_get = insights.get(doc.id)) != null ? _insights_get : null) : collection === 'aiCrmAnswers' ? staffAiCrmResult((_crm_get = crm.get(doc.id)) != null ? _crm_get : null) : null;
110
+ return composeStaffAiJobRow(doc.id, (_doc_data = doc.data()) != null ? _doc_data : {}, result, members);
111
+ }),
112
+ next
113
+ };
114
+ }
115
+ async function handler(request) {
116
+ var _headers_authorization, _query_orgId;
117
+ const { method, query, headers: rawHeaders } = await pluginRequestFromWeb(request);
118
+ const headers = rawHeaders;
119
+ if (method !== 'GET') {
120
+ return Response.json({
121
+ error: 'Method not allowed'
122
+ }, {
123
+ status: 405
124
+ });
125
+ }
126
+ const authorization = (_headers_authorization = headers.authorization) != null ? _headers_authorization : '';
127
+ const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
128
+ if (!idToken) return Response.json({
129
+ error: 'Unauthenticated'
130
+ }, {
131
+ status: 401
132
+ });
133
+ const orgId = String((_query_orgId = query.orgId) != null ? _query_orgId : '');
134
+ if (!orgId) return Response.json({
135
+ error: 'Missing orgId'
136
+ }, {
137
+ status: 400
138
+ });
139
+ const kind = query.kind;
140
+ if (!isStaffAiConversationKind(kind)) {
141
+ return Response.json({
142
+ error: 'kind must be assist or jobs'
143
+ }, {
144
+ status: 400
145
+ });
146
+ }
147
+ const after = typeof query.after === 'string' && query.after ? query.after : null;
148
+ try {
149
+ const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
150
+ if (!decoded.email_verified && !isImpersonationSession(decoded)) {
151
+ return emailUnverifiedResponse();
152
+ }
153
+ if (!decoded['staff']) {
154
+ return Response.json({
155
+ error: 'Staff only'
156
+ }, {
157
+ status: 403
158
+ });
159
+ }
160
+ const db = firebaseAdmin.app().firestore();
161
+ if (!(await db.collection('orgs').doc(orgId).get()).exists) {
162
+ return Response.json({
163
+ error: 'No such organization'
164
+ }, {
165
+ status: 404
166
+ });
167
+ }
168
+ const page = await readStaffAiConversations(db, orgId, kind, after);
169
+ // Written before the words leave: a read the log cannot record is not served.
170
+ await addAdminAudit(db, {
171
+ actorUid: decoded.uid,
172
+ action: ACTION,
173
+ target: `orgs/${orgId}`,
174
+ before: null,
175
+ after: {
176
+ kind,
177
+ rows: page.rows.length,
178
+ after
179
+ }
180
+ });
181
+ return Response.json(page, {
182
+ headers: {
183
+ 'Cache-Control': 'no-store'
184
+ }
185
+ });
186
+ } catch (error) {
187
+ console.error('[ai/admin/conversations]', error);
188
+ return Response.json({
189
+ error: 'The conversations could not be read.'
190
+ }, {
191
+ status: 500
192
+ });
193
+ }
194
+ }
195
+ export { handler as GET };
196
+
197
+ //# sourceMappingURL=ai-admin-conversations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-admin-conversations.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 { pluginRequestFromWeb } from '@aglyn/aglyn/server'\nimport {\n emailUnverifiedResponse,\n firebaseAdmin,\n isImpersonationSession,\n} from '@aglyn/tenant-data-admin'\nimport { addAdminAudit } from '@aglyn/tenant-data-admin/server/admin-audit-write'\nimport {\n composeStaffAiAssistRow,\n composeStaffAiJobRow,\n isStaffAiConversationKind,\n STAFF_AI_CONVERSATIONS_PAGE,\n staffAiCrmResult,\n staffAiInsightResult,\n staffAiJobResultCollection,\n type StaffAiConversationKind,\n type StaffAiConversationsResponse,\n} from '../usage/staff-org-ai-conversations'\n\n/**\n * `/api/ai/admin/conversations` (AGL-3675): one page of what an org asked\n * Aglyn AI and what it answered, for the staff org page.\n *\n * `?orgId=` and `?kind=assist|jobs`, newest first, and `?after=<id>` for the\n * next page. Staff-gated exactly as `/api/ai/admin/org` is. The collections\n * it reads have no client rules at all — default-deny for every browser —\n * which is why the read is here, on the Admin SDK.\n *\n * ## Every page read is audited, and the audit holds no text\n *\n * Unlike the usage card, which loads with the page and so writes no audit\n * row, this route returns what a person typed, and staff read it only when\n * they ask to. Each page writes an `org.ai-conversations-viewed` access row\n * naming who read which list of which org and how many rows came back. The\n * Privacy policy says our audit log records an AI job \"without the text of\n * its brief or of what it wrote\", so the row carries none of it.\n */\n\n/** Members read to name who asked; past it a row shows the uid alone. */\nconst MEMBERS_SCAN = 500\n\nconst ACTION = 'org.ai-conversations-viewed'\n\ntype Firestore = FirebaseFirestore.Firestore\ntype OrgRef = FirebaseFirestore.DocumentReference\n\n/** One page of a collection under the org, newest first, after the document `after` names. */\nasync function readPage(\n orgRef: OrgRef,\n collection: 'assistExchanges' | 'aiJobs',\n after: string | null,\n): Promise<{ docs: FirebaseFirestore.QueryDocumentSnapshot[]; next: string | null }> {\n let query = orgRef.collection(collection).orderBy('createdAt', 'desc').limit(STAFF_AI_CONVERSATIONS_PAGE + 1)\n if (after) {\n const cursor = await orgRef.collection(collection).doc(after).get()\n if (cursor.exists) query = query.startAfter(cursor)\n }\n const snapshot = await query.get()\n const docs = snapshot.docs.slice(0, STAFF_AI_CONVERSATIONS_PAGE)\n const next = snapshot.docs.length > STAFF_AI_CONVERSATIONS_PAGE ? docs[docs.length - 1]?.id ?? null : null\n return { docs, next }\n}\n\nasync function readMembers(orgRef: OrgRef): Promise<Map<string, Record<string, unknown>>> {\n const snapshot = await orgRef.collection('members').limit(MEMBERS_SCAN).get()\n return new Map(snapshot.docs.map((doc) => [doc.id, doc.data() ?? {}]))\n}\n\n/** The documents `refs` name, by id, in one round trip; a missing one is absent. */\nasync function readAll(\n db: Firestore,\n refs: FirebaseFirestore.DocumentReference[],\n): Promise<Map<string, Record<string, unknown>>> {\n if (!refs.length) return new Map()\n const snapshots = await db.getAll(...refs)\n return new Map(\n snapshots.filter((snapshot) => snapshot.exists).map((snapshot) => [snapshot.id, snapshot.data() ?? {}]),\n )\n}\n\n/** One page of the org's conversations of `kind`. Exported for the spec. */\nexport async function readStaffAiConversations(\n db: Firestore,\n orgId: string,\n kind: StaffAiConversationKind,\n after: string | null,\n): Promise<StaffAiConversationsResponse> {\n const orgRef = db.collection('orgs').doc(orgId)\n if (kind === 'assist') {\n const [{ docs, next }, members] = await Promise.all([readPage(orgRef, 'assistExchanges', after), readMembers(orgRef)])\n // The signal shares the exchange's id (AGL-1972): the route, the model, the cost and the thumbs.\n const signals = await readAll(db, docs.map((doc) => orgRef.collection('assistSignals').doc(doc.id)))\n return {\n kind,\n rows: docs.map((doc) => composeStaffAiAssistRow(doc.id, doc.data() ?? {}, signals.get(doc.id) ?? null, members)),\n next,\n }\n }\n const [{ docs, next }, members] = await Promise.all([readPage(orgRef, 'aiJobs', after), readMembers(orgRef)])\n const insightIds = docs.filter((doc) => staffAiJobResultCollection(doc.get('kind')) === 'aiInsights').map((doc) => doc.id)\n const crmIds = docs.filter((doc) => staffAiJobResultCollection(doc.get('kind')) === 'aiCrmAnswers').map((doc) => doc.id)\n const [insights, crm] = await Promise.all([\n readAll(db, insightIds.map((id) => orgRef.collection('aiInsights').doc(id))),\n readAll(db, crmIds.map((id) => orgRef.collection('aiCrmAnswers').doc(id))),\n ])\n return {\n kind,\n rows: docs.map((doc) => {\n const collection = staffAiJobResultCollection(doc.get('kind'))\n const result =\n collection === 'aiInsights'\n ? staffAiInsightResult(insights.get(doc.id) ?? null)\n : collection === 'aiCrmAnswers'\n ? staffAiCrmResult(crm.get(doc.id) ?? null)\n : null\n return composeStaffAiJobRow(doc.id, doc.data() ?? {}, result, members)\n }),\n next,\n }\n}\n\nasync function handler(request: Request): Promise<Response> {\n const { method, query, headers: rawHeaders } = await pluginRequestFromWeb(request)\n const headers = rawHeaders as Partial<Record<string, string>>\n if (method !== 'GET') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return Response.json({ error: 'Unauthenticated' }, { status: 401 })\n const orgId = String(query.orgId ?? '')\n if (!orgId) return Response.json({ error: 'Missing orgId' }, { status: 400 })\n const kind = query.kind\n if (!isStaffAiConversationKind(kind)) {\n return Response.json({ error: 'kind must be assist or jobs' }, { status: 400 })\n }\n const after = typeof query.after === 'string' && query.after ? query.after : null\n\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return emailUnverifiedResponse()\n }\n if (!decoded['staff']) {\n return Response.json({ error: 'Staff only' }, { status: 403 })\n }\n const db = firebaseAdmin.app().firestore()\n if (!(await db.collection('orgs').doc(orgId).get()).exists) {\n return Response.json({ error: 'No such organization' }, { status: 404 })\n }\n const page = await readStaffAiConversations(db, orgId, kind, after)\n // Written before the words leave: a read the log cannot record is not served.\n await addAdminAudit(db, {\n actorUid: decoded.uid,\n action: ACTION,\n target: `orgs/${orgId}`,\n before: null,\n after: { kind, rows: page.rows.length, after },\n })\n return Response.json(page, { headers: { 'Cache-Control': 'no-store' } })\n } catch (error) {\n console.error('[ai/admin/conversations]', error)\n return Response.json({ error: 'The conversations could not be read.' }, { status: 500 })\n }\n}\n\nexport { handler as GET }\n"],"names":["pluginRequestFromWeb","emailUnverifiedResponse","firebaseAdmin","isImpersonationSession","addAdminAudit","composeStaffAiAssistRow","composeStaffAiJobRow","isStaffAiConversationKind","STAFF_AI_CONVERSATIONS_PAGE","staffAiCrmResult","staffAiInsightResult","staffAiJobResultCollection","MEMBERS_SCAN","ACTION","readPage","orgRef","collection","after","docs","query","orderBy","limit","cursor","doc","get","exists","startAfter","snapshot","slice","next","length","id","readMembers","Map","map","data","readAll","db","refs","snapshots","getAll","filter","readStaffAiConversations","orgId","kind","members","Promise","all","signals","rows","insightIds","crmIds","insights","crm","result","handler","request","headers","method","rawHeaders","Response","json","error","status","authorization","idToken","startsWith","undefined","String","decoded","app","auth","verifyIdToken","email_verified","firestore","page","actorUid","uid","action","target","before","console","GET"],"mappings":"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"}
@@ -14,11 +14,11 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type AiImageMode, type AiSvgStyle } from '../providers/image-contract';
17
+ import { type AiImageMode, type AiRasterStyle, type AiSvgStyle } from '../providers/image-contract';
18
18
  /** What a person reads when a picture failed on our side. */
19
19
  export declare const AI_MEDIA_OURS_COPY = "This one's on us \u2014 you weren't charged. The picture didn't come out right; try again, or describe it a little differently.";
20
20
  /** What the door answers on a deployment that makes no photos. */
21
- export declare const AI_MEDIA_NO_PHOTOS_COPY = "Photos aren't available here yet. Choose Illustration to draw one instead.";
21
+ export declare const AI_MEDIA_NO_PHOTOS_COPY = "Photos and painted styles aren't available here yet. Choose a vector kind, such as Illustration, to draw one instead.";
22
22
  /** Every control character as a space, squeezed, trimmed and cut. */
23
23
  export declare function cleanAiImagePrompt(value: unknown): string;
24
24
  /**
@@ -40,7 +40,7 @@ export interface AiMediaImageInput {
40
40
  prompt: string;
41
41
  aspectRatio: string;
42
42
  count: number;
43
- /** An illustration's kind; unread for a photo. */
43
+ /** The kind: an SVG style for an illustration, an image-provider style for a photo. */
44
44
  style: string;
45
45
  /** An illustration's colors: the site theme's, or these. */
46
46
  palette: {
@@ -56,8 +56,8 @@ export interface AiMediaProvenance {
56
56
  model: string;
57
57
  prompt: string;
58
58
  mode: AiImageMode;
59
- /** An illustration's kind; absent on a photo. */
60
- style?: AiSvgStyle;
59
+ /** The kind the picture was made as. */
60
+ style?: AiSvgStyle | AiRasterStyle;
61
61
  aspectRatio: string;
62
62
  /** The meter's signal id for the request that made it. */
63
63
  signalId: string | null;
@@ -18,17 +18,19 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  // The POST climbs `aiGateLadder`, whose lockdown rung is the verdict, and
19
19
  // every stored picture also passes the upload route's.
20
20
  import { mediaFilterKeys } from "@aglyn/aglyn/app-utils/media-metadata";
21
+ import { resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
21
22
  import { MEDIA_ALT_MAX_LENGTH } from "@aglyn/aglyn/app-utils/media-alt";
22
23
  import { aiMediaCreditsPerPicture } from "../model/ai-media-credits.js";
23
24
  import { aiSvgThemePalette, isAiSvgColor } from "../model/ai-svg.js";
24
25
  import { estimateAiBilledUsd } from "../providers/catalog.js";
25
26
  import { AI_UPSTREAM_FAILURE_COPY, AiUpstreamError } from "../providers/contract.js";
26
- import { AI_IMAGE_ASPECT_RATIOS, AI_IMAGE_MAX_COUNT, AI_IMAGE_PROMPT_MAX_CHARS, AI_SVG_MAX_COLORS, AI_SVG_STYLES, AiImageSafetyRefusal, isAiImageAspectRatio } from "../providers/image-contract.js";
27
+ import { AI_IMAGE_ASPECT_RATIOS, AI_IMAGE_MAX_COUNT, AI_IMAGE_PROMPT_MAX_CHARS, AI_SVG_MAX_COLORS, AI_SVG_STYLES, AiImageSafetyRefusal, aiImageSizeForPlan, isAiImageAspectRatio, isAiRasterStyle } from "../providers/image-contract.js";
27
28
  import { vertexImageProvider } from "../providers/vertex-image.js";
28
29
  import { aiGateLadder } from "../runtime/ai-gate.js";
29
30
  import { aiInventoryTheme } from "../runtime/site-inventory.js";
30
31
  import { assistBandRefuses, assistCreditsFromUsd } from "../usage/assist-credits.js";
31
32
  import { publicAssistQuota, recordAssistCost, releaseAssistMessage } from "../usage/assist-usage.js";
33
+ import { aiMediaRasterPrompt } from "./ai-media-raster-prompt.js";
32
34
  import { aiMediaSvgModel, generateAiMediaSvg } from "./ai-media-svg.js";
33
35
  /**
34
36
  * "Create with AI" in Media (AGL-3602): pictures from a description, stored
@@ -40,9 +42,11 @@ import { aiMediaSvgModel, generateAiMediaSvg } from "./ai-media-svg.js";
40
42
  *
41
43
  * Two modes:
42
44
  *
43
- * - `photo` — Google's image models on Vertex AI (`vertex-image.ts`). Off
44
- * unless the deployment configured them; the door answers 404 with a
45
- * sentence the dialog shows.
45
+ * - `photo` — Google's image models on Vertex AI (`vertex-image.ts`), for
46
+ * every kind that is not SVG: a photo, a photographic genre, an art style
47
+ * or a design asset, each the description followed by its kind's fixed
48
+ * style wording (`ai-media-raster-prompt.ts`). Off unless the deployment
49
+ * configured them; the door answers 404 with a sentence the dialog shows.
46
50
  * - `illustration` — an SVG drawn by the text provider every other AI door
47
51
  * uses (`ai-media-svg.ts`): an illustration, an icon, a tileable pattern or
48
52
  * a simple logo mark, in the site's theme colors or the person's own. No
@@ -91,7 +95,7 @@ import { aiMediaSvgModel, generateAiMediaSvg } from "./ai-media-svg.js";
91
95
  windowMs: 60000
92
96
  };
93
97
  /** What a person reads when a picture failed on our side. */ export const AI_MEDIA_OURS_COPY = "This one's on us — you weren't charged. The picture didn't come out right; try again, or describe it a little differently.";
94
- /** What the door answers on a deployment that makes no photos. */ export const AI_MEDIA_NO_PHOTOS_COPY = "Photos aren't available here yet. Choose Illustration to draw one instead.";
98
+ /** What the door answers on a deployment that makes no photos. */ export const AI_MEDIA_NO_PHOTOS_COPY = "Photos and painted styles aren't available here yet. Choose a vector kind, such as Illustration, to draw one instead.";
95
99
  /** The provider photos come from; a spec stands one in through jest. */ function imageProvider() {
96
100
  return vertexImageProvider;
97
101
  }
@@ -145,7 +149,7 @@ export function parseAiMediaImageInput(body) {
145
149
  prompt: cleanAiImagePrompt(body == null ? void 0 : body['prompt']),
146
150
  aspectRatio: text('aspectRatio', 8),
147
151
  count: Number.isFinite(count) ? count : 0,
148
- style: text('style', 24) || 'illustration',
152
+ style: text('style', 24) || ((body == null ? void 0 : body['mode']) === 'illustration' ? 'illustration' : 'photo'),
149
153
  palette: (palette == null ? void 0 : palette['source']) === 'custom' ? {
150
154
  source: 'custom',
151
155
  colors: colors.map((color)=>String(color != null ? color : '').trim().toLowerCase()).slice(0, AI_SVG_MAX_COLORS + 1)
@@ -298,6 +302,9 @@ export async function POST(request) {
298
302
  if (input.count < 1 || input.count > AI_IMAGE_MAX_COUNT) {
299
303
  return refuse(`Make between 1 and ${AI_IMAGE_MAX_COUNT} pictures at a time.`, 400);
300
304
  }
305
+ if (input.mode === 'photo' && !isAiRasterStyle(input.style)) {
306
+ return refuse('Choose a kind of picture from the list.', 400);
307
+ }
301
308
  if (input.mode === 'illustration') {
302
309
  if (!AI_SVG_STYLES.includes(input.style)) {
303
310
  return refuse('Choose an illustration, an icon, a pattern or a logo mark.', 400);
@@ -307,7 +314,10 @@ export async function POST(request) {
307
314
  }
308
315
  }
309
316
  const model = input.mode === 'photo' ? provider.defaultModel() : svgModel;
310
- const perPicture = aiMediaCreditsPerPicture(input.mode, model);
317
+ // The size is the plan's, never the request's: 512 px on Free, 1K on a
318
+ // paid plan, each estimated and charged at its own per-picture rate.
319
+ const imageSize = aiImageSizeForPlan(resolveEffectivePlan(gate.org) === 'free');
320
+ const perPicture = aiMediaCreditsPerPicture(input.mode, model, imageSize);
311
321
  const estimate = perPicture * input.count;
312
322
  const credits = publicAssistQuota(gate.reservation).credits;
313
323
  if (credits && assistBandRefuses(gate.org) && credits.remaining < estimate) {
@@ -342,11 +352,13 @@ export async function POST(request) {
342
352
  let declineReason = null;
343
353
  let style;
344
354
  if (input.mode === 'photo') {
355
+ style = input.style;
345
356
  try {
346
357
  const result = await provider.generate({
347
358
  model,
348
- prompt: input.prompt,
359
+ prompt: aiMediaRasterPrompt(input.prompt, style),
349
360
  aspectRatio,
361
+ size: imageSize,
350
362
  count: input.count
351
363
  });
352
364
  photoUsage = result.usage;
@@ -383,14 +395,15 @@ export async function POST(request) {
383
395
  } else {
384
396
  var _ref1;
385
397
  var _declined_;
386
- style = input.style;
398
+ const svgStyle = input.style;
399
+ style = svgStyle;
387
400
  const palette = await illustrationPalette(gate, input);
388
401
  const settled = await Promise.allSettled(Array.from({
389
402
  length: input.count
390
403
  }, (_unused, variant)=>generateAiMediaSvg({
391
404
  model,
392
405
  prompt: input.prompt,
393
- style: style,
406
+ style: svgStyle,
394
407
  aspectRatio,
395
408
  palette,
396
409
  variant,
@@ -467,7 +480,8 @@ export async function POST(request) {
467
480
  // Only what was stored is settled: a photo's pictures at the per-picture
468
481
  // rate with the request's prompt and thinking, an illustration's tokens.
469
482
  const usage = input.mode === 'photo' ? _extends({}, photoUsage, {
470
- images: stored.length
483
+ images: stored.length,
484
+ imageSize
471
485
  }) : sumUsage(stored.map((entry)=>entry.picture.usage));
472
486
  let signalId = null;
473
487
  try {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-media-image.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\n// lockdown-423: via libs/plugins/ai/src/lib/runtime/ai-gate.ts\n// The POST climbs `aiGateLadder`, whose lockdown rung is the verdict, and\n// every stored picture also passes the upload route's.\n\nimport { mediaFilterKeys } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { MEDIA_ALT_MAX_LENGTH } from '@aglyn/aglyn/app-utils/media-alt'\nimport { aiMediaCreditsPerPicture } from '../model/ai-media-credits'\nimport { aiSvgThemePalette, isAiSvgColor } from '../model/ai-svg'\nimport { estimateAiBilledUsd } from '../providers/catalog'\nimport { AI_UPSTREAM_FAILURE_COPY, AiUpstreamError, type AiUsage } from '../providers/contract'\nimport {\n AI_IMAGE_ASPECT_RATIOS,\n AI_IMAGE_MAX_COUNT,\n AI_IMAGE_PROMPT_MAX_CHARS,\n AI_SVG_MAX_COLORS,\n AI_SVG_STYLES,\n AiImageSafetyRefusal,\n isAiImageAspectRatio,\n type AiImageAspectRatio,\n type AiImageMode,\n type AiImageProvider,\n type AiSvgStyle,\n} from '../providers/image-contract'\nimport { vertexImageProvider } from '../providers/vertex-image'\nimport { aiGateLadder, type AiGateContext } from '../runtime/ai-gate'\nimport { aiInventoryTheme } from '../runtime/site-inventory'\nimport { assistBandRefuses, assistCreditsFromUsd } from '../usage/assist-credits'\nimport {\n publicAssistQuota,\n recordAssistCost,\n releaseAssistMessage,\n} from '../usage/assist-usage'\nimport { aiMediaSvgModel, generateAiMediaSvg, type AiMediaSvgOutcome } from './ai-media-svg'\n\n/**\n * \"Create with AI\" in Media (AGL-3602): pictures from a description, stored\n * in the library that asked.\n *\n * `POST /api/ai/media/images { orgId, library, hostId?, folderId?, mode,\n * prompt, aspectRatio, count, style?, palette? }` answers\n * `{ mediaIds, filtered, failed, credits, warning? }`.\n *\n * Two modes:\n *\n * - `photo` — Google's image models on Vertex AI (`vertex-image.ts`). Off\n * unless the deployment configured them; the door answers 404 with a\n * sentence the dialog shows.\n * - `illustration` — an SVG drawn by the text provider every other AI door\n * uses (`ai-media-svg.ts`): an illustration, an icon, a tileable pattern or\n * a simple logo mark, in the site's theme colors or the person's own. No\n * new vendor, so it runs wherever text AI does.\n *\n * There is no GET: the button draws from the shell's own gates, and this door\n * decides when someone asks for a picture.\n *\n * ## A request, not a job\n *\n * Both modes answer inside the request, the way \"Save as a component with\n * AI\" does, and are metered on the same meter every door uses.\n *\n * ## The money, in order\n *\n * 1. `aiGateLadder` RESERVES before anything is spent: the `aiGenerative`\n * entitlement, `release_ai_generative`, the `ai-generate` kill switch,\n * `ai.generate` on the site, the per-account and per-address windows, the\n * Free taste's own rungs and the band.\n * 2. A workspace whose band is a wall (Free, or a hard cap) is refused before\n * the provider is called when the pictures' estimate is more than it has\n * left.\n * 3. The provider is called. A fault of ours or the provider's hands the\n * reservation back and charges nothing. A declined description charges\n * only what the decline spent, recorded as a declined request (which a\n * Free workspace is never charged for, and which its daily refusal pause\n * counts).\n * 4. Each picture is stored, and only what was stored is SETTLED, at billed\n * rates above the provider's: a photo per picture plus its prompt and\n * thinking, an illustration as the tokens that drew it. A picture held\n * back, one the library refused, and an illustration that failed its\n * safety check twice are never charged — that last one is ours.\n *\n * ## Stored the way any upload is stored\n *\n * Each picture is posted to the console's own `/api/media/upload` with the\n * caller's credential, so the scope, the uploads lockdown, the structural\n * inspection, SVG sanitizing, the quarantine list, the storage band, the CDN\n * variants and the storage counter all apply exactly as they do to a file\n * the person dropped in. This door writes no media document of its own; it\n * only adds, to the documents that route made, the alt text and the record\n * of how the picture was made.\n */\n\n/** The per-account window: a person makes a few sets a minute, not dozens. */\nconst RATE_LIMIT = { key: 'ai-media-image', limit: 6, windowMs: 60_000 }\n\n/** What a person reads when a picture failed on our side. */\nexport const AI_MEDIA_OURS_COPY =\n \"This one's on us — you weren't charged. The picture didn't come out right; try again, or describe it a little differently.\"\n\n/** What the door answers on a deployment that makes no photos. */\nexport const AI_MEDIA_NO_PHOTOS_COPY =\n \"Photos aren't available here yet. Choose Illustration to draw one instead.\"\n\n/** The provider photos come from; a spec stands one in through jest. */\nfunction imageProvider(): AiImageProvider {\n return vertexImageProvider\n}\n\n/** Every control character as a space, squeezed, trimmed and cut. */\nexport function cleanAiImagePrompt(value: unknown): string {\n let out = ''\n for (const char of String(value ?? '')) {\n const code = char.charCodeAt(0)\n out += code < 32 || code === 127 || code === 0x2028 || code === 0x2029 ? ' ' : char\n }\n return out.replace(/\\s+/g, ' ').trim().slice(0, AI_IMAGE_PROMPT_MAX_CHARS)\n}\n\n/**\n * The alt text a picture is stored with: the sentence given, as a sentence,\n * cut at a word inside the library's alt limit. A person edits it like any\n * other.\n */\nexport function aiImageAltText(text: string): string {\n const clean = text.replace(/\\s+/g, ' ').trim()\n if (!clean) return ''\n const sentence = clean.charAt(0).toUpperCase() + clean.slice(1)\n if (sentence.length <= MEDIA_ALT_MAX_LENGTH) return sentence\n const cut = sentence.slice(0, MEDIA_ALT_MAX_LENGTH - 1)\n const space = cut.lastIndexOf(' ')\n return `${(space > 40 ? cut.slice(0, space) : cut).trim()}…`\n}\n\nconst EXTENSIONS: Readonly<Record<string, string>> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/svg+xml': 'svg',\n}\n\n/** A file name from the description's first words: `ai-a-red-barn-2.png`. */\nexport function aiImageFileName(prompt: string, index: number, mimeType: string): string {\n const words = prompt\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[^a-z0-9\\s-]/g, ' ')\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 6)\n .join('-')\n .slice(0, 60)\n .replace(/-+$/, '')\n return `ai-${words || 'image'}-${index + 1}.${EXTENSIONS[mimeType] ?? 'png'}`\n}\n\n/** What the library asked for, read and bounded. */\nexport interface AiMediaImageInput {\n orgId: string\n library: 'host' | 'org'\n /** The site whose library this is, or the site on screen for the org's. */\n hostId: string | null\n folderId: string | null\n mode: AiImageMode\n prompt: string\n aspectRatio: string\n count: number\n /** An illustration's kind; unread for a photo. */\n style: string\n /** An illustration's colors: the site theme's, or these. */\n palette: { source: 'theme' } | { source: 'custom'; colors: string[] }\n}\n\nexport function parseAiMediaImageInput(body: Record<string, unknown> | null): AiMediaImageInput {\n const text = (key: string, limit = 128) => {\n const value = body?.[key]\n return typeof value === 'string' ? value.trim().slice(0, limit) : ''\n }\n const count = Math.floor(Number(body?.['count'] ?? 1))\n const palette = body?.['palette'] as Record<string, unknown> | undefined\n const colors = Array.isArray(palette?.['colors']) ? (palette?.['colors'] as unknown[]) : []\n return {\n orgId: text('orgId'),\n library: body?.['library'] === 'org' ? 'org' : 'host',\n hostId: text('hostId') || null,\n folderId: text('folderId', 64) || null,\n mode: body?.['mode'] === 'illustration' ? 'illustration' : 'photo',\n prompt: cleanAiImagePrompt(body?.['prompt']),\n aspectRatio: text('aspectRatio', 8),\n count: Number.isFinite(count) ? count : 0,\n style: text('style', 24) || 'illustration',\n palette:\n palette?.['source'] === 'custom'\n ? {\n source: 'custom',\n colors: colors\n .map((color) => String(color ?? '').trim().toLowerCase())\n .slice(0, AI_SVG_MAX_COLORS + 1),\n }\n : { source: 'theme' },\n }\n}\n\n/** One stored picture, or the upload route's refusal. */\ntype StoreOutcome = { mediaId: string } | { error: string; status: number }\n\n/**\n * Posts one picture to the console's own upload route, as the caller. The\n * same body the library's Upload button sends, so the route cannot tell the\n * two apart and applies every check it applies to that one.\n */\nasync function storeThroughUploadRoute(\n request: Request,\n input: AiMediaImageInput,\n image: { base64: string; mimeType: string },\n fileName: string,\n): Promise<StoreOutcome> {\n const scope =\n input.library === 'org'\n ? { orgId: input.orgId, ...(input.hostId ? { forHostId: input.hostId } : {}) }\n : { hostId: input.hostId }\n let response: Response\n try {\n response = await fetch(new URL('/api/media/upload', request.url), {\n method: 'POST',\n headers: {\n Authorization: request.headers.get('authorization') ?? '',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...scope,\n fileName,\n contentType: image.mimeType,\n folderId: input.folderId,\n data: image.base64,\n }),\n })\n } catch {\n return { error: 'The picture could not be saved to the media library.', status: 502 }\n }\n const payload = (await response.json().catch(() => ({}))) as Record<string, unknown>\n const mediaId = typeof payload['mediaId'] === 'string' ? payload['mediaId'] : ''\n if (response.ok && mediaId) return { mediaId }\n return {\n error:\n typeof payload['error'] === 'string'\n ? payload['error']\n : 'The picture could not be saved to the media library.',\n status: response.ok ? 502 : response.status,\n }\n}\n\n/** The record a generated picture keeps of how it was made. */\nexport interface AiMediaProvenance {\n model: string\n prompt: string\n mode: AiImageMode\n /** An illustration's kind; absent on a photo. */\n style?: AiSvgStyle\n aspectRatio: string\n /** The meter's signal id for the request that made it. */\n signalId: string | null\n generatedBy: string\n}\n\n/**\n * Adds the alt text and the provenance to a document the upload route made.\n * An `update`, which cannot mint a document: the route minted it.\n */\nasync function annotateStoredImage(\n gate: AiGateContext,\n input: AiMediaImageInput,\n mediaId: string,\n alt: string,\n provenance: AiMediaProvenance,\n): Promise<void> {\n const scopeRef =\n input.library === 'org'\n ? gate.firestore.collection('orgs').doc(input.orgId)\n : gate.firestore.collection('hosts').doc(String(input.hostId))\n const mediaRef = scopeRef.collection('media').doc(mediaId)\n const stored = ((await mediaRef.get()).data() ?? {}) as Parameters<typeof mediaFilterKeys>[0]\n await mediaRef.update({\n alt,\n description: 'Created with AI from a description.',\n aiGenerated: { ...provenance, createdAt: new Date() },\n // What the library filters by, restamped with the alt now set: the upload\n // route wrote \"Alt text is missing\" before the alt existed.\n ...mediaFilterKeys({ ...stored, alt }),\n })\n}\n\n/** The colors an illustration is drawn in. */\nasync function illustrationPalette(\n gate: AiGateContext,\n input: AiMediaImageInput,\n): Promise<string[]> {\n if (input.palette.source === 'custom') return input.palette.colors\n if (!input.hostId) return []\n const snapshot = await gate.firestore.collection('hosts').doc(input.hostId).get()\n return aiSvgThemePalette(aiInventoryTheme(snapshot.exists ? snapshot.data() ?? null : null)?.colors)\n}\n\nconst ZERO_USAGE: AiUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\n\nconst sumUsage = (usages: readonly AiUsage[]): AiUsage =>\n usages.reduce<AiUsage>(\n (sum, usage) => ({\n inputTokens: sum.inputTokens + usage.inputTokens,\n outputTokens: sum.outputTokens + usage.outputTokens,\n cacheReadTokens: sum.cacheReadTokens + usage.cacheReadTokens,\n cacheWriteTokens: sum.cacheWriteTokens + usage.cacheWriteTokens,\n }),\n ZERO_USAGE,\n )\n\n/** A picture made, before it is stored. */\ninterface MadePicture {\n base64: string\n mimeType: string\n alt: string\n /** What making it spent, charged only once it is stored. */\n usage: AiUsage\n}\n\nexport async function POST(request: Request): Promise<Response> {\n let body: Record<string, unknown> | null\n try {\n body = (await request.json()) as Record<string, unknown>\n } catch {\n body = null\n }\n const input = parseAiMediaImageInput(body)\n const provider = imageProvider()\n const svgModel = input.mode === 'illustration' ? aiMediaSvgModel() : undefined\n // A deployment that makes no pictures of this kind has no such door, so\n // nothing about the caller or the workspace is read first.\n if (input.mode === 'photo' && !provider.configured()) {\n return Response.json({ error: AI_MEDIA_NO_PHOTOS_COPY, reason: 'unavailable' }, { status: 404 })\n }\n if (input.mode === 'illustration' && !svgModel) {\n return Response.json({ error: 'Not found' }, { status: 404 })\n }\n\n const gate = await aiGateLadder(\n { request, orgId: input.orgId, hostId: input.hostId },\n {\n feature: 'aiGenerative',\n releaseFlag: 'release_ai_generative',\n lockdownFeature: 'ai-generate',\n permission: 'ai.generate',\n rateLimit: RATE_LIMIT,\n },\n )\n if (gate instanceof Response) return gate\n // Nothing is spent until a provider answers, so every exit before it\n // hands the reservation back.\n const release = () =>\n releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(() => undefined)\n const refuse = async (error: string, status: number, extra: Record<string, unknown> = {}) => {\n await release()\n return Response.json({ error, ...extra }, { status })\n }\n\n if (input.library === 'host' && !input.hostId) {\n return refuse('Open the site whose media library the pictures go in.', 400)\n }\n if (!input.prompt) return refuse('Describe the picture you want first.', 400)\n if (!isAiImageAspectRatio(input.aspectRatio)) {\n return refuse(`Choose a shape: ${AI_IMAGE_ASPECT_RATIOS.join(', ')}.`, 400)\n }\n const aspectRatio: AiImageAspectRatio = input.aspectRatio\n if (input.count < 1 || input.count > AI_IMAGE_MAX_COUNT) {\n return refuse(`Make between 1 and ${AI_IMAGE_MAX_COUNT} pictures at a time.`, 400)\n }\n if (input.mode === 'illustration') {\n if (!(AI_SVG_STYLES as readonly string[]).includes(input.style)) {\n return refuse('Choose an illustration, an icon, a pattern or a logo mark.', 400)\n }\n if (\n input.palette.source === 'custom' &&\n (!input.palette.colors.length ||\n input.palette.colors.length > AI_SVG_MAX_COLORS ||\n !input.palette.colors.every(isAiSvgColor))\n ) {\n return refuse(`Give between 1 and ${AI_SVG_MAX_COLORS} colors as hex values, such as #1a73e8.`, 400)\n }\n }\n\n const model = input.mode === 'photo' ? provider.defaultModel() : (svgModel as string)\n const perPicture = aiMediaCreditsPerPicture(input.mode, model)\n const estimate = perPicture * input.count\n const credits = publicAssistQuota(gate.reservation).credits\n if (credits && assistBandRefuses(gate.org) && credits.remaining < estimate) {\n return refuse(\n credits.remaining < perPicture\n ? 'This workspace has used its AI credits for the month.'\n : `That needs about ${estimate} credits and this workspace has ${credits.remaining} left this month. Make fewer pictures.`,\n 429,\n { reason: 'quota', quota: publicAssistQuota(gate.reservation) },\n )\n }\n\n /** Records what a decline spent: a declined request, never a picture. */\n const recordDecline = (usage: AiUsage, declinedModel: string) =>\n recordAssistCost(gate.firestore, gate.orgId, {\n route: '/api/ai/media/images',\n hostId: input.hostId,\n model: declinedModel,\n tier: 'entitled',\n usage: { ...usage, images: 0 },\n docsPaths: [],\n stopReason: 'refusal',\n free: gate.reservation.free ?? null,\n uid: gate.uid,\n kind: 'image',\n }).catch((meterError) =>\n console.error('ai media refusal not recorded', { orgId: gate.orgId, meterError }),\n )\n\n let made: MadePicture[]\n /** Pictures asked for and not made: held back, declined or failed on our side. */\n let unmade: number\n /** Tokens a photo request spent beside its pictures, charged once any is stored. */\n let photoUsage: AiUsage = ZERO_USAGE\n let declineReason: string | null = null\n let style: AiSvgStyle | undefined\n\n if (input.mode === 'photo') {\n try {\n const result = await provider.generate({\n model,\n prompt: input.prompt,\n aspectRatio,\n count: input.count,\n })\n photoUsage = result.usage\n unmade = result.filtered\n made = result.images.map((image) => ({\n ...image,\n alt: aiImageAltText(input.prompt),\n usage: ZERO_USAGE,\n }))\n } catch (error) {\n if (error instanceof AiImageSafetyRefusal) {\n // Declined, not failed: no picture is charged, and the request is\n // recorded as a declined one so the Free taste's refusal pause\n // counts it.\n await recordDecline(ZERO_USAGE, model)\n return Response.json({ error: error.message, reason: 'safety' }, { status: 422 })\n }\n await release()\n console.error('ai photo generation failed', { orgId: gate.orgId, error })\n const status = error instanceof AiUpstreamError && error.accountProblem ? 503 : 502\n return Response.json({ error: AI_UPSTREAM_FAILURE_COPY }, { status })\n }\n } else {\n style = input.style as AiSvgStyle\n const palette = await illustrationPalette(gate, input)\n const settled = await Promise.allSettled(\n Array.from({ length: input.count }, (_unused, variant) =>\n generateAiMediaSvg({\n model,\n prompt: input.prompt,\n style: style as AiSvgStyle,\n aspectRatio,\n palette,\n variant,\n count: input.count,\n }),\n ),\n )\n const outcomes = settled.flatMap((entry) =>\n entry.status === 'fulfilled' ? [entry.value] : [],\n )\n if (!outcomes.length) {\n await release()\n console.error('ai illustration generation failed', {\n orgId: gate.orgId,\n error: (settled[0] as PromiseRejectedResult | undefined)?.reason,\n })\n return Response.json({ error: AI_UPSTREAM_FAILURE_COPY }, { status: 502 })\n }\n const drawn = outcomes.filter(\n (outcome): outcome is Extract<AiMediaSvgOutcome, { kind: 'drawn' }> => outcome.kind === 'drawn',\n )\n const declined = outcomes.filter((outcome) => outcome.kind === 'declined')\n unmade = input.count - drawn.length\n declineReason =\n (declined[0] as Extract<AiMediaSvgOutcome, { kind: 'declined' }> | undefined)?.reason ?? null\n made = drawn.map((outcome) => ({\n base64: Buffer.from(outcome.svg, 'utf8').toString('base64'),\n mimeType: 'image/svg+xml',\n alt: aiImageAltText(outcome.alt || input.prompt),\n usage: outcome.usage,\n }))\n if (!made.length) {\n if (declined.length) {\n // The model declined: what it spent is a declined request.\n await recordDecline(sumUsage(declined.map((outcome) => outcome.usage)), model)\n return Response.json({ error: declineReason, reason: 'safety' }, { status: 422 })\n }\n // Every picture failed its check twice. Ours, so nothing is charged.\n await release()\n return Response.json({ error: AI_MEDIA_OURS_COPY, reason: 'ours' }, { status: 502 })\n }\n }\n\n const stored: Array<{ mediaId: string; picture: MadePicture }> = []\n let refusal: { error: string; status: number } | null = null\n for (const [index, picture] of made.entries()) {\n const outcome = await storeThroughUploadRoute(\n request,\n input,\n picture,\n aiImageFileName(input.prompt, index, picture.mimeType),\n )\n if ('mediaId' in outcome) stored.push({ mediaId: outcome.mediaId, picture })\n else refusal ??= outcome\n }\n\n if (!stored.length) {\n // Made and not kept: the library refused every picture (its storage\n // band, a lockdown, a permission), so nothing is charged.\n await release()\n return Response.json(\n { error: refusal?.error ?? 'The pictures could not be saved.' },\n { status: refusal?.status ?? 502 },\n )\n }\n\n // Only what was stored is settled: a photo's pictures at the per-picture\n // rate with the request's prompt and thinking, an illustration's tokens.\n const usage =\n input.mode === 'photo'\n ? { ...photoUsage, images: stored.length }\n : sumUsage(stored.map((entry) => entry.picture.usage))\n let signalId: string | null = null\n try {\n signalId = await recordAssistCost(gate.firestore, gate.orgId, {\n route: '/api/ai/media/images',\n hostId: input.hostId,\n model,\n tier: 'entitled',\n usage,\n docsPaths: [],\n stopReason: 'end_turn',\n free: gate.reservation.free ?? null,\n uid: gate.uid,\n kind: 'image',\n })\n } catch (error) {\n // The pictures are in the library and were paid for by us; a meter\n // that could not be written is not a reason to hide them.\n console.error('ai media usage not recorded', { orgId: gate.orgId, error })\n }\n\n await Promise.all(\n stored.map(({ mediaId, picture }) =>\n annotateStoredImage(gate, input, mediaId, picture.alt, {\n model,\n prompt: input.prompt,\n mode: input.mode,\n ...(style ? { style } : {}),\n aspectRatio,\n signalId,\n generatedBy: gate.uid,\n }).catch((error) => console.error('ai media alt text not written', { mediaId, error })),\n ),\n )\n\n const warning =\n refusal?.error ??\n (declineReason && unmade > 0 ? declineReason : unmade > 0 && input.mode === 'illustration'\n ? AI_MEDIA_OURS_COPY\n : null)\n return Response.json({\n mediaIds: stored.map((entry) => entry.mediaId),\n filtered: unmade,\n failed: made.length - stored.length,\n // What was charged, as the meter priced it.\n credits: assistCreditsFromUsd(estimateAiBilledUsd(usage, model)),\n ...(warning ? { warning } : {}),\n })\n}\n"],"names":["mediaFilterKeys","MEDIA_ALT_MAX_LENGTH","aiMediaCreditsPerPicture","aiSvgThemePalette","isAiSvgColor","estimateAiBilledUsd","AI_UPSTREAM_FAILURE_COPY","AiUpstreamError","AI_IMAGE_ASPECT_RATIOS","AI_IMAGE_MAX_COUNT","AI_IMAGE_PROMPT_MAX_CHARS","AI_SVG_MAX_COLORS","AI_SVG_STYLES","AiImageSafetyRefusal","isAiImageAspectRatio","vertexImageProvider","aiGateLadder","aiInventoryTheme","assistBandRefuses","assistCreditsFromUsd","publicAssistQuota","recordAssistCost","releaseAssistMessage","aiMediaSvgModel","generateAiMediaSvg","RATE_LIMIT","key","limit","windowMs","AI_MEDIA_OURS_COPY","AI_MEDIA_NO_PHOTOS_COPY","imageProvider","cleanAiImagePrompt","value","out","char","String","code","charCodeAt","replace","trim","slice","aiImageAltText","text","clean","sentence","charAt","toUpperCase","length","cut","space","lastIndexOf","EXTENSIONS","aiImageFileName","prompt","index","mimeType","words","toLowerCase","normalize","split","filter","Boolean","join","parseAiMediaImageInput","body","count","Math","floor","Number","palette","colors","Array","isArray","orgId","library","hostId","folderId","mode","aspectRatio","isFinite","style","source","map","color","storeThroughUploadRoute","request","input","image","fileName","scope","forHostId","response","fetch","URL","url","method","headers","Authorization","get","JSON","stringify","contentType","data","base64","error","status","payload","json","catch","mediaId","ok","annotateStoredImage","gate","alt","provenance","scopeRef","firestore","collection","doc","mediaRef","stored","update","description","aiGenerated","createdAt","Date","illustrationPalette","snapshot","exists","ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","sumUsage","usages","reduce","sum","usage","POST","provider","svgModel","undefined","configured","Response","reason","feature","releaseFlag","lockdownFeature","permission","rateLimit","release","reservation","refuse","extra","includes","every","model","defaultModel","perPicture","estimate","credits","org","remaining","quota","recordDecline","declinedModel","route","tier","images","docsPaths","stopReason","free","uid","kind","meterError","console","made","unmade","photoUsage","declineReason","result","generate","filtered","message","accountProblem","declined","settled","Promise","allSettled","from","_unused","variant","outcomes","flatMap","entry","drawn","outcome","Buffer","svg","toString","refusal","picture","entries","push","signalId","all","generatedBy","warning","mediaIds","failed"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/ai-media-image.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\n// lockdown-423: via libs/plugins/ai/src/lib/runtime/ai-gate.ts\n// The POST climbs `aiGateLadder`, whose lockdown rung is the verdict, and\n// every stored picture also passes the upload route's.\n\nimport { mediaFilterKeys } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { MEDIA_ALT_MAX_LENGTH } from '@aglyn/aglyn/app-utils/media-alt'\nimport { aiMediaCreditsPerPicture } from '../model/ai-media-credits'\nimport { aiSvgThemePalette, isAiSvgColor } from '../model/ai-svg'\nimport { estimateAiBilledUsd } from '../providers/catalog'\nimport { AI_UPSTREAM_FAILURE_COPY, AiUpstreamError, type AiUsage } from '../providers/contract'\nimport {\n AI_IMAGE_ASPECT_RATIOS,\n AI_IMAGE_MAX_COUNT,\n AI_IMAGE_PROMPT_MAX_CHARS,\n AI_SVG_MAX_COLORS,\n AI_SVG_STYLES,\n AiImageSafetyRefusal,\n aiImageSizeForPlan,\n isAiImageAspectRatio,\n isAiRasterStyle,\n type AiImageAspectRatio,\n type AiImageMode,\n type AiImageProvider,\n type AiRasterStyle,\n type AiSvgStyle,\n} from '../providers/image-contract'\nimport { vertexImageProvider } from '../providers/vertex-image'\nimport { aiGateLadder, type AiGateContext } from '../runtime/ai-gate'\nimport { aiInventoryTheme } from '../runtime/site-inventory'\nimport { assistBandRefuses, assistCreditsFromUsd } from '../usage/assist-credits'\nimport {\n publicAssistQuota,\n recordAssistCost,\n releaseAssistMessage,\n} from '../usage/assist-usage'\nimport { aiMediaRasterPrompt } from './ai-media-raster-prompt'\nimport { aiMediaSvgModel, generateAiMediaSvg, type AiMediaSvgOutcome } from './ai-media-svg'\n\n/**\n * \"Create with AI\" in Media (AGL-3602): pictures from a description, stored\n * in the library that asked.\n *\n * `POST /api/ai/media/images { orgId, library, hostId?, folderId?, mode,\n * prompt, aspectRatio, count, style?, palette? }` answers\n * `{ mediaIds, filtered, failed, credits, warning? }`.\n *\n * Two modes:\n *\n * - `photo` — Google's image models on Vertex AI (`vertex-image.ts`), for\n * every kind that is not SVG: a photo, a photographic genre, an art style\n * or a design asset, each the description followed by its kind's fixed\n * style wording (`ai-media-raster-prompt.ts`). Off unless the deployment\n * configured them; the door answers 404 with a sentence the dialog shows.\n * - `illustration` — an SVG drawn by the text provider every other AI door\n * uses (`ai-media-svg.ts`): an illustration, an icon, a tileable pattern or\n * a simple logo mark, in the site's theme colors or the person's own. No\n * new vendor, so it runs wherever text AI does.\n *\n * There is no GET: the button draws from the shell's own gates, and this door\n * decides when someone asks for a picture.\n *\n * ## A request, not a job\n *\n * Both modes answer inside the request, the way \"Save as a component with\n * AI\" does, and are metered on the same meter every door uses.\n *\n * ## The money, in order\n *\n * 1. `aiGateLadder` RESERVES before anything is spent: the `aiGenerative`\n * entitlement, `release_ai_generative`, the `ai-generate` kill switch,\n * `ai.generate` on the site, the per-account and per-address windows, the\n * Free taste's own rungs and the band.\n * 2. A workspace whose band is a wall (Free, or a hard cap) is refused before\n * the provider is called when the pictures' estimate is more than it has\n * left.\n * 3. The provider is called. A fault of ours or the provider's hands the\n * reservation back and charges nothing. A declined description charges\n * only what the decline spent, recorded as a declined request (which a\n * Free workspace is never charged for, and which its daily refusal pause\n * counts).\n * 4. Each picture is stored, and only what was stored is SETTLED, at billed\n * rates above the provider's: a photo per picture plus its prompt and\n * thinking, an illustration as the tokens that drew it. A picture held\n * back, one the library refused, and an illustration that failed its\n * safety check twice are never charged — that last one is ours.\n *\n * ## Stored the way any upload is stored\n *\n * Each picture is posted to the console's own `/api/media/upload` with the\n * caller's credential, so the scope, the uploads lockdown, the structural\n * inspection, SVG sanitizing, the quarantine list, the storage band, the CDN\n * variants and the storage counter all apply exactly as they do to a file\n * the person dropped in. This door writes no media document of its own; it\n * only adds, to the documents that route made, the alt text and the record\n * of how the picture was made.\n */\n\n/** The per-account window: a person makes a few sets a minute, not dozens. */\nconst RATE_LIMIT = { key: 'ai-media-image', limit: 6, windowMs: 60_000 }\n\n/** What a person reads when a picture failed on our side. */\nexport const AI_MEDIA_OURS_COPY =\n \"This one's on us — you weren't charged. The picture didn't come out right; try again, or describe it a little differently.\"\n\n/** What the door answers on a deployment that makes no photos. */\nexport const AI_MEDIA_NO_PHOTOS_COPY =\n \"Photos and painted styles aren't available here yet. Choose a vector kind, such as Illustration, to draw one instead.\"\n\n/** The provider photos come from; a spec stands one in through jest. */\nfunction imageProvider(): AiImageProvider {\n return vertexImageProvider\n}\n\n/** Every control character as a space, squeezed, trimmed and cut. */\nexport function cleanAiImagePrompt(value: unknown): string {\n let out = ''\n for (const char of String(value ?? '')) {\n const code = char.charCodeAt(0)\n out += code < 32 || code === 127 || code === 0x2028 || code === 0x2029 ? ' ' : char\n }\n return out.replace(/\\s+/g, ' ').trim().slice(0, AI_IMAGE_PROMPT_MAX_CHARS)\n}\n\n/**\n * The alt text a picture is stored with: the sentence given, as a sentence,\n * cut at a word inside the library's alt limit. A person edits it like any\n * other.\n */\nexport function aiImageAltText(text: string): string {\n const clean = text.replace(/\\s+/g, ' ').trim()\n if (!clean) return ''\n const sentence = clean.charAt(0).toUpperCase() + clean.slice(1)\n if (sentence.length <= MEDIA_ALT_MAX_LENGTH) return sentence\n const cut = sentence.slice(0, MEDIA_ALT_MAX_LENGTH - 1)\n const space = cut.lastIndexOf(' ')\n return `${(space > 40 ? cut.slice(0, space) : cut).trim()}…`\n}\n\nconst EXTENSIONS: Readonly<Record<string, string>> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/svg+xml': 'svg',\n}\n\n/** A file name from the description's first words: `ai-a-red-barn-2.png`. */\nexport function aiImageFileName(prompt: string, index: number, mimeType: string): string {\n const words = prompt\n .toLowerCase()\n .normalize('NFKD')\n .replace(/[^a-z0-9\\s-]/g, ' ')\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 6)\n .join('-')\n .slice(0, 60)\n .replace(/-+$/, '')\n return `ai-${words || 'image'}-${index + 1}.${EXTENSIONS[mimeType] ?? 'png'}`\n}\n\n/** What the library asked for, read and bounded. */\nexport interface AiMediaImageInput {\n orgId: string\n library: 'host' | 'org'\n /** The site whose library this is, or the site on screen for the org's. */\n hostId: string | null\n folderId: string | null\n mode: AiImageMode\n prompt: string\n aspectRatio: string\n count: number\n /** The kind: an SVG style for an illustration, an image-provider style for a photo. */\n style: string\n /** An illustration's colors: the site theme's, or these. */\n palette: { source: 'theme' } | { source: 'custom'; colors: string[] }\n}\n\nexport function parseAiMediaImageInput(body: Record<string, unknown> | null): AiMediaImageInput {\n const text = (key: string, limit = 128) => {\n const value = body?.[key]\n return typeof value === 'string' ? value.trim().slice(0, limit) : ''\n }\n const count = Math.floor(Number(body?.['count'] ?? 1))\n const palette = body?.['palette'] as Record<string, unknown> | undefined\n const colors = Array.isArray(palette?.['colors']) ? (palette?.['colors'] as unknown[]) : []\n return {\n orgId: text('orgId'),\n library: body?.['library'] === 'org' ? 'org' : 'host',\n hostId: text('hostId') || null,\n folderId: text('folderId', 64) || null,\n mode: body?.['mode'] === 'illustration' ? 'illustration' : 'photo',\n prompt: cleanAiImagePrompt(body?.['prompt']),\n aspectRatio: text('aspectRatio', 8),\n count: Number.isFinite(count) ? count : 0,\n style: text('style', 24) || (body?.['mode'] === 'illustration' ? 'illustration' : 'photo'),\n palette:\n palette?.['source'] === 'custom'\n ? {\n source: 'custom',\n colors: colors\n .map((color) => String(color ?? '').trim().toLowerCase())\n .slice(0, AI_SVG_MAX_COLORS + 1),\n }\n : { source: 'theme' },\n }\n}\n\n/** One stored picture, or the upload route's refusal. */\ntype StoreOutcome = { mediaId: string } | { error: string; status: number }\n\n/**\n * Posts one picture to the console's own upload route, as the caller. The\n * same body the library's Upload button sends, so the route cannot tell the\n * two apart and applies every check it applies to that one.\n */\nasync function storeThroughUploadRoute(\n request: Request,\n input: AiMediaImageInput,\n image: { base64: string; mimeType: string },\n fileName: string,\n): Promise<StoreOutcome> {\n const scope =\n input.library === 'org'\n ? { orgId: input.orgId, ...(input.hostId ? { forHostId: input.hostId } : {}) }\n : { hostId: input.hostId }\n let response: Response\n try {\n response = await fetch(new URL('/api/media/upload', request.url), {\n method: 'POST',\n headers: {\n Authorization: request.headers.get('authorization') ?? '',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ...scope,\n fileName,\n contentType: image.mimeType,\n folderId: input.folderId,\n data: image.base64,\n }),\n })\n } catch {\n return { error: 'The picture could not be saved to the media library.', status: 502 }\n }\n const payload = (await response.json().catch(() => ({}))) as Record<string, unknown>\n const mediaId = typeof payload['mediaId'] === 'string' ? payload['mediaId'] : ''\n if (response.ok && mediaId) return { mediaId }\n return {\n error:\n typeof payload['error'] === 'string'\n ? payload['error']\n : 'The picture could not be saved to the media library.',\n status: response.ok ? 502 : response.status,\n }\n}\n\n/** The record a generated picture keeps of how it was made. */\nexport interface AiMediaProvenance {\n model: string\n prompt: string\n mode: AiImageMode\n /** The kind the picture was made as. */\n style?: AiSvgStyle | AiRasterStyle\n aspectRatio: string\n /** The meter's signal id for the request that made it. */\n signalId: string | null\n generatedBy: string\n}\n\n/**\n * Adds the alt text and the provenance to a document the upload route made.\n * An `update`, which cannot mint a document: the route minted it.\n */\nasync function annotateStoredImage(\n gate: AiGateContext,\n input: AiMediaImageInput,\n mediaId: string,\n alt: string,\n provenance: AiMediaProvenance,\n): Promise<void> {\n const scopeRef =\n input.library === 'org'\n ? gate.firestore.collection('orgs').doc(input.orgId)\n : gate.firestore.collection('hosts').doc(String(input.hostId))\n const mediaRef = scopeRef.collection('media').doc(mediaId)\n const stored = ((await mediaRef.get()).data() ?? {}) as Parameters<typeof mediaFilterKeys>[0]\n await mediaRef.update({\n alt,\n description: 'Created with AI from a description.',\n aiGenerated: { ...provenance, createdAt: new Date() },\n // What the library filters by, restamped with the alt now set: the upload\n // route wrote \"Alt text is missing\" before the alt existed.\n ...mediaFilterKeys({ ...stored, alt }),\n })\n}\n\n/** The colors an illustration is drawn in. */\nasync function illustrationPalette(\n gate: AiGateContext,\n input: AiMediaImageInput,\n): Promise<string[]> {\n if (input.palette.source === 'custom') return input.palette.colors\n if (!input.hostId) return []\n const snapshot = await gate.firestore.collection('hosts').doc(input.hostId).get()\n return aiSvgThemePalette(aiInventoryTheme(snapshot.exists ? snapshot.data() ?? null : null)?.colors)\n}\n\nconst ZERO_USAGE: AiUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\n\nconst sumUsage = (usages: readonly AiUsage[]): AiUsage =>\n usages.reduce<AiUsage>(\n (sum, usage) => ({\n inputTokens: sum.inputTokens + usage.inputTokens,\n outputTokens: sum.outputTokens + usage.outputTokens,\n cacheReadTokens: sum.cacheReadTokens + usage.cacheReadTokens,\n cacheWriteTokens: sum.cacheWriteTokens + usage.cacheWriteTokens,\n }),\n ZERO_USAGE,\n )\n\n/** A picture made, before it is stored. */\ninterface MadePicture {\n base64: string\n mimeType: string\n alt: string\n /** What making it spent, charged only once it is stored. */\n usage: AiUsage\n}\n\nexport async function POST(request: Request): Promise<Response> {\n let body: Record<string, unknown> | null\n try {\n body = (await request.json()) as Record<string, unknown>\n } catch {\n body = null\n }\n const input = parseAiMediaImageInput(body)\n const provider = imageProvider()\n const svgModel = input.mode === 'illustration' ? aiMediaSvgModel() : undefined\n // A deployment that makes no pictures of this kind has no such door, so\n // nothing about the caller or the workspace is read first.\n if (input.mode === 'photo' && !provider.configured()) {\n return Response.json({ error: AI_MEDIA_NO_PHOTOS_COPY, reason: 'unavailable' }, { status: 404 })\n }\n if (input.mode === 'illustration' && !svgModel) {\n return Response.json({ error: 'Not found' }, { status: 404 })\n }\n\n const gate = await aiGateLadder(\n { request, orgId: input.orgId, hostId: input.hostId },\n {\n feature: 'aiGenerative',\n releaseFlag: 'release_ai_generative',\n lockdownFeature: 'ai-generate',\n permission: 'ai.generate',\n rateLimit: RATE_LIMIT,\n },\n )\n if (gate instanceof Response) return gate\n // Nothing is spent until a provider answers, so every exit before it\n // hands the reservation back.\n const release = () =>\n releaseAssistMessage(gate.firestore, gate.orgId, gate.reservation).catch(() => undefined)\n const refuse = async (error: string, status: number, extra: Record<string, unknown> = {}) => {\n await release()\n return Response.json({ error, ...extra }, { status })\n }\n\n if (input.library === 'host' && !input.hostId) {\n return refuse('Open the site whose media library the pictures go in.', 400)\n }\n if (!input.prompt) return refuse('Describe the picture you want first.', 400)\n if (!isAiImageAspectRatio(input.aspectRatio)) {\n return refuse(`Choose a shape: ${AI_IMAGE_ASPECT_RATIOS.join(', ')}.`, 400)\n }\n const aspectRatio: AiImageAspectRatio = input.aspectRatio\n if (input.count < 1 || input.count > AI_IMAGE_MAX_COUNT) {\n return refuse(`Make between 1 and ${AI_IMAGE_MAX_COUNT} pictures at a time.`, 400)\n }\n if (input.mode === 'photo' && !isAiRasterStyle(input.style)) {\n return refuse('Choose a kind of picture from the list.', 400)\n }\n if (input.mode === 'illustration') {\n if (!(AI_SVG_STYLES as readonly string[]).includes(input.style)) {\n return refuse('Choose an illustration, an icon, a pattern or a logo mark.', 400)\n }\n if (\n input.palette.source === 'custom' &&\n (!input.palette.colors.length ||\n input.palette.colors.length > AI_SVG_MAX_COLORS ||\n !input.palette.colors.every(isAiSvgColor))\n ) {\n return refuse(`Give between 1 and ${AI_SVG_MAX_COLORS} colors as hex values, such as #1a73e8.`, 400)\n }\n }\n\n const model = input.mode === 'photo' ? provider.defaultModel() : (svgModel as string)\n // The size is the plan's, never the request's: 512 px on Free, 1K on a\n // paid plan, each estimated and charged at its own per-picture rate.\n const imageSize = aiImageSizeForPlan(resolveEffectivePlan(gate.org) === 'free')\n const perPicture = aiMediaCreditsPerPicture(input.mode, model, imageSize)\n const estimate = perPicture * input.count\n const credits = publicAssistQuota(gate.reservation).credits\n if (credits && assistBandRefuses(gate.org) && credits.remaining < estimate) {\n return refuse(\n credits.remaining < perPicture\n ? 'This workspace has used its AI credits for the month.'\n : `That needs about ${estimate} credits and this workspace has ${credits.remaining} left this month. Make fewer pictures.`,\n 429,\n { reason: 'quota', quota: publicAssistQuota(gate.reservation) },\n )\n }\n\n /** Records what a decline spent: a declined request, never a picture. */\n const recordDecline = (usage: AiUsage, declinedModel: string) =>\n recordAssistCost(gate.firestore, gate.orgId, {\n route: '/api/ai/media/images',\n hostId: input.hostId,\n model: declinedModel,\n tier: 'entitled',\n usage: { ...usage, images: 0 },\n docsPaths: [],\n stopReason: 'refusal',\n free: gate.reservation.free ?? null,\n uid: gate.uid,\n kind: 'image',\n }).catch((meterError) =>\n console.error('ai media refusal not recorded', { orgId: gate.orgId, meterError }),\n )\n\n let made: MadePicture[]\n /** Pictures asked for and not made: held back, declined or failed on our side. */\n let unmade: number\n /** Tokens a photo request spent beside its pictures, charged once any is stored. */\n let photoUsage: AiUsage = ZERO_USAGE\n let declineReason: string | null = null\n let style: AiSvgStyle | AiRasterStyle | undefined\n\n if (input.mode === 'photo') {\n style = input.style as AiRasterStyle\n try {\n const result = await provider.generate({\n model,\n prompt: aiMediaRasterPrompt(input.prompt, style),\n aspectRatio,\n size: imageSize,\n count: input.count,\n })\n photoUsage = result.usage\n unmade = result.filtered\n made = result.images.map((image) => ({\n ...image,\n alt: aiImageAltText(input.prompt),\n usage: ZERO_USAGE,\n }))\n } catch (error) {\n if (error instanceof AiImageSafetyRefusal) {\n // Declined, not failed: no picture is charged, and the request is\n // recorded as a declined one so the Free taste's refusal pause\n // counts it.\n await recordDecline(ZERO_USAGE, model)\n return Response.json({ error: error.message, reason: 'safety' }, { status: 422 })\n }\n await release()\n console.error('ai photo generation failed', { orgId: gate.orgId, error })\n const status = error instanceof AiUpstreamError && error.accountProblem ? 503 : 502\n return Response.json({ error: AI_UPSTREAM_FAILURE_COPY }, { status })\n }\n } else {\n const svgStyle = input.style as AiSvgStyle\n style = svgStyle\n const palette = await illustrationPalette(gate, input)\n const settled = await Promise.allSettled(\n Array.from({ length: input.count }, (_unused, variant) =>\n generateAiMediaSvg({\n model,\n prompt: input.prompt,\n style: svgStyle,\n aspectRatio,\n palette,\n variant,\n count: input.count,\n }),\n ),\n )\n const outcomes = settled.flatMap((entry) =>\n entry.status === 'fulfilled' ? [entry.value] : [],\n )\n if (!outcomes.length) {\n await release()\n console.error('ai illustration generation failed', {\n orgId: gate.orgId,\n error: (settled[0] as PromiseRejectedResult | undefined)?.reason,\n })\n return Response.json({ error: AI_UPSTREAM_FAILURE_COPY }, { status: 502 })\n }\n const drawn = outcomes.filter(\n (outcome): outcome is Extract<AiMediaSvgOutcome, { kind: 'drawn' }> => outcome.kind === 'drawn',\n )\n const declined = outcomes.filter((outcome) => outcome.kind === 'declined')\n unmade = input.count - drawn.length\n declineReason =\n (declined[0] as Extract<AiMediaSvgOutcome, { kind: 'declined' }> | undefined)?.reason ?? null\n made = drawn.map((outcome) => ({\n base64: Buffer.from(outcome.svg, 'utf8').toString('base64'),\n mimeType: 'image/svg+xml',\n alt: aiImageAltText(outcome.alt || input.prompt),\n usage: outcome.usage,\n }))\n if (!made.length) {\n if (declined.length) {\n // The model declined: what it spent is a declined request.\n await recordDecline(sumUsage(declined.map((outcome) => outcome.usage)), model)\n return Response.json({ error: declineReason, reason: 'safety' }, { status: 422 })\n }\n // Every picture failed its check twice. Ours, so nothing is charged.\n await release()\n return Response.json({ error: AI_MEDIA_OURS_COPY, reason: 'ours' }, { status: 502 })\n }\n }\n\n const stored: Array<{ mediaId: string; picture: MadePicture }> = []\n let refusal: { error: string; status: number } | null = null\n for (const [index, picture] of made.entries()) {\n const outcome = await storeThroughUploadRoute(\n request,\n input,\n picture,\n aiImageFileName(input.prompt, index, picture.mimeType),\n )\n if ('mediaId' in outcome) stored.push({ mediaId: outcome.mediaId, picture })\n else refusal ??= outcome\n }\n\n if (!stored.length) {\n // Made and not kept: the library refused every picture (its storage\n // band, a lockdown, a permission), so nothing is charged.\n await release()\n return Response.json(\n { error: refusal?.error ?? 'The pictures could not be saved.' },\n { status: refusal?.status ?? 502 },\n )\n }\n\n // Only what was stored is settled: a photo's pictures at the per-picture\n // rate with the request's prompt and thinking, an illustration's tokens.\n const usage =\n input.mode === 'photo'\n ? { ...photoUsage, images: stored.length, imageSize }\n : sumUsage(stored.map((entry) => entry.picture.usage))\n let signalId: string | null = null\n try {\n signalId = await recordAssistCost(gate.firestore, gate.orgId, {\n route: '/api/ai/media/images',\n hostId: input.hostId,\n model,\n tier: 'entitled',\n usage,\n docsPaths: [],\n stopReason: 'end_turn',\n free: gate.reservation.free ?? null,\n uid: gate.uid,\n kind: 'image',\n })\n } catch (error) {\n // The pictures are in the library and were paid for by us; a meter\n // that could not be written is not a reason to hide them.\n console.error('ai media usage not recorded', { orgId: gate.orgId, error })\n }\n\n await Promise.all(\n stored.map(({ mediaId, picture }) =>\n annotateStoredImage(gate, input, mediaId, picture.alt, {\n model,\n prompt: input.prompt,\n mode: input.mode,\n ...(style ? { style } : {}),\n aspectRatio,\n signalId,\n generatedBy: gate.uid,\n }).catch((error) => console.error('ai media alt text not written', { mediaId, error })),\n ),\n )\n\n const warning =\n refusal?.error ??\n (declineReason && unmade > 0 ? declineReason : unmade > 0 && input.mode === 'illustration'\n ? AI_MEDIA_OURS_COPY\n : null)\n return Response.json({\n mediaIds: stored.map((entry) => entry.mediaId),\n filtered: unmade,\n failed: made.length - stored.length,\n // What was charged, as the meter priced it.\n credits: assistCreditsFromUsd(estimateAiBilledUsd(usage, model)),\n ...(warning ? { warning } : {}),\n })\n}\n"],"names":["mediaFilterKeys","resolveEffectivePlan","MEDIA_ALT_MAX_LENGTH","aiMediaCreditsPerPicture","aiSvgThemePalette","isAiSvgColor","estimateAiBilledUsd","AI_UPSTREAM_FAILURE_COPY","AiUpstreamError","AI_IMAGE_ASPECT_RATIOS","AI_IMAGE_MAX_COUNT","AI_IMAGE_PROMPT_MAX_CHARS","AI_SVG_MAX_COLORS","AI_SVG_STYLES","AiImageSafetyRefusal","aiImageSizeForPlan","isAiImageAspectRatio","isAiRasterStyle","vertexImageProvider","aiGateLadder","aiInventoryTheme","assistBandRefuses","assistCreditsFromUsd","publicAssistQuota","recordAssistCost","releaseAssistMessage","aiMediaRasterPrompt","aiMediaSvgModel","generateAiMediaSvg","RATE_LIMIT","key","limit","windowMs","AI_MEDIA_OURS_COPY","AI_MEDIA_NO_PHOTOS_COPY","imageProvider","cleanAiImagePrompt","value","out","char","String","code","charCodeAt","replace","trim","slice","aiImageAltText","text","clean","sentence","charAt","toUpperCase","length","cut","space","lastIndexOf","EXTENSIONS","aiImageFileName","prompt","index","mimeType","words","toLowerCase","normalize","split","filter","Boolean","join","parseAiMediaImageInput","body","count","Math","floor","Number","palette","colors","Array","isArray","orgId","library","hostId","folderId","mode","aspectRatio","isFinite","style","source","map","color","storeThroughUploadRoute","request","input","image","fileName","scope","forHostId","response","fetch","URL","url","method","headers","Authorization","get","JSON","stringify","contentType","data","base64","error","status","payload","json","catch","mediaId","ok","annotateStoredImage","gate","alt","provenance","scopeRef","firestore","collection","doc","mediaRef","stored","update","description","aiGenerated","createdAt","Date","illustrationPalette","snapshot","exists","ZERO_USAGE","inputTokens","outputTokens","cacheReadTokens","cacheWriteTokens","sumUsage","usages","reduce","sum","usage","POST","provider","svgModel","undefined","configured","Response","reason","feature","releaseFlag","lockdownFeature","permission","rateLimit","release","reservation","refuse","extra","includes","every","model","defaultModel","imageSize","org","perPicture","estimate","credits","remaining","quota","recordDecline","declinedModel","route","tier","images","docsPaths","stopReason","free","uid","kind","meterError","console","made","unmade","photoUsage","declineReason","result","generate","size","filtered","message","accountProblem","declined","svgStyle","settled","Promise","allSettled","from","_unused","variant","outcomes","flatMap","entry","drawn","outcome","Buffer","svg","toString","refusal","picture","entries","push","signalId","all","generatedBy","warning","mediaIds","failed"],"mappings":"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"}
@@ -0,0 +1,34 @@
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 { AiRasterStyle } from '../providers/image-contract';
18
+ /**
19
+ * The style wording each image-provider kind adds to a description
20
+ * (AGL-3602). The person's description comes first, so it decides what is
21
+ * in the picture; the wording after it decides how the picture is made — a
22
+ * photograph's lens, light and composition, an art style's medium and
23
+ * strokes, a design asset's layout.
24
+ *
25
+ * It is fixed text of ours and carries nothing of the workspace: what reaches
26
+ * the image provider is still the description and the shape, as the
27
+ * published subprocessor row says.
28
+ *
29
+ * `photo` adds nothing: its description is sent exactly as written, as it
30
+ * was before the other kinds existed.
31
+ */
32
+ export declare const AI_MEDIA_RASTER_STYLE_WORDING: Readonly<Record<AiRasterStyle, string>>;
33
+ /** The prompt one picture is asked for: the description, then the kind's style wording. */
34
+ export declare function aiMediaRasterPrompt(description: string, style: AiRasterStyle): string;